【经典算法】--- 拓扑排序(通过入度表,DFS与栈,队列分别实现)

介绍

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

方法

1. 通过入度表(直接遍历)

  • 思路:
  1. 在有向图中选一个没有前驱的顶点并且输出
  2. 从图中删除该顶点和所有以它为尾的弧(白话就是:删除所有和它有关的边)
  3. 重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。
  • 代码演示:
#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. 通过队列实现

  • 思路:
  1. 通过遍历,将所有入度为0的进入队列。并将与之相连的结点的入度-1。
  2. 然后一个一个的出队列,出队列的同时判断与出队列结点相连的结点是否入度为0,为0则进栈。
  3. 循环第一二步,直到所有节点被选择或者栈空。(其实栈空的时候,所有节点就是被选择了)
  • 代码演示:

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;
}

运行结果如下:

猜你喜欢

转载自blog.csdn.net/qq_41879343/article/details/89435119