這兒是我的筆記,希望大家可以友好交流!!謝謝#__#
參考站上的大神的資料學習,還是很有效的,感覺很好。
圖的表示一般有兩種方式,鄰接矩陣和鄰接鏈表,圖本身也有兩種有向和無向,先從簡單的開始,有向理解了之後,無向圖也能很快的寫出來了,並且要是輸入量較大,鄰接矩陣往往會很浪費考空間(稀圖),因此鄰接矩陣一般適合頂點數較少的情況下。
**圖的表示**
struct ListGraph;
typedef struct ListGraph LISTGRAPH;
typedef struct ListGraph* LISTGRAPH_T;
struct VexNode;
typedef struct VexNode VEXNODE;
typedef struct VexNode* VEXNODE_T;
struct Edge_Node;
typedef struct Edge_Node EDGENODE;
typedef struct Edge_Node* EDGENODE_T;
//以下的結構體放在實現文件中
struct ListGraph
{
int vex_num;
int edge_num;
VEXNODE_T vex_hash[MAXNUM_VEX];
};
struct VexNode
{
char* vex_name;//節點的信息
EDGENODE_T head;//頭插法
};
struct Edge_Node
{
int vex_index;
EDGENODE_T next;
};
ListGraph是圖結構,包含有當前頂點數,邊數,還有一唯的頂點數組,存放頂點結構體變量(這個數組其實可以看成一個特殊的哈希表,只不過這裏是順序存儲頂點);
VexNode 是頂點的結構體,包含頂點的信息(名稱等),還有表示邊的及結構體(也就是第一個節點,邊鏈表的第一個節點);
Edge_Node是邊的結構體,包含有另一頂點在頂點數組的序號(位置)。
爲了條理清晰些,先看簡單頭文件(只用於生成圖,打印圖)
/**最大的頂點個數*/
#define MAXNUM_VEX 20
//訪問函數指針變量
typedef void (*VisitFun)(char*);
typedef enum
{
false = 0,
true,
}bool;
struct ListGraph;
typedef struct ListGraph LISTGRAPH;
typedef struct ListGraph* LISTGRAPH_T;
struct VexNode;
typedef struct VexNode VEXNODE;
typedef struct VexNode* VEXNODE_T;
struct Edge_Node;
typedef struct Edge_Node EDGENODE;
typedef struct Edge_Node* EDGENODE_T;
/***********對於圖的基本構成接口************/
LISTGRAPH_T ListGraph_Init(LISTGRAPH_T LG);
bool ListGraph_InsertOneVex(LISTGRAPH_T LG,char* vexname);
bool ListGraph_InsertOneEdge(LISTGRAPH_T LG,char* start_vex,char* end_vex);
int ListGraph_GetIndexByVexName(LISTGRAPH_T LG,char* vexname);
char* ListGraph_GetNameByVexIndex(LISTGRAPH_T LG,int vexindex);
EDGENODE_T ListGraph_GetEdgeHeadNode(LISTGRAPH_T LG,char* vexname);
bool ListGraph_DeleteOneVex(LISTGRAPH_T LG,char* vexname);
bool ListGraph_DeleteOneArc(LISTGRAPH_T LG,char* start_vex,char* end_vex);
void ListGraph_DeleteGraph(LISTGRAPH_T LG);
void ListGraph_Printf(LISTGRAPH_T LG);
void LIStGraph_PrintfVex(LISTGRAPH_T LG);
void ListGraph_PrintfEdge(LISTGRAPH_T LG);
基本操作有:(全部的代碼後面再粘貼上來)
1:找到頂點的位置,沒找到返回-1,否則返回序號
ListGraph_GetIndexByVexName
2:返回頂點的名稱信息
ListGraph_GetNameByVexIndex
3:讀入一個頂點:
ListGraph_InsertOneVex
只要新加頂點A時不超過最大頂點數以及現有頂點中不含有A(避免重複),就正確加入到後面。
4:讀入一條邊
ListGraph_InsertOneEdge
先找到起點的頂點的序號,找到對應鏈表,將包含終點位置信息的邊結構體進行插入到鏈表中(本文中採用頭插)
5:刪除一個頂點
ListGraph_DeleteOneVex
找到對應頂點的序號index,刪除頂點對應的鏈表(他的爲起點的邊都要刪除),然後將index後面的頂點往移動;最後去刪除以該頂點爲終點的邊,在尋找時要注意所有終點的位置(邊結構體中的頂點序號)大於index都要減1。
6:刪除一條弧線
ListGraph_DeleteOneArc
這裏簡單化了,少考慮了一點,就是若起點只有一條出邊的話,要刪除頂點;所以這裏只是刪除了終點的邊結構體。(從這一點讓頂點包含出入度的信息是很重要的,這裏也簡化了,只是在後續的深度遍歷時從頭到尾算了一遍入度)
LISTGRAPH_T ListGraph_Init(LISTGRAPH_T LG)
{
LG = (LISTGRAPH_T)malloc(sizeof(LISTGRAPH));
if(LG == NULL)
return NULL;
memset(LG,0,sizeof(LISTGRAPH));
return LG;
}
bool ListGraph_InsertOneVex(LISTGRAPH_T LG,char* vexname)
{
VEXNODE_T newvnode = NULL;
int m = LG->vex_num + 1;
if(m == MAXNUM_VEX)
{
printf("不能再添加頂點了\n");
return false;
}
m = ListGraph_GetIndexByVexName(LG,vexname);
if(m != -1)
{
printf("出現了相同的頂點,插入頂點失敗\n");
return false;
}
//等於-1說明可以加入頂點
newvnode = (VEXNODE_T)malloc(sizeof(VEXNODE));
if(newvnode == NULL)
{
printf("插入頂點失敗\n");
return false;
}
memset(newvnode,0,sizeof(VEXNODE));
newvnode->vex_name = vexname;
newvnode->head = NULL;
LG->vex_hash[LG->vex_num] = newvnode;
LG->vex_num++;
}
bool ListGraph_InsertOneEdge(LISTGRAPH_T LG,char* start_vex,char* end_vex)
{
if(LG == NULL)
{
printf("ListGraph_InsertOneEdge圖是無效的\n",NULL);
return false;
}
int start_index = ListGraph_GetIndexByVexName(LG,start_vex);
int end_index = ListGraph_GetIndexByVexName(LG,end_vex);
if(start_index == -1 || end_index == -1)
{
printf("插入弧失敗\n");
return false;
}
//獲取弧線的鏈表頭結點
LG->vex_hash[start_index]->head = EdgeList_InsertToHead(LG->vex_hash[start_index]->head,end_index);
LG->edge_num++;
return true;
}
int ListGraph_GetIndexByVexName(LISTGRAPH_T LG,char* vexname)
{
if(LG == NULL)
return -1;
int i = 0;
for( i = 0;i < LG->vex_num;i++)
{
if(!strcmp(LG->vex_hash[i]->vex_name,vexname))
{
return i;
}
}
return -1;
}
char* ListGraph_GetNameByVexIndex(LISTGRAPH_T LG,int vexindex)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return NULL;
}
return LG->vex_hash[vexindex]->vex_name;
}
EDGENODE_T ListGraph_GetEdgeHeadNode(LISTGRAPH_T LG,char* vexname)
{
if(LG == NULL)
{
printf("ListGraph_InsertOneEdge圖是無效的\n",NULL);
return false;
}
int index = ListGraph_GetIndexByVexName(LG,vexname);
return LG->vex_hash[index]->head;
}
bool ListGraph_DeleteOneVex(LISTGRAPH_T LG,char* vexname)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return false;
}
int i = ListGraph_GetIndexByVexName(LG,vexname);
if(i == -1)
{
printf("沒有對應的頂點\n");
return false;
}
//刪除頂點帶的鏈表
Edge_DeleteAll(LG,LG->vex_hash[i]->head);
/**接着要將頂點往前移動*/
int j = i;
for(;j < LG->vex_num - 1; j++ )
{
LG->vex_hash[j] = LG->vex_hash[j+1];
}
LG->vex_hash[j+1] = NULL;
LG->vex_num--;
/**************************刪除弧線*******************************************/
for(int k = 0; k < LG->vex_num; k++)
{
EDGENODE_T head = LG->vex_hash[k]->head;
EDGENODE_T prenode = head;/**爲了好刪除,每次要保留上次的節點*/
while(head != NULL)//有弧
{
if(head->vex_index == i)//是以待刪除的頂點爲入度
{
if(head == LG->vex_hash[k]->head)//節點是第一個節點
{
LG->vex_hash[k]->head = head->next;
free(head);
head = NULL;
LG->edge_num--;
break;
}
else
{
prenode->next = head->next;
free(head);
head = NULL;
LG->edge_num--;
break;
}
}
else//注意要將頂點的位置改變下
{
if(head->vex_index > i)
head->vex_index--;
prenode = head;/**保留本次*/
head = head->next;
}
}
}
return true;
}
bool ListGraph_DeleteOneArc(LISTGRAPH_T LG,char* start_vex,char* end_vex)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return false;
}
int start_index = ListGraph_GetIndexByVexName(LG,start_vex);
int end_index = ListGraph_GetIndexByVexName(LG,end_vex);
if(start_index == -1 || end_index == -1)
return false;
EDGENODE_T head = LG->vex_hash[start_index]->head;
EDGENODE_T prenode = head;
while(head != NULL)
{
if(head->vex_index == end_index)
{
if(head == LG->vex_hash[start_index]->head)
{
LG->vex_hash[start_index]->head = head->next;
free(head);
head = NULL;
LG->edge_num--;
break;
}
else
{
prenode->next = head->next;
free(head);
head = NULL;
LG->edge_num--;
break;
}
}
else
{
prenode = head;
head = head->next;
}
}
}
void LIStGraph_PrintfVex(LISTGRAPH_T LG)
{
printf("圖現在有多少個%d頂點\n",LG->vex_num);
for(int i = 0; i <LG->vex_num ; i++)
{
printf("頂點%d:%s\t",i,LG->vex_hash[i]->vex_name);
}
printf("\n");
}
void ListGraph_PrintfEdge(LISTGRAPH_T LG)
{
printf("圖現在有%d條邊\n",LG->edge_num);
for(int i = 0; i <LG->vex_num ; i++)
{
EDGENODE_T head = ListGraph_GetEdgeHeadNode(LG,LG->vex_hash[i]->vex_name);
EDGENODE_T p = head;
while(p!=NULL)
{
printf("(%s %s)\t",LG->vex_hash[i]->vex_name,ListGraph_GetNameByVexIndex(LG,p->vex_index));
p = p->next;
}
}
printf("\n");
}
void ListGraph_Printf(LISTGRAPH_T LG)
{
LIStGraph_PrintfVex(LG);
ListGraph_PrintfEdge(LG);
}
void ListGraph_DeleteGraph(LISTGRAPH_T LG)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return ;
}
int n = LG->vex_num;
for(int i = 0; i < n; i++)
{
Edge_DeleteAll(LG,LG->vex_hash[i]->head);
LG->vex_hash[i]->head = NULL;
}
free(LG->vex_hash);
free(LG);
}
加入深度優先和廣度優先搜索
圖的深度優先搜索有點像樹的先序遍歷,廣度優先遍歷又有點像樹的按層次遍歷需要藉助輔助隊列,因此深度優先搜索可以用遞歸也可以不用,則需要另外需要輔助棧。
另外有個結構經常用到以隊列爲例:
//或許外面還會有個循環
EnQueue(queue,data);
while(!Queue_Isempty(queue))
{
DeQueue(queue);
//得到出來的元素判斷處理
//其他判斷
EnQueue(queue);
}
【深度優先搜索】:
(表現爲從一個頂點A觸發,若有邊則到第一個領接頂點B,又到B處得到B的第一個領接頂點;相反,廣度優先搜索時,基本會按鏈表方向的)
首先訪問出發點v,並將其標記爲已訪問過;然後依次從v出發搜索v的每個鄰接點w。若w未曾訪問過,則以w爲新的出發點繼續進行深度優先遍歷,直至圖中所有和源點v有路徑相通的頂點(亦稱爲從源點可達的頂點)均已被訪問爲止。若此時圖中仍有未訪問的頂點,則另選一個尚未訪問的頂點作爲新的源點重複上述過程,直至圖中所有頂點均已被訪問爲止。
圖的深度優先遍歷類似於樹的前序遍歷。
1:驅動函數(需要藉助輔助數組)
void ListGraph_DFSTranverse(LISTGRAPH_T LG,VisitFun visit);
2:搜索的遞歸函數(也先不要輔助數組,所以定義爲全局變量)
void ListGraph_DFS(LISTGRAPH_T LG ,VisitFun visit,int index);
void ListGraph_DFSTranverse(LISTGRAPH_T LG,VisitFun visit)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return ;
}
for(int i = 0;i < MAXNUM_VEX; i++)
{
visited[i] = 0;
}
for(int i = 0; i < LG->vex_num; i++)
{
if(!visited[i])//未訪問過的話
{
ListGraph_DFS(LG,visit,i);
}
}
}
void ListGraph_DFS(LISTGRAPH_T LG ,VisitFun visit,int index)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return ;
}
char* data = LG->vex_hash[index]->vex_name;
visit(data);
visited[index] = 1;//置訪問標誌
EDGENODE_T head = LG->vex_hash[index]->head;
//找一個沒有訪問過的頂點進行遞歸
if(head != NULL && !visited[head->vex_index])
ListGraph_DFS(LG,visit,head->vex_index);
}
【廣度優先搜索】
1、從圖中某個頂點V0出發,並訪問此頂點;
2、從V0出發,訪問V0的各個未曾訪問的鄰接點W1,W2,…,Wk;然後,依次從W1,W2,…,Wk出發訪問各自未被訪問的鄰接點;
類似與層次遍歷,會將V0的所有第一個領接頂點訪問完,纔會去訪問第二個層次,要藉助輔助隊列
輔助隊列部分,當然可以也可以用數組,不用這個鏈式隊列
struct QueueNode
{
int vex_index;
struct QueueNode* next;
};
struct ListQueue
{
struct QueueNode* front;
struct QueueNode* rear;
};
LISTQUEUE_T ListQueue_Init(LISTQUEUE_T LQ)
{
LQ = (LISTQUEUE_T)malloc(sizeof(LISTQUEUE));
if(LQ == NULL)
return NULL;
LQ->front = LQ->rear = (QUEUENODE_T)malloc(sizeof(QUEUENODE));
if(LQ->front == NULL)
{
free(LQ);
return NULL;
}
LQ->front->next = LQ->rear->next = NULL;
return LQ;
}
bool ListQueue_IsEmpty(LISTQUEUE_T LQ)
{
if(LQ != NULL)
return LQ->front->next == NULL ? true : false;
else
return false;
}
bool ListQueue_EnQueue(LISTQUEUE_T LQ,int data)
{
if(LQ == NULL)
return false;
QUEUENODE_T newnode = (QUEUENODE_T)malloc(sizeof(QUEUENODE));
if(newnode == NULL)
return false;
newnode -> next = NULL;
newnode->vex_index = data;
LQ->rear->next = newnode;
LQ->rear = newnode;
return true;
}
bool ListQueue_DeQueue(LISTQUEUE_T LQ,int* data)
{
if(LQ == NULL || ListQueue_IsEmpty(LQ))
return false;
*data = LQ->front->next->vex_index;
QUEUENODE_T tmp = LQ->front->next;
LQ->front->next = tmp->next;
free(tmp);
tmp = NULL;
if(ListQueue_IsEmpty(LQ))
{
LQ->rear = LQ->front;
}
return true;
}
搜索實現部分:
void ListGraph_BFSTranverse(LISTGRAPH_T LG,VisitFun visit)
{
if(LG == NULL)
{
printf("圖是無效的NULL\n");
return ;
}
for(int i = 0; i < MAXNUM_VEX;i++)
visited[i] = 0;
LISTQUEUE_T queue = ListQueue_Init(queue);
char* data = NULL;
//也保證從順序進行,若是連通圖,則可以i=0就可以遍歷完
for(int i = 0; i < LG->vex_num;i++)
{
if(!visited[i])
{
ListQueue_EnQueue(queue,i);
data= LG->vex_hash[i]->vex_name;
visit(data);//訪問
visited[i] = 1;//置訪問標誌
while(!ListQueue_IsEmpty(queue))
{
int index = ListQueue_DeQueue(queue,&index);
/**要找index所在那條鏈表相對index的下一個領接頂點*/
EDGENODE_T head = LG->vex_hash[index]->head;
while(head != NULL)
{
//用輔助數組表示沒找到
if(visited[head->vex_index])
{
head = head->next;
}
//找到了領接點
else
{
ListQueue_EnQueue(queue,head- >vex_index);
data = LG->vex_hash[head->vex_index]->vex_name;
visit(data);
visited[head->vex_index] = 1;
}
}
}
}
}
}
【拓展排序】
拓展排序適用於有向無環圖,訪問的順序一定是起點在前,終點在後,若有ABCD頂點,A->B,C->B,C->D,則訪問是ACBD。
思想:將途中沒有入度爲0的頂點入隊1,再出隊得到隊首頂點V將V放入結果隊列2中,將v的所有領接邊的入度減去1,此時減的過程中判斷入度爲0的話就入隊。
/**用於有向圖的拓補排序*/
void ListGraph_TuoBuSort(LISTGRAPH_T LG,char** vexnode)
{ /**輔助隊列,壓入入度爲0的頂點*/
LISTQUEUE_T queue = ListQueue_Init(queue);
int Rudu[MAXNUM_VEX] = {0};
/**統計各個頂點的入度正確*/
for(int i = 0; i < LG->vex_num; i++)
{
EDGENODE_T head = LG->vex_hash[i]->head;
while (head != NULL)
{
Rudu[head->vex_index]++;
head = head->next;
}
}
printf("\n");
int index = 0;
int res_index = 0;
char* result[MAXNUM_VEX] = {0};
for(int i = 0; i < LG->vex_num; i++)
{
if(Rudu[i] == 0)
{
ListQueue_EnQueue(queue,i);
while(!ListQueue_IsEmpty(queue))
{
ListQueue_DeQueue(queue,&res_index);
vexnode[index++] = LG->vex_hash[res_index]->vex_name;
EDGENODE_T head = LG->vex_hash[res_index]->head;
while(head != NULL)
{
Rudu[head->vex_index]--;/**所有這個頂點相關的點入度都-1*/
if(Rudu[head->vex_index] != 0)
{
head = head->next;
}
else
{
ListQueue_EnQueue(queue,head->vex_index);
}
}
}
}
}
}
還有行文中關於鏈表的部分
bool EdgeList_IsEmpty(EDGENODE_T head)
{
if(head == NULL)
return true;
return false;
}
EDGENODE_T EdgeList_InsertToHead(EDGENODE_T head,int index)
{
EDGENODE_T newedgenode= (EDGENODE_T )malloc(sizeof(EDGENODE));
if(newedgenode == NULL)
return NULL;
memset(newedgenode,0,sizeof(EDGENODE));
newedgenode->vex_index = index;
newedgenode->next = NULL;
if(EdgeList_IsEmpty(head))
{
head = newedgenode;
return head;
}
//EDGENODE_T tmp = head;
newedgenode->next = head;
head = newedgenode;
//head->next = newedgenode;
return head;
}
void Edge_DeleteAll(LISTGRAPH_T LG,EDGENODE_T head)
{
EDGENODE_T p = head;
while( p != NULL)
{
EDGENODE_T tmp = p->next;
free(p);
LG->edge_num--;
p = tmp;
}
}
到此,圖的領接表示就完了。