介绍
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。
方法
1. 通过入度表(直接遍历)
- 思路:
- 在有向图中选一个没有前驱的顶点并且输出
- 从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
- 重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。
- 代码演示:
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 1000;
int T[maxn][maxn];//标记图
int D[maxn];//标记顶点的入度
int n;//顶点个数
int m;//边的个数
int arr[maxn];//记录排序后的顺序
int num = 0;
int topSort()
{
for (int i = 0; i < n; i++)//得到每个节点
{
int j;
for (j = 1; j <= n; j++)//遍历找到入度为0的结点
{
if (D[j] == 0)
{
arr[num++] = j;//标记到arr数组中
D[j]--;//标记已使用
break;
}
}
for (int k = 1; k <= n; k++)//遍历所有边
{
if (T[j][k] == 1)//找到以j开头的边
{
T[j][k]--;//去掉该边
D[k]--;//k顶点入度减一
}
}
}
return 0;
}
int main()
{
int x, y;
memset(T, 0, sizeof(T));
memset(D, 0, sizeof(D));
cout << "请输入顶点个数:";
cin >> n;
cout << "请输入边的个数:";
cin >> m;
cout << "请输入边:" << endl;
for (int i = 0; i < m; i++)
{
cin >> x >> y;
T[x][y] = 1;//标记边
D[y]++;//记录入度
}
topSort();
if (num == n)
{
cout << "排序后:" << endl;
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
else
{
cout << "该图存在回路,无法使用拓扑排序" << endl;
}
return 0;
}
运行结果如下:
2. 通过DFS和栈实现
- 思路:
找到顶点,递归遍历到最后的结点,通过回溯将遍历到的点入栈,那么先进栈的必定是只有入度的结点,只有出度的结点必定在最后进栈,最后通过出栈可以得到排序后的顺序。 - 注意:
在DFS中,依次打印所遍历到的顶点;而在拓扑排序时,顶点必须比其邻接点先出现。
在DFS实现拓扑排序时,用栈来保存拓扑排序的顶点序列;并且保证在某顶点入栈前,其所有邻接点已入栈。 - 实现过程为:
- 代码演示:
2.1 数组实现:
#include <iostream>
using namespace std;
const int MAXSIZE = 100;
using namespace std;
int arr[MAXSIZE];//记录排序后的顺序
int num = 0;
int T[MAXSIZE][MAXSIZE];//标记图
int V;//顶点个数
int E;//边的个数
int visit[MAXSIZE]{ 0 };
//栈
struct Stack
{
int data[MAXSIZE];
int top;
};
//初始化栈
Stack *initStack()
{
Stack *s = (Stack *)malloc(sizeof(Stack));
s->top = -1;
return s;
}
//判断是否栈空
int isEmpty(Stack *s)
{
return s->top == -1;
}
//入栈
int push(Stack *s, int x)
{
if (s->top < MAXSIZE)
{
s->data[++s->top] = x;
return 0;
}
cout << "栈满" << endl;
return -1;
}
//出栈
int pop(Stack *s)
{
if (s->top == -1)
{
cout << "栈空" << endl;
return -1;
}
return s->data[s->top--];
}
int dfs(int n, int visit[], Stack *s)
{
visit[n] = 1;
for (int i = 0; i < V; i++)
{
if (T[n][i] == 1 && !visit[i])//遍历有连接的并且未选择过的
{
dfs(i, visit, s);
}
}
push(s, n);
return 0;
}
int topSort()
{
Stack *s = initStack();
for (int i = 0; i < V; i++)
{
if (!visit[i])
{
dfs(i, visit, s);
}
}
while (s->top != -1)//将栈中的所有元素放入arr数组中
{
arr[num++] = pop(s);
}
return 0;
}
int main()
{
int src, dest;
memset(T, 0, sizeof(T));
memset(visit, 0, sizeof(visit));
cout << "请输入顶点个数:";
cin >> V;
cout << "请输入边的个数:";
cin >> E;
cout << "请输入边:" << endl;
for (int i = 0; i < E; i++)
{
cin >> src >> dest;
T[src][dest] = 1;//标记边
}
topSort();
if (num == V)
{
cout << "排序后(以0开始):" << endl;
for (int i = 0; i < V; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
else
{
cout << "该图存在回路,无法使用拓扑排序" << endl;
}
return 0;
}
2.2 链表实现:
#include <iostream>
#include <cstring>
const int MAXSIZE = 100;
using namespace std;
int arr[MAXSIZE];//记录排序后的顺序
int num = 0;
//栈
struct Stack
{
int data[MAXSIZE];
int top;
};
//标记目的节点
struct AdjListNode
{
int dest;
AdjListNode *next;
};
//指向图中不同的起点
struct AdjList
{
AdjListNode* head;
};
//图
struct Graph
{
int V, E;
AdjList *array;
};
//初始化栈
Stack *initStack()
{
Stack *s = (Stack *)malloc(sizeof(Stack));
s->top = -1;
return s;
}
//判断是否栈空
int isEmpty(Stack *s)
{
return s->top == -1;
}
//入栈
int push(Stack *s, int x)
{
if (s->top < MAXSIZE)
{
s->data[++s->top] = x;
return 0;
}
cout << "栈满" << endl;
return -1;
}
//出栈
int pop(Stack *s)
{
if (s->top == -1)
{
cout << "栈空" << endl;
return -1;
}
return s->data[s->top--];
}
//创建图
Graph *createGraph(int V, int E)
{
Graph *g = (Graph *)malloc(sizeof(Graph));
g->V = V;
g->E = E;
g->array = (AdjList *)malloc(V * sizeof(AdjList));
for (int i = 0; i < V; i++)
{
g->array[i].head = NULL;
}
return g;
}
//添加边
int addEdge(Graph *g, int src, int dest)
{
AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));
node->dest = dest;
node->next = g->array[src].head;
g->array[src].head = node;
return 0;
}
//dfs深度优先遍历
int dfs(int n, int visit[], Graph *g, Stack *s)
{
visit[n] = 1;//标记为选择过
AdjListNode *node = g->array[n].head;//获取目的元素的指针
while (node)//遍历从第n个结点能连接到的所有结点
{
if (!visit[node->dest])//判断是否被选择过
{
dfs(node->dest, visit, g, s);
}
node = node->next;//选择下一个从第n个结点能连接到的结点
}
push(s, n);//入栈
return 0;
}
//拓扑排序
int topSort(Graph *g)
{
//初始化一个数组,标记图中所有的结点是否被选择
int *visit = (int *)malloc(g->V * sizeof(int));
memset(visit, 0, g->V * sizeof(int));
Stack *s = initStack();
for (int i = 0; i < g->V; i++)
{
if (!visit[i])
{
dfs(i, visit, g, s);
}
}
while (s->top != -1)//将栈中的所有元素放入arr数组中
{
arr[num++] = pop(s);
}
return 0;
}
int main()
{
int V, E, src, dest;
cout << "请输入顶点数:";
cin >> V;
cout << "请输入边数:";
cin >> E;
Graph *g = createGraph(V, E);
cout << "请输入边(以0开始):" << endl;
for (int i = 0; i < V; i++)
{
cin >> src >> dest;
addEdge(g, src, dest);
}
topSort(g);
cout << "排序后:" << endl;
for (int i = 0; i < V; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
运行结果如下:
3. 通过队列实现
- 思路:
- 通过遍历,将所有入度为0的进入队列。并将与之相连的结点的入度-1。
- 然后一个一个的出队列,出队列的同时判断与出队列结点相连的结点是否入度为0,为0则进栈。
- 循环第一二步,直到所有节点被选择或者栈空。(其实栈空的时候,所有节点就是被选择了)
- 代码演示:
3.1 数组实现:
#include <iostream>
using namespace std;
const int MAXSIZE = 100;
using namespace std;
int arr[MAXSIZE];//记录排序后的顺序
int num = 0;
int T[MAXSIZE][MAXSIZE];//标记图
int D[MAXSIZE];
int V;//顶点个数
int E;//边的个数
int visit[MAXSIZE]{ 0 };
//队列
struct Queue
{
int data[MAXSIZE];
int front;
int rear;
};
//初始化队列
Queue *initQueue()
{
Queue *q = (Queue *)malloc(sizeof(Queue));
q->front = 0;
q->rear = 0;
return q;
}
//判断是否队列空
int isEmpty(Queue *q)
{
return q->rear == q->front;
}
//入队列
int push(Queue *q, int x)
{
if ((q->rear + 1) % MAXSIZE == q->front)
{
cout << "队列已满!" << endl;
return -1;
}
else
{
q->data[q->rear] = x;
q->rear = (q->rear + 1) % MAXSIZE;
}
return 0;
}
//出队列
int pop(Queue *q)
{
int x;
if (isEmpty(q))
{
cout << "队列空" << endl;
return -1;
}
else
{
x = q->data[q->front];
q->front = (q->front + 1) % MAXSIZE;
return x;
}
}
int topSort()
{
Queue *q = initQueue();
for (int i = 0; i < V; i++)
{
if (D[i] == 0)//将入度为0的结点进队列
{
visit[i] = 1;//标记为已选择
push(q, i);//入队列
}
}
while (!isEmpty(q))
{
int x = pop(q);//出队列,并将数据存储在x中
for (int i = 0; i < V; i++)//遍历所有以x为起点的结点,并将结点中的入度-1
{
if (T[x][i] == 1)
{
T[x][i] = 0;
D[i]--;
}
}
arr[num++] = x;//将出队列的数据放进arr数组中
for (int i = 0; i < V; i++)
{
if (D[i] == 0 && visit[i] == 0)
{
visit[i] = 1;//标记已选择
push(q, i);//入队列
}
}
}
return 0;
}
int main()
{
int src, dest;
memset(T, 0, sizeof(T));
memset(D, 0, sizeof(D));
memset(visit, 0, sizeof(visit));
cout << "请输入顶点个数:";
cin >> V;
cout << "请输入边的个数:";
cin >> E;
cout << "请输入边:" << endl;
for (int i = 0; i < E; i++)
{
cin >> src >> dest;
T[src][dest] = 1;//标记边
D[dest]++;//记录入度
}
topSort();
if (num == V)
{
cout << "排序后(以0开始):" << endl;
for (int i = 0; i < V; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
else
{
cout << "该图存在回路,无法使用拓扑排序" << endl;
}
return 0;
}
运行结果如下:
3.2 链表实现:
#include <iostream>
#include <cstring>
const int MAXSIZE = 100;
using namespace std;
int arr[MAXSIZE];//记录排序后的顺序
int num = 0;
//队列
struct Node
{
int data;
Node *next;
};
struct QNode
{
Node *front, *rear;
int MAXSIZE;
};
//标记目的结点
struct AdjListNode
{
int dest;//标记目的结点
AdjListNode *next;
};
//指向图中不同的起点
struct AdjList
{
int indegree;//标记入度
AdjListNode* head;
};
//图
struct Graph
{
int V, E;
AdjList *array;
};
//初始化队列
QNode *initQueue()
{
QNode *q = (QNode *)malloc(sizeof(QNode));
q->front = NULL;
return q;
}
//判断是否队列空
int isEmpty(QNode *q)
{
return q->front == NULL;
}
//入队列
int push(QNode *q, int x)
{
Node *n = (Node *)malloc(sizeof(Node));
n->data = x;
n->next = NULL;
if (q->front == NULL)
{
q->front = n;
q->rear = n;
}
else
{
q->rear->next = n;
q->rear = n;
}
return 0;
}
//出队列
int pop(QNode *q)
{
int x;
Node *frontCell;
if (isEmpty(q))
{
cout << "队列空" << endl;
return -1;
}
else
{
frontCell = q->front;
if (q->front == q->rear)//判断队列中是否只有一个元素
{
q->front = q->rear = NULL;//删除后队列置空
}
else
{
q->front = q->front->next;
}
x = frontCell->data;
return x;
}
}
//创建图
Graph *createGraph(int V, int E)
{
Graph *g = (Graph *)malloc(sizeof(Graph));
g->V = V;
g->E = E;
g->array = (AdjList *)malloc(V * sizeof(AdjList));
for (int i = 0; i < V; i++)
{
g->array[i].indegree = 0;
g->array[i].head = NULL;
}
return g;
}
//添加边
int addEdge(Graph *g, int src, int dest)
{
AdjListNode *node = (AdjListNode *)malloc(sizeof(AdjListNode));
node->dest = dest;
node->next = g->array[src].head;
g->array[src].head = node;
g->array[dest].indegree++;
return 0;
}
//拓扑排序
int topSort(Graph *g)
{
//初始化一个数组,标记图中所有的结点是否被选择
int *visit = (int *)malloc(g->V * sizeof(int));
memset(visit, 0, g->V * sizeof(int));
QNode *q = initQueue();
for (int i = 0; i < g->V; i++)
{
if (g->array[i].indegree == 0)//将入度为0的结点进队列
{
visit[i] = 1;//标记为已选择
push(q, i);//入队列
}
}
while (!isEmpty(q))
{
int x = pop(q);//出队列,并将数据存储在x中
AdjListNode *a = g->array[x].head;
while (a)//遍历所有以x为起点的结点,并将结点中的入度-1
{
g->array[a->dest].indegree--;
a = a->next;
}
arr[num++] = x;//将出队列的数据放进arr数组中
for (int i = 0; i < g->V; i++)
{
if (g->array[i].indegree == 0 && visit[i] == 0)
{
visit[i] = 1;//标记已选择
push(q, i);//入队列
}
}
}
return 0;
}
int main()
{
int V, E, src, dest;
cout << "请输入顶点数:";
cin >> V;
cout << "请输入边数:";
cin >> E;
Graph *g = createGraph(V, E);
cout << "请输入边(以0开始):" << endl;
for (int i = 0; i < V; i++)
{
cin >> src >> dest;
addEdge(g, src, dest);
}
topSort(g);
cout << "排序后:" << endl;
for (int i = 0; i < V; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
运行结果如下: