图-拓扑排序+练习题

前言:图-拓扑排序的学习和实现笔记

参考文章:https://www.jianshu.com/p/b59db381561a

有向无环图(DAG)

在学习拓扑排序之前,先了解一个概念有向无环图(DAG)

有向无环图DAG的定义:有向无环图指的是一个无回路的有向图。

如果有一个非有向无环图,且A点出发向B经C可回到A,形成一个环。

将从C到A的边方向改为从A到C,则变成有向无环图,比如下面这张图的话那么就是一张有向无环图

如果是下面这种多了一条4->1的边长的话,那么类似下面这种的话就是非DAG图,也就是非有向无环图

知识点:有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说。

什么是AOV网

它的全称是Activity On Vertex NetWork,用顶点表示的活动的网

而在AOV网中,都是用DAG图(有向无环图)表示一个工程。

其中的顶点表示活动,有向边<Vi,Vj>表示活动Vi必须先于活动Vj进行,如下图所示,这种就是一个AOV网表示的一个工程

这里举个例子如下所示,类似这种的话在AOV网中,其中构成了一个环路,那么就不能称之为一个AOV网

什么是拓扑排序

总结下拓扑排序的作用,简而言之其实就是判断一个有向图是否是无环图,但是在AOV网中通过拓扑排序,我们可以实现排序工程中的每个顶点之间的活动顺序,上面的例子经过排序过后就如下所示

在图论中,拓扑排序(Topological Sorting)是一个有向无环图(DAG,Directed Acyclic Graph)的所有顶点的线性序列。

且该序列必须满足下面两个条件:

  • 每个顶点出现且只出现一次。

  • 若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

如何手动写出有向无环图的拓扑排序

一个知识点:通常,一个有向无环图可以有一个或多个拓扑排序序列。

  • 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。

  • 从图中删除该顶点和所有以它为起点的有向边。

  • 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

代码实现有向无环图的拓扑排序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OK 1
#define ERROR 0
#define MAX 32767
typedef int Status;

typedef struct _Graph
{
	int vexNum;	// 顶点数量
	int arcNum;	// 边长数量
	int** arcs;		// 边长的0或者1,大小就是arcsNum*arcsNum
	char* vexs;		// 顶点的名称
}Graph, *PGraph;

// 图的初始化
Graph* initGraph(int vexNum)
{
	Graph* pGraph = NULL;
	if (pGraph == NULL)
	{
		pGraph = (Graph*)malloc(sizeof(Graph));
		memset(pGraph, 0, sizeof(Graph));
		if (pGraph == NULL)
			return NULL;
		pGraph->vexNum = vexNum; // 顶点的数量
		pGraph->vexs = (char*)malloc(sizeof(pGraph->vexNum));
		memset(pGraph->vexs, 0, sizeof(pGraph->vexNum));
		pGraph->arcNum = 0; // 初始化为0,因为到时候craeteGraph的时候完了之后再进行最终边数的计算
		pGraph->arcs = (int**)malloc(sizeof(int*)* pGraph->vexNum);
		memset(pGraph->arcs, sizeof(int*)* pGraph->vexNum, 0);
		// 一个图中每个顶点都有对应的n个顶点的边数
		for (int i = 0; i < pGraph->vexNum; i++)
		{
			pGraph->arcs[i] = malloc(sizeof(int)* pGraph->vexNum);
			memset(pGraph->arcs[i], 0, sizeof(int)*pGraph->vexNum);
		}
	}
	return pGraph;
}

// 顶点和边之间的关系初始化
Status createGraph(Graph** pGraph, char* vexs, int* arcs)
{
	if (*pGraph == NULL)
		return ERROR;
	for (int i = 0; i<(*pGraph)->vexNum; i++)
	{
		*((*pGraph)->vexs + i) = *(vexs + i);
		for (int j = 0; j<(*pGraph)->vexNum; j++)
		{
			(*pGraph)->arcs[i][j] = *(arcs + i*((*pGraph)->vexNum) + j);
			printf("%d ", (*pGraph)->arcs[i][j]);
			if ((*pGraph)->arcs[i][j] == 1)
				(*pGraph)->arcNum++;
		}
		printf("\n");
	}
	// 无向图的边数还需要除以2
	(*pGraph)->arcNum /= 2;
	return OK;
}

// 深度优先遍历
Status DFS(Graph* pGraph, int* iVisitedArray, int visitedIndex)
{
	if (pGraph == NULL)
		return ERROR;
	iVisitedArray[visitedIndex] = 1;
	printf("%c ", pGraph->vexs[visitedIndex]);
	for (int i = 0; i<pGraph->vexNum; i++)
	{
		if (pGraph->arcs[visitedIndex][i] == 1 && !iVisitedArray[i])
			DFS(pGraph, iVisitedArray, i);
	}
	return OK;
}

void getTopoSort(Graph* pGraph)
{
	int iFlag; // 标识符
	int visitedArray[5] = { 0 };
	for (int m = 0;m<pGraph->vexNum;m++)
	{
		for (int i = 0; i < pGraph->vexNum; i++)
		{
			iFlag = 1;
			if (!visitedArray[i])
			{
				for (int j = 0; j < pGraph->vexNum; j++)
				{
					if (pGraph->arcs[j][i] != 0 && pGraph->arcs[j][i] != MAX)
					{
						iFlag = 0; break;
					}
				}
				if (iFlag)
				{
					for (int k = 0; k < pGraph->vexNum; k++){ if (pGraph->arcs[i][k] == 1)pGraph->arcs[i][k]--; }
					printf("输出顶点%c\n", pGraph->vexs[i]);
					visitedArray[i] = 1;
					break;
				}
			}
		}
	}
}

int main()
{
	// 要初始化的矩阵
	int visited[5] = { 0 };
	int initArcs[5][5] =
	{
		0,1,MAX,1,MAX,
		MAX,0,1,1,MAX,
		MAX,MAX,0,MAX,1,
		MAX,MAX,1,0,1,
		MAX,MAX,MAX,MAX,0
	};
	char initVexs[6] = "12345";
	Graph* pGraph = initGraph(5); // 要初始化的顶点的数量
	createGraph(&pGraph, initVexs, (int*)initArcs);
	getTopoSort(pGraph);
	return 0;
	
}

练习题

问题描述:有一串数字1到5,按照下面的关于顺序的要求,重新排列并打印出来。要求如下:2在5前出现,3在2前出现,4在1前出现,1在3前出现。

看到题目就知道是进行构造相关的无环图dag,那么这里可以通过dfs或者bfs来进行实现,我这里的话就通过dfs来进行实现,go代码如下

思路就是先构造对应的主键的map,然后通过遍历map来进行判断是否能够构造出对应的完整连续的dag来,可以的话则成功

package main

import (
	"fmt"
)

// 问题描述:有一串数字1到5,按照下面的关于顺序的要求,重新排列并打印出来。要求如下:2在5前出现,3在2前出现,4在1前出现,1在3前出现

var gmap2 map[string]string = map[string]string{
	"2": "5",
	"3": "2",
	"4": "1",
	"1": "3",
}

//反转数组顺序
func reverse(arr []string) {
	for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
		arr[i], arr[j] = arr[j], arr[i]
	}
}

var q []string
var visited_ map[string]bool

func tapo_sort3(k string) {
	if !visited_[k] {
		visited_[k] = true
		q = append(q, k)
		if gmap2[gmap2[k]] != "" {
			q = append(q, gmap2[k])
			tapo_sort3(gmap2[gmap2[k]])
		}
	}
}

func main() {
	for k, _ := range gmap2 {
		fmt.Printf("\t %s\n", k)
		q = make([]string, 0)
		visited_ = make(map[string]bool, 0)
		tapo_sort3(k)
		// reverse(q)
		fmt.Printf("topusort: %v \n", q)
	}

}

posted @ 2022-04-13 18:21  zpchcbd  阅读(382)  评论(0)    收藏  举报