數據結構之克魯斯卡爾算法

前言:和Prim算法一樣,Kruskal 算法也是用來生成最小生成樹的,這篇文章來學習一下Kruskal算法的實現

一、實現流程

初始化的時候,將所有的邊用一個數組存儲,并且按權值從小到大進行排序,每次選一個權值最小的邊加入到生成樹中,但是在加入之前,需要判斷加入的這條邊會不會使生成樹形成環路。接下來我們分步驟看一下算法的執行過程
我們來看這樣一個圖
在這里插入圖片描述
1、邊數組初始化
從小到大存儲邊
在這里插入圖片描述

2、權值最小,為1的邊連接4號節點和6號節點,這兩個節點不在同一棵樹中,不會形成環路,因此加入生成樹
在這里插入圖片描述
3、下一個權值最小的邊,0-2 ,權值為2,這個邊及加入生成樹也不會形成環路,所以加入生成樹
在這里插入圖片描述
4、下一個權值最小的邊是 5-7,同上也直接加入樹
在這里插入圖片描述
5、下一個邊 0-2,同樣加入生成樹
在這里插入圖片描述
6、下一個 0-3 的邊,也不會形成環路,加入
在這里插入圖片描述

7、下一個 3-5 的邊也可以加入生成樹
在這里插入圖片描述

8、下一個 0-4 ,也可以直接加入
在這里插入圖片描述

9、至此,所有的節點已經全部加入到生成樹中,算法結束

二、代碼實現

1、定義常數
#define MaxSize 100
#define MaxEdge 200  // 最大邊數
#define MaxVex 100   // 最大頂點數
2、結構體

需要兩個結構體,一個是圖,一個是邊
圖需要包括點的個數,邊的個數,和鄰接矩陣

typedef struct {int vexnum;  // 頂點數int arcnum;  // 邊數int arcs[MaxVex][MaxVex];  // 鄰接矩陣
} MGraph;

邊的結構體需要包含邊的兩個頂點,和邊的權值

typedef struct{int a,b; // 邊的兩個頂點int weight; // 邊的權值
} Edge
3、初始化變量和工具函數

需要一個邊數組,存儲所有的邊

Edge edges[MaxEdge]; // 變數組

Kcuscal 算法需要找權值最小的邊,所以對所有的邊進行排序,c語言中有一個內置的 qsort 方法,可以對任何類型進行排序,接收四個參數:
參數一:void* base,待排數組
參數二:size_t num,待排元素的個數
參數三:size_t size,每個元素的大小,單位為字節,使用 sizeof() 函數獲取
參數四:int (*compare)(const void * , const void *),排序函數
需要一個排序函數 compare,將權值比較小的邊放在前面
void * 表示泛型指針類型,表示a可以指向任何類型的數據
(Edge*) 是一個類型轉換,由于在入參中我們定義的 a 可以是任何類型的數據,這里使用 (Edge*) 將a轉換為指向 Edge 結構體的指針。
Edge *edgeA 聲明了一個新指針,并且將 a 指針的值賦值給它,現在 edgeA 就是一個指向 Edge 結構體的指針。
返回值,如果是負數,表示 edgeA->weight 小,則不更換數據的位置;如果是正數,表示 edgeA->weight 小,則要更換數據的位置。

int compare(const void *a, const void *b){Edge *edgeA = (Edge *) a;Edge *edgeB = (Edge *) b;return edgeA->weight - edgeB->weight;
}

另外判斷邊是否能夠加入到生成樹中的時候,需要判斷這條邊和生成樹是不是在同一棵樹中,如果在同一棵樹中,那么加入這條邊,一定會形成環路。我們在合并兩個樹的時候學過,將一個樹 A 合并到另一棵樹 B 中,就是將樹的根節點 A 的父節點更新成另一棵樹的根節點 B。
所以我們需要維護一個數組 parent,來存儲所有的節點的父節點,初始化的時候,都初始化為-1

int parent[MaxVex]; // 根節點數組(并查集)

節點當加入到生成樹中就需要進行合并操作,需要更新節點對應的根節點。
所以我們需要一個查找根節點的函數 Find,來查找一個節點所在的樹的根節點
如果 parent[x] 小于 0 ,說明當前節點還沒有加入到生成樹中,就直接返回節點本身。如果parent[x] 大于 0,則向上找父節點,直到找到 parent[x] 小于 0 的的點。

// 并查集的Find操作
int Find(int *parent,int x){while(parent[x]>=0) x=parent[x]; // 循環向上尋找下標為x頂點的根return x; // while循環結束時找到了根的下標
}
4、主函數

在主函數中需要做的事情:
1、給邊數組 edges 按權值排序
2、初始化 parent 數組為 -1
3、遍歷邊,找邊的兩個頂點的 parent,如果 parent 不相同,表示兩個頂點不在同棵樹中,則將其中一個頂點的 parent 指向另一個頂點,也就是將兩個頂點合并在一棵樹中

void MiniSpanTree_Kruskal(Gragh G){int i,n,m;// edges 排序qsort(edges, G.arcnum, sizeof(Edge), compare);// 初始化parentfor(i=0;i<G.vexnum;i++) parent[i] = -1;// 遍歷所有邊for(i=0;i<G.arcnum;i++){n = Find(edges[i].a);  // 第一個節點所在的樹的根節點m = Find(edges[i].b);  // 第二個節點所在的樹的根節點if(n!=m){  // 根節點不同,說明這兩個節點位于兩棵不同的樹,則合并這兩棵樹parent[n] = m;printf("(%d->%d) 權值:%d\n", edges[i].a, edges[i].b, edges[i].weight);}}
}

三、和Prim算法的對比

組成樹的元素有兩個,一個是節點,一個是邊。Prim 算法主要關注節點,找和當前的最小生成樹距離最近的節點,把節點加入到生成樹中。而Kruskal算法主要關注的是邊,先將所有的邊排序,將權值最小并且不會形成環路的邊依次加入到生成樹中。由于Kruskal算法要對邊進行對比排序,所以Kruskal算法的執行效率取決于邊的多少,適合邊少的圖,我們叫做稀疏圖。而Prim算法主要關注節點,適合邊多的圖(邊多就相當于節點少了),我們叫做稠密圖。
下面我們來分析一下時間復雜度,假設圖的節點數為 v ,邊數為 e,
Prim 算法需要執行兩層循環,每層執行的次數都是 v,所以Prim算法的時間復雜度是 O(v^2)。
Kruckal 算法 qsort()方法的時間復雜度是 O(elog2e),它是時間復雜度最高的方法,
外層需要遍歷所有的邊,時間復雜度是 O(e), Find 方法的并查集操作的時間復雜度可以優化到很小,可以忽略,所以整個算法的時間復雜度是 O(elog2e)

四、測試代碼

在 main 函數中,需要初始化 edges 數組

// 克魯斯卡爾算法
// 求最小生成樹的算法
#include <stdio.h>
#include <stdlib.h>#define MaxSize 100
#define MaxEdge 200  // 最大邊數
#define MaxVex 100   // 最大頂點數// 圖的鄰接矩陣表示
typedef struct {int vexnum;  // 頂點數int arcnum;  // 邊數int arcs[MaxVex][MaxVex];  // 鄰接矩陣
} MGraph;typedef struct {int a,b; // 邊的兩個頂點int weight; // 邊的權值
}Edge;// 并查集的Find操作
int Find(int *parent,int x){while(parent[x]>=0) x=parent[x]; // 循環向上尋找下標為x頂點的根return x; // while循環結束時找到了根的下標
}// 比較函數,用于qsort排序
// const 放在*的左邊,表示指針指向的數據不可變,但是指針的指向可變
int compare(const void *a, const void *b) {Edge *edgeA = (Edge*)a;Edge *edgeB = (Edge*)b;return edgeA->weight - edgeB->weight; // 按權值從小到大排序
}Edge edges[MaxEdge]; // 邊數組
int parent[MaxVex]; // 父親頂點數組(并查集)void MiniSpanTree_Kruskal(MGraph G){int i,n,m;// printf("\n排序前的邊數組:\n");// for(i=0; i<G.arcnum; i++) {//     printf("(%d-%d) 權值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }// 按權值由小到大對邊排列qsort(edges, G.arcnum, sizeof(Edge), compare);// printf("\n按權值排序后的邊數組:\n");// for(i=0; i<G.arcnum; i++) {//     printf("(%d-%d) 權值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }for(i=0;i<G.vexnum;i++) parent[i]=-1; // 初始化并查集// printf("\n最小生成樹的邊:\n");for(i=0;i<G.arcnum;i++){ // 遍歷每一條邊n=Find(parent,edges[i].a); // n是這條邊的第一個頂點的根節點所在的下標m=Find(parent,edges[i].b); // m是這條邊的第二個頂點的根節點所在的下標if(n!=m){parent[n] = m; // 并操作printf("(%d->%d) 權值:%d\n", edges[i].a, edges[i].b, edges[i].weight);}}
}// 初始化圖的邊
void initGraph(MGraph *G) {// 示例圖:6個頂點,9條邊G->vexnum = 6;  // 頂點數 0-5G->arcnum = 9;  // 邊數// 手動添加邊的信息到edges數組// 頂點0-1,權值4edges[0].a = 0; edges[0].b = 1; edges[0].weight = 4;// 頂點0-2,權值6edges[1].a = 0; edges[1].b = 2; edges[1].weight = 6;// 頂點0-3,權值16edges[2].a = 0; edges[2].b = 3; edges[2].weight = 16;// 頂點1-2,權值10edges[3].a = 1; edges[3].b = 2; edges[3].weight = 10;// 頂點1-4,權值7edges[4].a = 1; edges[4].b = 4; edges[4].weight = 7;// 頂點2-3,權值14edges[5].a = 2; edges[5].b = 3; edges[5].weight = 14;// 頂點2-4,權值3edges[6].a = 2; edges[6].b = 4; edges[6].weight = 3;// 頂點2-5,權值8edges[7].a = 2; edges[7].b = 5; edges[7].weight = 8;// 頂點4-5,權值9edges[8].a = 4; edges[8].b = 5; edges[8].weight = 9;
}int main() {MGraph G;// printf("=== 克魯斯卡爾最小生成樹算法演示 ===\n\n");// 初始化圖initGraph(&G);// printf("原始圖的邊信息:\n");// printf("頂點數:%d,邊數:%d\n", G.vexnum, G.arcnum);// printf("所有邊:\n");// for(int i = 0; i < G.arcnum; i++) {//     printf("(%d-%d) 權值:%d\n", edges[i].a, edges[i].b, edges[i].weight);// }// printf("\n開始執行克魯斯卡爾算法:\n");// printf("================================\n");// 執行克魯斯卡爾算法MiniSpanTree_Kruskal(G);// printf("================================\n");// printf("算法執行完成!\n");return 0;
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/92199.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/92199.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/92199.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MongoDB 查詢時區問題

MongoDB默認時區是UTC&#xff0c;比北京時區晚八小時&#xff0c;北京時間UTC8h。 // 北京時間的 2024-10-01 08:00:00 // (>) 大于 - $gt // (<) 小于 - $lt // (>) 大于等于 - $gte // (< ) 小于等于 - $lte// Z代表UTC時區1、{"gmtCreate":{"$…

Windows VS2019 編譯 Apache Thrift 0.15.0

隨著微服務架構的普及,高效的跨語言遠程過程調用(RPC) 成為了構建分布式系統的重要基礎。Apache Thrift 是 Facebook 開源的一個輕量級、高性能的 RPC 框架,它允許開發者通過一個通用的接口定義語言(IDL)來定義服務接口和數據結構,并自動生成多種語言的客戶端和服務端代…

搭建種草商城框架指南

一、引言在當今電商市場&#xff0c;種草商城以其獨特的社交化購物模式受到越來越多用戶的喜愛。搭建一個功能完善、體驗良好的種草商城框架&#xff0c;需要綜合考慮前端界面、后端服務、數據庫設計等多個方面。本文將為你詳細介紹搭建種草商城框架的關鍵要點和技術選型。二、…

docker--掛載

設置容器的掛載 需要注意 掛載行為會覆蓋容器目標目錄的原有內容(未驗證)。 查看容器的掛載情況 在容器外部查看: docker inspect <容器名或容器ID> | grep -A n "Mounts" -A n 的含義 -A 是 --after-context 的縮寫,表示顯示匹配行及其后 n 行。 "Mo…

以Streamable HTTP方式訪問mcp server的過程

一、mcp server 部署 使用fastmcp框架 部署 mcp server&#xff0c; 以下是源代碼 # 引入 fastmcp 依賴包 from fastmcp import FastMCP# 新建fastmcp實例&#xff0c; 名字叫做 weather mcp FastMCP("weather")mcp.tool(name"weather", tags{"weath…

二次元 IP 虛擬數字人宣傳:漫畫角色動態直播與衍生周邊預售聯動

當漫畫角色從靜態畫稿中走出&#xff0c;以動態直播的形式與粉絲實時互動&#xff0c;再順勢開啟衍生周邊預售 —— 虛擬數字人技術正重塑二次元 IP 的宣傳邏輯。這種 “動態直播 周邊預售” 的聯動模式&#xff0c;不僅打破了次元壁&#xff0c;更讓 IP 熱度高效轉化為商業價…

如何在服務器上獲取Linux目錄大小

目前我在管理一臺hostease的服務器時遇到服務器磁盤空間不足的情況。隨著在系統中添加更多文件&#xff0c;這些系統文件目錄也變得越來越大。過大的目錄也消耗了系統資源&#xff0c;導致系統運行緩慢。后來我通過下列的方法對服務器上的磁盤空間使用進行了逐一檢查。在這篇綜…

來伊份養饞記社區零售 4.0 上海首店落滬:重構 “家門口” 的生活服務生態

7 月 19 日&#xff0c;來伊份與養饞記戰略合作的首個 “社區零售 4.0” 門店在上海松江泗涇鎮泗寶路正式開業。這不僅是雙方自今年 1 月達成戰略合作后的實質性落地&#xff0c;更是 3 月 “社區生活新生態” 構想的首次規模化實踐&#xff0c;標志著零食行業巨頭與社區零售新…

從C++開始的編程生活(3)——引用類型、內聯inline和nullptr

前言 本系列文章承接C語言的學習&#xff0c;需要有C語言的基礎才能學會哦~ 第3篇主要講的是有關于C的引用類型、內聯inline和nullptr。 C才起步&#xff0c;都很簡單呢&#xff01; 目錄 前言 引用類型 基本語法 特性 應用 const引用 基本語法 引用與指針的關系 內聯…

makefile-- 其他函數

fuctionsjoin?$(join <list1>,<list2>)連接函數把list2 中單詞對應的添加到list1 的后面若list1 的單詞個數> list2 &#xff0c;多出的list1 保持不變若list2 的單詞個數> list21&#xff0c;多出的list2 添加到list1 后面foreach?$(foreach <var>…

【unity實戰】使用unity的Navigation+LineRenderer實現一個3D人物尋路提前指示預測移動軌跡的效果,并可以適配不同的地形

文章目錄 前言 實戰 1、實現要點 1.1 NavMesh.CalculatePath方法計算兩個點之間的導航路徑 1.2 尋找投射的地面點 2、代碼實現如下 3、烘培地面導航網格 4、添加導航玩家代理,并掛載前面的腳本 5、創建Line Renderer,并放在角色下面作為子物體 6、運行游戲查看效果 專欄推薦 …

寶塔申請證書錯誤,提示 module ‘OpenSSL.crypto‘ has no attribute ‘sign‘

遇到"module OpenSSL.crypto has no attribute sign"錯誤時&#xff0c;通常是由于pyOpenSSL版本兼容性問題導致的?。以下是解決方案&#xff1a;通過SSH連接到服務器&#xff0c;執行以下命令安裝指定版本的pyOpenSSL&#xff1a;btpip install pyOpenSSL24.2.1-U然…

【ffmpeg源碼學習】詳解pkg-config的作用

文章目錄 前言 一、什么是pkg-config? 二、為什么需要 pkg-config? 三、pkg-config 的工作原理 3.1 .pc 文件 3.2 查詢流程 3.3 查找路徑 四、pkg-config 在 FFmpeg 中的作用 五、pkg-config 的常用命令 六、在項目中的實際用法 6.1 makefile示例: 6.2 cmake示例: 6.3 gcc命…

PHPStorm攜手ThinkPHP8:開啟高效開發之旅

目錄一、前期準備1.1 開發環境搭建1.2 配置 Xdebug二、PHPStorm 集成 ThinkPHP82.1 導入 ThinkPHP8 項目2.2 配置 PHP 解釋器2.3 配置服務器三、ThinkPHP8 項目開發基礎3.1 項目結構剖析3.2 控制器與方法創建3.3 視圖渲染與數據傳遞四、數據庫操作與模型定義4.1 數據庫配置4.2 …

HTTP性能優化實戰技術詳解(2025)

HTTP性能優化實戰技術詳解本文基于提供的文章大綱&#xff0c;對HTTP性能優化進行擴展說明。文章結構清晰&#xff0c;從理解瓶頸到具體優化策略&#xff0c;再到監控與高級技巧&#xff0c;逐步展開。每個部分包括背景介紹、核心原理、實施步驟、示例或工具推薦&#xff0c;確…

探索文件系統:軟硬鏈接的奧秘

目錄 1.文件系統 1.1 磁盤物理存儲結構 1.2 磁盤邏輯存儲結構 1.3 inode編號 2. 軟硬鏈接 2.1 軟鏈接 2.2 硬鏈接 2.3 目錄文件的軟硬鏈接 1.文件系統 在一臺電腦中&#xff0c;大部分文件都不是被打開的&#xff0c;這些文件都在磁盤中進行保存。已經打開的文件需要管…

3x3矩陣教程

3x3矩陣教程 1. 簡介 三維矩陣是線性代數中的重要概念&#xff0c;用于表示三維空間中的線性變換。本教程將介紹如何使用C實現三維矩陣的基本運算和變換。 2. 代碼實現 2.1 頭文件 (matrix3x3.h) #ifndef MATRIX3X3_H #define MATRIX3X3_H#include <array> #include <…

深度學習前置知識

文章目錄介紹數據操作張量張量的定義1. **張量的維度&#xff08;Rank&#xff09;**2. **張量的形狀&#xff08;Shape&#xff09;**簡單的數據預處理&#xff08;插值線性代數微積分概率論1. 基本概念(1) 隨機試驗與事件(2) 概率公理&#xff08;Kolmogorov公理&#xff09;…

XSS學習總結

一.XSS概述 跨站腳本攻擊&#xff08;Cross-Site Scripting&#xff0c;XSS&#xff09;是一種常見的網絡安全漏洞&#xff0c;攻擊者通過在網頁上注入惡意腳本代碼&#xff0c;從而在用戶的瀏覽器上執行惡意操作。這些腳本可以是 JavaScript、HTML 或其他網頁腳本語言。一旦用…

計算機網絡中:傳輸層和網絡層之間是如何配合的

可以把網絡層和傳輸層想成一個“快遞系統”&#xff1a; 網絡層&#xff08;IP 層&#xff09; 郵政系統&#xff1a;只負責把“包裹”&#xff08;IP 數據報&#xff09;從 A 地搬到 B 地&#xff0c;不保證順序、不保證不丟、不保證不重復。傳輸層&#xff08;TCP/UDP 層&am…