// 圖的數組(鄰接矩陣)存儲表示 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_NAME 3 // 頂點字符串的最大長度+1 #define MAX_VERTEX_NUM 20 typedef int InfoType; // 存放網的權值 typedef char VertexType[MAX_NAME]; // 字符串類型 typedef enum{DG, DN, AG, AN}GraphKind; // {有向圖,有向網,無向圖,無向網} int visited[MAX_VERTEX_NUM]; // 訪問標志數組 void(*VisitFunc)(char* v); // 函數變量 typedef struct ArcNode { int adjvex; // 該弧所指向的頂點的位置 struct ArcNode *nextarc; // 指向下一條弧的指針 InfoType *info; // 網的權值指針 }ArcNode; // 表結點 typedef struct VNode { VertexType data; // 頂點信息 ArcNode *firstarc; // 第一個表結點的地址,指向第一條依附該頂點的弧的指針 }VNode, AdjList[MAX_VERTEX_NUM];// 頭結點 typedef struct { AdjList vertices; int vexnum, arcnum; // 圖的當前頂點數和弧數 int kind; // 圖的種類標志 }ALGraph; typedef int QElemType; // 隊列類型 //單鏈隊列--隊列的鏈式存儲結構 typedef struct QNode { QElemType data; //數據域 struct QNode *next; //指針域 }QNode, *QueuePtr; typedef struct { QueuePtr front, rear; //隊頭指針。指針域指向隊頭元素 隊尾指針。指向隊尾元素 }LinkQueue; //定位功能 // 若G中存在頂點u,則返回該頂點在圖中位置;否則返回-1。int LocateVex(ALGraph G,VertexType u) { int i; for(i=0;i<G.vexnum;++i) if(strcmp(u,G.vertices[i].data)==0) return i; return -1; } //創建圖 // 採用鄰接表存儲結構,構造沒有相關信息的圖G(用一個函數構造4種圖)。 int CreateGraph(ALGraph *G) { int i,j,k; int w; // 權值 VertexType va,vb; ArcNode *p; printf("請輸入圖的類型(有向圖:0, 有向網:1, 無向圖:2, 無向網:3): \n"); scanf("%d", &(*G).kind); printf("請輸入圖的頂點數和邊數:(空格)\n"); scanf("%d%d", &(*G).vexnum, &(*G).arcnum); printf("請輸入%d個頂點的值(<%d個字符):\n",(*G).vexnum,MAX_NAME); for(i = 0; i < (*G).vexnum; ++i) // 構造頂點向量 { scanf("%s", (*G).vertices[i].data); (*G).vertices[i].firstarc = NULL; } if((*G).kind == 1 || (*G).kind == 3) // 網 printf("請順序輸入每條弧(邊)的權值、弧尾和弧頭(以空格作為間隔):\n"); else // 圖 printf("請順序輸入每條弧(邊)的弧尾和弧頭(以空格作為間隔):\n"); for(k = 0;k < (*G).arcnum; ++k) // 構造表結點鏈表 { if((*G).kind==1||(*G).kind==3) // 網 scanf("%d%s%s",&w,va,vb); else // 圖 scanf("%s%s",va,vb); i = LocateVex(*G,va); // 弧尾 j = LocateVex(*G,vb); // 弧頭 p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = j; if((*G).kind == 1 || (*G).kind == 3) // 網 { p->info = (int *)malloc(sizeof(int)); *(p->info) = w; } else p->info = NULL; // 圖 p->nextarc = (*G).vertices[i].firstarc; // 插在表頭 (*G).vertices[i].firstarc = p; if((*G).kind >= 2) // 無向圖或網,產生第二個表結點 { p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = i; if((*G).kind == 3) // 無向網 { p->info = (int*)malloc(sizeof(int)); *(p->info) = w; } else p->info = NULL; // 無向圖 p->nextarc = (*G).vertices[j].firstarc; // 插在表頭 (*G).vertices[j].firstarc = p; } } return 1; } // 銷毀圖G void DestroyGraph(ALGraph *G) { int i; ArcNode *p,*q; for(i = 0;i < (*G).vexnum; ++i) { p = (*G).vertices[i].firstarc; while(p) { q = p->nextarc; if((*G).kind%2) // 網 free(p->info); free(p); p=q; } } (*G).vexnum=0; (*G).arcnum=0; } // 返回v的值。
VertexType* GetVex(ALGraph G, int v) { if(v>=G.vexnum||v<0) exit(0); return &G.vertices[v].data; } // 對v賦新值value。 int PutVex(ALGraph *G,VertexType v,VertexType value) { int i; i=LocateVex(*G,v); if(i > -1) // v是G的頂點 { strcpy((*G).vertices[i].data,value); return 1; } return 0; } // 返回v的第一個鄰接頂點的序號。若頂點在G中沒有鄰接頂點,則返回-1。 int FirstAdjVex(ALGraph G,VertexType v) { ArcNode *p; int v1; v1 = LocateVex(G,v); // v1為頂點v在圖G中的序號 p = G.vertices[v1].firstarc; if(p) return p->adjvex; else return -1; } // 返回v的(相對於w的)下一個鄰接頂點的序號。若w是v的最后一個 // 鄰接點, 則返回-1。 int NextAdjVex(ALGraph G,VertexType v,VertexType w) { ArcNode *p; int v1,w1; v1 = LocateVex(G,v); // v1為頂點v在圖G中的序號 w1 = LocateVex(G,w); // w1為頂點w在圖G中的序號 p = G.vertices[v1].firstarc; while(p&&p->adjvex != w1) // 指針p不空且所指表結點不是w p = p->nextarc; if(!p||!p->nextarc) // 沒找到w或w是最后一個鄰接點 return -1; else // p->adjvex==w // 返回v的(相對於w的)下一個鄰接頂點的序號 return p->nextarc->adjvex; } // 在圖G中增添新頂點v(不增添與頂點相關的弧,留待InsertArc()去做)。 void InsertVex(ALGraph *G,VertexType v) { strcpy((*G).vertices[(*G).vexnum].data,v); // 構造新頂點向量 (*G).vertices[(*G).vexnum].firstarc=NULL; (*G).vexnum++; // 圖G的頂點數加1 } // 刪除G中頂點v及其相關的弧。 int DeleteVex(ALGraph *G,VertexType v) { int i,j; ArcNode *p,*q; j=LocateVex(*G,v); // j是頂點v的序號 if(j < 0 ) // v不是圖G的頂點 return 0; p = (*G).vertices[j].firstarc; // 刪除以v為出度的弧或邊 while( p ) { q = p; p = p->nextarc; if((*G).kind % 2) // 網 free(q->info); free(q); (*G).arcnum--; // 弧或邊數減1 } (*G).vexnum--; // 頂點數減1 for(i = j; i < (*G).vexnum; i++) // 頂點v后面的頂點前移 (*G).vertices[i] = (*G).vertices[i+1]; // 刪除以v為入度的弧或邊且必要時改動表結點的頂點位置值 for(i = 0; i < (*G).vexnum; i++) { p = (*G).vertices[i].firstarc; // 指向第1條弧或邊 while(p) // 有弧 { if(p->adjvex == j) // 是以v為入度的邊。 { if(p == (*G).vertices[i].firstarc) // 待刪結點是第1個結點 { (*G).vertices[i].firstarc = p->nextarc; if((*G).kind % 2) // 網 free(p->info); free(p); p = (*G).vertices[i].firstarc; if((*G).kind < 2) // 有向 (*G).arcnum--; // 弧或邊數減1 } else { q->nextarc = p->nextarc; if((*G).kind%2) // 網 free(p->info); free(p); p = q->nextarc; if((*G).kind < 2) // 有向 (*G).arcnum--; // 弧或邊數減1 } } else { if(p->adjvex > j) p->adjvex--; // 改動表結點的頂點位置值(序號) q = p; p = p->nextarc; } } } return 1; } // 在G中增添弧<v,w>,若G是無向的,則還增添對稱弧<w,v>。
int InsertArc(ALGraph *G,VertexType v, VertexType w) { ArcNode *p; int w1,i,j; i=LocateVex(*G,v); // 弧尾或邊的序號 j=LocateVex(*G,w); // 弧頭或邊的序號 if(i < 0 || j < 0) return 0; (*G).arcnum++; // 圖G的弧或邊的數目加1 if((*G).kind%2) // 網 { printf("請輸入弧(邊)%s→%s的權值: ",v,w); scanf("%d",&w1); } p=(ArcNode*)malloc(sizeof(ArcNode)); p->adjvex=j; if((*G).kind%2) // 網 { p->info=(int*)malloc(sizeof(int)); *(p->info)=w1; } else p->info = NULL; p->nextarc = (*G).vertices[i].firstarc; // 插在表頭 (*G).vertices[i].firstarc = p; if((*G).kind >= 2) // 無向,生成還有一個表結點 { p = (ArcNode*)malloc(sizeof(ArcNode)); p->adjvex = i; if((*G).kind == 3) // 無向網 { p->info = (int*)malloc(sizeof(int)); *(p->info) = w1; } else p->info=NULL; p->nextarc=(*G).vertices[j].firstarc; // 插在表頭 (*G).vertices[j].firstarc=p; } return 1; } // 在G中刪除弧<v,w>,若G是無向的,則還刪除對稱弧<w,v>。 int DeleteArc(ALGraph *G,VertexType v,VertexType w) { ArcNode *p,*q; int i,j; i = LocateVex(*G,v); // i是頂點v(弧尾)的序號 j = LocateVex(*G,w); // j是頂點w(弧頭)的序號 if(i < 0 || j < 0 || i == j) return 0; p=(*G).vertices[i].firstarc; // p指向頂點v的第一條出弧 while(p&&p->adjvex!=j) // p不空且所指之弧不是待刪除弧<v,w> { // p指向下一條弧 q=p; p=p->nextarc; } if(p&&p->adjvex==j) // 找到弧<v,w> { if(p==(*G).vertices[i].firstarc) // p所指是第1條弧 (*G).vertices[i].firstarc=p->nextarc; // 指向下一條弧 else q->nextarc=p->nextarc; // 指向下一條弧 if((*G).kind%2) // 網 free(p->info); free(p); // 釋放此結點 (*G).arcnum--; // 弧或邊數減1 } if((*G).kind>=2) // 無向,刪除對稱弧<w,v> { p=(*G).vertices[j].firstarc; // p指隙サ鉾的第一條出弧 while(p&&p->adjvex!=i) // p不空且所指之弧不是待刪除弧<w,v> { // p指向下一條弧 q=p; p=p->nextarc; } if(p&&p->adjvex==i) // 找到弧<w,v> { if(p==(*G).vertices[j].firstarc) // p所指是第1條弧 (*G).vertices[j].firstarc=p->nextarc; // 指向下一條弧 else q->nextarc=p->nextarc; // 指向下一條弧 if((*G).kind==3) // 無向網 free(p->info); free(p); // 釋放此結點 } } return 1; } // 從第v個頂點出發遞歸地深度優先遍歷圖G。 void DFS(ALGraph G,int v) { int w; VertexType v1,w1; strcpy(v1,*GetVex(G,v)); visited[v] = 1; // 設置訪問標志為1(已訪問) VisitFunc(G.vertices[v].data); // 訪問第v個頂點 for(w = FirstAdjVex(G,v1); w >= 0; w = NextAdjVex(G,v1,strcpy(w1,*GetVex(G,w)))) if(!visited[w]) DFS(G,w); // 對v的尚未訪問的鄰接點w遞歸調用DFS } // 對圖G作深度優先遍歷。
void DFSTraverse(ALGraph G,void(*Visit)(char*)) { int v; // 使用全局變量VisitFunc,使DFS不必設函數指針參數 VisitFunc = Visit; for(v = 0; v < G.vexnum; v++) visited[v] = 0; // 訪問標志數組初始化 for(v = 0; v < G.vexnum; v++) if(!visited[v]) DFS(G,v); // 對尚未訪問的頂點調用DFS printf("\n"); } // 構造一個空隊列Q。
int InitQueue(LinkQueue *Q) { (*Q).front = (*Q).rear = (QueuePtr)malloc(sizeof(QNode)); //動態分配一個空間 if(!(*Q).front) exit(0); (*Q).front->next = NULL; //隊頭指針指向空。無數據域,這樣構成了一個空隊列 return 1; } // 插入元素e為Q的新的隊尾元素。
int EnQueue(LinkQueue *Q, QElemType e) { QueuePtr p = (QueuePtr)malloc(sizeof(QNode)); if( !p ) // 存儲分配失敗 exit(0); // 生成一個以為e為數據域的隊列元素 p->data = e; p->next = NULL; //將該新隊列元素接在隊尾的后面 (*Q).rear->next = p; (*Q).rear = p; return 1; } // 若隊列不空,刪除Q的隊頭元素,用e返回其值,並返回1,否則返回0。
int DeQueue(LinkQueue *Q,QElemType *e) { QueuePtr p; if((*Q).front==(*Q).rear) return 0; p=(*Q).front->next; //隊頭元素 *e=p->data; (*Q).front->next=p->next; if((*Q).rear==p) (*Q).rear=(*Q).front; free(p); return 1; } // 若Q為空隊列,則返回1,否則返回0。
int QueueEmpty(LinkQueue Q) { if(Q.front == Q.rear) return 1; else return 0; } //按廣度優先遍歷圖G。使用輔助隊列Q和訪問標志數組visited。 void BFSTraverse(ALGraph G,void(*Visit)(char*)) { int v,u,w; VertexType u1,w1; LinkQueue Q; for(v = 0; v < G.vexnum; ++v) visited[v]=0; // 置初值 InitQueue(&Q); // 置空的輔助隊列Q for(v = 0; v < G.vexnum; v++) // 假設是連通圖,僅僅v=0就遍歷全圖 if(!visited[v]) // v尚未訪問 { visited[v]=1; Visit(G.vertices[v].data); EnQueue(&Q,v); // v入隊列 while(!QueueEmpty(Q)) // 隊列不空 { DeQueue(&Q,&u); // 隊頭元素出隊並置為u strcpy(u1,*GetVex(G,u)); for(w = FirstAdjVex(G,u1); w >= 0; w = NextAdjVex(G, u1, strcpy(w1, *GetVex(G,w)))) if(!visited[w]) // w為u的尚未訪問的鄰接頂點 { visited[w] = 1; Visit(G.vertices[w].data); EnQueue(&Q,w); // w入隊 } } } printf("\n"); } // 輸出圖的鄰接表G。 void Display(ALGraph G) { int i; ArcNode *p; switch(G.kind) { case DG: printf("有向圖\n"); break; case DN: printf("有向網\n"); break; case AG: printf("無向圖\n"); break; case AN: printf("無向網\n"); } printf("%d個頂點:\n",G.vexnum); for(i = 0; i < G.vexnum; ++i) printf("%s ",G.vertices[i].data); printf("\n%d條弧(邊):\n", G.arcnum); for(i = 0; i < G.vexnum; i++) { p = G.vertices[i].firstarc; while(p) { if(G.kind <= 1) // 有向 { printf("%s→%s ",G.vertices[i].data, G.vertices[p->adjvex].data); if(G.kind == DN) // 網 printf(":%d ", *(p->info)); } else // 無向(避免輸出兩次) { if(i < p->adjvex) { printf("%s-%s ",G.vertices[i].data, G.vertices[p->adjvex].data); if(G.kind == AN) // 網 printf(":%d ",*(p->info)); } } p=p->nextarc; } printf("\n"); } } void print(char *i) { printf("%s ",i); } int main() { int i,j,k,n; ALGraph g; VertexType v1,v2; printf("請選擇有向圖\n"); CreateGraph(&g); Display(g); printf("刪除一條邊或弧,請輸入待刪除邊或弧的弧尾 弧頭:\n"); scanf("%s%s",v1,v2); DeleteArc(&g,v1,v2); Display(g); printf("改動頂點的值。請輸入原值 新值: "); scanf("%s%s",v1,v2); PutVex(&g,v1,v2); Display(g); printf("插入新頂點。請輸入頂點的值: "); scanf("%s",v1); InsertVex(&g,v1); Display(g); printf("插入與新頂點有關的弧或邊,請輸入弧或邊數目: "); scanf("%d",&n); for(k=0;k<n;k++) { printf("請輸入還有一頂點的值: "); scanf("%s",v2); printf("對於有向圖,請輸入還有一頂點的方向(0:弧頭 1:弧尾): "); scanf("%d",&j); if(j) InsertArc(&g,v2,v1); else InsertArc(&g,v1,v2); } Display(g); printf("刪除頂點及相關的弧或邊,請輸入頂點的值: "); scanf("%s",v1); DeleteVex(&g,v1); Display(g); printf("深度優先搜索的結果:\n"); DFSTraverse(g,print); printf("廣度優先搜索的結果:\n"); BFSTraverse(g,print); DestroyGraph(&g); system("pause"); return 0; }
效果: