圖論(二) 最短路徑:Dijkstra算法與Floyd算法


圖論系列文章

圖論(一) 鄰接表與鄰接矩陣

圖論(二) 最短路徑:Dijkstra算法與Floyd算法

圖論(三)最小生成樹:Prim算法與Kruskal算法

圖論(四)拓撲排序與關鍵路徑


前言

再學習了鄰接矩陣與鄰接表之後,我們立刻迎來來圖的應用:求兩頂點之間的最小距離。
其中最爲重要的有兩種算法:

  • Djikstra算法 只能求出一個頂點到其他所有頂點的最短距離
  • Floyd算法 能求解出任意兩點之間的最小距離

接下來我們將使用下圖作爲圖的創建,提前將信息輸入到 graph.conf 中,再讓程序進行讀取。
在這裏插入圖片描述

  • graph.conf
7
10
A B C D E F G
0 1 5
0 2 2
1 3 1
2 3 6
1 4 6
3 4 1
3 5 2
2 5 8
4 6 7
5 6 3

# 第一行 頂點個數
# 第二行 邊個數
# 第三行 頂點的元素
# 往下 邊連接的兩個頂點 以及 權

一.Dijkstra算法求解單源最短路徑

在上一篇中,我們已經瞭解到,如果是非加權圖(或者是權值均爲1的加權圖), 直接使用BFS方法就可以算出單源的最短距離。
但如果是權值不相等的加權圖,那麼做一點小小的改進,也就成了Dijkstra算法。
Djikstra思想:
使用BFS,比較並記錄單源點到其他點的最小路徑。
在這裏插入圖片描述
在這裏插入圖片描述

  • 程序:
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <stack>
#include <queue>
using namespace std;

/*********************************圖*********************************************/
/*
因爲使用Dijkstra算法中只可能是單節點向前推薦,所以採用有向圖的方式(實際上無向圖也可以,運算時看作有向圖)
採用鄰接表形式創建圖(有向圖,連通圖, 加權圖):
vertex : 頂點
EdgeNode: 邊節點

*/
using ElemType = char;
struct EdgeNode
{
    int index;  // 用於尋找頂點位置的索引
    int weight; //權
    struct EdgeNode *next;
    EdgeNode(int index, int weight) : index(index), weight(weight), next(NULL) {}
};

struct Vertex
{
    int index;
    ElemType val;
    struct EdgeNode *adjNode;
    Vertex(int index, ElemType val) : index(index), val(val), adjNode(NULL) {}
};

/*********************************類申明*********************************************/
class Graph
{
    vector<Vertex *> _vertexs; //頂點
    int _vertexNum;            //頂點數量
    int _edgeNum;              //邊的數量,注意是無向圖
    vector<int> visited;       //判斷定點是否訪問過
public:
    Graph();
    int dijkstra_minDistance(int beg, int end); //Dijkstra算法 求解加權圖的最短距離

    void printAdjList(); //測試鄰接表是否正確組合
    Vertex *getRoot() const { return _vertexs[0]; }

    void resetVisited()
    {
        for (int i = 0; i < _vertexNum; i++)
            visited[i] = 0;
    }

private:
    void createGraph();
    void insertEdgeNode(Vertex *v, EdgeNode *node); //插入邊節點,用頭插法
};

/*********************************類定義*********************************************/
Graph::Graph()
{
    createGraph();
    visited.resize(_vertexNum);
}

void Graph::createGraph()
{
    std::ifstream ifs("graph.conf", ios::in);
    string str;
    //第一行,讀取圖頂點的個數
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _vertexNum;
    }

    //第二行,讀取邊的數量
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _edgeNum;
    }

    //第三行,讀取頂點元素
    {
        getline(ifs, str);
        stringstream ss(str);
        ElemType val;
        for (int i = 0; i < _vertexNum; i++)
        {
            ss >> val;
            Vertex *newNode = new Vertex(i, val);
            _vertexs.push_back(newNode);
        }
    }

    //接下來,進行邊的連接
    {
        for (int i = 0; i < _edgeNum; i++)
        {
            getline(ifs, str);
            stringstream ss(str);
            int index1, index2, weight; //用邊連接的兩個頂點的編號
            ss >> index1 >> index2 >> weight;
            EdgeNode *newNode = new EdgeNode(index2, weight);
            insertEdgeNode(_vertexs[index1], newNode);
        }
    }

    ifs.close();
}

void Graph::insertEdgeNode(Vertex *v, EdgeNode *node)
{
    node->next = v->adjNode;
    v->adjNode = node;
}

void Graph::printAdjList()
{
    for (int i = 0; i < _vertexNum; i++)
    {
        cout << "Vertex " << _vertexs[i]->val << " -> ";
        Vertex *v = _vertexs[i];
        EdgeNode *n = v->adjNode;
        while (n)
        {
            cout << _vertexs[n->index]->val << "(weight:" << n->weight << ")"
                 << " -> ";
            n = n->next;
        }
        cout << "NULL" << endl;
    }
}

//Dijkstra算法,本質上還是使用BFS遍歷,只是在每一層判斷路徑和
int Graph::dijkstra_minDistance(int beg, int end)
{
    vector<int> path;
    path.resize(_vertexNum);
    vector<int> distance;
    for (int i = 0; i < _vertexNum; i++)
        distance.push_back(65536);
    distance[beg] = 0;

    std::queue<int> que;
    que.push(beg);

    while (!que.empty())
    {
        Vertex *v = _vertexs[que.front()];
        que.pop();
        EdgeNode *node = v->adjNode;
        while (node)
        {
            que.push(node->index);
            if (distance[node->index] > distance[v->index] + node->weight)
            {
                distance[node->index] = distance[v->index] + node->weight;
                path[node->index] = v->index;
            }
            node = node->next;
        }
    }
    
    //打印path操作
    vector<ElemType> printPath;
    int i = end;
    while (i != beg)
    {
        printPath.push_back(_vertexs[i]->val);
        i = path[i];
    }
    printPath.push_back(_vertexs[beg]->val);
    for (auto it = printPath.rbegin(); it != printPath.rend(); it++)
        cout << *it << "->";
    cout << endl;
    
    return distance[end];
}

/*********************************測試函數*********************************************/
//打印
void test0()
{
    Graph graph;
    graph.printAdjList();
    int beg = 0;
    int end = 6;
    cout << graph.dijkstra_minDistance(beg, end) << endl;
}

int main()
{
    test0();
    return 0;
}

二.Floyd算法求解多源最短路徑

Dijkstra算法只能求解一個點到其他店的最短路徑,那麼如果要求任意兩點之間的最短路徑,雖然也可以將Dijkstra算法再執行N次,但是Floyd算法卻更簡潔有效。
Floyd算法核心:必須使用鄰接矩陣,依次以不同的頂點爲中繼頂點,在能夠作爲中繼節點到達的情況下,依次計算出最小路徑。
Floy算法也是屬於動態規劃中的重要算法。

_matrix[i][j] = std::min(_matrix[i][j], _matrix[i][k]+_matrix[k][j]);

在這裏插入圖片描述
在這裏插入圖片描述

  • 程序
#include <iostream>
#include <vector>
#include <fstream>
#include <sstream>
#include <stack>
#include <queue>
#include <climits>
#include <iomanip>
#include <algorithm>
using namespace std;

/*********************************圖*********************************************/
/*
實現Floyd算法必須使用鄰接矩陣的存儲方式,所以採用鄰接矩陣形式創建圖(無向圖,連通圖, 加權圖):
_matrix 矩陣
*/

using ElemType = char;

/*********************************類申明*********************************************/
class Graph
{
    vector<vector<int>> _matrix; //鄰接矩陣
    vector<ElemType> _vals;      //存放頂點元素
    int _vertexNum;              //頂點數量
    int _edgeNum;                //邊的數量,注意是無向圖
    vector<int> visited;         //判斷定點是否訪問過
public:
    Graph();
    void Floyd_minDistance(); //弗洛伊德算法 求解多源最短路徑

    void printAdjMatrix(); //測試鄰接矩陣是否正確組合

    void resetVisited()
    {
        for (int i = 0; i < _vertexNum; i++)
            visited[i] = 0;
    }

private:
    void createGraph();
};

/*********************************類定義*********************************************/
Graph::Graph()
{
    createGraph();
    visited.resize(_vertexNum);
}

void Graph::createGraph()
{
    std::ifstream ifs("graph.conf", ios::in);
    string str;
    //第一行,讀取圖頂點的個數
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _vertexNum;
        _matrix.resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            _matrix[i].resize(_vertexNum);
        for (int i = 0; i < _vertexNum; i++)
            for (int j = 0; j < _vertexNum; j++)
                if (i == j)
                    _matrix[i][j] = 0;
                else
                    _matrix[i][j] = INT_MAX;
    }

    //第二行,讀取邊的數量
    {
        getline(ifs, str);
        stringstream ss(str);
        ss >> _edgeNum;
    }

    //第三行,讀取頂點元素
    {
        getline(ifs, str);
        stringstream ss(str);
        ElemType val;
        for (int i = 0; i < _vertexNum; i++)
        {
            ss >> val;
            _vals.push_back(val);
        }
    }

    //接下來,進行邊的連接
    {
        for (int k = 0; k < _edgeNum; k++)
        {
            getline(ifs, str);
            stringstream ss(str);
            int i, j, weight; //用邊連接的兩個頂點的編號
            ss >> i >> j >> weight;
            _matrix[i][j] = weight;
            _matrix[j][i] = weight;
        }
    }

    ifs.close();
}

void Graph::printAdjMatrix()
{
    cout << "-----------------------------------" << endl;
    for (int i = 0; i < _vertexNum; i++)
    {
        for (int j = 0; j < _vertexNum; j++)
        {
            if (INT_MAX == _matrix[i][j])
                cout << "  ∞  ";
            else
                cout << "  " << _matrix[i][j] << "  ";
        }
        cout << endl;
    }
    cout << "-----------------------------------" << endl;
}

void Graph::Floyd_minDistance()
{
    for (int k = 0; k < _vertexNum; k++)
        for (int i = 0; i < _vertexNum; i++)
            for (int j = 0; j < _vertexNum; j++)
                if (0==_matrix[i][j] || INT_MAX==_matrix[i][k] || INT_MAX==_matrix[k][j])
                    continue;
                else
                    _matrix[i][j] = std::min(_matrix[i][j], _matrix[i][k]+_matrix[k][j]);
}

/*********************************測試函數*********************************************/
//打印
void test0()
{
    Graph graph;
    graph.printAdjMatrix();
    graph.Floyd_minDistance();
    cout << "After Floyd algorithm:" << endl;
    graph.printAdjMatrix();
}

int main()
{
    test0();
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章