前言:如果你已經學習了鄰接表的存儲思想,那么逆鄰接表也非常好理解,我們的重點是十字鏈表
首先我們來繼續介紹逆鄰接表,逆鄰接表和鄰接表是一樣的,只不過在鄰接表上,一個頂點后面連接的一串節點都是以頂點為弧尾的弧頭節點,我們建立鄰接表的時候就先查找一條邊的起點,然后往這個起點上連接新的頂點,那么逆鄰接表就是反過來,逆鄰接表中一個頂點后面的一串節點都是以頂點為弧頭的弧尾節點,我們建立逆鄰接表的時候,先查找一條邊的終點,然后往這個重點上連接包含起點信息的節點;
逆鄰接表就介紹到這里,下面我們介紹十字鏈表,首先我們拋出這樣一句話,十字鏈表是正鄰接表和逆鄰接表的合體,然后我們再介紹這樣一個觀念,我們在逆鄰接表或者鄰接表中所提到的節點,其實可以理解為弧節點,弧節點可以包含弧頭頂點在圖中的位置(我們在正鄰接表中遇到的),也可以包含弧尾頂點在圖中的位置(逆鄰接表);下面我們給出具體的例子幫助大家理解
現在思考下當我們要建立十字鏈表時會發生些什么:首先我們建立好頂點數組,和在鄰接表中的一樣,然后呢,我們開始添加 e01 這個邊,或者說弧現在我們要用弧節點的思路去理解他,如何添加這個弧,這個弧包含了哪些信息?弧包含了tail,代表弧尾表示的頂點在圖中的位置,也就是這個弧是由誰發射出來的,head呢,表示這個弧指向了誰,同樣儲存的是頂點在頂點數組中的索引;后面這兩項是地址,是什么類型的地址呢,這個地址指向的空間要放什么東西呢,放的仍然是弧節點,但是這兩個地址所放的不是同一個弧節點,這個tlink,指向的是下一個弧節點,tlink指向的這個弧節點的弧尾和tlink所在的弧的弧尾是相同的,按照這樣的想法,我們很容易想到沿着tlink一路走下去,我們就可以遍歷由同一個頂點發射的所有弧,相當於是一個正鄰接表,同理沿着hlink我們會遍歷指向同一個頂點的所有弧,而一個弧節點既有hlink,又有tlink,所以正鄰接表和逆鄰接表是交叉的,這就叫做十字鏈表,
那么如何來具體的建立一個十字鏈表呢,回顧上一節我們提到的,先建立頂點數組,然后按照用戶輸入的邊去建立弧節點,然后給弧節點填充相應的信息,再把弧節點鏈接到已有的十字鏈表上,鏈接的時候又需要分情況,我這個弧節點是不是處於鄰接表的第一個或者是逆鄰接表的第一個,如果是的話,需要操作頂點的firstin或者firstout,如果不是的話需要操作”最后一個“弧節點,那么這里和上一節的操作一樣,我們仍然需要去標記一下”最后一個弧節點“,只不過這里不僅要標記正鄰接表的最后一個弧節點,還需要標記逆鄰接表中處於最后一個的弧節點,
如此如此這番這番,思路就躍然紙上了(具體化了),下面我們給出具體代碼,並針對上一節中,判定是否一個頂點沒有弧節點相連(十字鏈表中其實是沒有正鄰接表‘也就是弧尾’或逆鄰接表‘也就是弧頭’相連,或者兩個都沒有)的判定進行了更正;
1 //驗證圖的十字鏈表存儲 2 #include <stdio.h> 3 #include <windows.h> 4 #define vexNum 10 5 6 typedef struct ArcNode 7 { 8 //索引 9 int tailVex; 10 int headVex; 11 12 //地址,按照tlink,可以一路遍歷完正鄰接表 13 struct ArcNode *tlink; 14 struct ArcNode *hlink; 15 16 //info代表弧的權重 17 int info; 18 19 } ArcNode; 20 21 typedef struct VexNode 22 { 23 char data; 24 ArcNode *last1End; //正鄰接表的最后一個弧節點 25 ArcNode *last2End; //逆鄰接表的最后一個弧節點 26 ArcNode *second1; //正、第一個弧節點 27 ArcNode *second2; //逆、第一個弧節點 28 } VexNode; 29 30 typedef struct graph 31 { 32 int vexN; 33 int edgeN; 34 VexNode adjList[vexNum]; 35 } graph; 36 37 //尋找頂點在圖中的位置 38 int locatVex(char vex, graph g) 39 { 40 int i = 0; 41 for (i; i < g.vexN; i++) 42 { 43 if (vex == g.adjList[i].data) 44 { 45 return i; 46 } 47 } 48 return -1; 49 } 50 void creatOLGraph(graph *g) 51 { 52 printf("請輸入節點數和邊數:\n"); 53 scanf("%d %d", &g->vexN, &g->edgeN); 54 getchar(); 55 printf("請輸入節點數組,不帶空格\n"); 56 int i = 0; 57 for (i; i < g->vexN; i++) 58 { 59 scanf("%c", &g->adjList[i].data); 60 g->adjList[i].second1 = NULL; 61 g->adjList[i].second2 = NULL; 62 g->adjList[i].last1End = NULL; 63 g->adjList[i].last2End = NULL; 64 } 65 getchar(); 66 printf("請輸入邊:\n"); 67 68 int v1, v2; 69 char vv1, vv2; 70 for (i = 0; i < g->edgeN; i++) 71 { 72 scanf("%c %c", &vv1, &vv2); 73 getchar(); 74 v1 = locatVex(vv1, *g); 75 v2 = locatVex(vv2, *g); 76 ArcNode *p; 77 p = (ArcNode *)malloc(sizeof(ArcNode)); 78 79 //同一個弧節點,先對正鄰接表操作,再對逆鄰接表操作 80 81 //弧節點的弧尾是v1 82 p->tailVex = v1; 83 if (g->adjList[v1].last1End == NULL) 84 { 85 //頂點的第一個弧節點是p,該頂點正鄰接表的最后一個弧節點也是p 86 g->adjList[v1].second1 = p; 87 g->adjList[v1].last1End = p; 88 } 89 else 90 { 91 //向正鄰接表中添加一個元素, 92 g->adjList[v1].last1End->tlink = p; 93 g->adjList[v1].last1End = p; 94 } 95 96 p->headVex = v2; 97 if (g->adjList[v2].last2End == NULL) 98 { 99 g->adjList[v2].second2 = p; 100 g->adjList[v2].last2End = p; 101 } 102 else 103 { 104 g->adjList[v2].last1End->hlink = p; 105 g->adjList[v2].last1End = p; 106 } 107 } 108 } 109 110 int main() 111 { 112 graph g; 113 creatOLGraph(&g); 114 //驗證十字鏈表的存儲 115 int i = 0; 116 for (i; i < g.vexN; i++) 117 { 118 ArcNode *show; 119 //之前我們做判斷,是判斷“最后一個節點”是否為頂點,現在我們更改了初始化條件,把之前的初始化為頂點地址改成了初始化為NULL這就避免了類型不兼容的警告 120 if (g.adjList[i].last2End != NULL) 121 { 122 printf("發往%c的弧,弧尾是 ", g.adjList[i].data); 123 show = g.adjList[i].second2; 124 while (show != g.adjList[i].last2End) 125 { 126 printf("%c、", g.adjList[show->tailVex].data); 127 show = show->hlink; 128 } 129 printf("%c\n", g.adjList[show->tailVex].data); 130 } 131 132 if (g.adjList[i].last1End != NULL) 133 { 134 printf("由%c發出的弧的弧頭是 ", g.adjList[i].data); 135 show = g.adjList[i].second1; 136 while (show != g.adjList[i].last1End) 137 { 138 printf("%c、", g.adjList[show->headVex].data); 139 show = show->tlink; 140 } 141 printf("%c\n", g.adjList[show->headVex].data); 142 } 143 } 144 system("pause"); 145 return 0; 146 }
參考:《新編數據結構案例教程(C/C++)版》薛曉亞主編