數據結構——圖(Code)

下面寫的代碼可能感覺會很亂,所以加上了目錄,意在爲了引導,也就是你需要回顧哪個算法,就能夠快速定位到該位置。

另外,閱讀代碼是件非常枯燥的事,所以儘量每一行都加的註釋,方便閱讀理解。

四、圖

(一)圖的順序存儲結構——鄰接矩陣
  1. 結構體的聲明

    typedef enum
    {
    	DG_GRAPH = 0,						//有向圖
    	DN_GRAPH							//無向圖
    }GRAPH_TYPE;							//圖的類型結構體
    typedef struct
    {
    	int no;								//頂點的編號
    	int data;							//頂點存放的數據
    
    }VERTEX_TYPE;							//頂點類型結構體
    
    typedef struct 
    {
    	int n, e;							//n爲頂點數,e爲邊數
    	int edge[MAXSIZE][MAXSIZE];			//存放鄰接矩陣的邊信息
    	VERTEX_TYPE ver_type[MAXSIZE];		//存放結點信息
    	GRAPH_TYPE graph_type;				//圖的類型(可寫可不寫)
    }MGRAPH;								//圖的鄰接矩陣結構體
    
  2. 圖的鄰接矩陣表示

    /*
    method:
    	構造圖的鄰接矩陣
    param:
    	m_graph		圖
    	vex_num		頂點的數量
    	edges		邊的信息
    */
    int CreateDG(MGRAPH** m_graph,GRAPH_TYPE graph_type ,int vex_num, int edges[][MAXSIZE])
    {
    	int edge_num = 0;
    	int i = 0, j = 0;
    	(*m_graph)->n = vex_num;									//結點個數賦值
    	for (i = 0; i < vex_num; i++)
    	{
    		(*m_graph)->ver_type[i].no = i;							//頂點序號爲下標
    		(*m_graph)->ver_type[i].data = i;						//頂點數據爲下標
    		for (j = 0; j < vex_num; j++)
    		{
    			(*m_graph)->edge[i][j] = edges[i][j];				//給圖的邊賦值
    			if (graph_type == DG_GRAPH)							//判斷有向圖還是無向圖
    			{
    				if (edges[i][j] == 1)							//計算邊的數量
    					++edge_num;
    			}
    			else if(graph_type == DN_GRAPH)						//判斷有向圖還是無向圖	
    			{
    				if (edges[i][j] != MAX_NUM && edges[i][j] != 0)	//計算邊的數量
    					++edge_num;
    			}
    		}
    	}
    	if(graph_type == DG_GRAPH)
    		(*m_graph)->e = edge_num;								//邊的個數
    	else if(graph_type == DN_GRAPH)		
    		(*m_graph)->e = edge_num / 2;							//無向圖邊的個數爲 1 的一半		
    	return OK;
    }
    
(二)圖的鏈式存儲結構——鄰接表
  1. 結構體的聲明

    #define OK 1
    #define ERROR -1
    #define MAXSIZE 6
    typedef struct ArcNode
    {
    	int adj_vertex;						//該邊所指向的頂點信息
    	struct ArcNode* nextarc;			//指向下一條邊的指針
    	int info;							//該邊的相關信息,如權值
    }ArcNode;								//弧指針結構體
    		
    typedef struct
    {
    	int data;							//頂點信息
    	ArcNode* first_arc;					//指向第一條邊的指針
    }V_NODE;								//頂點指針結構體
    
    typedef struct
    {
    	V_NODE adj_list[MAXSIZE];			//鄰接表
    	int n, e;							//n爲頂點數,e爲邊數
    }L_GRAPH;								//圖的鄰接表結構體
    
  2. 構造圖的鄰接表

    /*
    method:
    	構造圖,將鄰接矩陣轉化爲鄰接表
    param:
    	l_graph		圖的鄰接表
    	m_graph		圖的鄰接矩陣
    */
    int Atrix_To_List(L_GRAPH** l_graph,MGRAPH *m_graph)
    {
    	int i = 0, j = 0;
    	int flag = 0;																//第一次是將頂點表的指針指向邊表,第二次是邊表指針指向邊
    	ArcNode* arc_node;															//存放結點
    	ArcNode* temp = NULL;														//臨時存放結點
    	(*l_graph)->e = m_graph->e;													//邊數
    	(*l_graph)->n = m_graph->n;													//頂點數
    
    	for (i = 0; i < (*l_graph)->n; i++)											//根據頂點數來循環
    	{
    		(*l_graph)->adj_list[i].data = m_graph->ver_type[i].data;				//得到頂點的值
    		(*l_graph)->adj_list[i].first_arc = NULL;								//置空
    		flag = 0;																//每個頂點表都是 第一次是頂點表的指針指向邊表結點
    		for (j = 0; j < m_graph->n; j++)
    		{
    			if(m_graph->edge[i][j] == 1)										//如果該頂點連接的有邊
    			{
    				arc_node = (ArcNode*)__vcrt_malloc_normal(sizeof(ArcNode));		//創建內存
    				arc_node->adj_vertex = m_graph->ver_type[j].data;				//存放該頂點信息
    				arc_node->nextarc = NULL;										//將其像一個結點置位空
    				if (flag == 0)													//第一次是將頂點表指向邊表
    				{
    					(*l_graph)->adj_list[i].first_arc = arc_node;				//將頂點表的指針指向邊表
    					temp = arc_node;											//將臨時指針指向當前連接的結點
    					flag = 1;													//第二次不執行這裏
    				}
    				else
    				{
    					temp->nextarc = arc_node;									//常規鏈表操作
    					temp = arc_node;
    				}
    			}
    		}
    	}
    	return OK;
    }
    
  3. 十字鏈表的結構體聲明

    typedef struct ARC_BOX
    {
    	int tailvex, headvex;						//十字鏈表的弧尾,弧頭結點
    	int info;									//弧上的信息
    	struct ARC_BOX* hlink, *tlink;				//終點相同指針,起點相同指針
    }ARC_BOX;										//弧的結構體
    
    typedef struct
    {
    	int data;									//頂點信息
    	ARC_BOX* first_in,*first_out;				//入邊表的第一個結點,出邊表的第一個結點
    }VEX_NODE;										//頂點結構體
    
    typedef struct
    {
    	VEX_NODE cross_list[MAXSIZE];				//頂點結點,表頭
    	int vexnum, arcnum;							//有向圖結點數,弧數
    }OL_GRAPH;										//十字鏈表結構體
    
    1. 鄰接多重表結構體聲明
    typedef struct EBOX
    {
    	int mark;									//訪問標記
    	int ivex, jvex;								//該邊依附的兩個頂點信息
    	struct EBOX* ilink, * jlink;				//指針域
    	int info;									//該邊的信息
    }EBOX;
    
    typedef struct 
    {
    	int data;									//頂點的數據
    	EBOX* first_edge;							//第一條依附該頂點的邊
    }VEX_BOX;										//頂點的結構體
    
    typedef struct
    {
    	VEX_BOX adj_mulist[MAXSIZE];				//鄰接多重表
    	int vexnum, edgenum;						//結點數量,邊的數量
    }AML_GRAPH;										//結構體
    
(三)圖的遍歷
  1. 深度優先遍歷(DFS)

    /*
    method:
    	深度優先搜索遍歷
    param:
    	l_graph		圖的鄰接表
    	current_vex	當前結點位置
    */
    
    void DFS(L_GRAPH* l_graph,int current_vex)
    {
    	ArcNode* arc_node;
    	Vex_Visit[current_vex] = 1;								//將當前結點標記爲已訪問
    	Visit(current_vex);										//訪問當前結點的頂點表的數據
    	arc_node = l_graph->adj_list[current_vex].first_arc;	//指向頂點表的第一條邊
    	while (arc_node != NULL)								//邊表不爲空
    	{
    		if (Vex_Visit[arc_node->adj_vertex] == 0)			//判斷這個邊表的結點是否訪問過
    			DFS(l_graph, arc_node->adj_vertex);
    		arc_node = arc_node->nextarc;
    	}
    }
    
  2. 廣度優先搜索遍歷(BFS)

    /*
    method:
    	廣度優先搜索遍歷
    param:
    	l_graph		圖的鄰接表
    	current_vex	當前結點位置
    */
    void BFS(L_GRAPH* l_graph, int current_vex, int* visit)
    {
    	int temp_vex = 0;
    	ArcNode* arc_node;
    	int queue[MAXSIZE], front = 0, rear = 0;					//定義一個隊列
    	Vex_Visit_Init(visit);										//清空訪問數組內容,初始狀態
    	Visit(l_graph->adj_list[current_vex].data);					//訪問頂點表結點
    	visit[current_vex] = 1;										//標記該結點已訪問過
    
    	rear = (rear + 1) % MAXSIZE;								//入隊
    	queue[rear] = current_vex;
    
    	while (rear != front)										//隊內有元素
    	{
    		front = (front+1) % MAXSIZE;							//一個結點出隊
    		temp_vex = queue[front];
    		arc_node = l_graph->adj_list[temp_vex].first_arc;		//取得邊結點
    		while (arc_node != NULL)
    		{
    			if (visit[arc_node->adj_vertex] == 0)
    			{
    				rear = (rear + 1) % MAXSIZE;					//入隊
    				queue[rear] = arc_node->adj_vertex;
    				Visit(arc_node->adj_vertex);					//訪問該結點
    				visit[arc_node->adj_vertex] = 1;
    			}
    			arc_node = arc_node->nextarc;						//指針指向下一個結點
    		}
    	}
    }
    
  3. 訪問函數

    /*
    method:
    	訪問操作
    param:
    	data		結點的數據
    */
    
    void Visit(int data)
    {
    	printf("%d  ", data);
    }
    
    
  4. 判斷兩頂點是否有路徑

    /*
    method:
    	判斷兩個頂點之間是否有路徑
    param:
    	l_graph		圖的鄰接表
    	vex1,vex2	兩結點
    */
    int Trace(L_GRAPH* l_graph, int vex1, int vex2)
    {
    	Vex_Visit_Init(Vex_Visit);					//清空標記數組
    	DFS(l_graph, vex1);							//遍歷圖
    	for (int i = 0; i < l_graph->e; i++)		//遍歷整個標記數組
    	{
    		if (Vex_Visit[vex2] == 1)				//如果vex2被訪問過,其標記爲1
    			return OK;
    		else
    			return ERROR;
    	}
    }
    
(四)圖的應用
  1. 普里姆算法

    /*
    method:
    	構造最小生成樹,普里姆算法
    param:
    	m_graph		有向圖的鄰接矩陣,兩個結點直接無聯繫的,權值取無窮,最大
    	start_vex	選取的第一個頂點
    	sum			返回權值的總和
    */
    void Prim(MGRAPH* m_graph, int start_vex, int* sum)
    {
    	int low_cost[MAXSIZE],												//存放侯選邊的全部權值,也就是與查找結點相連接的邊的權值
    		vset[MAXSIZE],													
    		v;
    	int i = 0, j = 0, k = 0, min = 0;			
    
    	v = start_vex;														//將任意選擇一個結點賦給v,
    	for (i = 0; i < m_graph->n; ++i)
    	{	
    		low_cost[i] = m_graph->edge[start_vex][i];						//獲取該結點與其他結點之間的全部的權值,用於後面的篩選
    		vset[i] = 0;													//全部置零
    	}
    	vset[start_vex] = 1;												//選取該頂點,將其置爲1,標記入樹
    	(*sum) = 0;															//最小生成樹的權值,這裏先清零
    	for (i = 0; i < m_graph->n; ++i)									//搜索侯選邊最小權值
    	{
    		min = MAX_NUM;
    		for (j = 0; j < m_graph->n; ++j)								//選出所有與該結點相關的全部邊的最小權值,並獲得最小權值的結點 
    		{
    			if (vset[j] == 0 && low_cost[j] < min)						//判斷結點j是否被查找過,並且判斷該權值是否最小
    			{
    				min = low_cost[j];										//更新最小權值
    				k = j;													//記錄最小權值的結點
    			}
    		}
    		if (min != MAX_NUM)												//還有結點沒有檢測過
    		{
    			vset[k] = 1;		 										//將該結點併入樹中
    			v = k;														//以該結點爲起始結點
    			(*sum) += min;												//計算全部的權值
    		}
    
    		for (j = 0; j < m_graph->n; ++j)								//在新加入樹的結點下 尋找與該樹最關的最小權值
    		{
    			if (vset[j] == 0 && m_graph->edge[v][j] < low_cost[j])		//判斷哪個邊最短
    			{	
    				low_cost[j] = m_graph->edge[v][j];						//更新最短邊
    			}
    		}
    	}
    }
    
  2. 克魯斯卡爾算法

    typedef struct
    {
    	int vex_a, vex_b;				//vex_a和vex_b爲一條邊上的兩個頂點
    	int wight;						//wight爲邊上的權重 
    }ROAD;
    
    /*
    method:
    	快速排序算法
    param:
    	road		邊的信息數組,對lows-highs之間的數據進行排序
    	lows		最低下標
    	highs		最高下標
    */
    void quick_Sort(ROAD road[], int lows,int  highs)
    {
    	ROAD temp = { 0 };
    	int low = lows, high = highs;
    
    	if (lows < highs)
    	{
    		temp = road[lows];
    		while (low < high)
    		{
    			while (low < high && road[high].wight >= temp.wight)		//從右往左掃描 
    				--high;
    			if (low < high)
    			{
    				road[low] = road[high];									//將 該值放到左邊
    				++low;													//向後移動一個位置
    			}
    			while (low < high && road[low].wight < temp.wight)
    				++low;
    			if (low < high)
    			{
    				road[high] = road[low];
    				--high;
    			}
    		}
    		road[low] = temp;												//將temp放到最終位置
    		quick_Sort(road, lows,low-1);									//遞歸,對temp左邊的關鍵字排序
    		quick_Sort(road, low+1, highs);									//遞歸,對temp右邊的關鍵字排序
    	}
    }
    
    int parents[MAXSIZE] = { 0 };				//建立並查集 ,下標 表示頂點編號,元素代表它的上級
    
    int get_Root(int a)
    {	
    	while (a != parents[a])					//如果傳進來的值已經是根結點,則直接輸出,如果不是,一直尋找它的父結點
    		a = parents[a];						//將父結點的值傳進去
    	return  a;								//返回找到的根結點,
    }
    /*
    method:
    	克魯斯卡爾算法
    param:
    	m_graph		無向有權圖
    	sum			最小生成樹的權值總和
    	road		road爲結點之間的信息
    */
    void  Kruskal(MGRAPH * m_graph,int *sum,ROAD *road)
    {
    	int i = 0;
    	int  vex_num = 0, edge = 0, vex_a = 0, vex_b = 0;	//分別定義 頂點數,邊數,兩個結點vex_a,vex_b
    	vex_num = m_graph->n;								//獲取結點數
    	edge = m_graph->e;									//獲取邊數
    	*sum = 0;											//最小生成樹權值清零
    	for (i = 0; i < vex_num; i++)						//初始化並查集
    	{
    		parents[i] = i;									//剛開始,默認每個頂點都沒有聯繫,每個頂點都是一個集合	
    	}
    	quick_Sort(road, 0, m_graph->e - 1);				//將權值從小到大排序,使用的是快速排序法
    	for (i = 0; i < edge; ++i)
    	{
    		vex_a = get_Root(road[i].vex_a);				//取出最小的權值的邊相連的結點A	
    		vex_b = get_Root(road[i].vex_b);				//取出最小的權值的邊相連的結點B
    		if (vex_a != vex_b)								
    		{
    			parents[vex_a] = vex_b;						//將頂點合併進同一個樹中,因爲上面road裏面的兩個邊已經直到是連接的了,所以這裏合併進一個樹中
    			(*sum) += road[i].wight;
    		}
    	}
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章