数据结构和算法17 之拓扑排序

本文为博主原创文章,转载请注明出处:http://blog.csdn.net/eson_15/article/details/51194219

这一节我们学习一个新的排序算法,准确的来说,应该叫“有向图的拓扑排序”。所谓有向图,就是A->B,但是B不能到A。与无向图的区别是,它的边在邻接矩阵里只有一项(友情提示:如果对图这种数据结构部不太了解的话,可以先看一下这篇博文:数据结构和算法之
无向图
。因为拓扑排序是基于图这种数据结构的)。

有向图的邻接矩阵如下表所示:


A


B


C


A


0


1


1


B


0


0


1


C


0


0


0

所以针对前面讨论的无向图,邻接矩阵的上下三角是对称的,有一半信息是冗余的。而有向图的邻接矩阵中所有行列之都包含必要的信息,它的上下三角不是对称的。所以对于有向图,增加边的方法只需要一条语句:

//有向图中,邻接矩阵中只有一项
public void addEdge(int start, int end) {
	adjMat[start][end] = 1;
}

如果使用邻接表示意图,那么A->B表示A在它的链表中有B,但是B的链表中不包含A,这里就不多说了,本文主要通过邻接矩阵实现。

因为图是有向的,假设A->B->C->D这种,那这就隐藏了一种顺序,即要想到D,必须先过C,必须先过B,必须先过A。它们无形中形成了一种顺序,这种顺序在实际中还是用的挺广泛的,比如,要做web开发,必须先学java基础等等,这些都遵循一个顺序,所以拓扑排序的思想也是这样,利用有向图特定的顺序进行排序。但是拓扑排序的结果不是唯一的,比如A->B的同时,C->B,也就是说A和C都能到B,所以用算法生成一个拓扑排序时,使用的方法和代码的细节决定了会产生那种拓扑排序。

拓扑排序的思想虽然不寻常,但是却很简单,有两个必要的步骤:

1. 找到一个没有后继的顶点;

2.从图中删除这个顶点,在列表中插入顶点的标记

然后重复1和2,直到所有顶点都从图中删除,这时候列表显示的顶点顺序就是拓扑排序的结果了。

但是我们需要考虑一种特殊的有向图:环。即A->B->C->D->A。这种必然会导致找不着“没有后继的节点”,这样便无法使用拓扑排序了。

下面我们分析下拓扑排序的代码:

public void poto() {
	int orig_nVerts = nVerts; //记录有多少个顶点
	while(nVerts > 0) {
		//返回没有后继顶点的顶点
		int currentVertex = noSuccessors(); //如果不存在这样的顶点,返回-1
		if(currentVertex == -1) {
			System.out.println("ERROR: Graph has cycles!");
			return;
		}

		//sortedArray中存储排过序的顶点(从尾开始存)
		sortedArray[nVerts-1] = vertexArray[currentVertex].label;
		deleteVertex(currentVertex);//删除该顶点,便于下一次循环,寻找下一个没有后继顶点的顶点
	}
	System.out.println("Topologically sorted order:");
	for(int i = 0; i < orig_nVerts; i++) {
		System.out.print(sortedArray[i]);
	}
	System.out.println("");
}

主要的工作在while循环中进行,这个循环直到定点数为0时才退出:

1. 调用noSuccessors()找到任意一个没有后继的顶点;

2. 如果找到一个这样的顶点,把顶点放到sortedArray数组中,并且从图中删除这个顶点;

3. 如果不存在这样的顶点,则图必然存在环。

最后sortedArray数组中存储的就是排过序的顶点了。下面我们分析下noSuccessor()方法和deleteVertes()方法:

//return vertex with no successors
private int noSuccessors() {
	boolean isEdge;
	for(int row = 0; row < nVerts; row++) {
		isEdge = false;
		for(int col = 0; col < nVerts; col++) {
			if(adjMat[row][col] > 0) { //只要adjMat数组中存储了1,表示row->col
				isEdge = true;
				break;
			}
		}
		if(!isEdge) {//只要有边,返回最后一个顶点
			return row;
		}
	}
	return -1;
}

private void deleteVertex(int delVertex) {
	if(delVertex != nVerts -1) {
		for(int i = delVertex; i < nVerts-1; i++) { //delete from vertexArray
			vertexArray[i] = vertexArray[i+1];
		}
		//删除adjMat中相应的边
		for(int row = delVertex; row < nVerts-1; row++) {//delete row from adjMat
			moveRowUp(row, nVerts);
		}

		for(int col = delVertex; col < nVerts-1; col++) {//delete column from adjMat
			moveColLeft(col, nVerts-1);
		}
	}
	nVerts--;
}

从上面代码可以看出,删除一个顶点很简单,从vertexArray中删除,后面的顶点向前移动填补空位。同样的,顶点的行列从邻接矩阵中删除,下面的行和右面的列移动来填补空位。删除adjMat数组中的边比较简单,下面看看moveRowUp和moveColLeft的方法:

private void moveRowUp(int row, int length) {
	for(int col = 0; col < length; col++) {
		adjMat[row][col] = adjMat[row+1][col];
	}
}

private void moveColLeft(int col, int length) {
	for(int row = 0; row < length; row++) {
		adjMat[row][col] = adjMat[row][col+1];
	}
}

这样便介绍完了拓扑排序的所有过程了。下面附上完整的代码:

package graph;
/**
 * 有向图的拓扑排序:
 * 拓扑排序是可以用图模拟的另一种操作,它可以用于表示一种情况,即某些项目或事件必须按特定的顺序排列或发生。
 * 有向图和无向图的区别是:有向图的边在邻接矩阵中只有一项。
 * 拓扑排序算法的思想虽然不寻常但是很简单,有两个步骤是必须的:
 * 1. 找到一个没有后继的顶点
 * 2. 从图中删除这个顶点,在列表的前面插入顶点的标记
 * 重复这两个步骤,直到所有顶点都从图中删除,这时,列表显示的顶点顺序就是拓扑排序的结果。
 * 删除顶点似乎是一个极端的步骤,但是它是算法的核心,如果第一个顶点不处理,算法就不能计算出要处理的第二个顶点。
 * 如果需要,可以再其他地方存储图的数据(顶点列表或者邻接矩阵),然后在排序完成后恢复它们。
 * @author eson_15
 * @date 2016-4-20 12:16:11
 *
 */
public class TopoSorted {
	private final int MAX_VERTS = 20;
	private Vertex vertexArray[]; //存储顶点的数组
	private int adjMat[][]; //存储是否有边界的矩阵数组, 0表示没有边界,1表示有边界
	private int nVerts; //顶点个数
	private char sortedArray[]; //存储排过序的数据的数组

	public TopoSorted() {
		vertexArray = new Vertex[MAX_VERTS];
		adjMat = new int[MAX_VERTS][MAX_VERTS];
		nVerts = 0;
		for(int i = 0; i < MAX_VERTS; i++) {
			for(int j = 0; j < MAX_VERTS; j++) {
				adjMat[i][j] = 0;
			}
		}
		sortedArray = new char[MAX_VERTS];
	}

	public void addVertex(char lab) {
		vertexArray[nVerts++] = new Vertex(lab);
	}

	//有向图中,邻接矩阵中只有一项
	public void addEdge(int start, int end) {
		adjMat[start][end] = 1;
	}

	public void displayVertex(int v) {
		System.out.print(vertexArray[v].label);
	}

	/*
	 * 拓扑排序
	 */
	public void poto() {
		int orig_nVerts = nVerts; //remember how many verts
		while(nVerts > 0) {
			//get a vertex with no successors or -1
			int currentVertex = noSuccessors();
			if(currentVertex == -1) {
				System.out.println("ERROR: Graph has cycles!");
				return;
			}

			//insert vertex label in sortedArray (start at end)
			sortedArray[nVerts-1] = vertexArray[currentVertex].label;
			deleteVertex(currentVertex);
		}
		System.out.println("Topologically sorted order:");
		for(int i = 0; i < orig_nVerts; i++) {
			System.out.print(sortedArray[i]);
		}
		System.out.println("");
	}

	//return vertex with no successors
	private int noSuccessors() {
		boolean isEdge;
		for(int row = 0; row < nVerts; row++) {
			isEdge = false;
			for(int col = 0; col < nVerts; col++) {
				if(adjMat[row][col] > 0) {
					isEdge = true;
					break;
				}
			}
			if(!isEdge) {
				return row;
			}
		}
		return -1;
	}

	private void deleteVertex(int delVertex) {
		if(delVertex != nVerts -1) {
			for(int i = delVertex; i < nVerts-1; i++) { //delete from vertexArray
				vertexArray[i] = vertexArray[i+1];
			}

			for(int row = delVertex; row < nVerts-1; row++) {//delete row from adjMat
				moveRowUp(row, nVerts);
			}

			for(int col = delVertex; col < nVerts-1; col++) {//delete column from adjMat
				moveColLeft(col, nVerts-1);
			}
		}
		nVerts--;
	}

	private void moveRowUp(int row, int length) {
		for(int col = 0; col < length; col++) {
			adjMat[row][col] = adjMat[row+1][col];
		}
	}

	private void moveColLeft(int col, int length) {
		for(int row = 0; row < length; row++) {
			adjMat[row][col] = adjMat[row][col+1];
		}
	}
}

拓扑排序就介绍到这吧,如有错误之处,欢迎留言指正~

_____________________________________________________________________________________________________________________________________________________

-----乐于分享,共同进步!

-----更多文章请看:http://blog.csdn.net/eson_15

时间: 2024-10-18 15:08:25

数据结构和算法17 之拓扑排序的相关文章

算法总结之拓扑排序

拓扑排序 1.一般应用       拓扑排序常用来确定一个依赖关系集中,事物发生的顺序.例如,在日常工作中,可能会将项目拆分成A.B.C.D四个子部分来完成,但A依赖于B和D,C依赖于D.为了计算这个项目进行的顺序,可对这个关系集进行拓扑排序,得出一个线性的序列,则排在前面的任务就是需要先完成的任务. 2.实现的基本方法 (1)从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它. (2)从网中删去该顶点,并且删去从该顶点发出的全部有向边. (3)重复上述两步,直到剩余的网中不再存在没有前趋

ACM/ICPC 之 数据结构-邻接表+DP+队列+拓扑排序(TshingHua OJ-旅行商TSP)

做这道题感觉异常激动,因为在下第一次接触拓扑排序啊= =,而且看了看解释,猛然发现此题可以用DP优化,然后一次A掉所有样例,整个人激动坏了,哇咔咔咔咔咔咔咔~ 咔咔~哎呀,笑岔了- -|| 旅行商(TSP) Description Shrek is a postman working in the mountain, whose routine work is sending mail to n villages. Unfortunately, road between villages is

数据结构与算法系列研究九——排序算法的一些探讨

四种排序 一.实验内容     输入20个整数,分别用希尔排序.快速排序.堆排序和归并排序实现由小到大排序并输出排序结果.二.关键数据结构与核心算法   关键数据结构:由于是排序为了简单起见,选用线性表中的数组作为存储结构.   核心算法:   1.希尔排序    希尔排序的核心还是直接插入法,但是插入的位置有所讲究.要把数组分为许多段,每一段的长度除了最后的有可能不同之外,其他的都相同.该段的长度即为增量,在最后一次必须为一,此时程序变成了直接插入.每次进行隔段插入,不断地调整是的数组变得隔段

算法笔记_023:拓扑排序(Java)

目录 1 问题描述 2 解决方案 2.1 基于减治法实现 2.2 基于深度优先查找实现 1 问题描述 给定一个有向图,求取此图的拓扑排序序列. 那么,何为拓扑排序? 定义:将有向图中的顶点以线性方式进行排序.即对于任何连接自顶点u到顶点v的有向边uv,在最后的排序结果中,顶点u总是在顶点v的前面. 2 解决方案 2.1 基于减治法实现 实现原理:不断地做这样一件事,在余下的有向图中求取一个源(source)(PS:定义入度为0的顶点为有向图的源),它是一个没有输入边的顶点,然后把它和所有从它出发

数据结构与算法系列十(排序算法概述)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

数据结构之---C语言实现拓扑排序AOV图

//有向图的拓扑排序 //杨鑫 #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_NAME 3 #define MAX_VERTEX_NUM 20 typedef int InfoType; //存放网的权值 typedef char VertexType[MAX_NAME]; //字符串类型 typedef enum{DG, DN, AG, AN}GraphKind; //{有

数据结构--图(下)--拓扑排序

拓扑排序 思维导图也是图的一种 拓扑序:如果图中从V到W有一条有向路径,则V一定排在W之前.满足此条件的顶点排序成为一个拓扑序.  V->W 获得一个拓扑序的过程就是拓扑排序 AOV如果有合理的拓扑序,则必定是有向无环图(Directed Acyclic Graph,简称DAG). 第一排没有预修课程的课.然后抹掉顶点和边, 每一次输出哪个顶点呢,没有前驱顶点,就输出(入度为0的顶点). 最后的拓扑序就产生了 DAG有向无环图. 拓扑排序的应用 AOE(Activity On Edge)网络  

python数据结构与算法第八天【排序算法】

1.排序算法的稳定性 稳定排序算法会让原本有相同键值的记录维持相对次序 例如:对以下元组按照元组的第一个元素升序排列,元组如下: (4,1) (3,1) (3,7) (5,6) 若要满足条件,则可能的排序有: 情况一: (3,1) (3,7) (4,1) (5,6) 情况二: (3,7) (3,1) (4,1) (5,6) 虽然情况一和情况二都是满足条件的,但是情况二在满足条件下打破了原本无需改变的顺序 原文地址:https://www.cnblogs.com/liuzhiqaingxyz/p/

【数据结构与算法 02】选择排序

算法思想: 遍历数组array[N],索引为 i,然后算出[i+1,N-1]区间中的最小数,与array[i]交换,最后一个数因为只有一个,所以不用比较外循环为 N-2次 博客地址:http://blog.csdn.net/mkrcpp/article/details/39181077 import java.util.Arrays; /*** * @title 选择排序 * @author michael.mao * @date 2014年9月10日 下午2:32:01 * @version