圖的【十字鏈表】表示法是一種鏈式存儲結構,可以看成是【鄰接表】和【逆鄰接表】的組合
本文中用到的有向圖
/************************************************************************ 有向圖的存儲:十字鏈表 有向圖的十字鏈表存儲結構,是有一種鏈式存儲結構,可以看成是【鄰接表】和【逆鄰接表】 的結合。 圖中每條弧對應一個【弧結點】,每個頂點對應一個【頂點結點】 弧結點 -------------------------------------------- | tailvex | headvex | hlink | tlink | info | -------------------------------------------- talivex:以該弧為【弧尾】的結點在圖中的位置 headvex:以該弧為【弧頭】的結點在圖中的位置 hlink: 下一條與該弧有【相同弧頭的弧】 tlink: 下一條與該弧有【相同弧尾的弧】 info: 弧的相關信息,權值等 頂點結點 ----------------------------- | data | firstin | firstout | ----------------------------- data: 該結點的數據 firstin: 第一條以該弧為弧頭的【弧結點】 firstout:第一條以該弧為弧尾的【弧結點】 ************************************************************************/
相關數據結構
//頂點結點最大數量
int const MAX_VERTEXNUM = 100; //數據結構
typedef int InfoType; typedef int Data; //弧結點
typedef struct _tagArcBox { int tailvex; //該弧的弧尾結點在圖中的位置
int headvex; //該弧的弧頭結點在圖中的位置
struct _tagArcBox* hlink; //下一條與該弧有相同弧頭結點的弧
struct _tagArcBox* tlink; //下一條與該弧有相同弧尾結點的弧
InfoType info; //弧的相關信息
}ArcBox; //頂點結點
typedef struct _tagArcNode { Data data; //數據
ArcBox* firstin; //第一條以該節點為弧尾的弧
ArcBox* firstout; //第一條以該結點為弧頭的弧
}ArcNode; //十字鏈表存儲結構的有向圖
typedef struct _tagOLGraph { ArcNode vertex[MAX_VERTEXNUM]; //頂點向量
int vexnum; //頂點數
int arcnum; //弧樹
}OLGraph, *POLGraph;
從頂點向量中查找該頂點在圖中的位置(下標)
//輸入圖的【頂點向量】和某個頂點的數據 //獲取此頂點在頂點向量中的位置(下標)
int LocateNode(ArcNode* pNodesArr, Data data, int length) { if( NULL == pNodesArr ) return NULL; for (int i = 0; i < length; ++ i) { if (pNodesArr[i].data == data) return i; } return -1; } //輸入【圖】和某頂點的數據 //獲取該頂點在頂點向量中的位置(下標)
int LocateNode(POLGraph& pOLGraph, Data data) { return (NULL == pOLGraph ? NULL : LocateNode(pOLGraph->vertex, data, pOLGraph->vexnum) ); }
有向圖的創建
/************************************************************************ 當使用這種函數原型時: void CreateOLGraph(POLGPraph& pOLGraph) { pOLGraph = new OLGraph(); //codes pOLGraph->vexnum = num; //這里報運行時錯誤! why? } ************************************************************************/
//創建十字鏈表有向圖
POLGraph CreateOLGraph() { POLGraph pOlGraph = NULL; __try { pOlGraph = new OLGraph(); if(NULL == pOlGraph) {cerr << "申請圖結點失敗!\n"; return NULL;} int num = 0; cout << "請輸入圖的頂點數量:"; cin >> num; pOlGraph->vexnum = num; cout << endl; cout << "請輸入圖的弧的數量:"; cin >> num; pOlGraph->arcnum = num; cout << endl; Data data = 0; cout << endl << "--------開始 初始化頂點向量-----------"<<endl; for (int i = 0; i < pOlGraph->vexnum; ++i) { cout << "請輸入結點的值:"; cin >> data; pOlGraph->vertex[i].data = data; pOlGraph->vertex[i].firstin = NULL; pOlGraph->vertex[i].firstout = NULL; cout<<endl; } //for
cout <<endl<<"------------結束 初始化頂點向量------------"<<endl; cout<<endl<<endl; cout << "************開始 初始化弧結點 **************"<<endl; for(int i = 0; i < pOlGraph->arcnum; ++ i) { cout << "請輸入弧的弧尾結點:"; cin >> data; int begin = LocateNode(pOlGraph->vertex, data, pOlGraph->arcnum); if(-1 == begin) {cerr << "您輸入的弧尾結點不在圖中,請重新輸入"<<endl; --i; continue;} cout << "請輸入弧的弧頭結點:"; cin >> data; int end = LocateNode(pOlGraph->vertex, data, pOlGraph->arcnum); if(-1 == end) {cerr << "您輸入的弧頭結點不在圖中,請重新輸入"<<endl; -- i; continue;} cout << "請輸入弧的權值:"; cin >> data; cout<<endl<<endl; ArcBox* pArcBox = new ArcBox(); if(NULL == pArcBox) {cerr << "申請弧結點失敗!"<<endl; -- i; continue;} pArcBox->tailvex = begin; //該弧的弧尾在圖中的位置
pArcBox->headvex = end; //該弧的弧頭在圖中的位置
pArcBox->hlink = pOlGraph->vertex[end].firstin; //下一條與該弧有相同弧尾的弧結點
pArcBox->tlink = pOlGraph->vertex[begin].firstout; //下一條與該弧有相同弧頭的弧結點
pArcBox->info = data; //權值
pOlGraph->vertex[begin].firstout = pOlGraph->vertex[end].firstin = pArcBox; } //for
} //__try
__except(1) { cerr << endl<<"有異常發生"<<endl; } return pOlGraph; }
運行情況:
出度和入度
//求圖的出度 //先根據輸入的頂點的值,求得該點所在的頂點向量的分量 //然后得到該點的firstout,然后再得到所有與該弧有相同弧尾 //結點的弧(的條數)
int OutDegree(POLGraph& pOLGraph, Data data) { int nCount = 0; //根據結點的值定位該點在圖的頂點向量中的位置(下標)
int nIndex = LocateNode(pOLGraph, data); if(-1 == nIndex) {cerr << "該點不在圖中,所以沒有出度!\n"<<endl; return -1;} //得到該結點指針
ArcNode* pArcNode = &(pOLGraph->vertex[nIndex]); if(NULL == pArcNode) {cerr << "在圖中查找該頂點出錯!\n"<<endl; return -1;} //第一條該頂點為弧尾的弧(該頂點結點的第一條出邊)
ArcBox* pArcBox = pArcNode->firstout; //查找所有以該【頂點結點】為【弧尾】的【弧結點】
while(NULL != pArcBox) { ++ nCount; pArcBox = pArcBox->tlink; } return nCount; } //定點的入度
int InDegree(POLGraph& pOLGraph, Data data) { int nCount = 0; //定位該頂點結點在頂點向量中的位置(下標)
int nIndex = LocateNode(pOLGraph, data); if(-1 == nIndex){cerr << "該點不在圖中,所以沒有入度!\n"<<endl; return -1;} //得到該頂點結點的指針
ArcNode* pArcNode = &(pOLGraph->vertex[nIndex]); if(NULL == pArcNode) {cerr << "在圖中查找該點出錯!"<<endl; return -1;} //第一條以該頂點結點為弧頭的弧(該頂點結點的第一條入邊)
ArcBox* pArcBox = pArcNode->firstin; //查找所有以該【頂點結點】為【弧頭】的【弧結點】
while(NULL != pArcBox) { ++nCount; pArcBox = pArcBox->hlink; } return nCount; }
查找出度 / 入度
POLGraph pGraph = CreateOLGraph(); int num = 0; for(int i = 0; i < pGraph->vexnum + 3; ++ i) { cout << "請輸入帶查的頂點的值:"; cin >> num; cout<<"結點 "<< num << " 的出度OutDegree為:"<<OutDegree(pGraph, num); cout<<endl; cout<<"結點 "<< num << " 的入度InDegree為:"<<InDegree(pGraph, num); cout<<endl<<endl; }
深度優先遍歷(DFS)
/************************************************************************ 深度優先遍歷 從某頂點出發,訪問之,然后獲得該頂點的第一條出邊(firstout),如果該出邊 不為空,則獲得該條出邊的【弧頭】結點在圖中的位置(下標),查看此下標的結點 是否被訪問過,如果沒有則根據此下標獲取該結點,然后遞歸訪問之;如果此結點 被訪問過了,則說明出現回路,及此條弧指向了之前訪問過的結點,需要跳出循環, 否則出現死循環。 ************************************************************************/
//頂點結點是否遍歷過標志數組
bool* pVisited = NULL; void DFS(POLGraph& pOLGraph, ArcNode* pArcNode) { int nIndex = LocateNode(pOLGraph, pArcNode->data); pVisited[nIndex] = true; cout << "the node is "<<pArcNode->data<<endl; ArcBox* pArcBox = pArcNode->firstout; while(NULL != pArcBox) { //該弧的弧頭在圖中的位置
int nHeadVex = pArcBox->headvex; if (pVisited[nHeadVex] == false) { ArcNode* pHeadNode = &(pOLGraph->vertex[nHeadVex]); DFS(pOLGraph, pHeadNode); } //如果某條弧的弧頭結點已經被訪問過時,則說明已經有了回路,此時要跳出循環 //否則會在while中死循環
else { break; } } } //有向圖的深度優先遍歷
void DFSTraverse(POLGraph& pOLGraph) { if(NULL == pOLGraph) {cerr << "該圖為空!"; return;} pVisited = new bool[pOLGraph->vexnum](); for (int i = 0; i < pOLGraph->vexnum; ++ i) pVisited[i] = false; for (int i = 0; i < pOLGraph->vexnum; ++ i) { if (! pVisited[i]) { DFS(pOLGraph, &pOLGraph->vertex[i]); } } }
深度優先遍歷結果
廣度優先遍歷(BFS)
關於廣度優先遍歷,我見到兩種寫法
這兩種寫法大致如下
方式1
for(int i = 0; i < 圖中點的數量; ++ i) { if(結點 i 沒有被訪問過) { 訪問之 入隊 while(隊不空) { 出隊 訪問隊頭結點所有鄰接點,將訪問過的鄰接點的訪問標志數組的分量 置為true; } 將該結點 i 置為鄰接點訪問過 } }
方式2
int nStart = 0; //從圖中位置為nStart的結點開始遍歷
將該結點入隊 訪問該nStart結點 if(nStart結點的鄰接點沒有被訪問) { while(隊不空) { 隊頭元素出隊 while(隊頭元素仍然有出邊) { 訪問隊頭元素的所有出邊的弧頭結點,並置訪問過的結點的 訪問標志置為true } } //while
將該點的 鄰接點訪問標志 置為true } //if
下面是這兩種方式的代碼實現,圖的存儲結構是十字鏈表(有向圖的存儲結構)
//----------------------------方式1------------------------------------ //有向圖的廣度優先遍歷
void BFS(POLGraph& pOLGraph) { if(NULL == pOLGraph) return; bool *pVisitedLinjie = new bool[pOLGraph->vexnum]; pVisited = new bool[pOLGraph->vexnum]; for(int i = 0; i < pOLGraph->vexnum; ++ i){pVisited[i] = false; pVisitedLinjie[i] = false;} queue<ArcNode*> quArcNode; for (int i = 0; i < pOLGraph->vexnum; ++ i) { if (pVisited[i] == false) { cout << "data of node is "<<pOLGraph->vertex[i].data<<endl; quArcNode.push(&(pOLGraph->vertex[i])); while(! quArcNode.empty()) { //取隊頭元素, 並出隊
ArcNode* pArcNode = quArcNode.front(); quArcNode.pop(); //定位該隊頭元素在圖中的位置
int nIndex = LocateNode(pOLGraph, pArcNode->data); if(-1 == nIndex) return; //如果該結點的【鄰接點】【未被訪問】
if (pVisitedLinjie[nIndex] == false) { //訪問其所有鄰接點 //找隊頭元素的第一條【出邊】
ArcBox* pArcBox = pArcNode->firstout; while(NULL != pArcBox) { //定位該弧的【弧頭結點】在圖中的位置
int nHead = pArcBox->headvex; //如果該弧頭節點【未被訪問】,則訪問之,如果已被訪問則說明出現了環
if (false == pVisited[nHead]) { cout << "data of node is "<<pOLGraph->vertex[nHead].data<<endl; //將該弧頭節點置為【訪問過】
pVisited[nHead] = true; //將該結點 入隊
quArcNode.push(&(pOLGraph->vertex[nHead])); //下一條與該弧有相同【弧尾】的弧
pArcBox = pArcBox->tlink; } else { //出現了環,退出
break; } } //到這,該節點的所有鄰接點都訪問過了 //將其鄰接點訪問標記 標為【訪問過】
pVisitedLinjie[nIndex] = true; } //if
} //while
} } }
//------------------------------方式2------------------------------------ //nStart為從圖中位置為nStart的結點開始遍歷
void BFS(POLGraph& pOLGraph, int nStart) { if(NULL == pOLGraph) return; bool *pVisitedLinjie = new bool[pOLGraph->vexnum]; pVisited = new bool[pOLGraph->vexnum]; for(int i = 0; i < pOLGraph->vexnum; ++ i){pVisited[i] = false; pVisitedLinjie[i] = false;} cout << "data of node is "<<pOLGraph->vertex[nStart].data<<endl; pVisited[nStart] = true; queue<ArcNode*> quArcNode; quArcNode.push(&(pOLGraph->vertex[nStart])); if (pVisited[nStart] == false) { cout << "data of node is "<<pOLGraph->vertex[i].data<<endl; quArcNode.push(&(pOLGraph->vertex[i])); while(! quArcNode.empty()) { //取隊頭元素, 並出隊
ArcNode* pArcNode = quArcNode.front(); quArcNode.pop(); //定位該隊頭元素在圖中的位置
int nIndex = LocateNode(pOLGraph, pArcNode->data); if(-1 == nIndex) return; //如果該結點的【鄰接點】【未被訪問】
if (pVisitedLinjie[nIndex] == false) { //訪問其所有鄰接點 //找隊頭元素的第一條【出邊】
ArcBox* pArcBox = pArcNode->firstout; while(NULL != pArcBox) { //定位該弧的【弧頭結點】在圖中的位置
int nHead = pArcBox->headvex; //如果該弧頭節點【未被訪問】,則訪問之,如果已被訪問則說明出現了環
if (false == pVisited[nHead]) { cout << "data of node is "<<pOLGraph->vertex[nHead].data<<endl; //將該弧頭節點置為【訪問過】
pVisited[nHead] = true; //將該結點 入隊
quArcNode.push(&(pOLGraph->vertex[nHead])); //下一條與該弧有相同【弧尾】的弧
pArcBox = pArcBox->tlink; } else { //出現了環,退出
break; } } //到這,該節點的所有鄰接點都訪問過了 //將其鄰接點訪問標記 標為【訪問過】
pVisitedLinjie[nIndex] = true; } //if
} //while
} //if
}