算法笔记p414拓扑排序

有向无环图

如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图(DirectedAcyclic Graph,DAG)。

拓扑排序

拓扑排序是将有向无环图G的所有顶点排成一个线性序列,使得对图G中的任意两个顶点u、v,如果存在边u->v,那么在序列中u一定在v前面。这个序列又被称为拓扑序列。

求拓扑排序步骤

  1. 定义一个队列Q,并把所有入度为0的结点加入队列(算法笔记p251队列)。
  2. 取队首结点,输出。然后删去所有从它出发的边,并令这些边到达的顶点的入度减1,如果某个顶点的入度减为0,则将其加入队列。
  3. 反复进行②操作,直到队列为空。
  4. 如果队列为空时入过队的结点数目恰好为N,说明拓扑排序成功,图G为有向无环图;否则,拓扑排序失败,图G中有环。

代码实现

可使用邻接表实现拓扑排序。显然,由于需要记录结点的入度,因此需要额外建立一个数组inDegree[MAXV],并在程序一开始读入图时就记录好每个结点的入度。接下来就只需要按上面所说的步骤进行实现即可,拓扑排序的代码如下:

  1. C++实现:
#include <vector>
#include <queue>

#define MAXV 10000

using namespace std;

vector<int> G[MAXV];             	// 邻接表
int n, inDegree[MAXV];           	// 顶点数、入度

// 拓扑排序
bool topologicalSort() {
    int num = 0;
    queue<int> q;
    for (int i = 0; i < n; ++i)
        if (inDegree[i] == 0)
            q.push(i);              //将所有入度为0的顶点入队
    while (!q.empty()) {
        int u = q.front();          // 取队首顶点u
        // printf("%d", u);         // 此处可输出顶点u,作为拓扑序列中的顶点
        q.pop();
        for (int i = 0; i < G[u].size(); ++i) {
            int v = G[u][i];        // u的后继结点v
            inDegree[v]--;          // 顶点v的入度减1
            if (inDegree[v] == 0)   // 顶点v的入度减为0则入队
                q.push(v);
        }
        G[u].clear();               // 清空顶点u的所有出边(如无必要可不写)
        num++;                      // 加入拓扑序列的顶点数加1
    }
    if (num == n) return true;      // 加入拓扑排序的顶点数为n,说明拓扑排序成功
    else return false;              // 加入拓扑排序的顶点数小于n,说明拓扑排序失败
}
  • 拓扑排序的很重要的应用就是判断一个给定的图是否是有向无环图。正如上面的代码,如果 topologicalSort0函数返回true,则说明拓扑排序成功,给定的图是有向无环图;否则,说明拓扑排序失败,给定的图中有环。
  • 最后指出,如果要求有多个入度为0的顶点,选择编号最小的顶点,那么把queue改成priority_queue(堆实现),并保持队首元素是优先队列中最小的元素即可(算法笔记p335堆)。
  1. C语言实现:
#include <stdio.h>
#include <stdbool.h>

#define MaxSize 1000

// 图的邻接表实现数据结构定义
typedef int VertexType;
typedef int EdgeType;

// 边表结点
typedef struct ArcNode {
    int adjVertex;            	// 该弧所指向的顶点的位置
    EdgeType info;            	// 弧的权值
    struct ArcNode *next;    	// 指向下一条弧的指针
} ArcNode;
// 顶点表结点
typedef struct VertexNode {
    VertexType data;            // 顶点信息
    struct ArcNode *first;    	// 指向第一条依附该顶点的弧的指针
} AdjList;
// 图的链式存储(邻接表实现)
typedef struct ALGraph {
    AdjList vertices[MaxSize];  // 邻接表
    int verNum, arcNum;         // 顶点数和弧数
} ALGraph;

// 循环队列实现的数据结构定义
typedef int ElemType;

typedef struct Queue {
    ElemType data[MaxSize];
    int front, rear;
} Queue;

void init(Queue *q) {
    q->rear = 0;
    q->front = 0;
}

bool empty(Queue *q) {
    return q->front == q->rear;
}

bool full(Queue *q) {
    return (q->rear + 1) % MaxSize == q->front;
}

bool push(Queue *q, ElemType value) {
    if (full(q))
        return false;
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MaxSize;
    return true;
}

bool pop(Queue *q, ElemType *p) {
    if (empty(q))
        return false;
    *p = q->data[q->front];
    q->front = (q->front + 1) % MaxSize;
    return true;
}

// 入度
int inDegree[MaxSize];

// 计算图中各个顶点的入度
void countInDegree(ALGraph *G) {
    // 初始化
    for (int i = 0; i < G->verNum; ++i)
        inDegree[i] = 0;
    // 计算入度
    for (int i = 0; i < G->verNum; ++i) {
        ArcNode *node = G->vertices[i].first;
        while (node) {
            inDegree[node->adjVertex]++;
            node = node->next;
        }
    }
}

// 拓扑排序
bool topologicalSort(ALGraph *G) {
    int num = 0;                            	// 记录加入拓扑排序的顶点数
    Queue q;                                	// 拓扑队列
    init(&q);                            		// 初始化队列
    countInDegree(G);                    		// 计算图的入度
    for (int i = 0; i < G->verNum; ++i)
        if (inDegree[i] == 0)
            push(&q, i);                    	//将所有入度为0的顶点入队
    while (!empty(&q)) {
        int u;
        pop(&q, &u);                        	// 取队首顶点u并出队
        // printf("%d", u);                 	// 此处可输出顶点u,作为拓扑序列中的顶点
        ArcNode *i = G->vertices[u].first;
        while (i) {
            int v = G->vertices[i->adjVertex].data;
            inDegree[v]--;                  	// 顶点v的入度减1
            if (inDegree[v] == 0)           	// 顶点v的入度减为0则入队
                push(&q, v);
            i = i->next;
        }
        G->vertices[u].first = NULL;         	// 清空顶点u的所有出边(如无必要可不写)
        num++;                              	// 加入拓扑序列的顶点数加1
    }
    if (num == G->verNum) return true;       	// 加入拓扑排序的顶点数为n,说明拓扑排序成功
    else return false;                      	// 加入拓扑排序的顶点数小于n,说明拓扑排序失败
}

例题

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值