圖的基本內容
鄰接表和鄰接矩陣的實現方式將在具體算法實現中完成,不做贅述。
鄰接矩陣的遍歷:
1.深度優先遍歷:(遞歸實現)一直走不回頭
(1)訪問指定起始地點。
(2)若當前訪問頂點的鄰接頂點有未被訪問的頂點,就任選一個訪問。如果沒有就回退到最近訪問的頂點,直到與起始頂點相通的所有點被遍歷完。
(3)若途中還有頂點未被訪問,則再選一個點作爲起始頂點。重複步驟2(針對非連通圖)。
2.廣度優先遍歷:(隊列實現)走完一個點的所有臨界點再往下走
(1)訪問指定起始點。
(2)訪問當前頂點的鄰接頂點有未被訪問的頂點,並將之放入隊列中。
(3)刪除隊列的隊首節點。訪問當前隊列的隊首,重複步驟2。直到隊列爲空。
(4)若若途中還有頂點未被訪問,則再選一個點作爲起始頂點。重複步驟2。(針對非連通圖)。
#include<iostream>
#include<queue>
using namespace std;
const int MaxSize = 100;
int MinEdge(int a[],int n)
{
int min = 100;
int index = 1;
for(int i = 1;i<=n;i++)
{
if(a[i]<=min&&a[i]!=0)
{
min = a[i];
index = i;
}
}
return index;
}
template <typename DataType>
class MGraph
{
public:
MGraph(DataType a[],int n,int e);
~MGraph(){};
void DFT(int v);
void BFT(int v);
private:
DataType vertex[MaxSize];
int visted[MaxSize];
int edge[MaxSize][MaxSize];
int vertexNum,edgeNum;
};
template <typename DataType>
MGraph<DataType>::MGraph(DataType a[],int n,int e)
{
vertexNum = n;
edgeNum = e;
int w;
for(int i = 1;i<=n;i++)
{
vertex[i] = a[i];
}
for(int i = 1;i<=n;i++)
{
visted[i] = 0;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cin>>edge[i][j];
}
}
}
template <typename DataType>
void MGraph<DataType>::DFT(int v)//深度優先搜索
{
cout<<vertex[v];//輸出頂點具體信息
visted[v] = 1;//修改標記
for(int i = 1;i<=vertexNum;i++)//遍歷圖中的所有頂點
{
if(visted[i]!=1&&edge[v][i]<100)//如果與已知頂點相鄰並且沒有被訪問過
DFT(i);//深度優先遍歷這個鄰接點
}
}
template <typename DataType>
void MGraph<DataType>::BFT(int v)//廣度優先搜索
{
queue<int>q;
q.push(v);//起始頂點無條件入隊
cout<<vertex[v];//輸出頂點信息
visted[v] = 1;//修改起始點的訪問標識符
while(!q.empty())//當隊列不空時
{
v = q.front();//取隊首元素
q.pop();//刪除隊首元素
for(int i = 1;i<=vertexNum;i++)//遍歷所有的頂點
{
if(visted[i]!=1&&edge[v][i]<100)//如果與已知頂點相鄰並且沒有被訪問過
{
cout<<vertex[i];
visted[i] = 1;//修改訪問標誌
q.push(i);//鄰接點入隊
}
}
}
}
鄰接表的遍歷:
最小生成樹:(加點)
1.Prim算法
將圖分成兩部分,一部分爲待求最小生成樹,一部分爲沒有選過的點。
(1)起始最小生成樹爲空。
(2)將一個點加入到最小生成樹的集合中,遍歷所有與之相鄰的點,選擇權值最小的鄰接點加入最小生成樹集合中。
(3)遍歷與新加入頂點相鄰的點,選擇權值最小的鄰接點加入最小生成樹中,重複步驟2,直到所有點都加入最小生成樹中。
Prim算法的核心是記錄了目前未選取頂點與最小生成樹的最小權值,選完後更新記錄,直到算法完成。
#include<iostream>
#include<queue>
using namespace std;
const int MaxSize = 100;
int MinEdge(int a[],int n)//用於選取最小權值的邊
{
int min = 100;
int index = 1;
for(int i = 1;i<=n;i++)
{
if(a[i]<=min&&a[i]!=0)
{
min = a[i];
index = i;
}
}
return index;
}
template <typename DataType>
class MGraph
{
public:
MGraph(DataType a[],int n,int e);
~MGraph(){};
void Prim(int v);
private:
DataType vertex[MaxSize];
int visted[MaxSize];
int edge[MaxSize][MaxSize];
int vertexNum,edgeNum;
};
template <typename DataType>
MGraph<DataType>::MGraph(DataType a[],int n,int e)
{
vertexNum = n;
edgeNum = e;
int w;
for(int i = 1;i<=n;i++)
{
vertex[i] = a[i];
}
for(int i = 1;i<=n;i++)
{
visted[i] = 0;
}
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
cin>>edge[i][j];
}
}
}
template <typename DataType>
void MGraph<DataType>::Prim(int v)
{
int adjvex[MaxSize];//記錄與最小生成樹相鄰的頂點
//adjvex[i] = j;表達的意思是整個圖中頂點i與生成樹中的頂點j相鄰
int lowcost[MaxSize];//記錄與最小生成樹相鄰的頂點的最小權值
//lowcost[i] = w;表達的意思是整個圖中頂點i與生成樹的最小權值是w
for(int i = 1;i<=vertexNum;i++)
{
lowcost[i] = edge[v][i];
adjvex[i] = v;//初始圖中所有頂點與指定頂點相鄰
}
lowcost[v] = 0;//將指定頂點加入到最小生成樹中
int j;
for(int k = 1;k<vertexNum;k++)
{
j = MinEdge(lowcost,vertexNum);//選出最小值的點
cout<<lowcost[j]<<" ";
lowcost[j] = 0;
for(int i = 1;i<=vertexNum;i++)//遍歷所有的頂點更新記錄
{
if(edge[i][j]<lowcost[i])
{
lowcost[i] = edge[i][j];
adjvex[i] = j;
}
}
}
}
2.Kruskal算法(加邊)
(1)對圖中的所有邊進行排序;
(2)初始化最小生成樹爲包括所有點,0條邊的樹
(3)如果最小權值邊與生成樹不在同一連通分量,加入最小生成樹中
(4)如果在同一連通分量中,略過這條邊
(5)重複步驟4和5,直到選出頂點數-1條邊
使用邊集數組存儲邊,應用並查集合並。
合併兩棵樹:構建父子關係
判斷兩棵樹是否爲同一連通分量的方案:檢查它們的根是否相同
#include <iostream>
#include <algorithm>
using namespace std;
struct Edge{//定義邊集
int from,to,weight;
};
bool cmp(Edge&e1,Edge&e2){
return e1.weight<e2.weight;
}
class EdgeSet
{
private:
Edge*edge;
int vertexNum;
int edgeNum;
int FindRoot(int*parent,int index);//根查找函數
public:
EdgeSet(int n,int e);
~EdgeSet(){
};
void Kruskal();
};
EdgeSet::EdgeSet(int n,int e){
vertexNum = n;
edgeNum = e;
edge = new Edge[edgeNum];
int index = 0;
int a[vertexNum+1][vertexNum+1];
for(int i = 1;i<=vertexNum;i++)
for(int j = 1;j<=vertexNum;j++){
cin>>a[i][j];
}
for(int i = 1;i<=vertexNum;i++)
for(int j = i;j<=vertexNum;j++){
if(a[i][j]<100&&a[i][j]>0){
edge[index].from = i;
edge[index].to = j;
edge[index].weight = a[i][j];
index++;
}
}
sort(edge,edge+index,cmp);
}
int EdgeSet::FindRoot(int*parent,int index){
int root = index;
while(parent[root]>-1){
root = parent[root];
}
return root;
}
void EdgeSet::Kruskal(){
int*parent = new int[vertexNum+1];
for(int i = 0;i<=vertexNum;i++)
parent[i] = -1;//所有的頂點均沒有雙親
int count = 0;//記錄邊數
for(int k = 0;k<edgeNum;k++){
int i = FindRoot(parent,edge[k].from);
int j = FindRoot(parent,edge[k].to);
if(i!=j){//判斷是否爲同一連通分量
cout<<edge[k].from<<" "<<edge[k].to<<" ";
parent[j] = i;//合併
count++;
if(count==vertexNum-1)
break;
}
}
}
最短路徑算法(單源點到所有點)
1.Dijkstra算法:
在與指定單源點鄰接的所有點中,必有一條最短路徑長度是兩點之間的權值。因此最短路徑可以分成兩種情況:一種是已經存在的路徑,另一種是經過其他的頂點構建的新的路徑
(1)初始路徑爲單源點相鄰的頂點的權值,如果沒有鄰接則用一個默認的最大值代替
(2)選擇最小權值的點
(3)更新最短路徑長度
void MGraph::Dijkstra(int v){
int*dist = new int[vertexNum];//記錄單源點到各頂點的路徑長度,dist[i]:單源點到第i點的路徑長度
string*path = new string[edgeNum];//用於打印路徑具體情況
for(int i = 0;i<vertexNum;i++){//初始化
dist[i] = edge[v][i];
if(dist[i]!=MaxSize)
path[i] = vertex[v]+" "+vertex[i];
else
path[i] = "";
}
int index;
for(int num = 1;num<vertexNum;num++){
index = Min(dist,vertexNum);//選擇
for(int i = 0;i<vertexNum;i++){//嘗試所有的點
if(dist[i]>dist[index]+edge[index][i]){//更新
dist[i] = dist[index]+edge[index][i];
path[i] = path[index]+" "+vertex[i];
}
}
dist[index] = 0;//加入最短路徑
}
}
2.Floyd算法:
相對於Dijkstra更樸素的算法,走所有的路,選最短的那條。
n與n個點之間的所有路徑,可以調用n次Dijkstra算法等價實現
void MGraph::Floyd(){
int dist[MaxSize][MaxSize];//記錄路徑長度
string path[MaxSize][MaxSize];
for(int i = 0;i<vertexNum;i++){//初始化路徑長度爲權值大小
for(int j = 0;j<vertexNum;j++){
dist[i][j] = edge[i][j];
if(edge[i][j]!=MaxSize&&i!=j){
path[i][j] = vertex[i]+" "+vertex[j];
}
else
path[i][j] = "";
}
}
for(int k = 0;k<vertexNum;k++){//在第i個點和第j個點之間嘗試所有的點
for(int i = 0;i<vertexNum;i++){
for(int j = 0;j<vertexNum;j++){
if(dist[i][j]>dist[i][k]+dist[k][j]){//更新
dist[i][j] = dist[i][k]+dist[k][j];
path[i][j] = path[i][k]+" "+path[k][j].substr(3);
}
}
}
}
}
拓撲排序:
先說一下拓撲排序的概念:
對一個有向無環圖G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若邊(u,v)∈E(G),則u在線性序列中出現在v之前。通常,這樣的線性序列稱爲滿足拓撲次序的序列,簡稱拓撲序列。簡單的說,由某個集合上的一個偏序得到該集合上的一個全序,這個操作稱之爲拓撲排序。
———出自百度
算法描述:
(1)選擇沒有箭頭指入的節點中的任一個,可以作爲合法序列的起點,放入序列。
(2)當我們將某個節點放入序列後,去掉該節點以及從該節點指出的所有箭頭。在新的圖中,選擇沒有箭頭指向的節點,作爲序列中的下一個點。
(3) 重複上面的過程,直到所有的節點都被放入序列。
可以用隊列實現以上算法
(1)如果頂點的入度爲0,加入隊列中
(2)當隊列不爲空時
a.訪問隊首元素
b.將所有與隊首元素相連的所有點入度減一
c.檢查鄰接點的入度是否爲0,爲0入隊
#include <iostream>
#include <string.h>
#include <stack>
#include <sstream>
using namespace std;
string itos(int i){
stringstream s;
s<<i;
return s.str();
}
struct EdgeNode{
int index;
EdgeNode*next;
};
struct Vertex{
string vertex;
int in;
EdgeNode*first;
};
class AdjList{
private:
Vertex*v;
int vertexNum;
int edgeNum;
public:
AdjList(int n,int e);
~AdjList();
void TopSort();
};
AdjList::AdjList(int n,int e){
vertexNum = n;
edgeNum = e;
v = new Vertex[vertexNum+1];
for(int i = 1;i<=vertexNum;i++){
v[i].in = 0;
v[i].first = NULL;
v[i].vertex = "v"+itos(i);
}
int from,to;
for(int i = 1;i<=edgeNum;i++){
cin>>from>>to;
EdgeNode*s = new EdgeNode;
s->index = to;
s->next = v[from].first;
v[from].first = s;
v[to].in++;
}
}
AdjList::~AdjList(){
EdgeNode*p = NULL;
EdgeNode*q = NULL;
for(int i = 1;i<=vertexNum;i++){
p = q = v[i].first;
while(p!=NULL){
p = p->next;
delete q;
q = p;
}
}
}
void AdjList::TopSort(){
stack<Vertex>s;
for(int i = vertexNum;i>0;i--)
if(v[i].in==0)
s.push(v[i]);
while(!s.empty()){
cout<<s.top().vertex<<" ";
EdgeNode*p = s.top().first;
s.pop();
while(p!=NULL){
v[p->index].in--;
if(v[p->index].in==0)
s.push(v[p->index]);
p = p->next;
}
}
}
關鍵路徑:
struct Edge{
int from,to,e,l;
};
class AOE{
private:
int edgeNum;
int vertexNum;
Edge*edgeset;//邊集
int edge[MaxSize][MaxSize];//鄰接矩陣
string*vertex;//點集
public:
AOE(int n,int e);
~AOE(){
};
void CriPath();
};
AOE::AOE(int n,int e){
vertexNum = n;
edgeNum = e;
edgeset = new Edge[edgeNum+1];
vertex = new string[vertexNum+1];
int w,from,to;
for(int i = 1;i<=vertexNum;i++){
vertex[i] = 'v'+itos(i);
for(int j = 1;j<=vertexNum;j++){
edge[i][j] = MaxSize;
}
}
for(int i = 1;i<=edgeNum;i++){
cin>>from>>to>>w;
edge[from][to] = w;
edgeset[i].from = from;
edgeset[i].to = to;
edgeset[i].e = 0;
edgeset[i].l = 0;
}
}
void AOE::CriPath(){
int visit[vertexNum+1] = {
0
};
int vl[vertexNum+1] = {
MaxSize
};//事件最早發生時間
int ve[vertexNum+1] = {
0
};//事件最晚發生時間
ve[1] = 0;
queue<int>q;
q.push(1);
int i;
visit[1] = 1;
while(!q.empty()){
i = q.front();
q.pop();
for(int j = 1;j<=vertexNum;j++){
if(edge[i][j]!=MaxSize&&ve[j]<edge[i][j]+ve[i])
ve[j] = edge[i][j]+ve[i];
if(!visit[j])
q.push(j);
visit[j] = 1;
}
}
q.push(vertexNum);
for(int i = 1;i<=vertexNum;i++){
visit[i] = 0;
vl[i] = ve[vertexNum];
}
while(!q.empty()){
i = q.front();
//cout<<i<<" ";
q.pop();
for(int j = vertexNum;j>0;j--){
//for(int j = 1;j<=vertexNum;j++){
if(edge[j][i]!=MaxSize&&vl[j]>vl[i]-edge[j][i]){
vl[j] = vl[i]-edge[j][i];
}
if(!visit[j])
q.push(j);
visit[j] = 1;
}
}
for(int i = 1;i<=edgeNum;i++){
edgeset[i].e = ve[edgeset[i].from];
edgeset[i].l = vl[edgeset[i].to]-edge[edgeset[i].from][edgeset[i].to];
if(edgeset[i].e==edgeset[i].l)
cout<<vertex[edgeset[i].from]<<" ";//<<vertex[edgeset[i].to]<<" ";
}
cout<<vertex[vertexNum];
}