思維導圖
重要概念
圖的存儲結構
鄰接矩陣
1.其表示是唯一的,一維數組代表頂點,二維數組存儲圖中的邊或弧的信息。
2.對於含n個頂點的有向圖還是無向圖,無論邊的數目是多少,其存儲空間均為O(n^2)。
3.對於有向圖,鄰接矩陣數組第i行(或第i列)非0元素、非∞元素的個數正好是頂點i的出度(入度).
4.有向圖的鄰接矩陣要有頂點數組
5.常用於提取邊權值的算法
鄰接表
1.其表示是不唯一的。
2.適用於提取某個頂點的所有鄰接點的算法。
3.鄰接表畫法有三種分別是:無向圖、有向圖、有向帶權圖。
圖的遍歷
以鄰接表為存儲結構的DFS算法
int visited[MAX]={0};//全局數組
void DFS(AdjGraph *G,int V)
{ArcNode *p;
visited[v]=1;//置以訪問標記
printf("%d",v);
p=G->adjust[v].firstarc;//輸出被訪問頂點編號
while(!p==NULL)
{
if(visited[p->adjvex]==0)
DFS(G,p->adjvex);
p=p->nextarc//p指向下一個鄰接點
}
}
用圖遍歷求迷宮問題
用棧搜索迷宮路徑使用DFS算法,用隊列搜索迷宮路徑使用BFS算法。
最小生成樹
Prim算法(加點圖)
- 該算法時間復雜度為 O(n^2),與圖結構的邊數無關,適合稠密圖。
算法步驟
- 將初始化的頂點v加入U
- 重復以下步驟(n-1)次,使其他(n-1)個頂點加入U中
- 將頂點最小邊選出加入TE,該邊在U-V中頂點為k,將k加入U中
2.考察U-V中所有頂點,更新每組權值最小的邊,諾小於之前邊權值則該邊作為侯選邊
代碼實現
void MiniSpanTree_Prim(MGraph G)
{
int min;
int i,j,k;
int closest[MAXV]; //存貯最小邊在 U 中的頂點
int lowcost[MAXV]; //存儲最小邊上的權值
for(i = 1; i < G.n; i++) //初始化,給lowcost[]、closest[]置初值
{
lowcost[i] = G.edges[0][i]; //將 v0 頂點存在邊的權值存入 lowcost
closest[i] = 0; //初始化都為 v0 的下標
}
for(i = 1; i < G.n; i++)//找出(n-1)個頂點
{
min = INFINITY; //初始化最小權值為 ∞
j = 1;
k = 0;
while(j < G.n) //循環全部頂點
{
if(lowcost[j] != 0 && lowcost[j] < min)///如果權值不為0,並且權值小於min
{
min = lowcost[j]; //令當前權值為最小值
k = j; //當前最小值的下標賦給 k
}
j++;
}
cout <<closest[k] << k; //輸出當前頂點邊中權值最小邊
lowcost[k] = 0; //表示該節點已添加入生成樹
for(j = 1; j < G.n; j++)
{
if(lowcost[j] != 0 && G.edges[k][j] < lowcost[j])
{ //若下標為 k 的頂點各邊權值小於當前頂點未被加入生成樹權值
lowcost[j] = G.edges[k][j]; //將最小權值存入 lowcost
closest[j] = k; //將下標為 k 的頂點存入 closest
}
}
}
}
Kruskal算法(加邊圖)
- 該算法時間復雜度為O(e㏒2e),且算法執行時間僅有圖的邊數有關,更適合於稀疏圖。
算法步驟
- 將邊集數組權值按小到大排序
- 在邊集數組中選取邊(i,j),判斷是否產生回路,即查找頂點數組Vi,Vj所在連通分量n,m是否相等,諾不相等則不存在回路;諾相等,則選擇下一條權值最小的邊。
代碼實現
void MiniSpanTree_Kruskal(MGraph G)
{
int i,n,m;
Edge edges[MAXE]; //定義邊集數組
int parent[MAXV]; //判斷回路的並查集
for(i = 0; i < G.n; i++)//循環每一條邊
{
n = Find(parent, edges[i].begin);
m = Find(parent, edges[i].end);
if(m != n) //若 n 和 m 不相等,則說明回路不存在(這條邊的兩端點不在同一個連通)
{
parent[n] = m; //將此邊結尾頂點放入下標為起點的 parent 中
cout << edges[i].begin << edges[i].end << edges[i].weight << endl;
}
}
}
int Find(int *parent, int f) //查連線頂點的尾部下標
{
while(parent[f] > 0)
f = parent[f];
return f;
}
最短路徑
Dijkstra算法
- 算法時間復雜度為O(n^2)
算法設計
1.S[]:以求出最短路徑的頂點集合
2.path[]:從v0到終點vi的最短路徑上,vi的前驅節點;若v0到vi沒有路徑則用-1作為前驅節點序號
3.D[]:是記錄v0到vi的最短路徑的長度,若v0到vi有直接路徑則其初態為弧的權值,若無則為 ∞
代碼實現
void ShortestPath_DIJ(MGraph g, int v0)
{ //初始化
int v,min;
int Path[MAXV],S[MAXV],D[MAXV];
for(int i = 0; i < g.n; i++)
{
S[i] = false; //S 初始化為空集
D[i] = g.edges[v0][i]; //將 v0 到各個終點的弧的權值
if(D[i] <MAXINT) //v0 到 i 的弧存在,設置前驅為 v0
{
Path[i] = v0;
}
else //v0 到 i 的弧不存在,設置前驅為 -1
{
Path[i] = -1;
}
}
S[v0] = true; // v0 加入 S
D[v0] = 0; //源點到源點本身的距離為 0
/*初始化結束,開始最短路徑求解*/
for(int i = 1; i < g.n; i++) //依次考慮剩余的 n - 1 個頂點
{
min = MAXINT;
for(int j = 0; j < g.n; j++)
{
if(S[j] == false && D[j] < min) //若 vj 未被添加入 S 且路徑最短
{
v = j; //選擇當前最短路徑,終點是 v
min = D[j];
}
}
s[v] = true; //將 v 加入 S
for(int j = 0; j < g.n; j++)
{
if(S[j] == false && (D[v] + g.edges[v][j] < D[j])) //判斷是否要更新路徑
{
D[j] = D[v] + g.edges[v][j];
Path[j] = v; //修改 vj 的前驅為 v
}
}
}
}
Floyd算法
- 其時間復雜度為O(n^3)
算法實現
1.圖用鄰接矩陣存儲
2.二維數組D[][]:存放頂點對之間最短路徑長度
3.path[][]:從Vi到Vj最短路徑上,Vj的前驅節點序號
代碼實現
void ShortestPath_Floyd(MGraph g)
{
int Path[MAXV][MAXV]; //最短路徑上頂點 vj 的前驅節點的序號
int [MAXV][MAXV]; //記錄頂點 vi 和 vj 之間的最短路徑長度
int i,j,k;
for(int i = 1; i < g.n; i++)
{
for(int j = 1; j < g.n; j++)
{
D[i][j] = g.edges[i][j];
if(D[i][j] < MAXINT && i != j)
{
Path[i][j] = i; //若 i 和 j 之間有弧,則將 j 的前驅置為 i;
}
else
{
Path[i][j] = -1; //若 i 和 j 之間沒有弧,則將 j 的前驅置為 -1;
}
}
}
for(int k = 0; k < g.n; k++)//依次考察所有頂點
{
for(int i = 1; i < g.n; i++)
{
for(int j = 1; j < g.n; j++)
{
if(D[i][k] + D[k][j] < D[i][j]) //從 i 經過 k 到 j 的路徑更短
{
D[i][j] = D[i][k] + D[k][j]; //修改路徑長度
Path[i][j] = Path[k][j]; //修改最短路徑
}
}
}
}
Dispath(g,D,path)//函數輸出最短路徑
}
拓撲排序
1.從有向圖中選取一個沒有前驅的頂點並輸出他
2.從圖中刪去該頂點,及該頂點出發的邊
3.重復上面步驟直至圖中不存在沒有前驅的頂點
AOE網和關鍵路徑
1.事件的最早發生時間ve=頂點v的最早發生時間。
2.事件的最晚發生時間vl=即頂點v的最晚發生時間(每個事件最晚需要開始的時間)。
3.活動的最早開始時間e=弧ai的最早發生時間。
4.活動的最晚開始晚時間l=弧ai的最晚發生時間。
- 關鍵活動:l(i) = e(i)的活動
- 求關鍵活動
*e(i) = ve(j)
*l(i) = vl(k) - dut<j,k>//從后面頂點的vl 減去<j,k>邊的權值
疑難問題及解決方案
PTA題集
解決辦法
- 之前少考慮了當最短路徑的長度不變時,花費減小,要更新cost[]的值
Dijkstra算法理解不透徹
- 對於如何檢查路徑是否有回路的代碼不理解
解決辦法
查找網上內容,學會了如何通過s1和s2的計較,看他們是否相同,來判斷路徑中是否有回路,這個方法也可以用在拓撲排序中。