06 圖

06 圖

在這裏插入圖片描述

定義

圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示爲:G(V,E),其中,G表示一個圖,V是圖G中頂點的集合,E是圖G中邊的集合。

1. 在圖中數據元素,我們稱之爲頂點(Vertex)
2. 在圖中,任意兩個頂點之間都可能有關係,頂點之間的邏輯關係用邊來表示,邊集可以是空的。
  1. 各種圖

    1. 無向邊:

    若頂點vI到vj之間的邊沒有方向,則稱這條邊爲無向邊(Edge),用無序偶對(vI,vj)來表示。如果圖中任意兩個頂點之間的邊都是無向邊,則稱該圖爲無向圖(Undirected graphs)。

    1. 有向邊:

    若從頂點vI到vj的邊有方向,則稱這條邊爲有向邊,也稱爲弧(Arc)。如果圖中任意兩個頂點之間的邊都是有向邊,則稱該圖爲有向圖(Directed graphs)

    1. 無向完全圖:

    在無向圖中,如果任意兩個頂點之間都存在邊,則稱該圖爲無向完全圖

    1. 有向完全圖

    在有向圖中,如果任意兩個頂點之間都存在方向互爲相反的兩條弧,則稱該圖爲有向完全圖。

    有些圖的邊或弧具有與它相關的數字,這種與圖的邊或弧相關的數叫做權(Weight)。這些權可以表示從一個頂點到另一個頂點的距離或耗費。這種帶權的圖通常稱爲網(Network)

  2. 圖的頂點與邊間關係

    1. 路徑的長度:

    是路徑上的邊或弧的數目

  3. 連通圖相關術語

    1. 連通圖:

    在無向圖G中,如果從頂點v到頂點v’有路徑,則稱v和v’是連通圖。如果對於圖中任意兩個頂點vi、vj屬於V,vi和vj都是連通的,則稱G是連通圖(Connected Graph )

    1. 強連通圖:

    在有向圖G中,如果對於每一對vi、vj屬於V、vi不等於vj,從vi到vj和從vj到vi都存在路徑,則稱G是強連通圖。有向圖中的極大強連通子圖稱做有向圖的強連通分量。

  4. 圖的定義與術語總結

圖的存儲結構

ADT圖(Graph)

Data
	頂點的有窮非空集合和邊的集合
Operation

	CeateGraph(*G, V, VR):按照頂點集V和邊弧集VR的定義構造圖G
	DestroyGraph(*G):圖G存在則銷燬 
	LocateVex(G, u):若圖G中存在頂點u,則返回圖中的位置
	PutVex(G, v, value):將圖G中的頂點v賦值value
	FirstAdjVex(G, *v):返回頂點v的一個鄰接頂點,若頂點在G中無鄰接頂點返回空
	NextAdjVex(G, v, *w):返回頂點v相對於頂點w的下一個鄰接頂點,若w是v的最後一個鄰接點則返回“空”
	InsertVex(*G, v):在圖中增添新頂點v
	DeleteVex(*G, v):刪除圖G中頂點v及其相關的弧
	InsertArc(*G, v, w):在圖G中增添弧<v,w>,若G是無向圖,還需要增添對稱弧<w,v>。
	DeleteArc(*G, v, w):在圖G中刪除弧<v,w>,若G是無向圖,則還刪除對稱弧<w,v>
	DFSTraverse(G):對圖G中進行深度優先遍歷,在遍歷過程對每個頂點調用
	HFSTraverse(G):在圖G中進行廣度優先遍歷,在遍歷過程對每個頂點調用。

endADT
  1. 鄰接矩陣(Adjacency Matrix)

    圖的鄰接矩陣存儲方式使用兩個數組來表示圖。一個以爲數組存儲圖中頂點信息,一個二維數組(稱爲鄰接矩陣)存儲圖中的邊或弧的信息。

     #define MAXVEX 100 /* 最大頂點數,應由用戶定義 */
     #define INFINITY 65535
     
     typedef int Status;	/* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
     typedef char VertexType; /* 頂點類型應由用戶定義  */
     typedef int EdgeType; /* 邊上的權值類型應由用戶定義 */
     typedef struct
     {
     	VertexType vexs[MAXVEX]; /* 頂點表 */
     	EdgeType arc[MAXVEX][MAXVEX];/* 鄰接矩陣,可看作邊表 */
     	int numNodes, numEdges; /* 圖中當前的頂點數和邊數  */
     }MGraph;
    

    構造鄰接矩陣:時間複雜度:O(n2

     /* 建立無向網圖的鄰接矩陣表示 */
     void CreateMGraph(MGraph *G)
     {
     	int i,j,k,w;
     	printf("輸入頂點數和邊數:\n");
     	scanf("%d,%d",&G->numNodes,&G->numEdges); /* 輸入頂點數和邊數 */
     	for(i = 0;i <G->numNodes;i++) /* 讀入頂點信息,建立頂點表 */
     		scanf(&G->vexs[i]);
     	for(i = 0;i <G->numNodes;i++)
     		for(j = 0;j <G->numNodes;j++)
     			G->arc[i][j]=INFINITY;	/* 鄰接矩陣初始化 */
     	for(k = 0;k <G->numEdges;k++) /* 讀入numEdges條邊,建立鄰接矩陣 */
     	{
     		printf("輸入邊(vi,vj)上的下標i,下標j和權w:\n");
     		scanf("%d,%d,%d",&i,&j,&w); /* 輸入邊(vi,vj)上的權w */
     		G->arc[i][j]=w; 
     		G->arc[j][i]= G->arc[i][j]; /* 因爲是無向圖,矩陣對稱 */
     	}
     }
    
  2. 鄰接表

    我們把數組與鏈表相結合的存儲方式稱爲鄰接表(Adjacency List)

    鄰接表的結點定義

     typedef char VertexType; /* 頂點類型應由用戶定義 */
     typedef int EdgeType; /* 邊上的權值類型應由用戶定義 */
     
     typedef struct EdgeNode /* 邊表結點  */
     {
     	int adjvex;    /* 鄰接點域,存儲該頂點對應的下標 */
     	EdgeType info;		/* 用於存儲權值,對於非網圖可以不需要 */
     	struct EdgeNode *next; /* 鏈域,指向下一個鄰接點 */
     }EdgeNode;
     
     typedef struct VertexNode /* 頂點表結點 */
     {
     	VertexType data; /* 頂點域,存儲頂點信息 */
     	EdgeNode *firstedge;/* 邊表頭指針 */
     }VertexNode, AdjList[MAXVEX];
     
     typedef struct
     {
     	AdjList adjList; 
     	int numNodes,numEdges; /* 圖中當前頂點數和邊數 */
     }GraphAdjList;
    

    鄰接表的創建:對於n個頂點和e條邊來說,本算法的時間複雜度爲O(n+e)

     /* 建立圖的鄰接表結構 */
     void  CreateALGraph(GraphAdjList *G)
     {
     	int i,j,k;
     	EdgeNode *e;
     	printf("輸入頂點數和邊數:\n");
     	scanf("%d,%d",&G->numNodes,&G->numEdges); /* 輸入頂點數和邊數 */
     	for(i = 0;i < G->numNodes;i++) /* 讀入頂點信息,建立頂點表 */
     	{
     		scanf(&G->adjList[i].data); 	/* 輸入頂點信息 */
     		G->adjList[i].firstedge=NULL; 	/* 將邊表置爲空表 */
     	}
     	
     	
     	for(k = 0;k < G->numEdges;k++)/* 建立邊表 */
     	{
     		printf("輸入邊(vi,vj)上的頂點序號:\n");
     		scanf("%d,%d",&i,&j); /* 輸入邊(vi,vj)上的頂點序號 */
     		e=(EdgeNode *)malloc(sizeof(EdgeNode)); /* 向內存申請空間,生成邊表結點 */
     		e->adjvex=j;					/* 鄰接序號爲j */                         
     		e->next=G->adjList[i].firstedge;	/* 將e的指針指向當前頂點上指向的結點 */
     		G->adjList[i].firstedge=e;		/* 將當前頂點的指針指向e */               
     		
     		e=(EdgeNode *)malloc(sizeof(EdgeNode)); /* 向內存申請空間,生成邊表結點 */
     		e->adjvex=i;					/* 鄰接序號爲i */                         
     		e->next=G->adjList[j].firstedge;	/* 將e的指針指向當前頂點上指向的結點 */
     		G->adjList[j].firstedge=e;		/* 將當前頂點的指針指向e */               
     	}
     }
    
  3. 十字鏈表(Orthogonal List)

    把鄰接表和逆鄰接表結合起來,解決展示有向圖的問題,就有了十字鏈表

  4. 鄰接多重表

  5. 邊集數組

圖的遍歷

從圖中某一頂點出發訪遍圖中其餘頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷(Traversing Graph)

  1. 深度優先遍歷

    深度優先遍歷(Depth_First_Search),也有稱爲深度優先搜索,簡稱爲DFS。深度優先遍歷其實就是一個遞歸的過程,其實也是一個樹的前序遍歷過程。

    鄰接矩陣的深度優先遍歷

     /* 鄰接矩陣的深度優先遞歸算法 */
     void DFS(MGraph G, int i)
     {
     	int j;
      	visited[i] = TRUE;
      	printf("%c ", G.vexs[i]);/* 打印頂點,也可以其它操作 */
     	for(j = 0; j < G.numVertexes; j++)
     		if(G.arc[i][j] == 1 && !visited[j])
      			DFS(G, j);/* 對爲訪問的鄰接頂點遞歸調用 */
     }
     
     /* 鄰接矩陣的深度遍歷操作 */
     void DFSTraverse(MGraph G)
     {
     	int i;
      	for(i = 0; i < G.numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有頂點狀態都是未訪問過狀態 */
     	for(i = 0; i < G.numVertexes; i++)
      		if(!visited[i]) /* 對未訪問過的頂點調用DFS,若是連通圖,只會執行一次 */ 
     			DFS(G, i);
     }
    

    鄰接表的深度優先遍歷

     /* 鄰接表的深度優先遞歸算法 */
     void DFS(GraphAdjList GL, int i)
     {
     	EdgeNode *p;
      	visited[i] = TRUE;
      	printf("%c ",GL->adjList[i].data);/* 打印頂點,也可以其它操作 */
     	p = GL->adjList[i].firstedge;
     	while(p)
     	{
      		if(!visited[p->adjvex])
      			DFS(GL, p->adjvex);/* 對爲訪問的鄰接頂點遞歸調用 */
     		p = p->next;
      	}
     }
     
     /* 鄰接表的深度遍歷操作 */
     void DFSTraverse(GraphAdjList GL)
     {
     	int i;
      	for(i = 0; i < GL->numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有頂點狀態都是未訪問過狀態 */
     	for(i = 0; i < GL->numVertexes; i++)
      		if(!visited[i]) /* 對未訪問過的頂點調用DFS,若是連通圖,只會執行一次 */ 
     			DFS(GL, i);
     }
    

    總結:對於兩個不同存儲結構的深度優先遍歷算法,對於n個頂點e條邊的圖來說,鄰接矩陣由於是二維數組,要查找每個頂點的鄰接點需要訪問矩陣中的所有元素,因此都需要O(n2)的時間。而鄰接表做存儲結構時,找鄰接點所需要的時間取決於頂點和邊的數量,所以是O(n+e)。顯然對於點多邊少的稀疏圖來說,鄰接表的結構使得算法在時間效率上大大提高。

  2. 廣度優先遍歷

    廣度優先遍歷(Breadth_First_Search),又稱爲廣度優先搜索,簡稱BFS。

    鄰接矩陣的廣度優先遍歷

     /* 鄰接矩陣的廣度遍歷算法 */
     void BFSTraverse(MGraph G)
     {
     	int i, j;
     	Queue Q;
     	for(i = 0; i < G.numVertexes; i++)
            	visited[i] = FALSE;
         InitQueue(&Q);		/* 初始化一輔助用的隊列 */
         for(i = 0; i < G.numVertexes; i++)  /* 對每一個頂點做循環 */
         {
     		if (!visited[i])	/* 若是未訪問過就處理 */
     		{
     			visited[i]=TRUE;		/* 設置當前頂點訪問過 */
     			printf("%c ", G.vexs[i]);/* 打印頂點,也可以其它操作 */
     			EnQueue(&Q,i);		/* 將此頂點入隊列 */
     			while(!QueueEmpty(Q))	/* 若當前隊列不爲空 */
     			{
     				DeQueue(&Q,&i);	/* 將隊對元素出隊列,賦值給i */
     				for(j=0;j<G.numVertexes;j++) 
     				{ 
     					/* 判斷其它頂點若與當前頂點存在邊且未訪問過  */
     					if(G.arc[i][j] == 1 && !visited[j]) 
     					{ 
      						visited[j]=TRUE;			/* 將找到的此頂點標記爲已訪問 */
     						printf("%c ", G.vexs[j]);	/* 打印頂點 */
     						EnQueue(&Q,j);				/* 將找到的此頂點入隊列  */
     					} 
     				} 
     			}
     		}
     	}
     }
    

    鄰接表的廣度優先遍歷

     /* 鄰接表的深度優先遞歸算法 */
     void DFS(GraphAdjList GL, int i)
     {
     	EdgeNode *p;
      	visited[i] = TRUE;
      	printf("%c ",GL->adjList[i].data);/* 打印頂點,也可以其它操作 */
     	p = GL->adjList[i].firstedge;
     	while(p)
     	{
      		if(!visited[p->adjvex])
      			DFS(GL, p->adjvex);/* 對爲訪問的鄰接頂點遞歸調用 */
     		p = p->next;
      	}
     }
     
     /* 鄰接表的深度遍歷操作 */
     void DFSTraverse(GraphAdjList GL)
     {
     	int i;
      	for(i = 0; i < GL->numVertexes; i++)
      		visited[i] = FALSE; /* 初始所有頂點狀態都是未訪問過狀態 */
     	for(i = 0; i < GL->numVertexes; i++)
      		if(!visited[i]) /* 對未訪問過的頂點調用DFS,若是連通圖,只會執行一次 */ 
     			DFS(GL, i);
     }
    

最小生成樹

我們把構造連通網的最小代價生成樹稱爲最小生成樹(Minimum Cost Spanning Tree)

  1. 普利姆(Prim)算法

    普利姆算法生成最小生成樹

     /* Prim算法生成最小生成樹  */
     void MiniSpanTree_Prim(MGraph G)
     {
     	int min, i, j, k;
     	int adjvex[MAXVEX];		/* 保存相關頂點下標 */
     	int lowcost[MAXVEX];	/* 保存相關頂點間邊的權值 */
     	lowcost[0] = 0;/* 初始化第一個權值爲0,即v0加入生成樹 */
     			/* lowcost的值爲0,在這裏就是此下標的頂點已經加入生成樹 */
     	adjvex[0] = 0;			/* 初始化第一個頂點下標爲0 */
     	for(i = 1; i < G.numVertexes; i++)	/* 循環除下標爲0外的全部頂點 */
     	{
     		lowcost[i] = G.arc[0][i];	/* 將v0頂點與之有邊的權值存入數組 */
     		adjvex[i] = 0;					/* 初始化都爲v0的下標 */
     	}
     	for(i = 1; i < G.numVertexes; i++)
     	{
     		min = INFINITY;	/* 初始化最小權值爲∞, */
     						/* 通常設置爲不可能的大數字如32767、65535等 */
     		j = 1;k = 0;
     		while(j < G.numVertexes)	/* 循環全部頂點 */
     		{
     			if(lowcost[j]!=0 && lowcost[j] < min)/* 如果權值不爲0且權值小於min */
     			{	
     				min = lowcost[j];	/* 則讓當前權值成爲最小值 */
     				k = j;			/* 將當前最小值的下標存入k */
     			}
     			j++;
     		}
     		printf("(%d, %d)\n", adjvex[k], k);/* 打印當前頂點邊中權值最小的邊 */
     		lowcost[k] = 0;/* 將當前頂點的權值設置爲0,表示此頂點已經完成任務 */
     		for(j = 1; j < G.numVertexes; j++)	/* 循環所有頂點 */
     		{
     			if(lowcost[j]!=0 && G.arc[k][j] < lowcost[j]) 
     			{/* 如果下標爲k頂點各邊權值小於此前這些頂點未被加入生成樹權值 */
     				lowcost[j] = G.arc[k][j];/* 將較小的權值存入lowcost相應位置 */
     				adjvex[j] = k;				/* 將下標爲k的頂點存入adjvex */
     			}
     		}
     	}
     }
    
  2. 克魯斯卡爾(Kruskal)算法

最短路徑

  1. 迪斯杰特拉(Dijkstra)算法
  2. 佛洛依德(Floyd)算法

拓撲排序

  1. 介紹
  2. 算法

關鍵路徑

  1. 原理
  2. 算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章