DAG是公认的下一代区块链的标志。本文从算法基础去研究分析DAG算法,以及它是如何运用到区块链中,解决了当前区块链的哪些问题。

关键字:DAG,有向无环图,算法,背包,深度优先搜索,栈,BlockChain,区块链

图是数据结构中最为复杂的一种,我在上大学的时候,图的这一章会被老师划到考试范围之外,作为我们的课后兴趣部分。但实际上,图在信息化社会中的应用非常广泛。图主要包括:

  • 无向图,结点的简单连接
  • 有向图,连接有方向性
  • 加权图,连接带有权值
  • 加权有向图,连接既有方向性,又带有权值

图是由一组顶点和一组能够将两个顶点相连的边组成。

常见的地图,电路,网络等都是图的结构。

术语

  • 顶点:图中的一个点
  • 边:连接两个顶点的线段叫做边,edge
  • 相邻的:一个边的两头的顶点称为是相邻的顶点
  • 度数:由一个顶点出发,有几条边就称该顶点有几度,或者该顶点的度数是几,degree
  • 路径:通过边来连接,按顺序的从一个顶点到另一个顶点中间经过的顶点集合
  • 简单路径:没有重复顶点的路径
  • 环:至少含有一条边,并且起点和终点都是同一个顶点的路径
  • 简单环:不含有重复顶点和边的环
  • 连通的:当从一个顶点出发可以通过至少一条边到达另一个顶点,我们就说这两个顶点是连通的
  • 连通图:如果一个图中,从任意顶点均存在一条边可以到达另一个任意顶点,我们就说这个图是个连通图
  • 无环图:是一种不包含环的图
  • 稀疏图:图中每个顶点的度数都不是很高,看起来很稀疏
  • 稠密图:图中的每个顶点的度数都很高,看起来很稠密
  • 二分图:可以将图中所有顶点分为两部分的图

所以树其实就是一种无环连通图。

有向图

有向图是一幅有方向性的图,由一组顶点和有向边组成。所以,大白话来讲,有向图是包括箭头来代表方向的。

常见的例如食物链,网络通信等都是有向图的结构。

术语

上面我们介绍了顶点的度数,在有向图中,顶点被细分为了:

  • 出度:由一个顶点出发的边的总数
  • 入度:指向一个顶点的边的总数

接着,由于有向图的方向性,一条边的出发点称为头,指向点称为尾。

  • 有向路径:图中的一组顶点可以满足从其中任意一个顶点出发,都存在一条有向边指向这组顶点中的另一个。
  • 有向环:至少含有一条边的起点和终点都是同一个顶点的一条有向路径。
  • 简单有向环:一条不含有重复顶点和边的环。
  • 路径或环的长度就是他们包含的边数。

图的连通性在有向图中表现为可达性,由于边的方向性,可达性必须是通过顶点出发的边的正确方向,与另一个顶点可连通。

邻接表数组

可表示图的数据类型,意思就是如何通过一个具体的文件内容,来表示出一幅图的所有顶点,以及顶点间的边。

邻接表数组,以顶点为索引(注意顶点没有权值,只有顺序,因此是从0开始的顺序值),其中每个元素都是和该顶点相邻的顶点列表。

5 vertices, 3 edges
0: 4 1
1: 0
2:
3:
4:

背包

做一个背包集合,用来存储与一个顶点连通的顶点集合,因为不在意存储顺序,并且只进不出,所以选择背包结构来存储。温习一下背包

package algorithms.bag;import java.util.Iterator;// 定义一个背包集合,支持泛型,支持迭代
public class Bag<Item> implements Iterable<Item> {private class BagNode<Item> {Item item;BagNode next;}BagNode head;int size;@Overridepublic Iterator<Item> iterator() {return new Iterator<Item>() {BagNode node = head;@Overridepublic boolean hasNext() {return node.next != null;}@Overridepublic Item next() {Item item = (Item) node.item;node = node.next;return item;}};}public Bag() {head = new BagNode();size = 0;}// 往前插入public void add(Item item) {BagNode temp = new BagNode();// 以下两行代码一定要声明,不可直接使用temp = head,那样temp赋值的是head的引用,对head的所有修改会直接同步到temp,temp就不具备缓存的功能,引发bug。。temp.next = head.next;temp.item = head.item;head.item = item;head.next = temp;size++;}public boolean isEmpty() {return size == 0;}public int size() {return this.size;}public static void main(String[] args) {Bag<String> bags = new Bag();bags.add("hello");bags.add("yeah");bags.add("liu wen bin");bags.add("seminar");bags.add("1243");System.out.println(bags.size);//        for (Iterator i = bags.iterator(); i.hasNext(); ) {
//            System.out.println(i.next());
//        }// 由于Bag实现了Iterable接口,所以支持以下方式遍历for (String a : bags) {System.out.println(a);}}
}

有向图结构

下面代码实现一个有向图数据结构,并添加常用有向图属性和功能。

package algorithms.graph;import algorithms.bag.Bag;
import ioutil.In;
import ioutil.StdOut;import java.io.FileReader;public class Digraph {private final int V;// 顶点总数,定义final,第一次初始化以后不可更改。private int E;// 边总数private Bag<Integer>[] adj;// {邻接表}顶点为数组下标,值为当前下标为顶点值所连通的顶点个数。public Digraph(int v) {this.V = v;this.E = 0;adj = new Bag[V];for (int i = 0; i < V; i++) {adj[i] = new Bag<Integer>();}}public Digraph(In in) {this(in.readInt());int E = in.readInt();for (int i = 0; i < E; i++) {int v = in.readInt();int w = in.readInt();addEdge(v, w);}}public int V() {return this.V;}public int E() {return this.E;}/*** v和w是两个顶点,中间加一条边,增加稠密度。** @param v 大V是顶点总数,v是顶点值,所以并v不存在大小限制* @param w 同上。*/public void addEdge(int v, int w) {adj[v].add(w);E++;}/*** 返回一个顶点的连通顶点集合的迭代器** @param v* @return Bag本身就是迭代器,所以返回该顶点的连通顶点集合Bag即可。*/public Iterable<Integer> adj(int v) {return adj[v];}/*** 将图中所有方向反转** @return 返回一个图将所有方向反转后的副本*/public Digraph reverse() {Digraph R = new Digraph(V);for (int v = 0; v < V; v++) {for (int w : adj[v]) {// 遍历原图中跟v顶点连通的顶点w。R.addEdge(w, v);}}return R;}/*** 按照邻接表数组结构输出有向图内容** @return*/public String toString() {String s = V + " vertices, " + E + " edges\n";for (int v = 0; v < V; v++) {s += v + ": ";for (int w : this.adj(v)) {s += w + " ";}s += "\n";}return s;}public static void main(String[] args) {Digraph d = new Digraph(5);d.addEdge(0, 1);d.addEdge(1, 0);d.addEdge(2, 3);d.addEdge(0, 4);StdOut.println(d);/**输出:5 vertices, 3 edges0: 4 11: 02:3:4:*/}
}

以上背包和有向图代码相关解释请具体参照代码中注释。

可达性

上面提到了有向图中的可达性和图中的连通性的关系,可达性是连通性的特殊形式,对方向敏感,所以提到有向图,不可不研究可达性。

可达性解答了“从一个顶点v到达另一个顶点w,是否存在一条有向路径”等类似问题。

深度优先搜索

解答可达性问题,要借助深度优先搜索算法。为了更好的理解深度优先算法,先来搞清楚如何完全探索一个迷宫。

Tremaux搜索

完全探索一个迷宫的规则是:从起点出发,不走重复路线,走到终点走出迷宫。具体流程:

  • 每当第一次到达一个新的顶点或边时,标记上。
  • 在走的过程中,遇到一个已标记的顶点或边时,退回到上一个顶点。
  • 当回退到的顶点已没有可走的边时继续回退。

我想Tremaux搜索会给我们带来一些启发,回到图的深度优先搜索算法。

package algorithms.graph;import algorithms.bag.Bag;
import ioutil.StdOut;/*** 基于深度优先搜索(Depth First Search)解答有向图顶点可达性问题。*/
public class DigraphDFS {private boolean[] marked;// 是否标记过/*** 算法:在图中找到从某个顶点出发的所有顶点** @param digraph* @param start*/public DigraphDFS(Digraph digraph, int start) {marked = new boolean[digraph.V()];// 初始化marked数组dfs(digraph, start);}/*** 算法:在图中找到从某些顶点出发的所有顶点,这些顶点被作为一个集合传入。** @param digraph* @param startSet*/public DigraphDFS(Digraph digraph, Iterable<Integer> startSet) {marked = new boolean[digraph.V()];for (int w : startSet) {dfs(digraph, w);}}/*** 查询某个顶点是否被标记(是否可达,因为标记过就是可达的)** @param v* @return*/public boolean marked(int v) {return marked[v];}/*** 深度优先搜索核心算法,通过标记,在图中从v顶点出发找到有效路径* <p>* 返回的是通过标记形成的一条有效路径。** @param digraph* @param v*/private void dfs(Digraph digraph, int v) {marked[v] = true;// 标记起点可达。for (int w : digraph.adj(v)) {// 遍历v顶点可达的一级顶点。if (!marked[w]) dfs(digraph, w);// 如果发现w顶点未到达过,则继续从w开始dfs(即向前走了一步)}}public static void main(String[] args) {Digraph d = new Digraph(5);// 初始化五个顶点的图d.addEdge(0, 1);d.addEdge(1, 0);d.addEdge(2, 3);d.addEdge(0, 4);Bag<Integer> startSet = new Bag<>();startSet.add(2);DigraphDFS reachable = new DigraphDFS(d, startSet);for (int v = 0; v < d.V(); v++) {if (reachable.marked(v)) {StdOut.print(v + " ");}StdOut.println();}/*** 输出:*23*/}
}

startSet是入参条件,只有一个值为2,即在图中找寻2的有效路径,通过图中的边我们可以看出,2的有效路径只有3,所以输出是正确的。

可达性的一种应用:垃圾收集

我们都知道一般的对象垃圾收集都是计算它的引用数。在图结构中,把对象作为顶点,引用作为边,当一个对象在一段时间内未被他人引用的时候,这个顶点就是孤立的,对于其他有效路径上的顶点来说它就是不可达的,因此就不会被标记,这时候,例如JVM就会清除掉这些对象释放内存,所以JVM也是一直在跑类似以上这种DFS的程序,不断找到那些未被标记的顶点,按照一定时间规则进行清除。

有向无环图

不包含有向环的有向图就是有向无环图,DAG,Directed Acyclic Graph。

上面我们循序渐进的介绍了图,有向图,本节开始介绍有向无环图,概念也已经给出,可以看出有向无环图是有向图的一种特殊结构。那么第一个问题就是

如何监测有向图中没有有向环,也就是如何确定一个DAG。

寻找有向环

基于上面的问题,我们要做一个寻找有向环的程序,这个程序还是依赖DFS深度优先搜索算法,如果找不到,则说明这个有向图是DAG。

先来补个坑,其实前面包括背包我在之前都写过,但因为前面那篇文章是我第一篇博文,我还太稚嫩,没有掌握好的编辑器,也没有粘贴代码,所以这里有必要重新填坑。

package algorithms.stack;import ioutil.StdOut;import java.util.Iterator;
import java.util.NoSuchElementException;public class Stack<Item> implements Iterable<Item> {private int SIZE;private Node first;// 栈顶public Stack() {// 初始化成员变量SIZE = 0;first = null;}private class Node {private Item item;private Node next;}// 栈:往first位置插入新元素public void push(Item item) {Node temp = first;first = new Node();first.item = item;first.next = temp;SIZE++;}// 栈:从first位置取出新元素,满足LIFO,后进先出。public Item pop() {if (isEmpty()) throw new RuntimeException("Stack underflow");Item item = first.item;first = first.next;SIZE--;return item;}public boolean isEmpty() {return first == null;}public int size() {return this.SIZE;}@Overridepublic Iterator<Item> iterator() {return new Iterator<Item>() {Node node = first;@Overridepublic boolean hasNext() {return first != null;}@Overridepublic Item next() {if (!hasNext()) throw new NoSuchElementException();Item item = node.item;node = node.next;return item;}};}public static void main(String[] args){Stack<String> stack = new Stack<>();stack.push("heyheyhey");stack.push("howau");stack.push("231");StdOut.println(stack.SIZE);StdOut.println(stack.pop());}
}

我们要做寻找有向环的程序的话,要依赖栈的结构,所以上面把这个坑给填了,下面回归到寻找有向环的程序。(当然,你也可以直接使用java.util.Stack类)

package algorithms.graph;import ioutil.StdOut;import java.util.Stack;public class DirectedCycle {private boolean[] marked;// 以顶点为索引,值代表了该顶点是否标记过(是否可达)private Stack<Integer> cycle; // 用来存储有向环顶点。// *****重点理解这里start****private int[] edgeTo;// edgeTo[0]=1代表顶点1->0, to 0的顶点为1。// *****重点理解这里end****private boolean[] onStack;// 顶点为索引,值为该顶点是否参与dfs递归,参与为truepublic DirectedCycle(Digraph digraph) {// 初始化成员变量marked = new boolean[digraph.V()];onStack = new boolean[digraph.V()];edgeTo = new int[digraph.V()];cycle = null;// 检查是否有环for (int v = 0; v < digraph.V(); v++) {dfs(digraph, v);}}private void dfs(Digraph digraph, int v) {onStack[v] = true;// 递归开始,顶点上栈marked[v] = true;for (int w : digraph.adj(v)) {// 遍历一条边,v-> w// 终止条件:找到有向环if (hasCycle()) return;// 使用onStack标志位来记录有效路径上的点,如果w在栈上,说明w在前面当了出发点,if (!marked[w]) {edgeTo[w] = v;// to w的顶点为vdfs(digraph, w);} else if (onStack[w]) {// 如果指到了已标记的顶点,且该顶点递归栈上。(栈上都是出发点,而找到了已标记的顶点是终点,说明出发点和终点相同了。)cycle = new Stack<Integer>();for (int x = v; x != w; x = edgeTo[x]) {//起点在第一次循环中已经push了,不要重复cycle.push(x);// 将由v出发,w结束的环上中间的结点遍历push到cycle中。}cycle.push(w);// push终点}}onStack[v] = false;// 当递归开始结算退出时,顶点下栈。}public boolean hasCycle() {return cycle != null;}public Iterable<Integer> cycle() {return cycle;}public static void main(String[] args) {Digraph d = new Digraph(6);d.addEdge(0, 1);d.addEdge(1, 2);d.addEdge(2, 3);d.addEdge(3, 0);DirectedCycle directedCycle = new DirectedCycle(d);if (directedCycle.hasCycle()) {for (int a : directedCycle.cycle()) {StdOut.println(a);}} else {StdOut.println("DAG");}}
}

这段代码不长但其中算法比较复杂,我尽力在注释中做了详细解释,如有任何不明之处,欢迎随时留言给我。

以上程序的测试用图为

6 vertices, 4 edges
0: 1
1: 2
2: 3
3: 0
4:
5:

肉眼可以看出,这是一个0-1-2-3-0的一个有向环,所以以上程序的执行结果为:


3
2
1
0

先入栈的在后面,可以看出是0-1-2-3的有向环结构。如果我们将图的内容改为:

6 vertices, 4 edges
0: 1
1: 2
2: 3
3:
4:
5: 0

则明显最后一个拼图3-0被我们打破了,变成了无所谓的5-0,这时该有向图就不存在有向环。此时以上程序执行结果为:


DAG

DAG与BlockChain

上面一章节我们将DAG深挖了挖,我想到这里您已经和我一样对DAG的算法层面非常了解,那么它和如今沸沸扬扬的区块链有什么关联呢?本章节主要介绍这部分内容。

在前面的文章中,我们已经了解了区块链技术,无论是比特币还是以太坊,都是基于一条链式结构,实现了去中心化的,点对点的,trustless的一种新型技术。然而这条链式结构在面临业务拓展的时候屡屡遭受新的挑战,例如块存储量问题,交易速度问题,数据总量过大,单节点存储压力等等。而DAG是基于图的一种实现方式,之所以不允许有向环的出现,是因为DAG可以保证结点交易的顺序,可以通过上面介绍过的有效路径来找到那根主链。如果出现了有向环,那系统就乱了。如果没有有向环的话,DAG中可以有多条有效路径连接各个顶点,因此DAG可以说是更加完善,强大的新一代区块链结构。
转存失败重新上传取消
目前非常有名的采用DAG技术的区块链产品有DagCoin,IOTA,ByteBall等,他们都是基于DAG,在性能和储量上面有了全面的提升。

这里面仍然会有“分叉”的可能,处理方式也是相同的,看哪个结点能够有新的后续,这个部分我们在讲“叔块”的时候说过。

区块链采用DAG结构以后称为了blockless,无块化的结构,即我们不再将交易打包到块中,以块为单元进行存储,而是直接将交易本身作为基本单元进行存储。另外,DAG也有双花的可能,也是上面“分叉问题”引起的,但它在确认有效路径以后会自动恢复。同时,DAG是异步共识,具体机制还不了解,但它解决了交易性能问题。

总结

本文循序渐进地从图到有向图到有向无环图,详细地介绍了相关术语,api代码实现,也补充入了背包和栈的代码实现,重点研究了图的深度优先搜索算法以及寻找有向环算法。最后对DAG和区块链的关系进行了简介,希望随着技术发展,DAG有望成为真正的区块链3.0。

参考资料

Algorithms 4th,网上资料

更多文章请转到醒者呆的博客园。

算法精解:DAG有向无环图相关推荐

  1. 流程图形化-流程拆分(DAG(有向无环图)计算所有的路径)

    题记: 今天在工作中,实现业务流程图中流程图形化 Node & Flow的计算方式,其中大概是将大的流程图集合拆解成若干个小的子流程,这样在计算的时候,只需要计算子流程的结果就可以实现流程自动 ...

  2. 数据结构笔记-DAG有向无环图

    一 图的基本概念 图(Graph):是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合. 无向边:若顶点Vi和Vj之间 ...

  3. 有向无环图(DAG)的温故知新

    当我们学习数据结构的时候,总是觉得很枯燥,而当我们解决实际问题的时候,又往往因为对数据结构了解的匮乏而束手无策.从问题中来,到问题中去,在某一点上的深入思考并且不断的实践积累,或许是个笨办法,但笨办法 ...

  4. java 有向无环图 树_拓扑排序-有向无环图(DAG, Directed Acyclic Graph)

    条件: 1.每个顶点出现且只出现一次. 2.若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面. 有向无环图(DAG)才有拓扑排序,非DAG图没有拓扑排序一说. 一 ...

  5. 有向无环图Directed Acyclic Graph(DAG)

    1.DAG 有向无环图Directed Acyclic Graph(DAG) DAG是一个没有 有向循环的.有限的有向图 . 它由有限个顶点和有向边组成,每条有向边都从一个顶点指向另一个顶点: 从任意 ...

  6. 打印一种拓扑排序(假定给的是有向无环图时)DFS+栈

    一.定义: 在计算机科学领域,有向图的拓扑排序是其顶点的线性排序,使得对于从顶点u 到顶点v的每个有向边uv,u在排序中都在v之前. 例如,图形的顶点可以表示要执行的任务,并且边可以表示一个任务必须在 ...

  7. ardl模型stata命令_Stata新命令快讯: 有向无环图、模糊倍分法等

    编者按: 自今日起,Stata 连享会 (公众号 StataChina) 将开设「Stata新命令快讯」专栏,定期推送 SSC 网站上发布的最新命令,对于我们认为重要的命令进行标注. 对于您感兴趣的命 ...

  8. ardl模型stata命令_Stata新命令快讯:有向无环图、模糊倍分法等

    Stata 连享会   主页 || 视频 || 推文 温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验. 编者按: 定期递送 SSC 网站上发布的最新命令,对于我们认为重要的命令进行标注. 对于 ...

  9. C#实现有向无环图(DAG)拓扑排序

    对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在 ...

最新文章

  1. IDEA Java解析GeoJson.json文件
  2. mysql view 能和表关联吗_MySQL 事务隔离及实现,赶快码上收藏!满满干货
  3. Windows Server 2008 Server Core - 小脚印,大安全
  4. 从源码分析DEARGUI之add_menu
  5. 社会大学的作业,活动篇
  6. kaggle 训练自己的cycleGan
  7. Kafka删除topic Note: This will have no impact if delete.topic.enable is not【另外强烈推荐一个kafka小工具】
  8. Qt中QLabel的背景图片设置问题
  9. Highcharts 显示图表
  10. 在容器服务kubernetes上配置https
  11. 如何下载飞思卡尔单片机的S19文件
  12. 计算机考研复试面试常问问题 数据结构篇(下)
  13. t分布 u分布 卡方分布_t分布曲线和正太分布,和z分布,和卡方分布,和方差分析的f分布曲线有什么区别?...
  14. 怎么查期刊是核心、是EI,还是SCI
  15. Scrapy框架基础了解
  16. c#中regex的命名空间_C#_详解C#正则表达式Regex常用匹配,使用Regex类需要引用命名空间 - phpStudy...
  17. 仅限中国:AMD推出Radeon RX 560 XT
  18. 4/20 Fizz Buzz(412)
  19. java roll_Java Calendar roll()用法及代碼示例
  20. 递推法之-------核电站问题(超简洁代码!!!)

热门文章

  1. 2022-2028年中国有机硅室温胶行业市场运营格局及发展趋向分析报告
  2. 2022-2028年中国企业核心路由交换机行业市场前瞻与投资分析报告
  3. debian10 简单的CA使用
  4. 受用一生的高效 PyCharm 使用技巧(一)
  5. Pytorch学习记录-torchtext和Pytorch的实例( 使用神经网络训练Seq2Seq代码)
  6. SpringBoot配置文件YAML配置注入(详解)
  7. 边端云处理器系列技术参数
  8. Waymo的激光雷达计划:进展如何?
  9. 加密算法有哪些?对称加密和非对称加密的区别?
  10. python3生成二维码中间带logo,有底图,可自定义文字