01數據結構-Prim算法

01數據結構-Prim算法

  • 1.普利姆(Prim)算法
    • 1.1Prim算法定義
    • 1.2Prim算法邏輯
    • 1.3Prim代碼分析
  • 2.Prim算法代碼實現

1.普利姆(Prim)算法

1.1Prim算法定義

Prim算法在找最小生成樹的時候,將頂點分為兩類,一類是在查找的過程中已經包含在生成樹中的頂點(假設為A類),剩下的另一類為B類。

對于給定的連通圖,起始狀態全部頂點都為B類。在找最小生成樹時,選定任意一個頂點作為起始點,并將之從B類移至A類;然后找出B類中到A類中的頂點之間權值最小的頂點,將之從B類移至A類,如此重復,直到B類中沒有頂點為止。所走過的頂點和邊就是該連通圖的最小生成樹。(簡單對比一下:上一節課的Kruskal是找邊,這幾棵的Prim算法是找頂點。)

1.2Prim算法邏輯

動態維護一個所有待激活的頂點數組,從圖中任意選取一個頂點激活它,激活了后就能發現新的邊,找到權值數組里最小的邊,激活另外一個頂點,更新權值數組,再來找權值數組里最小的邊,激活另外的頂點,以此重復,直到所有頂點都激活。

如圖1我們先激活A,激活后發現新的邊,更新權值數組里的值,A連著B,F,G,所以很明顯我們需要更新B,F,G下的權值,其他沒有直接連接的我們暫時認為權值無窮大,找到權值數組里面最小的邊是AB這條,激活B頂點,發現新的邊,更新權值數組里的邊,B連著A,F,C,由于A和B已激活,我們看需不需要更新F和C下的權值,發現B到F的這條邊的權值是7<16,我們更新F下的權值為7,C下的權值為10,激活F頂點。
圖1
圖1

以此重復這個過程,最終結果如圖2,在找到C之后,就只有G沒有激活了,G的有三條邊,選取最小的那條邊8作為權值數組里面的值。

圖2圖2

如圖3,注意在這里Kruskal算法和Prim算法最后構成的最小生成樹一樣,但在實際過程中,這兩種算法構建出來的最小生成樹可能不一樣。
圖3
圖3

1.3Prim代碼分析

如圖4我們定義一個cost數組,里面存的是權值數組,初始時,每個邊都是INF(無窮大),一個visited數組存已經被激活的點,由于我不想只打印出最后的權值大小,我還想打印打出對應選取了哪些邊在最小生成樹中,初始化時全部設為-1,意味著沒有激活前,沒有其他的頂點到自己。

第一次激活A點,A連著有三條邊,分別對應的頂點是B,F,G,在cost中A激活那一行填寫對應的權值,在該行中找到最小的權值,標黃,cost標了黃色的格子意味著是當前一行權值數組中最小的邊,激活它,后面在更新權值數組的時候就可以不用管黃色的那一列了。在visited數組中,由于A的激活,我們發現三條邊對應的是B,F,G,說明A能到B,F,G我們就在A激活那一行中填寫A到了這三個頂點,但我們一次只能確定一個誰可以被保留,因為我們在costA激活的那一行中,一次只能找到一個最小權值,有的同學可能會問,為什么要把visited中A激活那一行F和G下的A也暫時保留呢,由上1.2分析得后面B是要連F得,但如果連接BF的那條邊很大,比連接AF下的邊大,那么我們就不是B到F,而是A到F,如果不保留的話就找不到了。由于我們激活的是B,又開始對B執行上述邏輯操作,一直到所有頂點被激活。

visited中A激活那里B列下的A要標黃,意味著確定了最小生成樹中其中某一條邊的頂點是這兩個

圖4
圖4

過程和結果如圖5:
圖5
圖5

注意實際代碼實現的時候并不是二維數組,而是意味數組,我這里畫成二維數組只是方便看過程。

2.Prim算法代碼實現

先來看測試的接口:

void setupMGraph(MatrixGraph *graph, int edgeValue) {//				  0    1    2    3    4    5   6char *names[] = {"A", "B", "C", "D", "E", "F", "G"};initMatrixGraph(graph, names, sizeof(names)/sizeof(names[0]), 0, edgeValue);addMGraphEdge(graph, 0, 1, 12);addMGraphEdge(graph, 0, 5, 16);addMGraphEdge(graph, 0, 6, 14);addMGraphEdge(graph, 1, 2, 10);addMGraphEdge(graph, 1, 5, 7);addMGraphEdge(graph, 2, 3, 3);addMGraphEdge(graph, 2, 4, 5);addMGraphEdge(graph, 2, 5, 6);addMGraphEdge(graph, 3, 4, 4);addMGraphEdge(graph, 4, 5, 2);addMGraphEdge(graph, 4, 6, 8);addMGraphEdge(graph, 5, 6, 9);
}void test02() {MatrixGraph graph;EdgeSet *result;int sumWeight = 0;setupMGraph(&graph, INF);result = malloc(sizeof(EdgeSet) * (graph.edgeNum - 1));if (result == NULL) {return;}sumWeight = PrimMGraph(&graph, 0, result);printf("sumWeight = %d\n", sumWeight);for (int i = 0; i < graph.nodeNum - 1; ++i) {printf("edge %d: <%s> ---- <%d> ---- <%s>\n", i + 1,graph.vex[result[i].begin].show, result[i].weight,graph.vex[result[i].end].show);}
}

我們在void setupMGraph(MatrixGraph *graph, int edgeValue)中沒有相連的兩個頂點之間的權值賦為INF,相連的兩個頂點間賦值相應的權值,在void test02()中用PrimMGraph(&graph, 0, result);得到權值總和并隨后打印出邊和頂點的信息

Prim算法核心:int PrimMGraph(const MatrixGraph* graph, int startV, EdgeSet* result);

/* graph: 表示指向鄰接矩陣的圖結構* startV:表示激活的第一個頂點坐標* result:表示最小生成樹的邊的激活情況*/
int PrimMGraph(const MatrixGraph* graph, int startV, EdgeSet* result) {int *cost = malloc(sizeof(int) * graph->nodeNum);			// 圖中各頂點的權值數組int *mark = malloc(sizeof(int) * graph->nodeNum);			// 圖中頂點激活的狀態int *visited = malloc(sizeof(int) * graph->nodeNum);		// 從哪個頂點開始訪問,-1表示沒有被訪問到int sum = 0;// 1. 更新第一個節點激活的狀態for (int i = 0; i < graph->nodeNum; i++) {cost[i] = graph->edges[startV][i];mark[i] = 0;// 1.1 更新visit信息,從哪個節點可以訪問if (cost[i] < INF) {visited[i] = startV;} else {visited[i] = -1;}}mark[startV] = 1;int k = 0;// 2. 動態激活節點,找最小值for (int i = 0; i < graph->nodeNum - 1; ++i) {// 找未加入樹且cost最小的頂點kint min = INF;k = 0;for (int j = 0; j < graph->nodeNum; ++j) {if (mark[j] == 0 && cost[j] < min) {min = cost[j];k = j;}}// 將k加入樹,記錄邊信息mark[k] = 1;result[i].begin = visited[k];result[i].end = k;result[i].weight = min;sum += min;// 每激活一個頂點,需要更新cost數組for (int j = 0; j < graph->nodeNum; ++j) {if (mark[j] == 0 && graph->edges[k][j] < cost[j]) {cost[j] = graph->edges[k][j];visited[j] = k;}}}free(cost);free(mark);free(visited);return sum;
}

我們需要申請三個空間分別用來存圖中各頂點的權值數組,圖中頂點激活的狀態和對應帶權邊的兩個頂點信息。

先來看1.更新第一個節點激活的狀態,在cost里面存放激活的第一個頂點與圖中其他頂點間的邊的權值大小,并默認所有頂點還未被訪問,然后更新visited數組中的信息,如果cost里面存的權值大小小于INF,則把對應visited數組下的值填成starV,說明兩個頂點之間有邊,否則就認為starV頂點與其無邊,在對應visited數組下設為-1,初始化完我們認為starV已被訪問:mark[startV] = 1;此時三個數組如圖6:
圖6
圖6

定義一個k標記待加入的頂點:
在每次循環中,程序會遍歷所有未加入生成樹的頂點(mark[j] == 0),找到 cost[j] 最小的頂點,并用 k 記錄該頂點的索引。這個頂點 k 就是本次要加入生成樹的頂點。
作為新頂點更新后續狀態:
當 k 被標記為 “已加入生成樹”(mark[k] = 1)后,程序會以 k 為新的起點,更新其他未加入頂點的 cost 和 visited 數組。此時 k 成為 “已生成樹” 的一部分,用于后續計算其他頂點與樹的最小連接邊權。

來看2,先定義一個最小值,這個最小值存的是INF,這樣比這個最小值小就可以加入cost里了,找未加入樹且cost最小的頂點k,找到過后將k加入樹,記錄邊信息并把k加入mark標記中說明已被訪問。每次激活一個頂點,需要更新cost里面的權值當然更新的時候只更新沒有訪問過的頂點,因為已被訪問過的頂點已經被我們納入了最小生成樹中,即我們不需要更新圖4,圖5中cost和visited里面標黃的部分。循環 graph->nodeNum - 1次,因為先前已經加入了starV頂點,初始時有 1 個頂點,循環 graph->nodeNum - 1 次后,新增 graph->nodeNum - 1 個頂點,總共加入的頂點數為graph->nodeNum個。

最后測一下:

#include <stdio.h>
#include <stdlib.h>
#include "Kruskal.h"
#include "Prim.h"void setupMGraph(MatrixGraph *graph, int edgeValue) {//				  0    1    2    3    4    5   6char *names[] = {"A", "B", "C", "D", "E", "F", "G"};initMatrixGraph(graph, names, sizeof(names)/sizeof(names[0]), 0, edgeValue);addMGraphEdge(graph, 0, 1, 12);addMGraphEdge(graph, 0, 5, 16);addMGraphEdge(graph, 0, 6, 14);addMGraphEdge(graph, 1, 2, 10);addMGraphEdge(graph, 1, 5, 7);addMGraphEdge(graph, 2, 3, 3);addMGraphEdge(graph, 2, 4, 5);addMGraphEdge(graph, 2, 5, 6);addMGraphEdge(graph, 3, 4, 4);addMGraphEdge(graph, 4, 5, 2);addMGraphEdge(graph, 4, 6, 8);addMGraphEdge(graph, 5, 6, 9);
}void test02() {MatrixGraph graph;EdgeSet *result;int sumWeight = 0;setupMGraph(&graph, INF);result = malloc(sizeof(EdgeSet) * (graph.edgeNum - 1));if (result == NULL) {return;}sumWeight = PrimMGraph(&graph, 0, result);printf("sumWeight = %d\n", sumWeight);for (int i = 0; i < graph.nodeNum - 1; ++i) {printf("edge %d: <%s> ---- <%d> ---- <%s>\n", i + 1,graph.vex[result[i].begin].show, result[i].weight,graph.vex[result[i].end].show);}
}int main() {test02();return 0;
}

結果:

D:\work\DataStruct\cmake-build-debug\03_GraphStruct\MiniTree.exe
sumWeight = 36
edge 1: <A> ---- <12> ---- <B>
edge 2: <B> ---- <7> ---- <F>
edge 3: <F> ---- <2> ---- <E>
edge 4: <E> ---- <4> ---- <D>
edge 5: <D> ---- <3> ---- <C>
edge 6: <E> ---- <8> ---- <G>進程已結束,退出代碼為 0

大概先寫這些吧,今天的博客就先寫到這,謝謝您的觀看。

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

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

相關文章

CacheBlend:結合緩存知識融合的快速RAG大語言模型推理服務

溫馨提示&#xff1a; 本篇文章已同步至"AI專題精講" CacheBlend&#xff1a;結合緩存知識融合的快速RAG大語言模型推理服務 摘要 大語言模型&#xff08;LLMs&#xff09;通常在輸入中包含多個文本片段&#xff0c;以提供必要的上下文。為了加速對較長LLM輸入的預…

Docker 在 Linux 中的額外資源占用分析

Docker 本身作為一個運行時環境&#xff0c;除了容器應用本身消耗的資源外&#xff0c;還會引入一些額外的開銷。主要體現在以下幾個方面&#xff1a; 1. 存儲空間占用 (Disk Space) 這是最顯著的額外開銷&#xff0c;主要來源于 Docker 的存儲驅動&#xff08;如 overlay2&…

[激光原理與應用-264]:理論 - 幾何光學 - 什么是焦距,長焦與短焦的比較

長焦與短焦透鏡是光學系統中兩類核心組件&#xff0c;其成像特性在焦距、視角、景深、像場特性及典型應用中存在顯著差異。以下從多個維度進行詳細對比&#xff1a;一、核心參數對比參數長焦透鏡短焦透鏡焦距范圍通常 >50mm&#xff08;全畫幅相機標準&#xff09;通常 <…

el-input 復制大量數據導致頁面卡頓問題解決

問題根源 復制粘貼操作會瞬間觸發大量 input 事件&#xff0c;導致 Vue 頻繁更新響應式數據&#xff0c;引發性能瓶頸。 解決方案&#xff1a;使用 .lazy 修飾符 <el-input v-model.lazy"inputValue" />

PCIe Electrical Idle Sequences ( EIOS and EIEOS )

前言 PCI Express (PCIe)協議中&#xff0c;EIOS (Electrical Idle Ordered Set) 和 EIEOS (Electrical Idle Exit Ordered Set) 是在高速鏈路管理和狀態切換過程中極為重要的特殊序列。下面做詳細解釋&#xff1a; 一、EIOS&#xff08;Electrical Idle Ordered Set&#xff0…

【GPT入門】第45課 無梯子,linux/win下載huggingface模型方法

【GPT入門】第45課 無梯子&#xff0c;下載huggingface模型方法1.下載模型代碼2. linux 設置鏡像與加速3.windows1.下載模型代碼 from transformers import AutoModelForCausalLM, BertTokenizer, BertForSequenceClassificationmodel_dir /root/autodl-tmp/model_hf# 加載模…

計算機網絡摘星題庫800題筆記 第5章 傳輸層

第5章 傳輸層5.1 傳輸層概述題組闖關1.Internet 傳輸層滑動窗口協議規定 ( )。 A. 網絡接收分組的最低效率&#xff0c;只需要重傳未被確認的分組 B. 固定的窗口大小&#xff0c;只需要重傳未被確認的分組 C. 網絡接收分組的最低效率&#xff0c;固定的窗口大小 D. 未被確認的分…

Apache虛擬主機三種配置實戰

一、虛擬主機概述 目的&#xff1a;實現單臺服務器部署多個獨立站點 三種部署方式&#xff1a; 相同IP 不同端口不同IP 相同端口相同IP和端口 不同域名&#xff08;FQDN&#xff09; 示例目標&#xff1a;在服務器上部署 baidu 和 taobao 兩個站點方式1&#xff1a;相同IP …

【SpringBoot】04 基礎入門 - 自動配置原理入門:依賴管理 + 自動配置

文章目錄前言一、Spring Boot Maven項目POM文件解析1. 基礎項目信息2. 父項目繼承3. 依賴管理4. 構建配置5. 屬性配置Spring Boot特性體現典型Spring Boot項目特點二、依賴管理1、父項目做依賴管理無需關注版本號&#xff0c;自動版本仲裁修改自動仲裁的版本官網文檔2、依賴項引…

機器學習—— TF-IDF文本特征提取評估權重 + Jieba 庫進行分詞(以《紅樓夢》為例)

使用 Jieba 庫進行 TF-IDF 關鍵詞提取&#xff08;以《紅樓夢》為例&#xff09;在中文文本分析中&#xff0c;TF-IDF&#xff08;Term Frequency - Inverse Document Frequency&#xff09; 是最常用的關鍵詞提取方法之一。它通過評估詞在單個文檔中的出現頻率和在所有文檔中的…

Kotlin語法整理

Kotlin語法整理 Kotlin語法整理 一、基本數據類型 共8種 二、變量的聲明三、條件 1. if…else if…else語句2. when 語句 四、循環 1. while 語句2. do…while 語句3. for 語句4. repeat 語句5. break 語句6. continue 語句 五、數組 1. 創建元素未初始化的數組2. 創建元素初始…

跨平臺低延遲的RTMP推流播放在無紙化會議與智慧教室的技術設計和架構實踐

?? 引言&#xff1a;讓每一塊屏幕“同頻”的核心技術 無紙化會議與智慧教室&#xff0c;正在從“輔助工具”走向“核心基礎設施”&#xff0c;成為政企數字化與教育信息化建設的標配。它們的核心訴求并不只是替代紙質文檔或黑板&#xff0c;而是要在多終端、多地點、多網絡環…

最優擴展大型語言模型測試時計算量可能比擴展模型參數更有效

摘要 通過增加測試時計算量使大型語言模型&#xff08;LLMs&#xff09;提升輸出效果&#xff0c;是構建能基于開放自然語言自主改進的通用智能體的重要步驟。本文研究LLMs推理階段計算量的擴展規律&#xff0c;重點回答以下問題&#xff1a;若允許LLM使用固定但可觀的推理階段…

GPT5評測對比與使用

經過長達一年的技術迭代&#xff0c;OpenAI正式推出GPT-5系列模型&#xff0c;包含GPT-5&#xff08;標準版&#xff09;、GPT-5-mini&#xff08;輕量版&#xff09;和GPT-5-nano&#xff08;極簡版&#xff09;三個版本&#xff0c;定價策略保持統一。本次升級在性能、效率與…

Git與CI/CD相關知識點總結

Git與CI/CD相關知識點總結 1. Git對象模型與存儲機制 1.1 Git對象類型 Commit對象&#xff1a;包含提交信息、作者、時間、父commit引用、樹對象引用Tree對象&#xff1a;描述目錄結構和文件引用Blob對象&#xff1a;實際的文件內容 1.2 存儲機制特點 增量存儲&#xff1a;每次…

CS2服務器是何方神圣

CS2服務器是何方神圣CS2「子刷新頻率」深度拆解&#xff1a;從官方宣言到“吞子彈”真相00 先給結論01 官方原話到底說了什么02 一條時間線看懂「Sub-tick」03 技術解剖&#xff1a;Sub-tick 的實現細節3.1 輸入包結構&#xff08;Valve 公開源碼節選&#xff09;3.2 連續積分&…

Docker守護進程安全加固在香港VPS環境的操作標準

Docker守護進程安全加固在香港vps環境的操作標準隨著云計算技術的普及&#xff0c;Docker守護進程安全加固已成為香港VPS環境中不可忽視的重要環節。本文將系統性地介紹如何通過配置優化、訪問控制、網絡隔離等維度&#xff0c;在香港虛擬私有服務器上建立符合企業級安全標準的…

Rust 項目編譯故障排查:從 ‘onnxruntime‘ 鏈接失敗到 ‘#![feature]‘ 工具鏈不兼容錯誤

Rust 項目編譯故障排查報告&#xff1a;從原生庫鏈接失敗到工具鏈不兼容 場景: 編譯一個本地 Rust 項目時遇到連續的編譯錯誤。一、 故障現象概述 在對一個 Rust 項目執行 cargo build 命令時&#xff0c;先后遇到了兩個不同性質的編譯錯誤&#xff0c;導致編譯流程中斷。初始錯…

K8s 1.32.6版本部署文檔

主機配置 作用IP地址操作系統配置關鍵組件k8s-master172.16.1.30Rocky Linux release 94C/4G/50GBkube-apiserver, etcd,dockerk8s-node1172.16.1.31Rocky Linux release94C/4G/50GBkubelet, kube-proxy,dockerk8s-node2172.16.1.32Rocky Linux release 94C/4G/50GBkubelet, k…

第十六屆藍橋杯大賽青少組 C++ 省賽真題解析(2025年8月10日)

第一題 題目:運行以下程序,輸出的結果是()。 #include<bits/stdc++.h> using namespace std; int func(int y) { y -= 5; cout << "x"; return 0; } int main() { int x = 10, y = 5; if (x > y || func(y)) cout &…