摘要:但是一个偏序关系,如果我们默认,先出现的排在前面,那么我们就能比较,的关系了。对于算法的每个节点,我们都有一个发现时间,一个访问时间,当运行完成时,对于图中的任意一条边,都有所以拓扑排序的次序与顶点的完成时间恰好相反。
1. 偏序和全序的概念
1.1. 偏序
设R是集合A上的一个二元关系,若R满足下列三个性质则称R为A上的偏序关系
自反性:对任意x∈A,有
反对称性:对任意的x,y∈A,如果
传递性:对任意x,y,z∈A,若
通俗解释:自然数之间的"大于等于"是一个偏序关系,例如自然数的一个子集A={1,2,3}
"大于等于"的一个子集R={<1,1>,<2,2>,<3,3>,<3,2>,<2,1>,<3,1>}对于自反的解释是1=1,2=2,3=3;对于反对称性,<3,2>,<2,1>,<3,1>∈R,但关系R中不存在元素<2,3>,<1,2><1,3>因为2<3,1<2,1<3,对于传递<3,2>,<2,1>∈R即3>2,2>1所以3>1即<3,1>∈R
1.2. 全序
全序是偏序的一个子集,即集合中任意两个元素之间都有明确的"序"的关系也就是下面的性质
完全性:集合中任意两个元素之间都有明确的"序"的关系,由此可见完全性包含了自反性,任意就包含了元素和元素自己
通俗解释:是偏序但不是全序,设集合A={1,2,3,b},b=2i+1;由于b是一个复数,所以其它的三个元素都不可以和它来比较大小
1.3. 算法的稳定性
如果我们要对下列数组的元素按照index的大小进行排序 [{id:a,index:1},{id:b,index:1},{id:c,index:2}],我们设第一个为A,第二个为B,第三个为C, 我们应该如何确定A和B之间的顺序呢?
由于A,B的index值相同,但A和B确实是不同的元素,因此我们无法确定他们的先后顺序,即A和B不可比较,所以在A,B,C三个元素组成的关系不具备全序关系。但是一个偏序关系,如果我们默认,先出现的排在前面,那么我们就能比较A,B的关系了。我们排序就有C,A,B
稳定的算法:是对于存在上述情况的元素总能按照元素出现的先后顺序排列的算法
不稳定的算法:是对于上述情况,不能保证先出现的排在前面由此我们说,直接插入排序,冒泡排序,归并排序,基数排序是稳定的而shell排序,堆排序,快速排序直接选择排序是不稳定的
2. 拓扑排序说明:本文图的构建方法及DFS算法可以参考 BFS,DFS 算法原理及js实现
我们每天早上起床后穿衣的过程可以分为很多步骤,例如,穿内裤,穿裤子,穿内裤必须在穿裤子之前,同样的穿袜子必须在穿鞋子之前等等,戴手表和其它的任何一个动作之间都没有明显的关系,因此放在这个线性序列中的哪里都无所谓
2.1. 拓扑排序定义
对于一个有向无环图G来说,拓扑排序就是对图G中的所有节点的一次线性排序,该排序满足如下条件,如果图G中包含边(u,v),则节点u一定在v的前面,可以将拓扑排序看作是将图的所有节点在一条直线上水平排开
3. Kahn算法3.1. 算法原理
对于一个有向无环AOV(顶点表示活动,边表示优先关系)图,我们重复执行以下两个步骤,直到不存在入度为0的顶点为止
(1)先择一个入度为0的顶点并输出 (2)从图中删除由该节点发出的所有边
这样得到的序列就是该图的拓扑排序,如果途中有环,则输出的顶点的数目小于图中节点的数目
3.2. 算法描述
L一个用来存放已排序顶点的List S一个用来存放如度为0的顶点List 当S不为空时执行循环执行以下步骤 从S中移除一个顶点(没有顺序要求,随意移除)n 将n插入到L中 循环遍历从n发出的边,对于所有的孩子节点m 移除边如果m的入度为0,则将m放入S中 如果循环结束后图中还有边则说明图中有环 否则L是拓扑排序的结果
3.3. 算法的js实现
//数据结构 邻接链表-顶点 function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始为 白色 this.pi = null; //初始为 无前驱 this.d = this.INFINITY; //初始为 无穷大 this.edges = null; //由顶点发出的所有边 this.value = null; //节点的标识 this.data = null; //节点的数据 this.incoming = 0; //节点的入度 } Vertex.prototype = { constructor: Vertex, WHITE: "white", //白色 GRAY: "gray", //灰色 BLACK: "black", //黑色 INFINITY: null, //d 为 null 时表示无穷大 } //数据结构 邻接链表-边 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //边所依附的节点的位置 this.sibling = null; this.w = null; //保存边的权值 } //数据结构 图-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; this.dict = {}; //字典 用来映射标节点的识符和数组中的位置 } Graph.prototype = { constructor: Graph, //这里加进来的已经具备了边的关系 addNode: function(node) { this.graph.push(node); }, getNode: function(index) { return this.graph[index]; }, //创建图的 节点 initVertex: function(vertexs) { //创建节点并初始化节点属性 value for (let value of vertexs) { let vertex = Vertex(); vertex.value = value.value; vertex.data = value.data; this.graph.push(vertex); } //初始化 字典 for (let i in this.graph) { this.dict[this.graph[i].value] = i; } }, //建立图中 边 的关系 initEdge: function(edges) { for (let field in edges) { let index = this.dict[field]; //从字典表中找出节点在 graph 中的位置 let vertex = this.graph[index]; //获取节点 vertex.edges = createLink(0, edges[field].length, edges[field], this.dict, this.graph); } } } //创建链表,返回链表的第一个节点 function createLink(index, len, edges, dict, vertexs) { if (index >= len) return null; let edgeNode = Edge(); edgeNode.index = dict[edges[index].id]; //边连接的节点 用在数组中的位置表示 参照字典 vertexs[edgeNode.index].incoming = vertexs[edgeNode.index].incoming + 1; //设置节点的入度 edgeNode.w = edges[index].w; //边的权值 edgeNode.sibling = createLink(++index, len, edges, dict, vertexs); //通过递归实现 回溯 return edgeNode; } // a内裤 b袜子 c手表 d裤子 e鞋 f腰带 g衬衣 h领带 i 夹克 vertexs = [{value: "a", data: "内裤"}, {value: "b", data: "袜子"}, {value: "c",data: "手表"}, {value: "d", data: "裤子"}, {value: "e",data: "鞋"}, {value: "f", data: "腰带"}, {value: "g",data: "衬衣"}, {value: "h", data: "领带"}, {value: "i",data: "夹克"}]; var edges = { a: [{id: "d", w: 1 }, {id: "e", w: 1 }], b: [{id: "e", w: 1}], c: [], d: [{id: "e", w: 1 }, {id: "f", w: 1 }], e: [], f: [{id: "i", w: 1}], g: [{id: "f", w: 1 }, {id: "h", w: 1 }], h: [{id: "i", w: 1}], i: [] } //kahn算法 function kahn(g) { let s = []; //用于存放入度为0的顶点 let l = []; //用来存放已经排好序的顶点 //初始化set 将图中所有入度为0的节点加入到set中 for(let v of g.graph) { if(v.incoming==0) s.push(v); } while(s.length > 0) { let u = s.shift(); l.push(u); if (u.edges == null) continue; let sibling = u.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); n.incoming = n.incoming - 1; //删除边 if(n.incoming == 0) s.push(n); //入度为0的放入s sibling = sibling.sibling; } } return l; } var g = Graph(); g.initVertex(vertexs); g.initEdge(edges); var r = kahn(g); console.log(r);
运行结果
4. 基于DFS的拓扑排序算法4.1. 算法原理
原理:拓扑排序的次序与顶点的完成时间恰好相反
对于拓扑排序,我们要做的是保证对于任意一条边(u,v),节点u一定出现在节点v前面。
对于DFS算法的每个节点,我们都有一个发现时间d,一个访问时间f,当DFS运行完成时,对于图中的任意一条边(u,v),都有 u.f>v.f,所以拓扑排序的次序与顶点的完成时间恰好相反。
当DFS在图上运行时,探索到任意一条边(u,v)时,u为灰色,所以v要么是白色,要么是黑色,如果v是白色,则v一定早于u被访问,即 u.f>v.f,当v为黑色时,此时v已经被访问过,而u还为灰色,即u没有被访问,所以v依然早于u被访问,仍有 u.f>v.f,由此可见上述结论成立
4.2. js实现
基于以上结论,我们用DFS实现拓扑排序,只需要在节点 的f被设置值即节点被访问后,将其加入一个后进先出队列中(调用unshift方法始终向数组的头部添加新元素)L中,当DFS运行结束后,L中的元素就是经过拓扑排序的结果
//数据结构 邻接链表-顶点 function Vertex() { if (!(this instanceof Vertex)) return new Vertex(); this.color = this.WHITE; //初始为 白色 this.pi = null; //初始为 无前驱 this.d = this.INFINITY; //初始为 无穷大 this.edges = null; //由顶点发出的所有边 this.value = null; //节点的标识 this.data = null; //节点的数据 this.incoming = 0; } Vertex.prototype = { constructor: Vertex, WHITE: "white", //白色 GRAY: "gray", //灰色 BLACK: "black", //黑色 INFINITY: null, //d 为 null 时表示无穷大 } //数据结构 邻接链表-边 function Edge() { if (!(this instanceof Edge)) return new Edge(); this.index = null; //边所依附的节点的位置 this.sibling = null; this.w = null; //保存边的权值 } //数据结构 图-G function Graph() { if (!(this instanceof Graph)) return new Graph(); this.graph = []; this.dict = {}; //字典 用来映射标节点的识符和数组中的位置 } Graph.prototype = { constructor: Graph, //这里加进来的已经具备了边的关系 addNode: function(node) { this.graph.push(node); }, getNode: function(index) { return this.graph[index]; }, //创建图的 节点 initVertex: function(vertexs) { //创建节点并初始化节点属性 value for (let value of vertexs) { let vertex = Vertex(); vertex.value = value.value; vertex.data = value.data; this.graph.push(vertex); } //初始化 字典 for (let i in this.graph) { this.dict[this.graph[i].value] = i; } }, //建立图中 边 的关系 initEdge: function(edges) { for (let field in edges) { let index = this.dict[field]; //从字典表中找出节点在 graph 中的位置 let vertex = this.graph[index]; //获取节点 vertex.edges = createLink(0, edges[field].length, edges[field], this.dict, this.graph); } } } //创建链表,返回链表的第一个节点 function createLink(index, len, edges, dict, vertexs) { if (index >= len) return null; let edgeNode = Edge(); edgeNode.index = dict[edges[index].id]; //边连接的节点 用在数组中的位置表示 参照字典 vertexs[edgeNode.index].incoming = vertexs[edgeNode.index].incoming + 1; //设置节点的入度 edgeNode.w = edges[index].w; //边的权值 edgeNode.sibling = createLink(++index, len, edges, dict, vertexs); //通过递归实现 回溯 return edgeNode; } // a内裤 b袜子 c手表 d裤子 e鞋 f腰带 g衬衣 h领带 i 夹克 vertexs = [{value: "a", data: "内裤"}, {value: "b", data: "袜子"}, {value: "c",data: "手表"}, {value: "d", data: "裤子"}, {value: "e",data: "鞋"}, {value: "f", data: "腰带"}, {value: "g",data: "衬衣"}, {value: "h", data: "领带"}, {value: "i",data: "夹克"}]; var edges = { a: [{id: "d", w: 1 }, {id: "e", w: 1 }], b: [{id: "e", w: 1}], c: [], d: [{id: "e", w: 1 }, {id: "f", w: 1 }], e: [], f: [{id: "i", w: 1}], g: [{id: "f", w: 1 }, {id: "h", w: 1 }], h: [{id: "i", w: 1}], i: [] } function DFS(g) { let t = 0; let l =[]; for (let v of g.graph) { if (v.color == v.WHITE) DFSVisit(g, v); } function DFSVisit(g, v) { t = t + 1; v.d = t; v.color = v.GRAY; let sibling = v.edges; while (sibling != null) { let index = sibling.index; let n = g.getNode(index); if (n.color == n.WHITE) { n.pi = v; DFSVisit(g, n); //先纵向找 } sibling = sibling.sibling; //利用递归的特性来回溯 } v.color = v.BLACK; t = t + 1; v.f = t; //设置完成时间 l.unshift(v); //拓扑排序的次序与顶点的完成时间恰好相反 } console.log(l) } var g = Graph(); g.initVertex(vertexs); g.initEdge(edges); DFS(g);
运行结果
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/92303.html
摘要:归并排序归并排序,或,是创建在归并操作上的一种有效的排序算法,效率为大符号。以此类推,直到所有元素均排序完毕。与快速排序一样都由托尼霍尔提出的,因而也被称为霍尔选择算法。 showImg(https://segmentfault.com/img/remote/1460000019096360);编译:周素云、蒋宝尚 学会了Python基础知识,想进阶一下,那就来点算法吧!毕竟编程语言只...
摘要:结构型模式适配器模式桥接模式装饰模式组合模式外观模式享元模式代理模式。行为型模式模版方法模式命令模式迭代器模式观察者模式中介者模式备忘录模式解释器模式模式状态模式策略模式职责链模式责任链模式访问者模式。 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 v1.1 2018-03-12 增加新技术知识、完善知识体系 v2.0 2019-02-19 结构...
摘要:安全性不可更改性排序结果不能被坏人的攻击更改。这也是很严重的公链安全事故。总而言之,通过设计安全的拓扑排序算法,解决交易顺序问题。区块排序的一致可以保证无效交易标记的一致。枢轴链和分叉链的区块奖励计算规则是一致的。 showImg(https://segmentfault.com/img/remote/1460000017710155?w=893&h=380); 12月27日,Conf...
摘要:安全性不可更改性排序结果不能被坏人的攻击更改。这也是很严重的公链安全事故。总而言之,通过设计安全的拓扑排序算法,解决交易顺序问题。区块排序的一致可以保证无效交易标记的一致。枢轴链和分叉链的区块奖励计算规则是一致的。 showImg(https://segmentfault.com/img/remote/1460000017710155?w=893&h=380); 12月27日,Conf...
阅读 1393·2021-11-08 13:14
阅读 745·2021-09-23 11:31
阅读 1037·2021-07-29 13:48
阅读 2780·2019-08-29 12:29
阅读 3370·2019-08-29 11:24
阅读 1898·2019-08-26 12:02
阅读 3687·2019-08-26 10:34
阅读 3435·2019-08-23 17:07