資料結構筆記(一)——C語言實現鄰接矩陣儲存的無向圖,判斷是否為連通圖,並且實現最小生成樹Prim演算法

bobodesu發表於2021-12-13

今天花了很多時間去實現學校佈置的作業,所以我突然想到我是否可以將這些經歷寫到網路上,以便更好的分享出去

本次實現的具體內容是:用C語言實現鄰接矩陣儲存的無向圖,判斷是否為連通圖,並且實現最小生成樹Prim演算法

(引用的話不重要)在此之前已經有過一段開發基礎了,但大部分都是用C++和其他語言,而用C語言很少,主要原因是其太靈活並且自身覺得困難,努力完成了上述內容後,我忽然發現其實也還好,無非就是需要思考的事情多了。

參考上述要求,我使用了並查集實現圖是否為連通圖,使用了堆排序(優先佇列)實現了Prim演算法。其實這些在C++當中很簡單,但是使用C語言就很麻煩,需要手動實現。具體整體邏輯如下

  1. 無向圖部分

使用結構體定義
其中有很多實現方法。其中最核心的是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;
}
  1. 並查集部分

在無向圖中,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);
	}
}
  1. 堆排序部分

在無向圖中,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;
}

相關文章