Prim算法、Kruskal算法、Dijkstra算法

無向加權圖

1.生成樹(minimum spanning trees)node

圖的生成樹是它一棵含有全部頂點的無環聯通子圖ios

最小生成樹:生成樹中權值和最小的(全部邊的權值之和)算法

Prim算法、Kruskal算法就是實現最小生成樹的算法數組

  • 應用前提:權值各不相同的連通子圖(權值相同,最小生成樹不惟一)

2.Prim算法函數

算法描述:

Prim算法是一種"加點法":
this

算法步驟:

1.定義圖中全部頂點集合\(V\),從頂點\(s\)開始;初始化生成樹頂點集合\(u={s}\),\(v=V-u\)
2.遍歷結點\({u,v}\),選擇一條權重最小的邊,加入到生成樹中。\(v\)也進入集合\(u\)
3.循環步驟2,直至有\(n-1\)條邊,或者全部頂點都在最小生成樹中。spa

算法實現
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

#define INFINITE 0xFFFFFFFF
#define VertexData unsigned int     // 圖頂點數據類型
#define UNIT unsigned int 
#define VertexCounts 6              // 圖頂點個數

char vextex[]={'A','B','C','D','E','F'};

struct node                         // prim算法中的邊一直更新,最小代價邊須要一直更新
{
    VertexData data;
    unsigned int lowestcost;
}closedge[VertexCounts];


typedef struct 
{
    VertexData u;
    VertexData v;
    unsigned int cost;
    
}Arc;                               // 圖中結點-邊信息

void AdjMatrix(unsigned int adjMat[][VertexCounts])      // 鄰接矩陣表示法
{
    for(int i=0;i<VertexCounts;i++)
        for(int j=0;j<VertexCounts;j++)
        {
            adjMat[i][j]=INFINITE;                       // 矩陣元素的初始化
        }
    adjMat[0][1] = 6; adjMat[0][2] = 1; adjMat[0][3] = 5;
    adjMat[1][0] = 6; adjMat[1][2] = 5; adjMat[1][4] = 3;
    adjMat[2][0] = 1; adjMat[2][1] = 5; adjMat[2][3] = 5; 
    adjMat[2][4] = 6; adjMat[2][5] = 4;
    adjMat[3][0] = 5; adjMat[3][2] = 5; adjMat[3][5] = 2;
    adjMat[4][1] = 3; adjMat[4][2] = 6; adjMat[4][5] = 6;
    adjMat[5][2] = 4; adjMat[5][3] = 2; adjMat[5][4] = 6;

}

int  Minmum(struct node* closedge)                       // 找到最小代價邊
{
    unsigned int min=INFINITE;
    int index=-1;                                        // 保存最小代價邊的頂點下標
    for(int i=0;i<VertexCounts;++i)
    {
        if(closedge[i].lowestcost<min && closedge[i].lowestcost!=0)
        {
            min=closedge[i].lowestcost;
            index=i;
        }
    }
    return index;
}

void MiniSpanTree_Prim(unsigned int adjMat[][VertexCounts],VertexData s) 
{
    for(i=0;i<VertexCounts;++i)                           // 頂點最小邊的初始化
    {
        closedge[i].lowestcost=INFINITE;
    }
    closedge[s].data=s;
    closedge[s].lowestcost=0;
    for(int i=0;i<VertexCounts;++i)
    {
        if(i!=s)
        {
            closedge[i].data=s;
            closedge[i].lowestcost=adjMat[s][i];
        }
    }

    for(int e=1;e<VertexCounts-1;e++)                     // 知足n-1邊時候結束循環
    {
        int k=Minmum(closedge);                           // 選擇最小代價邊
        cout<<vertex[closedge[k].data]<<"--"<<vertex[k]<<endl;

        closedge[k].lowestcost=0;                         // 代價置爲0
        for(int i=0;i<VertexCounts;i++)                   // 更新v中的代價信息
        {
            if(adjMat[k][i]<closedge[i].lowestcost)
            {
                closedge[i].data=k;
                closedge[i].lowestcost=adjMat[k][i];
            }
        }
    }
}

int main()
{
    unsigned int  adjMat[vexCounts][vexCounts] = { 0 };
    AdjMatrix(adjMat);                                    //鄰接矩陣
    cout << "Prim :" << endl;
    MiniSpanTree_Prim(adjMat,0);                          //Prim算法,從頂點0開始.
    return 0;
}

3.Kruskal算法.net

算法描述:

Kruskal算法是一種"加邊法":
code

算法步驟:

1.將圖中全部的邊按權重值進行排序
2.圖中n各頂點都是相互獨立的
3.權值由小到大選擇邊,兩個頂點應屬於兩顆不一樣的樹,這樣生成最小生成樹的一條邊。兩顆樹合併成一顆樹。
4.循環步驟3,直至有n-1條邊,或者全部頂點都在最小生成樹中。blog

#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

#define INFINITE 0xFFFFFFFF
#define VertexData unsigned int     // 圖頂點數據類型
#define UNIT unsigned int 
#define VertexCounts 6              // 圖頂點個數

char vextex[] = { 'A', 'B', 'C', 'D', 'E', 'F' };
typedef struct 
{
    VertexData u;
    VertexData v;
    unsigned int cost;              // 邊的代價
}Arc;                               // 原始圖的邊信息

void ReadArc(unsigned int adjMat[][VertexCounts],vector<Arc> &VertexArc) //保存圖的邊代價信息
{
    Arc* temp=NULL;
    for(unsigned int i=0;i<VertexCounts;i++)
    {
        for(unsigned int j=0;j<VertexCounts;j++)
        {
            if(adjMat[i][j]!=INFINITE)
            {
                temp=new Arc;
                temp->u=i;
                temp->v=j;
                temp->cost=adjMat[i][j];
                VertexArc.push_back(*temp);
            }
        }
    }
}



bool FindTree(VertexData u, VertexData v,vector<vector<VertexData> > &Tree)
{
    unsigned int index_u = INFINITE;
    unsigned int index_v = INFINITE;
    for (unsigned int i = 0; i < Tree.size();i++)  //檢查u,v分別屬於哪顆樹
    {
        if (find(Tree[i].begin(), Tree[i].end(), u) != Tree[i].end())
            index_u = i;
        if (find(Tree[i].begin(), Tree[i].end(), v) != Tree[i].end())
            index_v = i;
    }

    if (index_u != index_v)   //u,v不在一顆樹上,合併兩顆樹
    {
        for (unsigned int i = 0; i < Tree[index_v].size();i++)
        {
            Tree[index_u].push_back(Tree[index_v][i]);
        }
        Tree[index_v].clear();
        return true;
    }
    return false;
}

bool compare(Arc  A, Arc  B)                         // 比較權值的大小
{
    return A.cost < B.cost ? true : false;
}

void MinSpanTree_Kruskal(unsigned int adjMat[][VertexCounts])
{
    vector<Arc> VertexArc;
    ReadArc(adjMat,VertexArc);                       // 讀取邊信息
    sort(VertexArc.begin(),VertexArc.end(),compare); // 邊從小到大排列
    vetor<vector<VertexData>> Tree(VertexCounts);    // 6顆相互獨立的樹
    for(unsigned int i=0;<VertexCounts;i++)
    {
        Tree[i].push_back(i);                        // 每棵樹信息的獲取
    }

    for(unsigned int i=0;i<VertexArc.size();i++)
    {
        VertexData u=VertexArc[i].u;
        VertexData v=VertexArc[i].v;

        if(FindTree(u,v,Tree))                        // 檢查兩個頂點是否在一顆樹內
        {
            cout<<vertex[u]<<"--"<<vertex[v]<<endl;   
        }
    }
}

int main()
{
    unsigned int  adjMat[vexCounts][vexCounts] = { 0 };
    cout << "-------------" << endl << "Kruskal:" << endl;
    MiniSpanTree_Kruskal(adjMat);//Kruskal算法
    return 0;
}

上面兩個算法都是對於無向有權圖
在有向加權圖中,通常解決最短路徑問題

4.Dijkstra算法

算法描述

設G=(V,E)是一個帶權有向圖,把圖中頂點集合V分紅兩組,第一組爲已求出最短路徑的頂點集合(用S表示,初始時S中只有一個源點,之後每求得一條最短路徑 , 就將加入到集合S中,直到所有頂點都加入到S中,算法就結束了),第二組爲其他未肯定最短路徑的頂點集合(用U表示),按最短路徑長度的遞增次序依次把第二組的頂點加入S中。在加入的過程當中,總保持從源點v到S中各頂點的最短路徑長度不大於從源點v到U中任何頂點的最短路徑長度。此外,每一個頂點對應一個距離,S中的頂點的距離就是從v到此頂點的最短路徑長度,U中的頂點的距離,是從v到此頂點只包括S中的頂點爲中間頂點的當前最短路徑長度。

算法步驟:
  • 1.初始時,S只包含起點s;U包含除s外的其餘頂點,且U中頂點的距離爲"起點s到該頂點的距離"[例如,U中頂點v的距離爲(s,v)的長度,而後s和v不相鄰,則v的距離爲∞]。

  • 2.從U中選出"距離最短的頂點k",並將頂點k加入到S中;同時,從U中移除頂點k。

  • 3.更新U中各個頂點到起點s的距離。之因此更新U中頂點的距離,是因爲上一步中肯定了k是求出最短路徑的頂點,從而能夠利用k來更新其它頂點的距離;例如,(s,v)的距離可能大於(s,k)+(k,v)的距離。

  • 4.重複步驟(2)和(3),直到遍歷完全部頂點。

初始狀態:S是已計算出最短路徑的頂點集合,U是未計算除最短路徑的頂點的集合!

第1步:將頂點D加入到S中。 此時,S={D(0)}, U={A(∞),B(∞),C(3),E(4),F(∞),G(∞)}。 注:C(3)表示C到起點D的距離是3。

第2步:將頂點C加入到S中。 上一步操做以後,U中頂點C到起點D的距離最短;所以,將C加入到S中,同時更新U中頂點的距離。以頂點F爲例,以前F到D的距離爲∞;可是將C加入到S以後,F到D的距離爲9=(F,C)+(C,D)。此時,S={D(0),C(3)}, U={A(∞),B(23),E(4),F(9),G(∞)}。

第3步:將頂點E加入到S中。上一步操做以後,U中頂點E到起點D的距離最短;所以,將E加入到S中,同時更新U中頂點的距離。仍是以頂點F爲例,以前F到D的距離爲9;可是將E加入到S以後,F到D的距離爲6=(F,E)+(E,D)。此時,S={D(0),C(3),E(4)}, U={A(∞),B(23),F(6),G(12)}。

第4步:將頂點F加入到S中。 此時,S={D(0),C(3),E(4),F(6)}, U={A(22),B(13),G(12)}。

第5步:將頂點G加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12)}, U={A(22),B(13)}。

第6步:將頂點B加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12),B(13)}, U={A(22)}。

第7步:將頂點A加入到S中。 此時,S={D(0),C(3),E(4),F(6),G(12),B(13),A(22)}。

算法實現

#include<iostream>
#include<string>
using namespace std;

/*
本程序是使用Dijkstra算法實現求解最短路徑的問題
採用的鄰接矩陣來存儲圖
*/
//記錄起點到每一個頂點的最短路徑的信息
struct Dis {
    string path;
    int value;
    bool visit;
    Dis() {
        visit = false;
        value = 0;
        path = "";
    }
};

class Graph_DG {
private:
    int vexnum;   //圖的頂點個數
    int edge;     //圖的邊數
    int **arc;   //鄰接矩陣
    Dis * dis;   //記錄各個頂點最短路徑的信息
public:
    //構造函數
    Graph_DG(int vexnum, int edge);
    //析構函數
    ~Graph_DG();
    // 判斷咱們每次輸入的的邊的信息是否合法
    //頂點從1開始編號
    bool check_edge_value(int start, int end, int weight);
    //建立圖
    void createGraph();
    //打印鄰接矩陣
    void print();
    //求最短路徑
    void Dijkstra(int begin);
    //打印最短路徑
    void print_path(int);
};
//構造函數
Graph_DG::Graph_DG(int vexnum, int edge) {
    //初始化頂點數和邊數
    this->vexnum = vexnum;
    this->edge = edge;
    //爲鄰接矩陣開闢空間和賦初值
    arc = new int*[this->vexnum];
    dis = new Dis[this->vexnum];
    for (int i = 0; i < this->vexnum; i++) {
        arc[i] = new int[this->vexnum];
        for (int k = 0; k < this->vexnum; k++) {
            //鄰接矩陣初始化爲無窮大
                arc[i][k] = INT_MAX;
        }
    }
}
//析構函數
Graph_DG::~Graph_DG() {
    delete[] dis;
    for (int i = 0; i < this->vexnum; i++) {
        delete this->arc[i];
    }
    delete arc;
}

// 判斷咱們每次輸入的的邊的信息是否合法
//頂點從1開始編號
bool Graph_DG::check_edge_value(int start, int end, int weight) {
    if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) {
        return false;
    }
    return true;
}

void Graph_DG::createGraph() {
    cout << "請輸入每條邊的起點和終點(頂點編號從1開始)以及其權重" << endl;
    int start;
    int end;
    int weight;
    int count = 0;
    while (count != this->edge) {
        cin >> start >> end >> weight;
        //首先判斷邊的信息是否合法
        while (!this->check_edge_value(start, end, weight)) {
            cout << "輸入的邊的信息不合法,請從新輸入" << endl;
            cin >> start >> end >> weight;
        }
        //對鄰接矩陣對應上的點賦值
        arc[start - 1][end - 1] = weight;
        //無向圖添加上這行代碼
        //arc[end - 1][start - 1] = weight;
        ++count;
    }
}

void Graph_DG::print() {
    cout << "圖的鄰接矩陣爲:" << endl;
    int count_row = 0; //打印行的標籤
    int count_col = 0; //打印列的標籤
    //開始打印
    while (count_row != this->vexnum) {
        count_col = 0;
        while (count_col != this->vexnum) {
            if (arc[count_row][count_col] == INT_MAX)
                cout << "∞" << " ";
            else
            cout << arc[count_row][count_col] << " ";
            ++count_col;
        }
        cout << endl;
        ++count_row;
    }
}
void Graph_DG::Dijkstra(int begin){
    //首先初始化咱們的dis數組
    int i;
    for (i = 0; i < this->vexnum; i++) {
        //設置當前的路徑
        dis[i].path = "v" + to_string(begin) + "-->v" + to_string(i + 1);
        dis[i].value = arc[begin - 1][i];
    }
    //設置起點的到起點的路徑爲0
    dis[begin - 1].value = 0;
    dis[begin - 1].visit = true;

    int count = 1;
    //計算剩餘的頂點的最短路徑(剩餘this->vexnum-1個頂點)
    while (count != this->vexnum) {
        //temp用於保存當前dis數組中最小的那個下標
        //min記錄的當前的最小值
        int temp=0;
        int min = INT_MAX;
        for (i = 0; i < this->vexnum; i++) {
            if (!dis[i].visit && dis[i].value<min) {
                min = dis[i].value;
                temp = i;
            }
        }
        //cout << temp + 1 << "  "<<min << endl;
        //把temp對應的頂點加入到已經找到的最短路徑的集合中
        dis[temp].visit = true;
        ++count;
        for (i = 0; i < this->vexnum; i++) {
            //注意這裏的條件arc[temp][i]!=INT_MAX必須加,否則會出現溢出,從而形成程序異常
            if (!dis[i].visit && arc[temp][i]!=INT_MAX && (dis[temp].value + arc[temp][i]) < dis[i].value) {
                //若是新獲得的邊能夠影響其餘爲訪問的頂點,那就就更新它的最短路徑和長度
                dis[i].value = dis[temp].value + arc[temp][i];
                dis[i].path = dis[temp].path + "-->v" + to_string(i + 1);
            }
        }
    }

}
void Graph_DG::print_path(int begin) {
    string str;
    str = "v" + to_string(begin);
    cout << "以"<<str<<"爲起點的圖的最短路徑爲:" << endl;
    for (int i = 0; i != this->vexnum; i++) {
        if(dis[i].value!=INT_MAX)
        cout << dis[i].path << "=" << dis[i].value << endl;
        else {
            cout << dis[i].path << "是無最短路徑的" << endl;
        }
    }
}
//檢驗輸入邊數和頂點數的值是否有效,能夠本身推算爲啥:
//頂點數和邊數的關係是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
    if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
        return false;
    return true;
}
int main() {
    int vexnum; int edge;

    cout << "輸入圖的頂點個數和邊的條數:" << endl;
    cin >> vexnum >> edge;
    while (!check(vexnum, edge)) {
        cout << "輸入的數值不合法,請從新輸入" << endl;
        cin >> vexnum >> edge;
    }
    Graph_DG graph(vexnum, edge);
    graph.createGraph();
    graph.print();
    graph.Dijkstra(1);
    graph.print_path(1);
    system("pause");
    return 0;
}

/*
輸入:

6 8
1 3 10
1 5 30
1 6 100
2 3 5
3 4 50
4 6 10
5 6 60
5 4 20

*/

參考:
勿在浮沙築高臺
Ouyang_Lianjun-最短路徑問題---Dijkstra算法詳解

相關文章
相關標籤/搜索