淺談數據結構-關鍵路徑


上一章節講解了拓撲排序問題,拓撲排序是解決一個工程能否順序解決的問題,本質是一個廣度層次遍歷的過程,通過記錄頂點入度問題,進行逐步輸出的工作。在實際生活中,往往是求解工程完成需要最短時間問題。比如生活中生產一輛汽車,需要生產各種各樣的零件,最終組裝成車。例如生產輪子0.5天,發動機3天,底盤2天,其他部件2天,集中全部零件0.5天,組裝需要2天。請問組裝一輛汽車,最短需要多長時間。根據前面描述,我們構造這樣的AOV網絡圖,一看便知。

 

通過網絡中,我們很清晰的知道,關鍵路徑是5.5,如果發動機提高效率,那么關鍵路徑就是4.5。對於以項目關鍵問題就是尋找關鍵路徑,也許對於上圖很容易尋找,而對於下圖,就需要關鍵路徑算法,進行計算尋找。

 

關鍵路徑:路徑上各個活動所持續的時間之和為路徑長度,從源點到匯點具有最大長度的路徑叫關鍵路徑。

一、算法思想

在介紹關鍵路徑的時,先介紹幾個概念

  1. 事件最早發生時間ve(earliest time of vertex):頂點vk的最早發生時間,從始點到vi的最長(加權)路徑長度。
  2. 事件最晚發生時間vl(lastest time of vertex):頂點vk的最晚發生時間,在不拖延整個工期的條件下,vi的可能的最晚發生時間。。
  3. 活動最早發生時間e(earliest time of edge):活動ak的最早發生時間,等於事件vi的最早發生時間。
  4. 活動最晚發生時間l(lastest time of edge):弧ak的最晚發生時間,在不拖延整個工期的條件下,該活動的允許的最遲開始時間。

關鍵路勁算法原理:先獲取ve和vl,通過上述兩者獲取e和l,然后判斷e和l是否相等來判斷a是否是關鍵活動。

二、算法分析

關鍵路徑算法是一種典型的動態規划法,設圖G=(V, E)是個AOE網,結點編號為1,2,...,n,其中結點1與n 分別為始點和終點,ak=<i, j>∈E是G的一個活動。算法關鍵是確定活動的最早發生時間和最晚發生時間,進而獲取頂點的最早開始時間和最晚開始時間。

根據前面給出的定義,可推出活動的最早及最晚發生時間的計算方法:

e(k) = ve(i)

l(k) = vl(j) - len(i,j)

結點的最早發生時間的計算,需按拓撲次序遞推:

ve(1) = 0

ve(j) = MAX{ etv(i)+len(i, j) }

對所有<i,j> ∈E的i  結點的最晚發生時間的計算,需按逆拓撲次序遞推:

vl(n) = ve(n)

vl(i) = MIN{vl(j) - len(i, j)} 對所有<i,j>∈E的j

這種計算方法, 依賴於拓撲排序, 即計算ve( j) 前,應已求得j 的各前趨結點的ve值,而計算vl(i)前,應已求得i的各后繼結點的vl值。ve的計算可在拓撲排序過程中進行,即在每輸出一個結點i后,在刪除i的每個出邊<i,j>(即入度減1)的同時,執行

if ( ve[i]+len(i,j)) > ve[j] )

ve[j] = ve[i] + len(i,j)

三、例圖解析

所以根據算法:

四、代碼

//改進的拓撲排序,用於關鍵路徑算法
void GraphData::TopoLogicalSortAdv(GraphListAdv *pList)
{
    EdgeNode *e;
    int ncount = 0;  //統計輸出頂點個數,用於判斷是否有環
    int nIndex ; // y用於保存度為0的坐標
    stack<int> staNode;
    
    for (int i = 0;i < pList->numVertess; i++)
    {
        if (pList->vertexList[i].in == 0)
        {
            staNode.push(i);    //將入度為0的頂點入棧
        }
    }
    memset(this->veArray,0,pList->numVertess);    

    while(!staNode.empty())
    {
        nIndex = staNode.top();  // 獲取入度為0的頂點
        staNode.pop();     //出棧
        staEtv.push(nIndex);   //將彈出的頂點序號壓入拓撲序列堆棧
        printf("%c ->",pList->vertexList[nIndex].nNodeData);  //打印頂點
        ncount ++;
        //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧
        for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next)
        {
            //對此頂點弧表遍歷
            int temp = e->nNodevex;
            //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出
            if (!(--pList->vertexList[temp].in))
            {
                staNode.push(temp);
            }
            //求取各個頂點的事件最早發生時間。
            if (veArray[nIndex] + e->nNodeWeight > veArray[temp])
            {
                veArray[temp] = veArray[nIndex] + e->nNodeWeight;
            }
        }
    }
    if (ncount < pList->numVertess)
    {
        printf(" 圖有內環");
    }
}
//關鍵路徑算法
void GraphData::CriticalPath(GraphListAdv *pList)
{
    EdgeNode *node;
    int i,j,top,k;
    int ete,lte;
    TopoLogicalSortAdv(pList);
    for (i = 0; i< pList->numVertess;i++)
    {
        vlArray[i] = veArray[i];    //初始化vl
    }

    while(!staEtv.empty())
    {
        top = staEtv.top();
        staEtv.pop();
        for (node = pList->vertexList[top].pFirstNode;node;node = node->next)
        {
            k = node->nNodevex;
            if (vlArray[k] - node->nNodeWeight < vlArray[top])  //求取頂點事件的最晚發生時間
            {
                vlArray[top] = vlArray[k] + node->nNodeWeight; 
            }

        }
        
    }
    //求取關鍵路徑        
    for (i = 0;i < pList->numVertess;i++)
    {
        for (node = pList->vertexList[i].pFirstNode; node;node = node->next)
        {
            k = node->nNodevex;
            ete = veArray[k];
            lte = vlArray[k];
            if (ete == lte)
            {
                printf("<v%c,v%c> length: %d,  ",pList->vertexList[i].nNodeData,pList->vertexList[k].nNodeData,node->nNodeWeight);
            }
        }
    }

}
關鍵路徑

 

五、代碼分析

      image

程序最后的輸出結果如上圖。

在程序中首先是利用上一章節的拓撲排序進行改進,包括將頂點入棧,同時獲取頂點的最早活動時間,之后根據頂點的最早活動時間,逆序獲取頂點最晚活動時間,進而獲取項目的最晚活動時間數組。最終結果就如例圖解釋中所示,獲取到每個頂點的最早開始時間和最晚開始時間。

其中最早開始時間和最晚開始時間相等,說明在頂點的活動是沒有空閑時間的,關鍵路徑的活動各個頂點就是求取最大路徑長度,所以,兩者相等就是關鍵路徑的判斷條件,算法的關鍵是利用拓撲排序獲取頂點的最早開始時間和最晚開始時間。

六、圖相關程序代碼

  1 /* 鄰接矩陣表示的圖結構*/
  2 #include <stdio.h>
  3 #include <stdlib.h>
  4 #include <stack>
  5 using namespace std;
  6 
  7 
  8 #define  MAXVEX 100      //最大頂點數
  9 #define INFINITY 65535   //最大權值
 10 
 11 typedef int EdgeType;    //權值類型自己定義
 12 typedef char VertexType;  //頂點類型自己定義
 13 #pragma once
 14 
 15 #pragma region 鄰接矩陣結構體
 16 typedef struct 
 17 {
 18     VertexType vex[MAXVEX];   //頂點表
 19     EdgeType arg[MAXVEX][MAXVEX];  ///權值表-鄰接矩陣
 20     int numVertexes,numEdges;  //圖中的邊數和頂點數
 21 }GraphArray;
 22 //邊集數組
 23 typedef struct
 24 {
 25     int begin;
 26     int end;
 27     int weight;
 28 }Edge;
 29 #pragma endregion
 30 
 31 #pragma region 鄰接表結構體
 32 //邊表結點
 33 typedef struct EdgeNode
 34 {
 35     int nNodevex;     //鄰接點的點表中結點的坐標
 36     EdgeType nNodeWeight;   //用於網圖中邊的權值
 37     EdgeNode* next;       //鏈域,指向下一個鄰接點
 38 }EdgeNode,*pEdgeNode;
 39 //頂點表結點
 40 typedef struct VertexNode
 41 {
 42     VertexType nNodeData;   //頂點表中存儲的數據
 43     pEdgeNode pFirstNode;   //頂點表和邊表中關聯指針,指向邊表頭指針
 44 
 45 }VertexNode,*pVertexNode,VertexList[MAXVEX];
 46 typedef struct VertexNodeAdv
 47 {
 48     int in;                  //頂點的度
 49     VertexType nNodeData;   //頂點表中存儲的數據
 50     pEdgeNode pFirstNode;   //頂點表和邊表中關聯指針,指向邊表頭指針
 51 }VertexNodeAdv,*pVertexNodeAdv,VertexListAdv[MAXVEX];;
 52 //圖結構
 53 typedef struct
 54 {
 55     VertexList vertexList;
 56     int numVertess,numEdges;
 57 }GraphList;
 58 
 59 //圖結構
 60 typedef struct
 61 {
 62     VertexListAdv vertexList;
 63     int numVertess,numEdges;
 64 }GraphListAdv;
 65 #pragma endregion
 66 
 67 class GraphData
 68 {
 69 public:
 70     GraphData(void);
 71     ~GraphData(void);
 72     #pragma region 創建鄰接矩陣
 73     void CreateGraphArray(GraphArray* pGraphArray,int numVer,int numEdegs);
 74     int GetGraphLocation(GraphArray* pGraphArrray,char chpoint);
 75     #pragma endregion
 76 
 77     #pragma region 創建鄰接表
 78     void CreateGraphList(GraphList* pList,int numVer,int numEdegs);
 79     void CreateGraphListAdv(GraphListAdv* pList,int numVer,int numEdegs);
 80     int GetGraphListLocation(GraphList* pList,char chpoint);
 81     int GetGraphAdvListLocation(GraphListAdv* pList,char chpoint);
 82     #pragma endregion
 83 
 84     #pragma region 圖的遍歷
 85     //鄰接表的深度遞歸算法
 86     void DFS(GraphList *pList,int i);
 87     //鄰接表的深度遍歷操作
 88     void DFSTraverse(GraphList* pList);
 89     //鄰接矩陣的深度遞歸算法
 90     void DFS(GraphArray *pArray,int i);
 91     //鄰接矩陣的深度遍歷操作
 92     void DFSTraverse(GraphArray *pArray);
 93     //鄰接矩陣的廣度遍歷操作
 94     void BFSTraverse(GraphArray *pArray);
 95     //鄰接表的廣度遍歷操作
 96     void BFSTraverse(GraphList *pList);
 97     #pragma endregion
 98 
 99     #pragma region 最小生成樹
100     //prime算法
101     void MiniSpanTree_Prime(GraphArray *pArray);
102     //Kruskal算法(克魯斯卡爾)
103     void MiniSpanTree_Kruskal(GraphArray *pArray);
104     #pragma endregion
105 
106     #pragma region 最短路徑
107     //Dijkstra算法
108     void ShortPath_Dijkstra(GraphArray *pArray);
109     //Floyd算法
110     void ShortPath_Floyd(GraphArray *pArray);
111     #pragma endregion
112 
113     #pragma region 拓撲排序、關鍵路徑
114     //拓撲排序
115     void TopoLogicalSort(GraphListAdv *pList);
116     //改進的拓撲排序,用於關鍵路徑算法
117     void TopoLogicalSortAdv(GraphListAdv *pList);
118     //關鍵路徑算法
119     void CriticalPath(GraphListAdv *pList);
120     #pragma endregion
121 
122 private:
123     bool bVisited[MAXVEX];
124     int veArray[MAXVEX];
125     int vlArray[MAXVEX];
126     stack<int> staEtv;
127 
128 #pragma region 私有方法
129 
130     int FindLastLine(int *parent,int f);
131 
132     void InsertSort(Edge *pEdge,int k);
133 
134     void GraphToEdges(GraphArray *pArray,Edge *pEdge);
135 #pragma endregion
136 
137 };
圖的相關算法頭文件
  1 #include "GraphData.h"
  2 #include <queue>
  3 #include <stack>
  4 using namespace std;
  5 GraphData::GraphData(void)
  6 {
  7 }
  8 
  9 
 10 GraphData::~GraphData(void)
 11 {
 12 }
 13 
 14 #pragma region 創建鄰接矩陣
 15 int GraphData::GetGraphLocation(GraphArray* pGraphArrray,char chpoint)
 16 {
 17     int i = 0;
 18     for (i = 0;i< pGraphArrray->numVertexes;i++)
 19     {
 20         if (pGraphArrray->vex[i] == chpoint)
 21         {
 22             break;;
 23         }
 24     }
 25     if (i >= pGraphArrray->numVertexes)
 26     {
 27         return -1;
 28     }
 29     return i;
 30 }
 31 /// <summary>
 32 /// 創建鄰接矩陣
 33 /// </summary>        
 34 void GraphData::CreateGraphArray(GraphArray* pGraphArray,int numVer,int numEdegs)
 35 {
 36     int weight = 0;
 37     pGraphArray->numVertexes = numVer;
 38     pGraphArray->numEdges = numEdegs;
 39     
 40     //創建頂點表
 41     for (int i= 0; i < numVer;i++)
 42     {
 43         pGraphArray->vex[i] = getchar();
 44         while(pGraphArray->vex[i] == '\n')
 45         {
 46             pGraphArray->vex[i] = getchar();
 47         }
 48     }
 49 
 50     //創建鄰接表的邊矩陣
 51     for (int i = 0; i < numEdegs; i++)
 52     {
 53         for (int j = 0;j < numEdegs ; j++)
 54         {
 55             pGraphArray->arg[i][j] = INFINITY;
 56         }        
 57     }
 58     for(int k = 0; k < pGraphArray->numEdges; k++)
 59     {
 60         char p, q;
 61         printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n");
 62 
 63         p = getchar();
 64         while(p == '\n')
 65         {
 66             p = getchar();
 67         }
 68         q = getchar();
 69         while(q == '\n')
 70         {
 71             q = getchar();
 72         }
 73         scanf("%d", &weight);    
 74 
 75         int m = -1;
 76         int n = -1;
 77         m = GetGraphLocation(pGraphArray, p);
 78         n = GetGraphLocation(pGraphArray, q);
 79         if(n == -1 || m == -1)
 80         {
 81             fprintf(stderr, "there is no this vertex.\n");
 82             return;
 83         }
 84         //getchar();
 85         pGraphArray->arg[m][n] = weight;
 86         pGraphArray->arg[n][m] = weight;  //因為是無向圖,矩陣對稱
 87     }
 88     
 89 }
 90 
 91 #pragma endregion
 92 
 93 #pragma region 創建鄰接表
 94 void GraphData::CreateGraphList(GraphList* pList,int numVer,int numEdegs)
 95 {
 96     int weight = 0;
 97     GraphList *pGraphList = pList;
 98     pGraphList->numVertess = numVer;
 99     pGraphList->numEdges = numEdegs;
100     EdgeNode* firstNode,*secondNode;
101     //創建頂點表
102     for (int i= 0; i < numVer;i++)
103     {
104         pGraphList->vertexList[i].nNodeData = getchar();
105         pGraphList->vertexList[i].pFirstNode = NULL;
106         while(pGraphList->vertexList[i].nNodeData == '\n')
107         {
108             pGraphList->vertexList[i].nNodeData = getchar();
109         }
110     }
111 
112     //創建邊表    
113     for(int k = 0; k < pGraphList->numEdges; k++)
114     {
115         char p, q;
116         printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n");
117 
118         p = getchar();
119         while(p == '\n')
120         {
121             p = getchar();
122         }
123         q = getchar();
124         while(q == '\n')
125         {
126             q = getchar();
127         }
128         scanf("%d", &weight);    
129 
130         int m = -1;
131         int n = -1;
132         m = GetGraphListLocation(pGraphList, p);
133         n = GetGraphListLocation(pGraphList, q);
134         if(n == -1 || m == -1)
135         {
136             fprintf(stderr, "there is no this vertex.\n");
137             return;
138         }
139         //getchar();
140         //字符p在頂點表的坐標為m,與坐標n的結點建立聯系權重為weight
141         firstNode = new EdgeNode();
142         firstNode->nNodevex = n;
143         firstNode->next = pGraphList->vertexList[m].pFirstNode;
144         firstNode->nNodeWeight = weight;
145         pGraphList->vertexList[m].pFirstNode = firstNode;
146 
147         //第二個字符second
148         secondNode = new EdgeNode();
149         secondNode->nNodevex = m;
150         secondNode->next = pGraphList->vertexList[n].pFirstNode;
151         secondNode->nNodeWeight = weight;
152         pGraphList->vertexList[n].pFirstNode = secondNode;
153 
154     }
155 }
156 
157 int GraphData::GetGraphListLocation(GraphList* pList,char chpoint)
158 {
159     GraphList *pGraphList = pList;
160     int i = 0;
161     for (i = 0;i< pGraphList->numVertess;i++)
162     {
163         if (pGraphList->vertexList[i].nNodeData == chpoint)
164         {
165             break;;
166         }
167     }
168     if (i >= pGraphList->numVertess)
169     {
170         return -1;
171     }
172     return i;
173 }
174 
175 int GraphData::GetGraphAdvListLocation(GraphListAdv* pList,char chpoint)
176 {
177     GraphListAdv *pGraphList = pList;
178     int i = 0;
179     for (i = 0;i< pGraphList->numVertess;i++)
180     {
181         if (pGraphList->vertexList[i].nNodeData == chpoint)
182         {
183             break;;
184         }
185     }
186     if (i >= pGraphList->numVertess)
187     {
188         return -1;
189     }
190     return i;
191 }
192 #pragma endregion
193 
194 #pragma region 圖的遍歷
195 //鄰接表的深度遞歸算法
196 void GraphData::DFS(GraphList *pList,int i)
197 {
198     EdgeNode *itemNode;
199     bVisited[i] = true;
200     printf("%c",pList->vertexList[i].nNodeData); //打印頂點數據
201     itemNode = pList->vertexList[i].pFirstNode;
202     while(itemNode)
203     {
204         if (!bVisited[itemNode->nNodevex])
205         {
206             DFS(pList,itemNode->nNodevex);
207         }
208         itemNode = itemNode->next;
209     }
210 }
211 //鄰接表的深度遍歷操作
212 void  GraphData::DFSTraverse(GraphList* pList)
213 {
214     int i;
215     GraphList *pGraphList = pList;
216     for ( i = 0;pGraphList->numVertess;i++)
217     {
218         bVisited[i] = false;
219     }
220     for (i = 0;i < pGraphList->numVertess;i++)
221     {
222         if (!bVisited[i])
223         {
224             DFS(pGraphList,i);
225         }        
226     }
227 }
228 //鄰接矩陣的深度遞歸算法
229 void  GraphData::DFS(GraphArray *pArray,int i)
230 {
231     int j = 0;
232     printf("%c",pArray->vex[i]);   //打印頂點,也可以其他操作
233     for (j = 0; j< pArray->numVertexes;j++)
234     {
235         if (!bVisited[j]&&pArray->arg[i][j] == 1)    //關鍵之前初始化時,有關系的邊賦值為1,所以由此進行判斷
236         {
237             DFS(pArray,j);        //對為訪問的鄰接頂點遞歸調用
238         }
239     }
240 }
241 //鄰接矩陣的深度遍歷操作
242 void  GraphData::DFSTraverse(GraphArray *pArray)
243 {
244     int i;
245     for (i = 0;i < pArray->numVertexes;i++)
246     {
247         bVisited[i] = false;
248     }
249     for(i = 0; i< pArray->numVertexes;i++)
250     {
251         if (!bVisited[i])
252         {
253             DFS(pArray,i);
254         }
255     }
256 }
257 
258 //鄰接矩陣的廣度遍歷操作
259 void GraphData::BFSTraverse(GraphArray *pArray)
260 {
261     queue<int> itemQueue;
262     int i,j;
263     for(i = 0 ;i< pArray->numVertexes;i++)
264     {
265         bVisited[i] = false;
266     }
267     for(i = 0;i< pArray->numVertexes;i++)//每個頂點循環
268     {
269         if (!bVisited[i])//訪問未被訪問的頂點
270         {
271             bVisited[i] = true;
272             printf("%c",pArray->vex[i]);
273             itemQueue.push(i);   //將此結點入隊
274             while(!itemQueue.empty())
275             {
276                 int m = itemQueue.front();   //結點出隊
277                 itemQueue.pop();
278                 for(j = 0;j< pArray->numVertexes;j++)
279                 {
280                     //判斷其他頂點與當前頂點存在邊且未被訪問過。
281                     if (!bVisited[j] && pArray->arg[m][j] == 1)
282                     {
283                         bVisited[j] = true;
284                         printf("%c",pArray->vex[j]);
285                         itemQueue.push(j);
286                     }
287                 }
288             }
289         }
290     }
291 }
292 //鄰接表的廣度遍歷操作
293 void GraphData::BFSTraverse(GraphList *pList)
294 {
295     queue<int> itemQueue;
296     int i,j;
297     EdgeNode* pitemNode;
298     for(i = 0 ;i< pList->numVertess;i++)
299     {
300         bVisited[i] = false;
301     }
302     for(i = 0;i<pList->numVertess;i++)
303     {
304         if (!bVisited[i])
305         {
306             bVisited[i] = true;
307             printf("%c",pList->vertexList[i].nNodeData);
308             itemQueue.push(i);
309             while(!itemQueue.empty())
310             {
311                 int m = itemQueue.front();
312                 itemQueue.pop();
313                 pitemNode = pList->vertexList[m].pFirstNode;
314                 while(pitemNode)
315                 {
316                     if (bVisited[pitemNode->nNodevex])
317                     {
318                         bVisited[pitemNode->nNodevex] = true;
319                         printf("%c",pList->vertexList[pitemNode->nNodevex].nNodeData);
320                         itemQueue.push(pitemNode->nNodevex);
321                     }
322                     pitemNode = pitemNode->next;
323                 }
324 
325             }
326         }
327     }
328 }
329 #pragma endregion
330 
331 #pragma region 最小生成樹
332 //prime算法
333 void GraphData::MiniSpanTree_Prime(GraphArray *pArray)
334 {
335     int min,i,j,k;
336     int nNodeIndex[MAXVEX];      //保存相關頂點坐標,1就是已經遍歷訪問的過結點
337     int nNodeWeight[MAXVEX];     //保存某個頂點到各個頂點的權值,為不為0和最大值表示遍歷過了。
338     //兩個數組的初始化
339     printf("開始初始化,當前頂點邊的權值為:");
340     for(i = 0;i<pArray->numVertexes;i++)
341     {
342         nNodeIndex[i] = 0;
343         nNodeWeight[i] = pArray->arg[0][i];//設定在矩陣中第一個頂點為初始點。
344         printf(" %c",nNodeWeight[i]);
345     }
346     nNodeWeight[0] = 0;  //選取坐標點0為起始點。
347     //Prime算法思想
348     for (i = 1;i< pArray->numVertexes;i++)
349     {
350         min = INFINITY;    //初始化權值為最大值;
351         j = 1;
352         k = 0;
353         // 循環全部頂點,尋找與初始點邊權值最小的頂點,記下權值和坐標
354         while(j < pArray->numVertexes)
355         {
356             //如果權值不為0,且權值小於min,為0表示本身
357             if (nNodeWeight[j] != 0&&nNodeWeight[j] < min)
358             {
359                 min     = nNodeWeight[j];
360                 k = j;     //保存上述頂點的坐標值
361             }
362             j++;
363         }
364         printf("當前頂點邊中權值最小邊(%d,%d)\n",nNodeIndex[k] , k); //打印當前頂點邊中權值最小
365         nNodeWeight[k] = 0; //將當前頂點的權值設置為0,表示此頂點已經完成任務
366 
367         for (j = 1;j< pArray->numVertexes;j++)  //循環所有頂點,查找與k頂點的最小邊
368         {
369             //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值
370             if (nNodeWeight[j] != 0&&pArray->arg[k][j] < nNodeWeight[j])
371             {
372                 nNodeWeight[j] = pArray->arg[k][j];
373                 nNodeIndex[j] = k;     //將下標為k的頂點存入adjvex
374             }
375         }
376         //打印當前頂點狀況
377         printf("坐標點數組為:");
378         for(j = 0;j< pArray->numVertexes;j++)
379         {
380             printf("%3d ",nNodeIndex[j]);
381         }
382         printf("\n");
383         printf("權重數組為:");
384         for(j = 0;j< pArray->numVertexes;j++)
385         {
386             printf("%3d ",nNodeWeight[j]);
387         }
388         printf("\n");
389     }
390 
391 }
392 //Kruskal算法(克魯斯卡爾)
393 void GraphData::MiniSpanTree_Kruskal(GraphArray *pArray)
394 {
395     int i,n,m;
396     int parent[MAXVEX];    //定義邊集數組
397     Edge edges[MAXVEX];    //定義一數組用來判斷邊與邊是否形成環
398     //鄰接矩陣轉為邊集數組,並按照權值大小排序
399     GraphToEdges(pArray,edges);
400 
401     for (i =0; i< pArray->numVertexes;i++)
402     {
403         parent[i] = 0;    //初始化數組數值為0
404         
405     }
406 
407     //算法關鍵實現
408     for (i = 0;i < pArray->numVertexes;i++)    //循環每條邊
409     {
410         //根據邊集數組,查找出不為0的邊
411         n = FindLastLine(parent,edges[i].begin);
412         m = FindLastLine(parent,edges[i].end);
413         printf("邊%d的開始序號為:%d,結束為:%d)",i,n,m);
414         if(n != m)      //假如n與m不等,說明此邊沒有與現有生成樹形成環路
415         {
416             parent[n] = m;  //將此邊的結尾頂點放入下標為起點的parent中
417             //表示此頂點已經在生成樹集合中
418             printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
419         }
420     }
421     printf("\n");
422 }
423 #pragma endregion
424 
425 #pragma region 最短路徑
426 //Dijkstra算法
427 void GraphData::ShortPath_Dijkstra(GraphArray *pArray)
428 {
429     //Dijkstra算法和最小生成樹的算法,在某種程度是相似的
430     int min,i,j,k;
431     int nNodeIndex[MAXVEX];      //保存相關頂點坐標,1就是已經遍歷訪問的過結點(在最小生成樹中為數值表示遍歷過同時值與坐標是一條邊)
432     int nNodeWeight[MAXVEX];     //保存某個頂點到各個頂點的權值,為不為0和最大值表示遍歷過了。
433     int nPathLength[MAXVEX];     //坐標和元素表示為同時值和坐標表示一邊,與Primes有相同的
434     //兩個數組的初始化
435     printf("開始初始化,當前頂點邊的權值為:");
436     for(i = 0;i<pArray->numVertexes;i++)
437     {
438         nNodeIndex[i] = 0; 
439         nNodeWeight[i] = pArray->arg[0][i];//設定在矩陣中第一個頂點為初始點。
440         nPathLength[i] = 0;
441         printf(" %c",nNodeWeight[i]);
442     }
443     nNodeWeight[0] = 0;  //選取坐標點0為起始點。
444     nNodeIndex[0] = 1;  //這樣0就是起始點,設為1.(和Prime的不同)
445     //算法思想
446     for (i = 1;i< pArray->numVertexes;i++)
447     {
448         min = INFINITY;    //初始化權值為最大值;
449         j = 1;
450         k = 0;
451         // 循環全部頂點,尋找與初始點邊權值最小的頂點,記下權值和坐標
452         while(j < pArray->numVertexes)
453         {
454             //如果權值不為0,且權值小於min,為0表示本身
455             if (!nNodeIndex[j]&&nNodeWeight[j] < min)  //這里Prime是權重中不為0,
456             {
457                 min     = nNodeWeight[j];
458                 k = j;     //保存上述頂點的坐標值
459             }
460             j++;
461         }
462         printf("當前頂點邊中權值最小邊(%d,%d)\n",nNodeIndex[k] , k); //打印當前頂點邊中權值最小
463         //nNodeWeight[k] = 0; //將當前頂點的權值設置為0,表示此頂點已經完成任務
464         nNodeIndex[k] = 1;     //將目前找到的最近的頂點置為1
465 
466         //for (j = 1;j< pArray->numVertexes;j++)  //循環所有頂點,查找與k頂點的最小邊
467         //{
468         //    //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值
469         //    if (nNodeWeight[j] != 0&&pArray->arg[k][j] < nNodeWeight[j])
470         //    {
471         //        nNodeWeight[j] = pArray->arg[k][j];
472         //        nNodeIndex[j] = k;     //將下標為k的頂點存入adjvex
473         //    }
474         //}
475         //修正當前最短路徑及距離
476         for (j = 1;j< pArray->numVertexes;j++)  //循環所有頂點,查找與k頂點的最小邊
477         {
478             //若下標為k的頂點各邊權值小於此前這些頂點未被加入的生成樹權值
479             if (!nNodeIndex[j] && pArray->arg[k][j] + min< nNodeWeight[j])
480             {
481                 nNodeWeight[j] = pArray->arg[k][j] + min;
482                 nPathLength[j] = k;     //將下標為k的頂點存入adjvex
483             }
484         }
485         //打印當前頂點狀況
486         printf("坐標點數組為:");
487         for(j = 0;j< pArray->numVertexes;j++)
488         {
489             printf("%3d ",nPathLength[j]);
490         }
491         printf("\n");
492         printf("權重數組為:");
493         for(j = 0;j< pArray->numVertexes;j++)
494         {
495             printf("%3d ",nNodeWeight[j]);
496         }
497         printf("\n");
498     }
499 
500 }
501 //Floyd算法
502 void GraphData::ShortPath_Floyd(GraphArray *pArray)
503 {
504     int i,j,m,k;
505     int nNodeIndex[MAXVEX][MAXVEX];
506     int nNodeWeight[MAXVEX][MAXVEX];
507     
508     for ( i = 0;i< pArray->numVertexes;i++)
509     {
510         for (j = 0;j< pArray->numVertexes;j++)
511         { 
512             nNodeIndex[i][j] = j;                         /* 初始化 */
513             nNodeWeight[i][j] = pArray->arg[i][j];  /* [i][j]值即為對應點間的權值 */
514         }
515     }
516     for (i = 0;i< pArray->numVertexes;i++)
517     {
518         for (j = 0;j< pArray->numVertexes;j++)
519         {
520             for (k = 0;k<pArray->numVertexes;k++)
521             {
522                 if (pArray->arg[j][k] > pArray->arg[j][i] + pArray->arg[i][k])
523                 {
524                     /* 如果經過下標為k頂點路徑比原兩點間路徑更短 */
525                     nNodeWeight[j][k] = pArray->arg[j][i] + pArray->arg[i][k];  /* 將當前兩點間權值設為更小的一個 */
526                     nNodeIndex[j][k] = nNodeIndex[j][i];  /* 路徑設置為經過下標為k的頂點 */
527                 }
528             }
529         }
530     }
531     for (i = 0; i< pArray->numVertexes;i++)
532     {
533         for (j = 0;j< pArray->numVertexes;j++)
534         {
535             printf("v%d-v%d weight: %d",i,j,nNodeWeight[i][j]);
536                 m = nNodeIndex[i][j];    //獲得第一個路徑點的頂點下標
537             printf("path :%d",i);    //打印源點
538             while(m!=j)
539             {
540                 printf(" -> %d",m);   //打印路徑頂點
541                 m = nNodeIndex[m][j];   //獲取下一個路徑頂點下標
542             }
543             printf(" -> %d\n",m);    //打印路徑終點。
544         }
545 
546         printf("\n");
547     }
548 }
549 #pragma endregion
550 
551 #pragma region 拓撲排序、關鍵路徑
552 void GraphData::CreateGraphListAdv(GraphListAdv* pList,int numVer,int numEdegs)
553 {
554     int weight = 0;
555     GraphListAdv *pGraphList = pList;
556     pGraphList->numVertess = numVer;
557     pGraphList->numEdges = numEdegs;
558     EdgeNode* firstNode,*secondNode;
559     //創建頂點表
560     for (int i= 0; i < numVer;i++)
561     {
562         pGraphList->vertexList[i].nNodeData = getchar();
563         pGraphList->vertexList[i].pFirstNode = NULL;
564         while(pGraphList->vertexList[i].nNodeData == '\n')
565         {
566             pGraphList->vertexList[i].nNodeData = getchar();
567         }
568     }
569 
570     //創建邊表    
571     for(int k = 0; k < pGraphList->numEdges; k++)
572     {
573         char p, q;
574         printf("輸入邊(vi,vj)上的下標i,下標j和權值:\n");
575 
576         p = getchar();
577         while(p == '\n')
578         {
579             p = getchar();
580         }
581         q = getchar();
582         while(q == '\n')
583         {
584             q = getchar();
585         }
586         scanf("%d", &weight);    
587 
588         int m = -1;
589         int n = -1;
590         m = GetGraphAdvListLocation(pGraphList, p);
591         n = GetGraphAdvListLocation(pGraphList, q);
592         if(n == -1 || m == -1)
593         {
594             fprintf(stderr, "there is no this vertex.\n");
595             return;
596         }
597         //getchar();
598         //字符p在頂點表的坐標為m,與坐標n的結點建立聯系權重為weight
599         firstNode = new EdgeNode();
600         firstNode->nNodevex = n;
601         firstNode->next = pGraphList->vertexList[m].pFirstNode;
602         pGraphList->vertexList[n].in++;
603         firstNode->nNodeWeight = weight;
604         pGraphList->vertexList[m].pFirstNode = firstNode;
605 
606         ////第二個字符second
607         //secondNode = new EdgeNode();
608         //secondNode->nNodevex = m;
609         //secondNode->next = pGraphList->vertexList[n].pFirstNode;
610         //pGraphList->vertexList[n].in++;
611         //secondNode->nNodeWeight = weight;
612         //pGraphList->vertexList[n].pFirstNode = secondNode;
613 
614     }
615 }
616 void GraphData::TopoLogicalSort(GraphListAdv *pList)
617 {
618     EdgeNode *e;
619     int ncount = 0;  //統計輸出頂點個數,用於判斷是否有環
620     int nIndex ; // y用於保存度為0的坐標
621     stack<int> staNode;
622     for (int i = 0;i < pList->numVertess; i++)
623     {
624         if (pList->vertexList[i].in == 0)
625         {
626             staNode.push(i);    //將入度為0的頂點入棧
627         }
628     }
629     while(!staNode.empty())
630     {
631         nIndex = staNode.top();  // 獲取入度為0的頂點
632         staNode.pop();     //出棧
633         printf("%c ->",pList->vertexList[nIndex].nNodeData);  //打印頂點
634         ncount ++;
635         //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧
636         for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next)
637         {
638             //對此頂點弧表遍歷
639             int temp = e->nNodevex;
640             //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出
641             if (!(--pList->vertexList[temp].in))
642             {
643                 staNode.push(temp);
644             }
645         }
646     }
647     if (ncount < pList->numVertess)
648     {
649         printf(" 圖有內環");
650     }
651 
652 }
653 //改進的拓撲排序,用於關鍵路徑算法
654 void GraphData::TopoLogicalSortAdv(GraphListAdv *pList)
655 {
656     EdgeNode *e;
657     int ncount = 0;  //統計輸出頂點個數,用於判斷是否有環
658     int nIndex ; // y用於保存度為0的坐標
659     stack<int> staNode;
660     
661     for (int i = 0;i < pList->numVertess; i++)
662     {
663         if (pList->vertexList[i].in == 0)
664         {
665             staNode.push(i);    //將入度為0的頂點入棧
666         }
667     }
668     memset(this->veArray,0,pList->numVertess);    
669 
670     while(!staNode.empty())
671     {
672         nIndex = staNode.top();  // 獲取入度為0的頂點
673         staNode.pop();     //出棧
674         staEtv.push(nIndex);   //將彈出的頂點序號壓入拓撲序列堆棧
675         printf("%c ->",pList->vertexList[nIndex].nNodeData);  //打印頂點
676         ncount ++;
677         //對此頂點的處理:下一個頂點入度減1,如果為0還要進棧
678         for (e = pList->vertexList[nIndex].pFirstNode;e;e = e->next)
679         {
680             //對此頂點弧表遍歷
681             int temp = e->nNodevex;
682             //將temp號頂點鄰接點的入度減1,若為0,則入棧,以便於下次循環輸出
683             if (!(--pList->vertexList[temp].in))
684             {
685                 staNode.push(temp);
686             }
687             //求取各個頂點的事件最早發生時間。
688             if (veArray[nIndex] + e->nNodeWeight > veArray[temp])
689             {
690                 veArray[temp] = veArray[nIndex] + e->nNodeWeight;
691             }
692         }
693     }
694     if (ncount < pList->numVertess)
695     {
696         printf(" 圖有內環");
697     }
698 }
699 //關鍵路徑算法
700 void GraphData::CriticalPath(GraphListAdv *pList)
701 {
702     EdgeNode *node;
703     int i,j,top,k;
704     int ete,lte;
705     TopoLogicalSortAdv(pList);
706     for (i = 0; i< pList->numVertess;i++)
707     {
708         vlArray[i] = veArray[i];    //初始化vl
709     }
710 
711     while(!staEtv.empty())
712     {
713         top = staEtv.top();
714         staEtv.pop();
715         for (node = pList->vertexList[top].pFirstNode;node;node = node->next)
716         {
717             k = node->nNodevex;
718             if (vlArray[k] - node->nNodeWeight < vlArray[top])  //求取頂點事件的最晚發生時間
719             {
720                 vlArray[top] = vlArray[k] + node->nNodeWeight; 
721             }
722 
723         }
724         
725     }
726     //求取關鍵路徑        
727     for (i = 0;i < pList->numVertess;i++)
728     {
729         for (node = pList->vertexList[i].pFirstNode; node;node = node->next)
730         {
731             k = node->nNodevex;
732             ete = veArray[k];
733             lte = vlArray[k];
734             if (ete == lte)
735             {
736                 printf("<v%c,v%c> length: %d,  ",pList->vertexList[i].nNodeData,pList->vertexList[k].nNodeData,node->nNodeWeight);
737             }
738         }
739     }
740 
741 }
742 #pragma endregion
743 #pragma region 私有方法
744 
745 //查找連線頂點尾部
746 int GraphData::FindLastLine(int *parent,int f)
747 {
748     while(parent[f] >0)
749     {
750         f = parent[f];
751     }
752     return f;
753 }
754 //直接插入排序
755 void GraphData::InsertSort(Edge *pEdge,int k)
756 {
757     Edge *itemEdge = pEdge;
758     Edge item;
759     int i,j;
760     for (i = 1;i<k;i++)
761     {
762         if (itemEdge[i].weight < itemEdge[i-1].weight)
763         {
764             item = itemEdge[i];
765             for (j = i -1; itemEdge[j].weight > item.weight ;j--)
766             {
767                 itemEdge[j+1] = itemEdge[j];
768             }
769             itemEdge[j+1] = item;
770         }
771     }
772 }
773 //將鄰接矩陣轉化為邊集數組
774 void GraphData::GraphToEdges(GraphArray *pArray,Edge *pEdge)
775 {
776     int i;
777     int j;
778     int k;
779 
780     k = 0;
781     for(i = 0; i < pArray->numVertexes; i++)
782     {
783         for(j = i; j < pArray->numEdges; j++)
784         {
785             if(pArray->arg[i][j] < 65535)
786             {
787                 pEdge[k].begin = i;
788                 pEdge[k].end = j;
789                 pEdge[k].weight = pArray->arg[i][j];
790                 k++;
791             }
792         }
793     }
794 
795     printf("k = %d\n", k);
796     printf("邊集數組排序前,如下所示.\n");  
797     printf("edges[]     beign       end     weight\n");
798     for(i = 0; i < k; i++)
799     {
800         printf("%d", i);
801         printf("        %d", pEdge[i].begin);
802         printf("        %d", pEdge[i].end);
803         printf("        %d", pEdge[i].weight);
804         printf("\n");
805     }
806 
807 
808     //下面進行排序
809     InsertSort(pEdge, k);
810 
811     printf("邊集數組排序后,如下所示.\n");
812     printf("edges[]     beign       end     weight\n");
813     for(i = 0; i < k; i++)
814     {
815         printf("%d", i);
816         printf("        %d", pEdge[i].begin);
817         printf("        %d", pEdge[i].end);
818         printf("        %d", pEdge[i].weight);
819         printf("\n");
820     }
821 
822 }
823 #pragma endregion
圖相關算法源文件
 1 #include <iostream>
 2 #include "GraphData.h"
 3 using namespace std;
 4 //
 5 
 6 void PrintGrgph(GraphList *pGraphList)
 7 {
 8     int i =0;
 9     while(pGraphList->vertexList[i].pFirstNode != NULL && i<MAXVEX)
10     {
11         printf("頂點:%c  ",pGraphList->vertexList[i].nNodeData);
12         EdgeNode *e = NULL;
13         e = pGraphList->vertexList[i].pFirstNode;
14         while(e != NULL)
15         {
16             printf("%d  ", e->nNodevex);
17             e = e->next;
18         }
19         i++;
20         printf("\n");
21     }
22     
23 }
24 int main()
25 {
26     int numVexs,numEdges;
27     GraphData* pTestGraph = new GraphData();
28     GraphArray graphArray;
29     GraphArray* pGraphArray = &graphArray;
30     GraphList* pGgraphList = new GraphList();
31     GraphListAdv *pGraphListAdv = new GraphListAdv();
32     
33     cout<<"輸入頂點數和邊數"<<endl;
34     cin>>numVexs>>numEdges;
35     cout<<"頂點數和邊數為:"<<numVexs<<numEdges<<endl;
36     pTestGraph->CreateGraphListAdv(pGraphListAdv,numVexs,numEdges);
37     //pTestGraph->CreateGraphArray(pGraphArray,numVexs,numEdges);
38     //pTestGraph->MiniSpanTree_Prime(pGraphArray);
39     printf("構建的鄰接矩陣如下所示.\n");
40     /*for(int i = 0; i< numEdges;i++)
41     {
42         for (int j = 0;j< numEdges;j++)
43         {
44             printf("%5d  ", pGraphArray->arg[i][j]);
45         }
46         cout<<endl;
47     }*/
48     //pTestGraph->MiniSpanTree_Kruskal(pGraphArray);
49     /*pTestGraph->CreateGraphList(pGgraphList,numVexs,numEdges);
50     PrintGrgph(pGgraphList);*/
51     //pTestGraph->ShortPath_Dijkstra(pGraphArray);
52     //pTestGraph->ShortPath_Floyd(pGraphArray);
53     pTestGraph->CriticalPath(pGraphListAdv);
54     system("pause");
55 }
測試程序

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM