树的定义是,除根结点以外,树的每一个结点都刚好有一个父结点。
而若是违背了这一个前提,即容许树的每一个结点连通多个其它结点,再也不有父亲、孩子之说,即获得孩子的概念html
1、无向图java
2、有向图git
3、网络(加权图)算法
4、经常使用的图算法
1。 好比:各类遍历算法、寻找最短路径算法、寻找网络中最低代价路径的算法,回答一些简单图相关问题(如图是否连通,两个顶点间最短路径)
2。 遍历:数据库
深度优先遍历:相似于树的前序遍历
可是要注意的是:图中不存在根结点,所以图的遍历能够从其中的任一顶点开始编程
广度优先遍历的算法:用一个队列和一个无序列表来构造(使用队列管理遍历,使用无序列表构造出结果)
第一步,起始点进入队列traveralQueue,同时标记该顶点为已访问的( visited)
而后开始循环,该循直持续到 traveralQueue为空时中止,在这个循环中,从 traveralQueue中取出这个首顶点,并将它添加到 resultList的末端
接着,让全部与当前顶点邻接且还没有被标记为 visited的各个顶点依次进入队列 traversalQuet,同时把它们逐个标记为 visited
而后再重复上述循环
对每一个已访问的顶点都重复这一过程,直到 traversalQucue为空时结束,这时意味着没法再找到任何新的顶点
如今resultList即以广度优先次序(从给定的起始顶点开始)存放着各个顶点数组
深度优先遍历的算法:其构造使用了与广度优先遍历一样的逻辑,不过在深度优先遍历中用 traversalstack代替了 traversalQueue
算法中还有另外一处不一样:在顶点还没有添加到 resultList以前,并不想标记该顶点为visited网络
图的深度优先遍历与广度优先遍历惟一的不一样是:前者使用的是栈而不是队列来管理遍历数据结构
3。 测试连通性ide
4。 最小生成树
5。 断定最短路径:断定图的“最短”路径有两种状况
第一种:是断定起始顶与目标顶点之间的字面意义上的最短路径,也就是两个顶点之间的最小边数。
将广度优先遍历算法转变成寻找最短路径的算法,只需在遍历期间再对每一个顶点存另两个信息便可:从起始顶点到本顶点的路径长度,以及路径中做为本顶点前驱的那个顶点
接着修改循环,使得当抵达目标顶点时循环将终止,最短路径的路径长度就是从起始顶点到目标顶点前驱的路径长度再加1;
若是要输出这条最短路径上的顶点,只需沿前驱链回溯便可
第二种:寻找加权图的最便宜路径。这里不使用顶点队列(这要求咱们必须根据顶点的遭遇次数来探究图),而是用一个 minheap或优先队列来存储顶点,
基于总权重(从起地顶点到本顶点的权重和)来衡量顶点对,这样咱们老是能优先沿着最便宜的路径来游历
对每一个顶点都必须存储该顶点的标签,(迄今为止)从起始顶点到本顶点的最便宜路径的权重,路径上本顶点的前驱
在 minheap中将存储顶点、对每条已经遇到但还没有游历的候选路径来权衡顶点对,
从 minheap取出顶点的时候,会权衡取自 minheap的顶点对;
如遇到一个顶点的权重小于目前本顶点中已存储的权重,则会更新路径的代价
5、图的实现策略
1。邻接列表:
对于图节点来讲,每一个节点能够有多达n-1条边与其它结点相连,所以用一种相似于链表的动态节点来存储每一个节点带有的边,这种链表称为邻接列表
2.。邻接矩阵:
无向图的矩阵是对称的,因此对于无向图来讲,不必表示整个矩阵,只需给出矩阵对角线的一侧便可
对于有向图来讲,全部边都是定向的,故而矩阵不对称
百度的解释:
拓扑序:在图中从顶点A到顶点B有一条有向路径,则顶点A必定排在顶点B以前。知足这样的条件的顶点序列称为一个拓扑序。
what ???
这就是拓扑序的定义?(手动笑哭)
这么高大上的名字,就是这个意思?A到B有多条路径,每个路径,A都是起点,B是终点(本身瞎造的理解。。。)
那么这么 “ 简单 ” 的定义有什么用吗?还专门定义了一个名词?
那就是——拓扑排序:得到一个拓扑序列的过程
说实话,我也没以为这个排序有多么厉害。。。
可是,看了相关参考,才知道专门定义一个这样的名词是颇有必要的
举个栗子:
好比大学的课程安排:
例如:
若是你想学习离散数学,前提你必需要预修高等数学这门课。
若是你想学习数据结构这门课,那么你要先学了程序设计这门课,等等等等
因此大学都会根据课程之间的联系来安排学生学习课程的前后次序
因此这个前后顺序,就用到了有向图来表示:
好比下面的这张有向图:
图中的每个顶点对应每一门课,若是两门课之间是有预修关系的,即若是前一门课是后一门课的预修课程,那么表示这两门课的两个顶点间就有一条有向边
这样的图符合上面所说的拓扑序,即两个顶点之间的边表示的是两个之间的先后关系
选择不一样的课程都会按照必定的路径从A开始到B结束,这样多个课程混杂也就造成了如上的有向图
要明白这个问题,得先知道深度优先遍历是如何遍历的
课本说它与广度优先遍历的逻辑一致
百度的解释为:从图的某个顶点v出发,访问此顶点,而后从v的未被访问过的邻接点出发深度优先遍历图,直至图中的全部和v有路径相通的顶点都被访问到(对于连通图来说)
广度优先遍历中,顶点进入队列而后标记为已访问,而后开始循环,该循直持续到队列为空时中止,在这个循环中,从队列中取出这个首顶点,并将它添加到 resultList的末端
而深度优先遍历不一样的就在这里:在顶点还没有添加到 resultList以前,并不想标记该顶点为visited
这有什么区别吗?
我以为区别可能在于,深度遍历是用栈来实现的,不一样于队列实现的广度优先遍历,
队列能够按照先进先出的规则,将邻接的节点按照必定顺序直接设定为已访问存进去,取出来的时候即按照存进去的顺序取出来
而栈是后进先出的,因此在添加进栈的时候,不把他们标记为已访问,而当将他们取出来,放进resultList里的时候才将他们标记为已访问,
这样能够保证一条路走到黑?
结合百度的资料,总结一下两者的区别:
【参考资料】
图的深度优先遍历和广度优先遍历理解
总结深度优先与广度优先的区别
12、图的遍历--(2)深度优先搜索算法
图的深度优先遍历
对比着课本上给出的邻接矩阵的实现代码、其它网上博客,并参考了侯泽洋同窗的代码实现了邻接列表的代码实现,并记录下了以下分析:
1。 邻接矩阵是经过一个二维数组实现了对一个点与其它点是否连通的记录
而若是用邻接列表的话,则不须要用二维数组记录链接状况:直接将与某点邻接的点链在此点的后面便可
如图:
将节点存在列表当中,在每一个节点后面链上其它与其邻接的节点(造成链表)
对比代码(上方为邻接矩阵,下方为邻接列表):
protected int numVertices; // 当前顶点个数 protected boolean[][] adjMatrix; // 邻接矩阵 protected T[] vertices; // 顶点的值 protected int modCount;// 修改标记数 /* ****************************************************************************** */ protected int numVertices; // 当前顶点个数 protected List<List<Integer>> adjMatrix; // 邻接的节点链成的链表 protected List<T> vertices; //存放节点的列表 protected int modCount;
2。 添加节点addvertices操做
对比代码(上方为邻接矩阵,下方为邻接列表):
public void addVertex(T vertex) { // 若是顶点满了 if ((numVertices + 1) == adjMatrix.length) expandCapacity(); vertices[numVertices] = vertex;// 添加结点 // 添加的这个顶点和每个顶点的连边默认的设置 for (int i = 0; i < numVertices; i++) { adjMatrix[numVertices][i] = false; adjMatrix[i][numVertices] = false; } numVertices++; modCount++; } /* ******************************************************************************** */ public void addVertex(T vertex) { vertices.add(vertex);//直接添加到列表中 List list = new ArrayList(); list.add(numVertices); adjMatrix.add(list);//存储添加节点的索引值 numVertices++; modCount++; }
3。 删除节点removeVertex操做
邻接矩阵的节点删除操做,是直接经过覆盖完成的:
首先先判断删除的节点索引值是否存在
而后先将节点所在的二维数组中的值行列用下一行、右一列依次向上,向左进行覆盖
最后将节点数组中的要删除的节点处的下一位依次向上移,即完成删除操做
第二步是至关于完成了删除边的操做
邻接列表不用上述操做,直接执行列表具备的删除节点操做,接下来就是删除与节点邻接的边的操做(具体操做在下面删除边的代码中分析)
对比代码(上方为邻接矩阵,下方为邻接列表):
public void removeVertex(int index){ if (indexIsValid(index)) { for (int a = 0; a < vertices.length; a++) {//至关于完成了删除边的操做 adjMatrix[a][index] = adjMatrix[a][index + 1]; adjMatrix[index][a] = adjMatrix[index + 1][a]; } for (int i = index; i < numVertices; i++) {//删除节点 vertices[index] = vertices[index + 1]; } } } @Override public void removeVertex(T vertex) { if (isEmpty()) { throw new EmptyCollectionException("Graph"); } removeVertex(getIndex(vertex)); } /* ********************************************************************* */ public void removeVertex(T vertex) { int index = getIndex(vertex); if (indexIsValid(index)) vertices.remove(index);//删除节点 for (int i = 0;i < adjMatrix.get(index).size()-1;i++)//找到与节点相邻接的全部节点并删除边 { int x = adjMatrix.get(index).get(i+1); removeEdge(x,index); } adjMatrix.remove(index);//删除记录的节点的索引值 numVertices--; modCount++; }
4。 添加边addEdge操做
对比代码(上方为邻接矩阵,下方为邻接列表):
@Override public void addEdge(T v1, T v2) { addEdge(getIndex(v1), getIndex(v2)); } private void addEdge(int index1, int index2) { if (indexIsValid(index1) && indexIsValid(index2)) {//两个索引都存在 adjMatrix[index1][index2] = true; adjMatrix[index2][index1] = true; modCount++; } } /* ********************************************************************* */ public void addEdge(int index1,int index2) { if (indexIsValid(index1)&&indexIsValid(index2)) { (adjMatrix.get(index1)).add(index2); (adjMatrix.get(index2)).add(index1); modCount++; } } public void addEdge(T vertex1,T vertex2) { int index1 = getIndex(vertex1); int index2 = getIndex(vertex2); if (indexIsValid(index1)&&indexIsValid(index2)) { (adjMatrix.get(index1)).add(index2); (adjMatrix.get(index2)).add(index1); modCount++; } }
5。 删除边removeEdge操做
对比代码(上方为邻接矩阵,下方为邻接列表):
@Override public void removeEdge(T v1, T v2) { removeEdge(getIndex(v1), getIndex(v2)); } private void removeEdge(int index1, int index2) { if (indexIsValid(index1) && indexIsValid(index2)) { adjMatrix[index1][index2] = false; adjMatrix[index2][index1] = false; modCount++; } } /* ********************************************************************* */ @Override public void removeEdge(T vertex1, T vertex2) { int index1 = getIndex(vertex1); int index2 = getIndex(vertex2); if (indexIsValid(index1)&&indexIsValid(index2)) { if (adjMatrix.get(index1).contains(index2)) { (adjMatrix.get(index1)).remove(adjMatrix.get(index1).indexOf(index2)); (adjMatrix.get(index2)).remove(adjMatrix.get(index2).indexOf(index1)); modCount++; } } } public void removeEdge(int index1, int index2) { if (indexIsValid(index1)&&indexIsValid(index2)) { if ((adjMatrix.get(index1)).contains(index2)) { (adjMatrix.get(index1)).remove(adjMatrix.get(index1).indexOf(index2)); (adjMatrix.get(index2)).remove(adjMatrix.get(index2).indexOf(index1)); modCount++; } } }
【参考资料】
Java实现无向图邻接表
java:邻接表无向图的链表实现法
邻接表无向图(三)之 Java详解
用邻接表实现无向图
用邻接表表示图【java实现】
无向图的实现(邻接表) 图的遍历
无向网络只要在无向图的基础上,在边的相关方法里加上权重参数便可
在书上的邻接矩阵实现的代码基础上,把二维数组的true或false改为相关权重值便可,删除边,只要将其赋成无穷大POSITIVE_INFINITY或其余的什么
运行结果截图:
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 4/4 | |
第二周 | 560/560 | 1/2 | 6/10 | |
第三周 | 415/975 | 1/3 | 6/16 | |
第四周 | 1055/2030 | 1/4 | 14/30 | |
第五周 | 1051/3083 | 1/5 | 8/38 | |
第六周 | 785/3868 | 1/6 | 16/54 | |
第七周 | 733/4601 | 1/7 | 20/74 | |
第八周 | 2108/6709 | 1/8 | 20/74 | |
第九周 | 1425/8134 | 1/9 | 20/94 |