任務:給定一個有向圖,實現圖的深度優先, 廣度優先遍歷算法,拓撲有序序列,並輸出相關結果。
功能要求:輸入圖的基本信息,並建立圖存儲結構(有相應提示),輸出遍歷序列,然后進行拓撲排序,並測試該圖是否為有向無環圖,並輸出拓撲序列。
按照慣例,先上代碼,注釋超詳細:
#include<stdio.h> #include<stdlib.h> #include<malloc.h> #pragma warning(disable:4996) #define Max 20//定義數組元素最大個數(頂點最大個數) typedef struct node//邊表結點 { int adjvex;//該邊所指向結點對應的下標 struct node* next;//該邊所指向下一個結點的指針 }eNode; typedef struct headnode//頂點表結點 { int in;//頂點入度 char vertex;//頂點數據 eNode* firstedge;//指向第一條邊的指針,邊表頭指針 }hNode; typedef struct//鄰接表(圖) { hNode adjlist[Max];//以數組的形式存儲 int n, e;//頂點數,邊數 }linkG; //以鄰接表的存儲結構創建圖 linkG* creat(linkG* g) { int i, k; eNode* s;//邊表結點 int n1, e1; char ch; g = (linkG*)malloc(sizeof(linkG));//申請結點空間 printf("請輸入頂點數和邊數:"); scanf("%d%d", &n1, &e1); g->n = n1; g->e = e1; printf("頂點數:%d 邊數:%d\n", g->n, g->e); printf("請輸入頂點信息(字母):"); getchar();//因為接下來要輸入字符串,所以getchar用於承接上一條命令的結束符 for (i = 0; i < n1; i++) { scanf("%c", &ch); g->adjlist[i].vertex = ch;//獲得該頂點數據 g->adjlist[i].firstedge = NULL;//第一條邊設為空 } printf("\n打印頂點下標及頂點數據:\n"); for (i = 0; i < g->n; i++)//循環打印頂點下標及頂點數據 { printf("頂點下標:%d 頂點數據:%c\n", i, g->adjlist[i].vertex); } getchar(); int i1, j1;//相連接的兩個頂點序號 for (k = 0; k < e1; k++)//建立邊表 { printf("請輸入對<i,j>(空格分隔):"); scanf("%d%d", &i1, &j1); s = (eNode*)malloc(sizeof(eNode));//申請邊結點空間 s->adjvex = j1;//邊所指向結點的位置,下標為j1 s->next = g->adjlist[i1].firstedge;//將當前s的指針指向當前頂點上指向的結點 g->adjlist[i1].firstedge = s;//將當前頂點的指針指向s } return g;//返回指針g } int visited[Max];//標記是否訪問 void DFS(linkG* g, int i)//深度優先遍歷 { eNode* p; printf("%c ", g->adjlist[i].vertex); visited[i] = 1;//將已訪問過的頂點visited值改為1 p = g->adjlist[i].firstedge;//p指向頂點i的第一條邊 while (p)//p不為NULL時(邊存在) { if (visited[p->adjvex] != 1)//如果沒有被訪問 { DFS(g, p->adjvex);//遞歸 } p = p->next;//p指向下一個結點 } } void DFSTravel(linkG* g)//遍歷非連通圖 { int i; printf("深度優先遍歷;\n"); //printf("%d\n",g->n); for (i = 0; i < g->n; i++)//初始化為0 { visited[i] = 0; } for (i = 0; i < g->n; i++)//對每個頂點做循環 { if (!visited[i])//如果沒有被訪問 { DFS(g, i);//調用DFS函數 } } } void BFS(linkG* g, int i)//廣度優先遍歷 { int j; eNode* p; int q[Max], front = 0, rear = 0;//建立順序隊列用來存儲,並初始化 printf("%c ", g->adjlist[i].vertex); visited[i] = 1;//將已經訪問過的改成1 rear = (rear + 1) % Max;//普通順序隊列的話,這里是rear++ q[rear] = i;//當前頂點(下標)隊尾進隊 while (front != rear)//隊列非空 { front = (front + 1) % Max;//循環隊列,頂點出隊 j = q[front]; p = g->adjlist[j].firstedge;//p指向出隊頂點j的第一條邊 while (p != NULL) { if (visited[p->adjvex] == 0)//如果未被訪問 { printf("%c ", g->adjlist[p->adjvex].vertex); visited[p->adjvex] = 1;//將該頂點標記數組值改為1 rear = (rear + 1) % Max;//循環隊列 q[rear] = p->adjvex;//該頂點進隊 } p = p->next;//指向下一個結點 } } } void BFSTravel(linkG* g)//遍歷非連通圖 { int i; printf("廣度優先遍歷:\n"); for (i = 0; i < g->n; i++)//初始化為0 { visited[i] = 0; } for (i = 0; i < g->n; i++)//對每個頂點做循環 { if (!visited[i])//如果沒有被訪問過 { BFS(g, i);//調用BFS函數 } } } //因為拓撲排序要求入度為0,所以需要先求出每個頂點的入度 void inDegree(linkG* g)//求圖頂點入度 { eNode* p; int i; for (i = 0; i < g->n; i++)//循環將頂點入度初始化為0 { g->adjlist[i].in = 0; } for (i = 0; i < g->n; i++)//循環每個頂點 { p = g->adjlist[i].firstedge;//獲取第i個鏈表第1個邊結點指針 while (p != NULL)///當p不為空(邊存在) { g->adjlist[p->adjvex].in++;//該邊終點結點入度+1 p = p->next;//p指向下一個邊結點 } printf("頂點%c的入度為:%d\n", g->adjlist[i].vertex, g->adjlist[i].in); } } void topo_sort(linkG *g)//拓撲排序 { eNode* p; int i, k, gettop; int top = 0;//用於棧指針的下標索引 int count = 0;//用於統計輸出頂點的個數 int* stack=(int *)malloc(g->n*sizeof(int));//用於存儲入度為0的頂點 for (i=0;i<g->n;i++)//第一次搜索入度為0的頂點 { if (g->adjlist[i].in==0) { stack[++top] = i;//將入度為0的頂點進棧 } } while (top!=0)//當棧不為空時 { gettop = stack[top--];//出棧,並保存棧頂元素(下標) printf("%c ",g->adjlist[gettop].vertex); count++;//統計頂點 //接下來是將鄰接點的入度減一,並判斷該點入度是否為0 p = g->adjlist[gettop].firstedge;//p指向該頂點的第一條邊的指針 while (p)//當p不為空時 { k = p->adjvex;//相連接的頂點(下標) g->adjlist[k].in--;//該頂點入度減一 if (g->adjlist[k].in==0) { stack[++top] = k;//如果入度為0,則進棧 } p = p->next;//指向下一條邊 } } if (count<g->n)//如果輸出的頂點數少於總頂點數,則表示有環 { printf("\n有回路!\n"); } free(stack);//釋放空間 } void menu()//菜單 { system("cls");//清屏函數 printf("************************************************\n"); printf("* 1.建立圖 *\n"); printf("* 2.深度優先遍歷 *\n"); printf("* 3.廣度優先遍歷 *\n"); printf("* 4.求出頂點入度 *\n"); printf("* 5.拓撲排序 *\n"); printf("* 6.退出 *\n"); printf("************************************************\n"); } int main() { linkG* g = NULL; int c; while (1) { menu(); printf("請選擇:"); scanf("%d", &c); switch (c) { case 1:g = creat(g); system("pause"); break; case 2:DFSTravel(g); system("pause"); break; case 3:BFSTravel(g); system("pause"); break; case 4:inDegree(g); system("pause"); break; case 5:topo_sort(g); system("pause"); break; case 6:exit(0); break; } } return 0; }
實驗用圖:
運行結果:
關於深度優先遍歷
a.從圖中某個頂點v出發,訪問v。
b.找到剛訪問過得頂點的第一個未被訪問的鄰接點,訪問該頂點。以該頂點為新頂點,重復此步驟,直至剛訪問的頂點沒有未被訪問的鄰接點為止。
c.返回前一個訪問過得且扔有未被訪問的鄰接點的頂點,找到該頂點的下一個未被訪問的鄰接點,訪問該頂點。
d.重復步驟2,3,直至圖中所有頂點都被訪問過。
時間復雜性:采用鄰接表存儲方法,它的e條邊所對應的邊結點的總個數為2e,調用DFS的時間為O(e)。此時算法的時間復雜度為O(n+e).當n<=e時,若不計置visited的時間,則整個深度優先搜索所需要的時間為O(e)。
關於廣度優先遍歷
首先訪問指定出發頂點,然后依次訪問該頂點的所有未被訪問過的頂點,再接下來訪問鄰接點的未被訪問過的鄰接點,以此類推。由此可見,實現這個過程需要設置一個隊列結構。
廣度優先搜索是按層來處理頂點,距離開始點最近的那些頂點首先被訪問,而最遠的那些頂點則最后被訪問,這個和樹的層序變量很像,BFS的代碼使用了一個隊列。搜索步驟:
a .首先選擇一個頂點作為起始頂點,並將其visited設為0
b. 將起始頂點放入隊列中。
c. 從隊列首部選出一個頂點,並找出所有與之鄰接的頂點,將找到的鄰接頂點放入隊列尾部,將已訪問過頂點visited設置為1,沒訪問過的頂點是0。
d. 按照同樣的方法處理隊列中的下一個頂點。
時間復雜性:從算法中可知,每個頂點最多進隊一次,n個頂點一共有n次進隊操作,由於遍歷的過程實際上是通過邊或弧尋找鄰接點的過程,因此廣度優先搜索算法的時間復雜度與深度優先搜索算法地時間復雜度相同,兩者空間復雜度均為O(n)。
關於拓撲排序
在一個有向圖中,對所有的節點進行排序,要求沒有一個節點指向它前面的節點。
先統計所有節點的入度,對於入度為0的節點就可以分離出來,然后把這個節點指向的節點的入度減一。
一直做改操作,直到所有的節點都被分離出來。
如果最后不存在入度為0的節點,那就說明有環,不存在拓撲排序,也就是很多題目的無解的情況。
時間復雜性:如果AOV網絡有n個頂點,e條邊,在拓撲排序的過程中,搜索入度為零的頂點所需的時間是O(n)。在正常情況下,每個頂點進一次棧,出一次棧,所需時間O(n)。每個頂點入度減1的運算共執行了e次。所以總的時間復雜為O(n+e)。