拓撲排序 - 看這個就完了

大致含義

含義
表示一個遞進過程
每個點之間都有聯繫
而這些聯繫可以組成一個依次遞進的過程
比如:有結點A,B,C
C結點完成後才能做B結點,而B結點完成後才能進行A結點
即C->B->A 這樣一個排序的序列
所以拓撲排序就是進行這樣的一個過程
拓撲排序的過程
因爲是要找一個遞進過程
所以要先統計入度爲零的點
然後把度爲0的結點刪除 (其實不是真的刪除,下面代碼有)
如果發現這個度爲0的臨鄰接點在刪除度爲0的結點後度也是0
就繼續刪除
而這些每次刪除的結點組成的序列就是拓撲排序
有可能會不成功 不成功的原因是有迴路
所以函數裏最後判斷的是成功排序的數量是否等於總點數
幾個注意的點
拓撲排序的結果是很有可能不同的
因爲用的存儲方式和排序方式的不同
導致結果的不同
像 : 用隊列或棧存儲度爲0的點
用bfs的方式排序(找度爲0的點) 或 用dfs的方式
下面我的代碼都將會涉及到的

繁瑣版

這個是數據結構課程中講的完整版的拓撲排序的代碼
因爲是第一次學習,所以代碼就很麻煩
用的是鄰接表存圖 + bfs + 隊列

#include <iostream>
#include <queue>
#include <stack>
using namespace std;
#define MAX 100         //最大頂點數
typedef int Vertex;     // 用頂點下標表示頂點
typedef int WeightType; // 權重
typedef char DataType;  //存儲的數據類型

// 邊的定義
typedef struct ENode *PtrToENode;
struct ENode
{
    Vertex V1, V2;     // 有向邊 <V1, V2>
    WeightType Weight; // 權重
};
typedef PtrToENode Edge;

// 鄰接點的定義
typedef struct AdjVNode *PtrToAdjVNode;
struct AdjVNode
{
    Vertex Adjv;        // 鄰接點下標
    WeightType Weight;  // 邊權重
    PtrToAdjVNode Next; // 下一個鄰接點的指針
};

// 頂點表頭節點的定義
typedef struct Vnode
{
    PtrToAdjVNode FirstEdge; // 邊表頭指針
    // DataType Data;           // 存頂節點的數據
    // 注 : 很多時候頂點無數據 此時Data不用出現
} AdjList[MAX];

// 圖結點的定義
typedef struct GNode *PtrToGNode;
struct GNode
{
    int Nv;    // 頂點數
    int Ne;    // 邊數
    AdjList G; // 鄰接表  是一個數組
};
typedef PtrToGNode LGraph; // 以鄰接表的方式存儲的圖類型

LGraph CreateGraph(int VertexNum)
{
    Vertex V;
    LGraph Graph;

    Graph = (LGraph)malloc(sizeof(struct GNode));
    Graph->Nv = VertexNum;
    Graph->Ne = 0;
    // 初始化
    for (V = 0; V < Graph->Nv; V++)
        Graph->G[V].FirstEdge = NULL;

    return Graph;
}
void InsertEdge(LGraph Graph, Edge E)
{
    PtrToAdjVNode NewNode;

    // 插入邊<V1, V2>
    // 爲V2建立新的鄰接點
    NewNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
    NewNode->Adjv = E->V2;
    NewNode->Weight = E->Weight;
    // 將V2插入V1的表頭
    NewNode->Next = Graph->G[E->V1].FirstEdge;
    Graph->G[E->V1].FirstEdge = NewNode;

    // 若是無向圖 還要插入邊<V2, V1>
    // 爲V1建立新的鄰接點
    NewNode = (PtrToAdjVNode)malloc(sizeof(AdjVNode));
    NewNode->Adjv = E->V1;
    NewNode->Weight = E->Weight;
    // 將V1插入V2的表頭
    NewNode->Next = Graph->G[E->V2].FirstEdge;
    Graph->G[E->V2].FirstEdge = NewNode;
}

LGraph BuildGraph()
{
    LGraph Graph;
    Edge E;
    Vertex V;
    int Nv, i;

    cin >> Nv;
    Graph = CreateGraph(Nv); // 節點數

    cin >> Graph->Ne; // 邊數
    if (Graph->Ne != 0)
    {
        E = (Edge)malloc(sizeof(struct ENode)); // 建立邊結點
        // 讀入邊  起點 - 終點 - 權重
        for (i = 0; i < Graph->Ne; i++)
        {
            cin >> E->V1 >> E->V2 >> E->Weight;
            InsertEdge(Graph, E);
        }
    }

    // 如果頂點有數據 讀入數據
    // for (V = 0; V < Graph->Nv; i++)
    // {
    //     cin >> Graph->G[V].Data;
    // }

    return Graph;
}

/* 鄰接表存儲 - 拓撲排序算法 */

bool TopSort(LGraph Graph, Vertex TopOrder[])
{
    /* 對Graph進行拓撲排序,  TopOrder[]順序存儲排序後的頂點下標 */
    int Indegree[MAX], cnt;
    Vertex V;
    PtrToAdjVNode W;
    queue<int> Q;

    /* 初始化Indegree[] */
    for (V = 0; V < Graph->Nv; V++)
        Indegree[V] = 0;

    // 算一個頂點的入度
    /* 遍歷圖,得到Indegree[] */ //入度
    for (V = 0; V < Graph->Nv; V++)
        for (W = Graph->G[V].FirstEdge; W; W = W->Next)
            Indegree[W->Adjv]++; /* 對有向邊<V, W->AdjV>累計終點的入度 */

    // 初始化隊列
    /* 將所有入度爲0的頂點入列 */
    for (V = 0; V < Graph->Nv; V++)
        if (Indegree[V] == 0)
            Q.push(V);

    /* 下面進入拓撲排序 */
    // 後面判斷是否有迴路
    cnt = 0;
    while (!Q.empty())
    {
        // 入度爲0的就入列
        V = Q.front();
        Q.pop();             /* 彈出一個入度爲0的頂點 */
        // 存答案
        TopOrder[cnt++] = V; /* 將之存爲結果序列的下一個元素 */
        /* 對V的每個鄰接點W->AdjV */
        // 遍歷V結點的
        for (W = Graph->G[V].FirstEdge; W; W = W->Next)
            if (--Indegree[W->Adjv] == 0) /* 若刪除V使得W->AdjV入度爲0 */
                Q.push(W->Adjv);          /* 則該頂點入列 */
    }                                     /* while結束*/

    if (cnt != Graph->Nv)
        return false; /* 說明圖中有迴路, 返回不成功標誌 */
    else
        return true;
}
int main()
{
    
    return 0;
}

簡潔版

1.鄰接表 - vector + 隊列 + BFS

我覺得記住這個就夠了

// 拓撲
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int MAX = 100000;
// 鄰接點
vector<vector<int>> outdegree;
// 每個點的入度
vector<int> Indegree;
int N;
vector<int> TopOder;
bool TopSort()
{
    queue<int> q;

    // 初始化隊列
    // 初始入度爲0 加入初始隊列
    for (int i = 1; i <= N; i++)
    {
        if (Indegree[i] == 0)
            q.push(i);
    }
    // 進入拓撲排序
    int cnt = 0;
    // 結果
    while (!q.empty())
    {
        // 每次拿出入度爲0的結點
        int V = q.front();
        q.pop();
        cnt++;
        TopOder.push_back(V);

        // 看入度爲0的結點的下一個結點
        // 下一個結點如果減去入度爲的結點
        // 然後入度也爲0的話 就再加到隊列
        for (auto e : outdegree[V])
        {
            if (--Indegree[e] == 0)
                q.push(e);
        }
    }
    for (auto e : TopOder)
        cout << e << ' ';
    cout << endl;
    // 有循環圖的話cnt是不等於N的
    return cnt == N ? true : false;
}

int visited[MAX];
void dfs(int v)
{
    visited[v] = 1;
    for (auto e : outdegree[v])
    {
        if (!visited[e])
        {
            dfs(e);
        }
    }
    TopOder.push_back(v);
}

int main()
{
    int M;
    cin >> N >> M;
    outdegree.resize(N + 5);
    Indegree.resize(N + 5);
    for (int i = 0; i < M; i++)
    {
        int x, y;
        cin >> x >> y;
        // 記錄入度
        Indegree[y]++;
        outdegree[x].push_back(y);
    }
    cout << endl
         << TopSort();
    return 0;
}

2. 鄰接矩陣 + 棧 + BFS

鄰接矩陣 + 棧

/ 鄰接矩陣法
// #include <iostream>
// #include <stack>
// using namespace std;
// const int MAX = 1000;
// int out[MAX][MAX];
// int Indegree[MAX];
// int N;
// bool topological()
// {
//     // 記錄入度
//     for (int i = 1; i <= N; i++)
//     {
//         for (int j = 1; j <= N; j++)
//         {
//             if (out[i][j] == 1)
//             {
//                 ++Indegree[j];
//             }
//         }
//     }
//     stack<int> s;
//     for (int i = 1; i <= N; i++)
//     {
//         if (Indegree[i] == 0)
//             s.push(i);
//     }

//     int TopOder[MAX];
//     int cnt = 0;
//     while (!s.empty())
//     {
//         int x = s.top();
//         s.pop();
//         TopOder[++cnt] = x;

//         for (int i = 1; i <= N; i++)
//         {
//             if (out[x][i] == 1)
//             {
//                 if (--Indegree[i] == 0)
//                 {
//                     s.push(i);
//                 }
//             }
//         }
//     }
// }
// int main()
// {

//     return 0;
// }

3. 鄰接表 + DFS 代碼簡潔

過程
  • 假如有一個這樣的圖
    在這裏插入圖片描述
    在這裏插入圖片描述
假如先走1結點
DFS走到最深處
走到盡頭3結點 然後返回 在返回之前把3結點記錄到拓撲排序的數組中
在從3返回到4 把4在記錄到數組中
從4返回到1的鄰接點遍歷的過程中 去訪問2
就直接從2返回了 還有記錄哦
最後就回到1 然後記錄返回了
會發現 這樣的序列其實的反着的, 所以用棧存儲
還要注意一點
DFS在判斷是不是 迴路 的時候稍微有點動腦筋
設vis有三個值 -1 ,0, 1
-1 : 這個點已經訪問過 且 由別的結點爲頭去訪問過的
0 : 沒有訪問
1 : 這個點(y)已經訪問過 且 (vis[x] = 1) x就是之前出發的點 即y是由x過去的點
而現在又回到了x 所以就是一個迴路了
好處
DFS的代碼簡潔
但是思路較爲複雜
不用專門記錄入度

#include <iostream>
#include <queue>
#include <vector>
#include <stack>
using namespace std;
const int MAX = 100000;
// 鄰接點
vector<vector<int>> outdegree;
int N;
vector<int> visited;
// 結果
stack<int> TopOder;

bool dfs(int k)
{
    visited[k] = 1;
    for (auto e : outdegree[k])
    {
        // 這一次的dfs遍歷 這個結點已經訪問過
        // 所以出現了迴路
        if (visited[e] == 1)
            return false;
        // 沒有訪問過 並且之後不會出現迴路
        if (visited[e] == 0 && !dfs(e))
            return false;
    }
    // -1代表已訪問過, 但不是從當前系列dfs訪問來的
    visited[k] = -1;
    TopOder.push(k);
    return true;
}
bool TopSort()
{
    for (int i = 1; i <= N; i++)
    {
        if (!visited[i] && !dfs(i))
        {
            return false;
        }
    }
    return true;
}
int main()
{
    int M;
    cin >> N >> M;
    outdegree.resize(N + 5);
    visited.resize(N + 5);
    for (int i = 0; i < M; i++)
    {
        int x, y;
        cin >> x >> y;
        // 記錄入度
        outdegree[x].push_back(y);
    }

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