标签:更换 情况下 pat blog next 存储结构 生成树 解决 处理
无向图:一种边为无序结点对的图
如果无向图拥有最大数目的连通顶点的边,则认为这个无向图是完全的。
- 对有n个顶点的无向图,要使图完全就要求有n(n-1)/2条边。
如果无向图中的任意两个顶点之间都存在一条路径,则认为这个无向图是连通的。
无向树是一种连通的无环无向图,其中一个元素被指定为树根。
有向图(双向图):边为有序顶点对的图,eg:边(A,B)允许从A向B游历,但不允许反方向的游历。
如果有向图的每两个顶点之间都有两条方向相反的边连接,则认为这个有向图是完全的。
如果有向图的任意两个顶点之间都存在一条路径,且连接两个顶点的路径中所有的边都必须同向,则认为这个有向图是连通的。
有向树是一种指定了一个元素作为树根的有向图。
- 不存在其他顶点到树根的连接
- 每个非树根元素恰好有一个连接
- 树根到每个其他顶点都有一条路径
三元组来表示每条边:起始顶点、终止顶点、权重
- 无向网络的起始顶点与终止顶点可以互换
- 有向图必须包含每个有向连接的三元组
广度优先遍历(类似树的层序遍历)
- 首先从一个未走到过的顶点作为起始顶点,比如元素0顶点作为起点。
- 沿0顶点的边去尝试访问其它未走到过的顶点,首先发现2顶点还没有走到过,于是来到了2顶点。
- 返回到0顶点,再以0顶点作为出发点继续尝试访问其它未走到过的顶点,这样来到了1顶点。
- 返回到0顶点,再以0顶点作为出发点继续尝试访问其它未走到过的顶点,这样来到了5顶点。
- 但是,此时沿0顶点的边,已经不能访问到其它未走到过的顶点了,所以需要返回到2顶点。
- 沿2顶点的边去尝试访问其它未走到过的顶点,(因为1顶点已经走过了)首先发现3顶点还没有走到过,于是来到了3顶点。
- 返回到2顶点,再以2顶点作为出发点继续尝试访问其它未走到过的顶点,这样来到了4顶点。
- 但是,此时沿4顶点的边,已经不能访问到其它未走到过的顶点了,至此,所有顶点我们都走到过了,遍历结束。
深度优先遍历(类似树的前序遍历)
- 首先从一个未走到过的顶点作为起始顶点,比如元素0顶点作为起点。
- 沿0顶点的边去尝试访问其它未走到过的顶点,首先发现2顶点还没有走到过,于是来到了2顶点。
- 再以2顶点作为出发点继续尝试访问其它未走到过的顶点,这样又来到了1顶点。
- 再以1号顶点作为出发点继续尝试访问其它未走到过的顶点。
- 但是,此时沿1顶点的边,已经不能访问到其它未走到过的顶点了,所以需要返回到2顶点。
- 返回到2号顶点后,以2顶点作为出发点继续尝试访问其它未走到过的顶点,此时又会来到3顶点,再以3号顶点作为出发点继续访问其它未走到过的顶点,于是又来到了5号顶点。
- 但是,此时沿5顶点的边,已经不能访问到其它未走到过的顶点了,所以需要返回到3顶点。
- 返回到3顶点后,以3顶点作为出发点继续尝试访问其它未走到过的顶点,此时会来到4顶点,再以4顶点作为出发点继续访问其它未走到过的顶点。
- 但是,此时沿4顶点的边,已经不能访问到其它未走到过的顶点了,至此,所有顶点我们都走到过了,遍历结束。
深度优先遍历和广度优先遍历的位移不同是使用到了栈而不是队列来管理遍历。
最小生成树是边的权重总和和小于或等于同一个图中其他任何一棵生成树的权重总和。
- 非连通的无向图,不存在最小生成树
- 权重不一定和距离成正比
- 权重可能是0或负数
- 若存在相等的权重,那么最小生成树可能不唯一
- Prim算法:
方法二:Dijkstra算法
以0顶点为开始位置,0顶点到1顶点的权重为3,到2顶点的权重为max,到3顶点的权重为4,到4顶点的权重为5。
选取权重最小的顶点为1顶点,因为所有的权重都为正,那么0顶点到1顶点的最短距离就是3,再以1顶点开始,1顶点可以到4顶点和2顶点,到4顶点的位置是3+1=4<5,即0顶点到4顶点的最短距离是通过1顶点的,再看到2顶点的位置是3+10=13<max,即0顶点直接到2顶点的距离为max,但是通过1顶点可以缩短为13。
扣除0顶点和1顶点已经确定好了最短距离之后,选取最短的权重为0顶点到3顶点为4。
以3顶点开始,3顶点可以到2顶点,到2顶点的位置是4+7=11<13,即0顶点到2顶点的最短距离,从max到通过1顶点再到通过3顶点到2顶点。
扣除0顶点、1顶点和3顶点,已经确定好了最短距离之后,选取最小的权重为0顶点到1顶点再到4顶点为4,4顶点可以到1顶点、3顶点和2顶点,但是3顶点和1顶点已经确定好了,只剩2顶点,那么到2顶点的位置为3+1+6=10<11,即到2顶点的最短距离改为通过1顶点、4顶点最后到2顶点。
最后扣除0顶点、1顶点、3顶点和4顶点,只剩下2顶点,距离只剩10,所以到2顶点的最短距离为10,结束算法。
邻接列表
- 在邻接表的表示中,无向图的同一条边在邻接表中存储的两次。如果想要知道顶点的读,只需要求出所对应链表的结点个数即可。
- 有向图中每条边在邻接表中只出现一此,求顶点的出度只需要遍历所对应链表即可。求出度则需要遍历其他顶点的链表。
无向图的邻接列表:
有向图的邻接列表:
邻接矩阵
- 在邻接矩阵表示中,无向图的邻接矩阵是对称的。矩阵中第 i 行或 第 i 列有效元素个数之和就是顶点的度。
- 在有向图中 第 i 行有效元素个数之和是顶点的出度,第 i 列有效元素个数之和是顶点的入度。
无向图的邻接矩阵:
有向图的邻接矩阵:
- 邻接矩阵与邻接表优缺点:邻接矩阵的优点是可以快速判断两个顶点之间是否存在边,可以快速添加边或者删除边。而其缺点是如果顶点之间的边比较少,会比较浪费空间。因为是一个 n?nn?n 的矩阵。而邻接表的优点是节省空间,只存储实际存在的边。其缺点是关注顶点的度时,就可能需要遍历一个链表。还有一个缺点是,对于无向图,如果需要删除一条边,就需要在两个链表上查找并删除。
边集数组
- 边集数组由两个一维数组构成:一个存储顶点信息; 一个存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)、和权(weight)组成。
- 边集数组关注的是边的集合,在边集数组中要查找一个顶点的度需要扫描整个边数组,效率并不高。因此它更适合对边依次进行处理的操作,而不适合对顶点相关的操作。
无向图的边集数组:
有向图的边集数组:
十字链表
- 十字链表是为了便于求得图中顶点的度(出度和入度)而提出来的。用十字链表来存储有向图,可以达到高效的存取效果。它是综合邻接表和逆邻接表形式的一种链式存储结构。
绿色链表表示以结点A为弧头的弧组成的链表。黄色链表表示以结点A为弧尾的弧组成的链表。
邻接多重表
- 邻接多重表主要用于存储无向图。如果用邻接表存储无向图,每条边的两个边结点分别在以该边所依附的两个顶点为头结点的链表中,这给图的某些操作带来不便。因此,在进行这一类操作的无向图的问题中采用邻接多重表作存储结构更为适宜。
问题1解决方案:无向图的连通性在书上已经给出,字面理解就是任意两点之间都存在一条路径,而从编写的角度就是一个顶点开始深度优先遍历或是广度优先遍历得出的顶点数。而有向图的是具有方向的,如果这样的话我们如何判定有向图的连通性?在查询的过程中,发现有向图的连通性可以分成强连通图、弱连通图以及单向连通图。书上的图15.4的连通图就是一个弱连通图,如果是强连通图则需要在此基础上每两点之间都添加一条与之方向相反的线。针对有向图的测试连通性的方法可以用到Tarjan算法
- 在有向图中, 若对于任意两个两点之间, 都存在互逆的路径,则称此图是强连通图。即有向图中,若对于任意两个不同的顶点x和y,都存在从x到y以及从y到x的路径,则称G是强连通图。
- 有向图的所有的有向边替换为无向边,所得到的图称为原图的基图。如果一个有向图的基图是连通图,则有向图是弱连通图。
- 如果有向图中,对于任意节点v1和v2,至少存在从v1到v2和从v2到v1的路径中的一条,则原图为单向连通图。
- 强连通图、连通图、单向连通图三者之间的关系是,强连通图必然是单向连通的,单向连通图必然是弱连通图。
- 在Tarjan算法中为每个节点i维护了以下几个变量:
DFN[i]:深度优先搜索遍历时节点i被搜索的次序。
low[i]:节点i能够回溯到的最早位于栈中的节点。
flag[i]:标记几点i是否在栈中。- Tarjan算法的运行过程:
(1)首先就是按照深度优先搜索算法搜索的次序对图中所有的节点进行搜索。
(2)在搜索过程中,对于任意节点u和与其相连的节点v,根据节点v是否在栈中来进行不同的操作:
a.节点v不在栈中,即节点v还没有被访问过,则继续对v进行深度搜索。
b.节点v已经在栈中,即已经被访问过,则判断节点v的DFN值和节点u的low值的大小来更新节点u的low值。如果节点v的?DFN值要小于节点u的low值,根据low值的定义(能够回溯到的最早的已经在栈中的节点),我们需要用DFN值来更新u?的low值。
(3)在回溯过程中,对于任意节点u用其子节点v(其实不能算是子节点,只是在深度遍历的过程中,v是在u之后紧挨着u的节点)的? ?low值来更新节点u的low值。因为节点v能够回溯到的已经在栈中的节点,节点u也一定能够回溯到。因为存在从u到v的直接路径,所以v能够到的节点u也一定能够到。
(4)对于一个连通图,我们很容易想到,在该连通图中有且仅有一个节点u的DFN值和low值相等。该节点一定是在深度遍历的过程中,该连通图中第一个被访问过的节点,因为它的DFN值和low值最小,不会被该连通图中的其他节点所影响。
public void DFSRecursion() {
boolean[] visited = new boolean[numVertices];
for (int i = 0; i < numVertices; i++)
visited[i] = false;
for(int i=0;i<numVertices;i++) {
if (!visited[i]) {
depthFirstSearch(visited,i);
}
}
}
private void depthFirstSearch(boolean[] isVisited,int i) {
System.out.print(vertices[i]+" ");
isVisited[i]=true;
int w=getFirstNeighbor(i);
while (w!=-1) {
if (!isVisited[w]) {
depthFirstSearch(isVisited,w);
}
w=getNextNeighbor(i, w);
}
}
public void broadFirstSearch() {
boolean[] visited = new boolean[numVertices];
for (int i = 0; i < numVertices; i++)
visited[i] = false;
for(int i=0;i< numVertices;i++) {
if(!visited[i]) {
broadFirstSearch(visited, i);
}
}
}
private void broadFirstSearch(boolean[] isVisited,int i) {
int u,w;
LinkedList queue=new LinkedList();
System.out.print(vertices[i]+" ");
isVisited[i]=true;
queue.addLast(vertices[i]);
while (!queue.isEmpty()) {
u=((Integer)queue.removeFirst()).intValue();
w=getFirstNeighbor(u);
while(w!=-1) {
if(!isVisited[w]) {
System.out.print(vertices[w]+" ");
isVisited[w]=true;
queue.addLast(w);
}
w=getNextNeighbor(u, w);
}
}
}
问题2的解决方案:根据题目要求允许客户输入两个城市以及两个城市之间的价格,这就类似加权图,根据GraphADT接口以及实现的添加顶点和添加边的方法都没有涉及到权重(这里的权重为两个城市之间的价格),而无向图实现的是确定两个顶点之间是否有边,用一个二维布尔型数组存储对应顶点是否有边,我们的想法是可以将是否有边来改为数字,用票价来实现是否有联系。那么问题又来了,没有联系的如何来界定?定义为最大还是最小?如果定义为最小为零的话,我们的选择就要在排除零的前提下进行Dijkstra算法,但是如果我们将无联系的看作是无穷大的情况下,就会产生不一样的效果,在选取权重较小即最便宜路径的时候就会很方便,而且事实上提供的算法也是要将无联系的设为最大,就如同教材内容总结部分叙述的算法一样。但是,无论是无穷大还是无穷小多是用什么表示?和数序符号一样么?API提供了解决办法(侯泽洋同学提供):Double.POSITIVE_INFINITY
表示的是正无穷大值,Double.NEGATIVE_INFINITY
表示的是负无穷大值。这样就可以实现了,但是Dijkstra算法如何书写呢?又是一个问题好丧...在网上查了几个,没有适用成功所以通过看侯泽洋的博客找到了一个不错的博客,用他的算法来实现的具体代码,而且侯泽洋的代码层次设计的非常好,实现的也干净利落,很强大,是我值得学习的。此外,最短路径就可以用到Graph内的shortestPathLength
方法和iteratorShortestPath
实现,我在此基础上通过返回最短路径的数值,如果是1的话就会存在直达列车,大于1的话就会是不可直达,在同通过遍历最短路径的迭代方法进行输出最短路径就好。
错题已经在上周博客写过...
本周结对学习情况
20172314方艺雯
20172323王禹涵
结对学习内容:图和一堆算法
第十五章的图感觉和红黑树一样,要构造很乱的数据结构来实现,此外还有一堆算法可以优化图的操作(算法作用很好,但是难于理解,有的思路就难理解)。好在有些代码给出了,但是书上的遍历代码就没给全,导致始终读不懂一个方法具体干嘛的,具体实现的策略也不同于之前的数据结构。要保持好的心态来学习代码,切莫急功近利...
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 0/0 | 1/1 | 15/15 | |
第二周 | 703/703 | 1/2 | 20/35 | |
第三周 | 762/1465 | 1/3 | 20/55 | |
第四周 | 2073/3538 | 1/4 | 40/95 | |
第五周 | 981/4519 | 2/6 | 40/135 | |
第六周 | 1088/5607 | 2/8 | 50/185 | |
第七周 | 1203/6810 | 1/9 | 50/235 | |
第八周 | 2264/9074 | 2/11 | 50/285 | |
第九周 | 2045/11119 | 1/12 | 50/335 |
20172305 2018-2019-1 《Java软件结构与数据结构》第九周学习总结
标签:更换 情况下 pat blog next 存储结构 生成树 解决 处理
原文地址:https://www.cnblogs.com/sanjinge/p/9953399.html