《大話數據結構》讀書筆記(三)

第7章 圖(Graph)


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


無向邊(Edge),用無序偶對(Vi,Vj)來表示。

有向邊,也稱爲弧(Arc),用有序偶<Vi,Vj>來表示,Vi稱爲弧尾(Tail),Vj稱爲弧頭(Head),圖形顯示爲Vi---->Vj。


圖的存儲結構

1、鄰接矩陣(Adjacency Matrix)

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

typedef char VertexType;           /* 頂點類型應由用戶定義 */

typedef int EdgeType;              /* 邊上的權值類型應由用戶定義 */

#define MAXVEX 100                 /* 最大頂點數,應由用戶定義 */

#define INFINITY 65535             /* 用65535來代表無窮大 */

typedef struct
{
	VertextType vexs[MAXVEX];      /* 頂點表 */

	EdgeType arc[MAXVEX][MAXVEX];  /* 鄰接矩陣,可看做邊表 */

	int numVertexes, numEdges;     /* 圖中當前的頂點數和邊數 */

}MGraph;


2、鄰接表(adjacency List)

臨界表頂點採用一個一維數組存儲,另外對於頂點數組中,每個數據元素還需要存儲指向第一個鄰接點的指針,以便於查找該頂點的邊信息。

頂點的所有鄰接點構成一個線性表,用單鏈表存儲,無向圖爲頂點的邊表,有向圖爲頂點弧尾的出邊表。

typedef char VertexType;           /* 頂點類型應由用戶定義 */

typedef int EdgeType;              /* 邊上的權值類型應由用戶定義 */

typedef struct EdgeNode            /* 邊表結點 */
{
	int adjvex;                    /* 鄰接點域,存儲該頂點對應的下標 */

	EdgeType weight;               /* 用於存儲權值,對於非網圖可以不需要 */

	struct EdgeNode *next;         /* 鏈域,指向下一個鄰接點 */

}EdgeNode;

typedef struct VertexNode          /* 頂點表結點 */
{
	VertexType data;               /* 頂點域,存儲頂點信息 */

	EdgeNode* firstedge;           /* 邊表頭指針 */

}VertexNode, AdjList[MAXVEX];

typedef struct
{
	AdjList adjList;

	int numVertexes,numEdges;      /* 圖中當前頂點數和邊數 */

}GraphAdjList;

3、十字鏈表(Orthogonal List)

對於有向圖來說,鄰接表只關心出度問題,入度必須要遍歷整個圖才能知道。有向圖中採用了十字鏈表的存儲方法。

結構定義書中沒有給出,整理如下

typedef char VertexType;           /* 頂點類型應由用戶定義 */

typedef int EdgeType;              /* 邊上的權值類型應由用戶定義 */

typedef struct ArcNode            /* 弧表結點 */
{
	int tailvex;                  /* 弧起點在頂點表的下標 */
	
	int headvex;                  /* 弧終點在頂點表的下標 */

	struct ArcNode *headlink;     /* 入邊表指針域,指向終點相同的下一條邊 */

	struct ArcNode *taillink;     /* 邊表指針域,指向起點相同的下一條邊 */

	EdgeType weight;               /* 用於存儲權值,對於非網圖可以不需要 */

}ArcNode;

typedef struct VertexNode          /* 頂點表結點 */
{
	VertexType data;               /* 頂點域,存儲頂點信息 */

	ArcNode* firstin;              /* 表示入邊頭指針,指向該頂點的入邊表中的第一個結點 */

	ArcNode* firstout;             /* 表示出邊頭指針,指向該頂點的出邊表中的第一個結點 */

}VertexNode, OrtList[MAXVEX];

typedef struct
{
	OrtList ortList;

	int numVertexes,numEdges;      /* 圖中當前頂點數和邊數 */

}GraphOrtList;

4、鄰接多重表(Adjacency MulList)

鄰接多重表示對於無向圖的鄰接表的優化。

typedef char VertexType;         /* 頂點類型應由用戶定義 */

typedef int EdgeType;            /* 邊上的權值類型應由用戶定義 */

typedef emnu{ unvisited,visited} VisitIf;

typedef struct EBox
{
	VisitIf mark:                  /*訪問標記*/

	int ivex,jvex;                 /*該邊依附的兩個頂點的位置*/

	struct EBox ilink, jlink;      /*分別指向依附這兩個頂點的下一條邊*/

	InfoType info;                 /*該邊信息指針*/

}EBox;

typedef struct VexBox
{
	VertexType data;

	EBox fistedge;                 /*指向第一條依附該頂點的邊*/

}VexBox, Adjmulist[MAXVEX];

typedef struct
{
	Adjmulist adjmulist;

	int numVertexes,numEdges;      /* 無向圖中當前頂點數和邊數 */

}AMLGraph;

5、邊集數組

邊集數組是有兩個一維數組構成。一個是存儲頂點的信息;另一個是存儲邊的信息,這個邊數組每個數據元素 有一條邊的起點下標(begin)、終點下標(end)和權(weight)組成。


圖的遍歷

深度優先遍歷DFS(Depth_First_Search)

鄰接矩陣的深度優先遍歷算法,對於n個頂點e條邊的圖,要查找每個頂點的鄰接點需要訪問矩陣中的所有元素,因此需要O(n^2)的時間。

typedef int Boolean;      /* Boolean是布爾類型, 其值是true或false */

Boolen visited[MAX];     /* 訪問標誌的數組 */

/* 鄰接矩陣的深度優先遞歸算法 */

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(G, i);
}

鄰接表的深度優先遍歷算法,對於那個頂點e條邊的圖,找鄰接點所需的時間取決於頂點和邊的數量,O(n+e)。

/* 鄰接表的深度優先遞歸算法 */

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(GL, i);
}

廣度優先遍歷BFS(Breadth_First_Search)

鄰接矩陣的廣度遍歷

/* 鄰接矩陣的廣度遍歷算法 */

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);
				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 BFSTraverse(GraphAdjList GL)
{
	int i;
	
	EdgeNode *p;
	
	Queue Q;
	
	for(i=0; i<GL->numVertexes; i++)
		visited[i] = false;
	
	InitQueue(&Q);
	
	for(i=0; i<GL->numVertexes; i++)
	{
		if (! visited[i])
		{
			visited[i] = true;
			
			printf("%c ",GL->adjList[i].data);
			
			EnQueue(&Q,i);
			
			while(! QueueEmpty(Q))
			{
				DeQueue(&Q,i);
				
				p = GL->adjList[i].firstedge;
				
				while(p)
				{
					if (! visited[p->adjvex])
					{
						visited[p->adjvex] = true;
						
						printf("%c ",GL->adjList[p->adjvex].data);
						
						EnQueue(&Q, p->adjvex);
					}
					
					p = p->next;
				}
			}
		}
	}
}

最小生成樹(Minimum Cost Spanning Tree)


最小生成樹:構造連通圖的最小代價生成樹


普利姆(Prim)算法

普利姆算法是以頂點爲起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。

/* Prim算法生成最小生成樹 */

void MiniSpanTree_Prim( MGraph G )
{
	int min, i, j, k;
	
	int adjvex[MAXVEX];     /* 保存相關頂點下標 */
	
	int lowcost[MAXVEX];    /* 保存相關頂點間邊的權值 */
	
	lowcost[0] = 0;         /* 初始化第一個權值爲0, 即V0加入生成樹 */
	
	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;             /* 初始化最小權值爲無窮大 */
		
		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)",adjvex[k],k);
		
		lowcost[k] = 0;             /* 將當前頂點的權值設置爲0, 表示此頂點已經完成任務 */
		
		for(j=1;j<G.numVertexes; j++) /* 循環所有頂點 */
		{
			/* 若下標爲k頂點各邊權值小雨此前這些頂點未被加入生成樹權值 */
			if (lowcost[j] != 0 && G.arc[k][j] < lowcost[j])
			{
				lowcost[j] = G.arc[k][j];  /* 將較小權值存入lowcost */
				
				adjvex[j] = k;             /* 將下標爲k的頂點存入adjvex */
			}
		}
	}	
}

克魯斯卡爾(Kruskal)算法

克魯斯卡爾以邊爲目標去構建最小生成樹,採用圖的存儲結構中的邊集數組結構。

/* 對邊集數組Edge結構的定義 */
typedef struct
{
	int begin;
	
	int end;
	
	int weight;
	
}Edge;

/* Kruskal算法生成最小生成樹 */

void MiniSpanTree_Kruskal (MGraph G) /* 生成最小生成樹 */
{
	int i, n, m;
	
	Edge edges[MAXEDGE];    /* 定義邊集數組 */
	
	int parent[MAXVEX];     /* 定義一數組用來判斷邊與邊是否形成環路 */
	
	/* 此處省略將鄰接矩陣G轉化爲邊集數組edges,並按權由小到大排列的代碼 */
	
	for (i=0; i<G.numVertexes; i++)	
		parent[i] = 0;        /* 初始化數組爲0 */
		
	for(i=0; i<G.numEdges; i++)  /* 循環每一條邊 */
	{
		n = Find(parent, edges[i].begin);
		
		m = Find(parent, edges[i].end);
		
		if (n != m)      /* 假如n與m不等,說明此邊沒有與現有生成樹形成環路 */
		{
			parent[n] = m; /* 將此邊的結尾頂點放入下標爲起點的parent中,表示此頂點已經在生成樹集合中 */
			
			printf("(%d,%d) %d ",edges[i].begin,edges[i].end,edges[i].weight);
		}
		
	}
}

int Find(int* parent, int f)  /* 查找連線頂點的尾部下標 */
{
	while(parent[f] > 0)
		f = parent[f];
	
	return f;
}


對比兩個算法,克魯斯卡爾算法主要是針對邊來展開,邊數少時效率會非常高,所有對稀疏圖有很大優勢;而普利姆算法對於稠密圖,即邊數非常多的情況會更好一些。


最短路徑


迪傑斯特拉(Dijkstra)算法

#define MAXVEX 9

#define INFINITY 65535

typedef int Pathmatirx[MAXVEX];      /* 用於存儲最短路徑下標的數組 */

typedef int ShortPathTable[MAXVEX];  /* 用於存儲到各點最短路徑的權值 */

/* Dijkstra算法,求有向圖G的V0頂點到其餘頂點v最短路徑P[v]及帶權長度D[v] */
void ShortestPath_Dijkstra(MGraph G, int v0, Pathmatirx* P, ShortPathTable* D)
{
	int v,w,k,min;
	
	int final[MAXVEX];   /* final[w]=1表示求得頂點V0至Vw的最短路徑 */
	
	for(v=0,v<G.numVertexes;v++)  /* 初始化數據 */
	{
		final[v] = 0;      /* 全部頂點初始化爲未知最短路徑狀態 */
		
		(*D)[v] = G.matirx[v0][v]; /* 將與V0點有連線的頂點加上權值 */
		
		(*P)[v] = 0;		   /* 初始化路徑數組P爲0 */
	}
	
	(*D)[v0] = 0;        /* V0至V0路徑爲0 */
	
	final[v0] = 1;       /* V0至V0不需要求路徑 */
	
	/* 開始主循環,每次求得V0到某個V頂點的最短路徑 */
	for(v=1;v<G.numVertexes; v++) 
	{
		min = INFINITY;
		
		for(w=0;w<G.numVertexes;w++)   /* 尋找離V0最近的頂點 */
		{
			if (! final[w] && (*D)[w]<min)
			{
				k = w;
				min = (*D)[w];             /* w頂點離V0頂點更近 */
			}
		}
		
		final[k] = 1;   /* 將目前找到的最近的頂點置爲1 */
		
		for(w=0;w<G.numVertexes;w++)  /* 修正當前最短路徑及距離 */
		{
			/* 如果經過v頂點的路徑比現在這條路徑的長度短的話 */
			if (! final[w] && (min+G.matirx[k][w]<(*D)[w]))
			{
				/* 說明找到了更短的路徑,修改D[w]和P[w] */
				(*D)[w] = min + G.matirx[k][w]; /* 修改當前路徑的長度 */
				
				(*P)[w] = k;
			}
		}
	}
}


弗洛伊德(Floyd)算法

typedef int Pathmatirx[MAXVEX][MAXVEX];

typedef int ShortPathTable[MAXVEX][MAXVEX];

/* Floyd 算法,求網圖G中各頂點v到其餘頂點w最短路徑P[v][w]及帶權長度D[v][w] */
void ShortestPath_Floyd(MGraph G, Pathmatirx *P, ShortPathTable *D)
{
	int v,w,k;
	
	/*初始化D與P */
	for(v=0;v<G.numVertexes;v++)
	{
		for(w=0;w<G.numVertexes;w++)
		{
			(*D)[v][w] = G.matirx[v][w];   /* D[v][w]值即爲對應點間的權值 */
			
			(*P)[v][w] = w;                /* 初始化 P */
		}
	}
	
	for(k=0;k<G.numVertexes;k++)
	{
		for(v=0;v<G.numVertexes;v++)
		{
			for(w=0;w<G.numVertexes;w++)
			{
				/* 如果經過下標爲k頂點路徑比原兩點間路徑更短 */
				/* 將當前兩點間權值設爲更小的一個 */
				if ((*D)[v][w] > (*D)[v][k]+(*D)[k][w])
				{
					(*D)[v][w] = (*D)[v][k]+(*D)[k][w];
					
					(*P)[v][w] = (*P)[v][k];   /* 路徑設置經過下標爲k的頂點 */
				}
			}
		}
	}
}

拓撲排序

AOV網:在一個表示工程的有向圖中,用頂點表示活動,用弧表示活動之間的優先關係。

拓撲排序:對一個有向圖構造拓撲序列的過程。

typedef struct EdgeNode  /* 邊表結點 */
{
	int adjvex;        /* 鄰接點域,存儲該頂點對應的下標 */
	
	int weight;        /* 用於存儲權值,對於非網圖可以不需要 */
	
	struct EdgeNode *next; /* 鏈域,指向下一個鄰接點 */
	
}EdgeNode;

typedef struct VertexNode  /* 頂點表結點 */
{
	int in;                  /* 頂點入度 */
	
	int data;                /* 頂點域,存儲頂點信息 */
	
	EdgeNode *firstedge;     /* 邊表頭指針 */
	
}VertexNode, AdjList[MAXVEX];

typedef struct
{
	AdjList adjList;
	
	int numVertexes, numEdges; /* 圖中當前頂點數和邊數 */
	
}graphAdjList,*GraphAdjList;

/* 拓撲排序, 若GL無迴路, 則輸出拓撲排序序列並返回OK, 若有迴路返回ERROR */
Status TopologicalSort(GraphAdjList GL)
{
	EdgeNode *e;
	
	int i,k,gettop;
	
	int top = 0;        /* 用於棧指針下標 */
	
	int count = 0;      /* 用於統計輸出頂點的個數 */
	
	int *stack;         /* 建棧存儲入度爲0的頂點 */
	
	stack = (int *)malloc(GL->numVertexes * sizeof(int));
	
	for(i=0;i<GL.numVertexes;i++)
		if (GL->adjList[i].in = 0)
			stack[++top] = i; /* 將入度爲0的頂點入棧 */
	
	while(top != 0)
	{
		gettop = stack[top--];  /* 出棧 */
		
		printf("%d -> ",GL->adjList[gettop].data);
		
		count++;  /* 統計輸出頂點數 */
		
		/*對此頂點弧表遍歷 */
		for(e=GL->adjList[gettop].firstedge; e; e=e->next)
		{
			k = e->adjvex;   
			
			if (!(--GL->adjList[k].in))  /* 將k號頂點鄰接點的入度減1 */
				
				stack[++top] = k;          /* 若爲0則入棧,以便於下次循環輸出 */
		}
	}
	
	if (count < GL->numVertexes)		/* 如果count小於頂點數,說明存在環 */
		return ERROR;
	else
		return OK;	
}

關鍵路徑

int *etv, *ltv;     /* 事件最早發生時間和最遲發生時間數組 */

int *stack2;        /* 用於存儲拓撲序列的棧 */

int top2;           /* 用於stack2的指針 */

/* 拓撲排序,用於關鍵路徑計算 */
Status TopologicalSort(GraphAdjList GL)
{
	EdgeNode *e;
	
	int i,k,gettop;
	
	int top = 0;        /* 用於棧指針下標 */
	
	int count = 0;      /* 用於統計輸出頂點的個數 */
	
	int *stack;         /* 建棧存儲入度爲0的頂點 */
	
	stack = (int *)malloc(GL->numVertexes * sizeof(int));
	
	for(i=0;i<GL.numVertexes;i++)
		if (GL->adjList[i].in = 0)
			stack[++top] = i; /* 將入度爲0的頂點入棧 */
	
	top2 = 0 ;  /* 初始化爲0 */
	
	etv = (int *)malloc(GL->numVertexes*sizeof(int));
	
	for(i=0;i<GL->numVertexes;i++)
		etv[i] = 0;  /* 初始化爲0 */
	
	stack2 = (int *)malloc(GL->numVertexes * sizeof(int));  /* 初始化 */
	
	while(top != 0)
	{
		gettop = stack[top--];  /* 出棧 */
		
		printf("%d -> ",GL->adjList[gettop].data);
		
		count++;  /* 統計輸出頂點數 */
		
		stack2[++top2] = gettop;  /* 將彈出的頂點序號壓入拓撲序列的棧 */
		
		/*對此頂點弧表遍歷 */
		for(e=GL->adjList[gettop].firstedge; e; e=e->next)
		{
			k = e->adjvex;   
			
			if (!(--GL->adjList[k].in))  /* 將k號頂點鄰接點的入度減1 */
				
				stack[++top] = k;          /* 若爲0則入棧,以便於下次循環輸出 */
			
			if ((etv[gettop]+e->weight)>etv[k])  /* 求各頂點事件最早發生時間值 */
				etv[k] = etv[gettop]+e->weight;
		}
	}
	
	if (count < GL->numVertexes)		/* 如果count小於頂點數,說明存在環 */
		return ERROR;
	else
		return OK;	
}

/* 求關鍵路徑,GL爲有向圖,輸出GL的各項關鍵活動 */
void CriticalPath(GraphAdjList GL)
{
	EdgeNode *e;
	
	int i,gettop,k,j;
	
	int ete,lte;    /* 聲明活動最早發生時間和最遲發生時間變量 */
	
	TopologicalSort(GL);  /* 求拓撲序列,計算數組etv和stack2的值 */
	
	ltv=(int *)malloc(GL->numVertexes*sizeof(int));  /* 事件最晚發生時間 */
	
	for(i=0;i<GL->numVertexes;i++)
		ltv[i] = etv[GL->nmVertexes-1];      /* 初始化ltv */
	
	while(top2 != 0)                       /* 計算ltv */
	{
		gettop = stack2[top--];              /* 將拓撲序列出棧,後進先出 */
		
		for(e = GL->adjList[gettop].firstedge; e; e->next)
		{
			/* 求各頂點事件的最遲發生時間ltv值 */
			k = e->adjvex;
			
			/* 求各頂點事件最晚發生時間ltv */
			if (ltv[k]-e->weight<ltv[gettop])
				ltv[gettop] =  ltv[k]-e->weight;
			
		}
	}
	
	/* 求ete,lte和關鍵活動 */
	for(j=0;j<GL->numVertexes; j++)
	{
		for(e=GL->adjList[j].firstedge; e; e->next)
		{
			k = e->adjvex;
			
			ete = etv[j];              /* 活動最早發生時間 */
			
			lte = ltv[k] - e->weight;  /* 活動最遲發生時間 */
			
			if(ete == lte)             /* 兩者相等即在關鍵路徑上 */
			{
				printf("<v%d,v%d> length: %d, ",
				GL->adjList[j].data,GL-.adjList[k].data,e->weight);
			}
		}
	}
}




發佈了11 篇原創文章 · 獲贊 11 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章