今天花了很多時間去實現學校佈置的作業,所以我突然想到我是否可以將這些經歷寫到網路上,以便更好的分享出去
本次實現的具體內容是:用C語言實現鄰接矩陣儲存的無向圖,判斷是否為連通圖,並且實現最小生成樹Prim演算法
(引用的話不重要)在此之前已經有過一段開發基礎了,但大部分都是用C++和其他語言,而用C語言很少,主要原因是其太靈活並且自身覺得困難,努力完成了上述內容後,我忽然發現其實也還好,無非就是需要思考的事情多了。
參考上述要求,我使用了並查集實現圖是否為連通圖,使用了堆排序(優先佇列)實現了Prim演算法。其實這些在C++當中很簡單,但是使用C語言就很麻煩,需要手動實現。具體整體邏輯如下
- 無向圖部分
使用結構體定義
其中有很多實現方法。其中最核心的是UG_IsConnectedGraph()是否為連通圖、UG_GetMST()獲取最小生成樹。這倆分別使用到了並查集與堆排序。下方是無向圖部分程式碼,並查集和堆排序在後面
/*以下無向圖定義*/
const int INF = 1e9; //代表鄰接表未連線
struct UG { //無向帶權圖 Undirected Graph
int n;//元素個數
int* s; //鄰接矩陣,動態建立
};
typedef UG* UGPoi;
UGPoi UG_Init(int n);//建立無向圖
void UG_Link(UGPoi ug, int p, int q, int val);//將兩條邊連線起來
void UG_PrintGraph(UGPoi ug);//列印圖(鄰接矩陣)
bool UG_IsConnectedGraph(UGPoi ug);//是否是連通圖
UGPoi UG_GetMST(UGPoi ug);//獲取最小生成樹
struct __UG_HeapNode { //UG_GetMST()函式中需使用到的型別
//邊長,目標結點,原始節點
int len, poi, fro;
};
void* __UG_getheapnode(int fro, int poi, int len);//建立__UG_HeapNode結構體
bool __UG_heapcompare(void* a, void* b);//堆排序比較函式
/*其中實現如下*/
UGPoi UG_Init(int n) {
//申請UG結構體記憶體
UGPoi ug = (UGPoi)malloc(sizeof(UG));
//為s建立n*n的鄰接矩陣空間
ug->s = (int*)malloc(sizeof(int) * n * n);
for (int i = 0;i < n;i++)for (int j = 0;j < n;j++)
ug->s[i * n + j] = INF;
for (int i = 0;i < n;i++)ug->s[i * n + i] = 0;
ug->n = n;
return ug;
}
void UG_Link(UGPoi ug, int p, int q, int val) {
p--;q--;
ug->s[p * ug->n + q] = val;
ug->s[q * ug->n + p] = val;
}
void UG_PrintGraph(UGPoi ug) {
printf("(∞表示沒有連線)\n");
int n = ug->n;
for (int i = 0;i < n;i++) {
for (int j = 0;j < n;j++) {
if (ug->s[i * n + j] == INF) printf(" ∞ ");
else printf("%3d ", ug->s[i * n + j]);
}
printf("\n");
}
}
bool UG_IsConnectedGraph(UGPoi ug) {
//使用並查集來判斷是否為連通圖
int n = ug->n;
int rest = n;
int* uf = UnionFind_Init(n);
for (int i = 0;i < n;i++)
for (int j = i + 1;j < n;j++)
if (ug->s[i * n + j] != INF &&
UnionFind_Find(uf, i+1)!=UnionFind_Find(uf, j+1)
) {
UnionFind_Union(uf, i + 1, j + 1);
rest--;
}
if (rest == 1)return 1;
else return 0;
}
UGPoi UG_GetMST(UGPoi ug) {
//使用優先佇列存放Prim遍歷到的結點,每次取出最小的。優先佇列使用堆實現
int n = ug->n;
UGPoi res = UG_Init(n);
//建立堆,堆的最大大小與比較函式傳進去
HeapPoi heap = Heap_Init(n * n, sizeof(__UG_HeapNode), __UG_heapcompare);
//是否已遍歷到
int* isok = (int*)malloc(sizeof(int) * n);
memset(isok, 0, sizeof(int) * n);
Heap_Ins(heap, __UG_getheapnode(0, 0, 0));
while (!Heap_Empty(heap)) {
__UG_HeapNode* t = (__UG_HeapNode*)malloc(sizeof(__UG_HeapNode));
memcpy(t, Heap_Top(heap), sizeof(__UG_HeapNode));
Heap_Pop(heap);
//如果該節點已經遍歷過了,跳過
if (isok[t->poi])continue;
isok[t->poi] = 1; //如果沒有遍歷過,將其設為已遍歷,防止後續迴圈遍歷到
//將其該邊加入到res結果當中
UG_Link(res, t->fro+1, t->poi+1 , t->len);
//找與之相鄰的結點,將其加入堆當中
for (int i = 0;i < n;i++) {
if (i == t->poi) continue;
if (ug->s[t->poi * n + i] != INF && isok[i] == 0) {
__UG_HeapNode* readyins = (__UG_HeapNode*)__UG_getheapnode(t->poi, i, ug->s[t->poi * n + i]);
Heap_Ins(heap, readyins);
}
}
}
return res;
}
void* __UG_getheapnode(int fro, int poi, int len) {
__UG_HeapNode* res = (__UG_HeapNode*)malloc(sizeof(__UG_HeapNode));
res->len = len;
res->poi = poi;
res->fro = fro;
return (void*)res;
}
bool __UG_heapcompare(void* a, void* b) {
int p = ((__UG_HeapNode*)a)->len;
int q = ((__UG_HeapNode*)b)->len;
return p < q;
}
- 並查集部分
在無向圖中,UG_IsConnectedGraph()函式中使用到了並查集,程式碼如下
/*以下為並查集定義*/
int* UnionFind_Init(int n); //建立森林,返回森林首指標
int UnionFind_Find(int* s, int n); //Find查詢
void UnionFind_Union(int* s, int a, int b); //Union合併
/*以下為並查集實現*/
int* UnionFind_Init(int n) {
int* s = (int*)malloc(sizeof(int) * (n + 1));
for (int i = 0;i <= n;i++) {
s[i] = i;
}
return s;
}
int UnionFind_Find(int *s, int n) {
if (s[n] != n)return s[n] = UnionFind_Find(s,s[n]);
return n;
}
void UnionFind_Union(int *s, int a, int b) {
if (UnionFind_Find(s,a) != UnionFind_Find(s,b)) {
s[UnionFind_Find(s,a)] = UnionFind_Find(s,b);
}
}
- 堆排序部分
在無向圖中,UG_GetMST()函式中使用到了堆排序,程式碼如下
/*以下為堆定義*/
struct Heap {
int n; //堆中元素數量
void* s;//堆資料元素,使用預先申請好的記憶體線性儲存
bool (*compare)(void* a, void* b);//堆資料元素比較函式
int node_size;//堆資料元素型別位元組大小
};
typedef Heap* HeapPoi;
//初始化堆。需填好可能使用的最大數量、資料元素型別位元組大小、資料元素比較函式
HeapPoi Heap_Init(int n, int node_size, bool(*compare)(void* a, void* b));
void Heap_Ins(HeapPoi h, void* v);//向堆中新增元素並自動排序
void* Heap_Top(HeapPoi h);//取堆頂
void Heap_Pop(HeapPoi h);//彈出堆頂,並自動排序
bool Heap_Empty(HeapPoi h);//堆是否為空
void* __Heap_getattr(HeapPoi h, int shifting);//取堆中第shifting個資料元素,相當於偏移量
void __Heap_swap(HeapPoi h, int a, int b);//交換兩個資料元素
/*以下為堆實現*/
void* __Heap_getattr(HeapPoi h, int shifting) {
return (void*)((char*)h->s + shifting * h->node_size);
}
void __Heap_swap(HeapPoi h, int a, int b) {
void* t = malloc(h->node_size);
memcpy(t, __Heap_getattr(h, a), h->node_size);
memcpy(__Heap_getattr(h, a), __Heap_getattr(h, b), h->node_size);
memcpy(__Heap_getattr(h, b), t, h->node_size);
}
HeapPoi Heap_Init(int n, int node_size, bool(*compare)(void *a,void *b)) {
HeapPoi h = (HeapPoi)malloc(sizeof(Heap));
h->compare = compare;
h->s = (void*)malloc(node_size * n);
h->node_size = node_size;
h->n = 0;
return h;
}
void Heap_Ins(HeapPoi h, void* v) {
memcpy(__Heap_getattr(h, h->n), v, h->node_size);
h->n++;
int p = h->n - 1;
while (p > 0) {
//獲取到 p的雙親結點
int f = (p & 1) ? p / 2 : (p - 1) / 2;
//判斷s[p]是否不小於s[f],如果是break
if (!h->compare(__Heap_getattr(h, p), __Heap_getattr(h, f))) break;
//將s[p]和s[f]交換
__Heap_swap(h, p, f);
p = f;
}
}
void* Heap_Top(HeapPoi h){
return __Heap_getattr(h, 0);
}
void Heap_Pop(HeapPoi h) {
h->n--;
if (h->n == 0)return;
//將s[0]替換為s[n]
memcpy(__Heap_getattr(h, 0), __Heap_getattr(h, h->n), h->node_size);
int p = 0;
while (1) {
int son = -1;
if (p * 2 + 1 < h->n) son = p * 2 + 1;
if (p * 2 + 2 < h->n && h->compare(__Heap_getattr(h, p*2+2), __Heap_getattr(h, son) ) ) {
son = p * 2 + 2;
}
if (son == -1 || h->compare(__Heap_getattr(h, p), __Heap_getattr(h, son)))break;
__Heap_swap(h, son, p);
p = son;
}
}
bool Heap_Empty(HeapPoi h) {
return h->n == 0;
}