20172314 2018-2019-1《程序设计与数据结构》第九周学习总结

教材学习内容总结

无向图

  • 与树相似,由顶点和边链接构成。图只有顶点没有边时,也是图,是一种特殊形式。
  • 由相互连通的结点对表示,(A,B)表示从顶点A到顶点B有一条边,无序结点对表示顶点A,B之间的边能够从两个方向游历,边记作(A,,B)或(B,A)都可
  • 无向图就是边全为无序结点对的图
    html

  • 两个顶点之间如有连通边,则称这两个顶点是邻接的。他们互为邻居
  • 路径表示一系列的边,路径的长度表示路径中边的条数(或顶点数减一);无向图中路径ABD与路径DBA等同。
  • 注意区分:若是无向图拥有最大数目的连通顶点的边,则无向图是彻底的;若是无向图中任意两个顶点之间都存在一条路径,则认为无向图是连通的。如图是非连通无向图,D与另外三个顶点之间均没有任何路径。
    java

  • 注意区分:联通一个节点及其自身的边称为自循环,边(A,A)表示链接A到自身的一个环;环路是一种首顶点和末顶点相同且没有重边的路径
  • 没有环路的图称为无环的
  • 无向树是一种连通的无环无向图,其中一个元素被指定为树根node

有向图

  • 有向图也称双向图,是一种边为有序顶点对的图,边(A,B)和(B,A)是不一样的有向边
    git

  • 有向图的路径是链接两个顶点的有向边序列。路径ABD与路径DBA是不一样的
  • 如有向图中任意两个顶点之间都存在一条路径,则认为该有向图是连通的。下图中第一个图是连通的,第二个图不是,由于没有任何路径能从其它顶点游历到1,也不能从6出发游历任何其余顶点
    算法

  • 若是有向图中没有环路,且有一条从A到B的边,那么就能够把A安排在顶点B以前,这种排列获得的顶点次序称为拓扑序
  • 有向树是一种指定了一个元素做为树根的有向图,该图具备以下属性:
    • 不存在其余顶点到树根的链接
    • 每一个非树根元素刚好有一个链接
    • 树根到每一个其它顶点都有一条路径

网络

  • 网络又称为加权图,是一种每条边都带有权重或代价的图
  • 加权图中的路径权重是该路径各边权重之和
  • 用三元组表示每条边,包括起始顶点、终止顶点和权重。对于有向图来讲,必须包含每一个有向链接的三元组

经常使用的图算法

  • 遍历
    • 广度优先遍历(BFS):相似于树的层次遍历,使用队列和无序列表
    • 深度优先遍历(DFS):相似于树的前序遍历,使用栈和无序列表
  • 测试连通性
    • 简单解释:在一个含有n个顶点的图中,当且仅当对每一个顶点v,从v开始的广度优先遍历的resultList大小都是n,则该图就是连通的。例如

    无向连通图及以每一个顶点为起点的广度优先遍历


    无向非连通图及以每一个顶点为起点的广度优先遍历

    数组

  • 最小生成树
    • 生成树是一棵含有图中全部顶点和部分边(但可能不是全部边)的树。
    • 因为树也是图,有些图自己就是一棵生成树,这时该图的惟一辈子成树将包含全部边。
    • 最小生成树(保持图连通的最少边)(生成树不是惟一的)边的权重总和小于或等于同一图中其余任何一棵生成树的权重总和
    • 最小生成树集合包括n个点和n-1条边。且没有回路。
    • 最小生成树生成过程:从网络中任意选取一个起始顶点,并添加到最小生成树中,而后将全部含起始顶点的边按照权重次序添加到minheap中(若是处理的是有向网络,则只会添加那些以这个特定顶点为起点的边),接着从minheap中取出最小边,并与新顶点添加到最小生成树中。往minheap中添加全部含该新顶点且另外一顶点尚不在最小生成树中的边。继续这一过程,直到最小生成树含有原始图中的全部顶点(或minheap为空)时结束。
  • 判断最短路径
    • 两顶点之间最小边数
      • 在编历时记录从起始顶点到本顶点的路径长度,以及路径中做为本顶点前驱的那个顶点。并当抵达目标顶点时循环终止,最短路径长度就是获得的路径长度加1
    • 加权图的最便宜路径
      • 使用minheap或优先队列来存储顶点,基于总权重衡量顶点对,每一个顶点都必须存储该顶点的标签(本顶点以前最便宜的路径权重及路径上本顶点的前驱),从minheap取出顶点的时候,会权衡顶点对,若是未按由小到大取出顶点,则会更新路径

图的实现策略

  • 邻接列表
    • 用相似于链表的动态结点来存储每一个结点带有的边。这种链表称为邻接列表
    • 对网络或加权图来讲,每条边会存储成一个含权重的三元组;对无向图而言,边(A,B)会同时出如今顶点A和顶点B的邻接列表中
  • 邻接矩阵
    • 能够用集合储存边
    • 用称为邻接矩阵的二维数组存储顶点,每一个单元表示两个顶点的交接状况,由表示是否连通的布尔值表示
    无向图的邻接矩阵

    由于是无向图,因此该矩阵沿对角线对称,只需给出一侧便可
    有向图的邻接矩阵

    邻接矩阵也能够用于网络或加权图,只须要在矩阵的各个单元中存储一个表明边权重的对象,没有边的单元为null
  • 用邻接矩阵实现无向图
    • addEdge方法:使用getIndex方法定位索引并调用addEdge方法进行赋值
    • addVertex方法:往图中添加一个顶点包括在数组的下一个可用位置添加该顶点,把邻接矩阵中全部恰当的位置都设置成false
    • expandCapacity方法:不只扩展顶点数组并把已有顶点复制到新数组中,并且还必须扩展邻接列表的容量并把旧内容复制到新列表中

教材学习中的问题和解决过程

  • 问题一:广度优先遍历和深度优先遍历的实现过程及其原理
  • 问题一解决:经过查找资料其过程可认为:网络

    广度优先遍历(使用一个队列):
    • 首先选择一个顶点做为起始顶点,让起始顶点进入队列中,并将其染成灰色(visited),其他顶点为白色。
    • 开始循环:从队列首部选出一个顶点,添加到resultList末端(涂黑),并找出全部与之邻接的顶点,放入队列尾部(涂灰),没访问过的顶点是白色。而后再次取出新的起始顶点涂黑放入resultList中,如此循环。若是顶点的颜色是灰色,表示已经发现而且放入了队列,若是顶点的颜色是白色,表示尚未发现。
    • 基本就是出队的顶点变成黑色,在队列里的是灰色,还没入队的是白色。





    深度优先遍历(使用栈):
    • 其整体思想为:访问顶点v,依次从v的未被访问的邻接点出发,到一条路径的尽头时,将其入栈返回上一个顶点,若其有其余孩子,继续向下。直到全部顶点都被访问。

    过程为:
    • 先往栈中压入右节点,再压左节点,这样出栈就是先左节点后右节点了
    • 首先将顶点A压入栈中,stack(A)
    • 将A弹出,进入无序列表中,同时将v的右孩子B和左孩子C压入栈中,此时左孩子在栈的顶部,stack(B,C)
    • 将B顶点弹出,同时将B的子顶点E,D压入栈中,此时D在栈的顶部,stack(D,E,C)
    • 将D顶点弹出,没有子顶点压入,此时E在栈的顶部,stack(E,C)
    • 将E顶点弹出,同时将E的子顶点I压入,stack(I,C)
    • 依次往下,最终遍历完成

    二者之间惟一不一样之处是:深度优先遍历使用栈而不是队列来管理遍历学习

  • 问题二:图的邻接列表较难理解
  • 问题二解决:邻接列表是对每一个顶点创建一个链表,表示以改顶点为起点的全部顶点。经过访问一个顶点的链表便可得出该顶点的全部边。由此也可也得出以改顶点为起点的一条路径。
    有向图:


    一共有1234四个顶点,因此创建四个顶点,以1为起点的有234,以2为起点的只有4,以3为起点的没有,以4为起点的只有3。
    无向图:


    一共有ABCD四个顶点,创建4个链表。A顶点链接的边是BCD,与B顶点链接的是ABD,与C顶点链接的只有A,与D顶点链接的是AB。测试

代码调试中的问题和解决过程

  • 问题一:在iteratorBFS方法中,spa

    if (!indexIsValid(startIndex))
            return resultList.iterator();
    不清楚iterator方法的做用
  • 问题一解决:首先,这个方法的做用是为了使类可迭代,使类迭代须要有如下几步:
    • 在类声明中加入implements Iterable ,对应的接口为(即java.lang.Iterator)

      public interface Iterable<Item>{
          Iterator<Item> iterator();
      }
    • 在类中实现iterator()方法,返回一个本身定义的迭代器Iterator

      public Iterator<Item> iterator(){
          //若是须要逆序遍历数组,自定义一个逆序迭代数组的迭代器
           return new ReverseArrayIterator();
      }
    • 在类中设置内部类(如private class ReverseArrayIterator() ),内部类声明中加入implements Iterator ,对应的接口为(即java.util.Iterator)

      public interface Iterator {  
            boolean hasNext();  
            Object next();  
            void remove();  
      }

      这些是在第四周中学到过的,好比ArrayList类,彻底符合以上步骤。
      在这里,因为索引无效,必需要返回一个相同类型的值,实际上就是空的。

  • 问题二:对广度优先遍历代码的理解
  • 问题二解决:见注释

    private Iterator<T> iteratorBFS(int startIndex) {
        Integer x;
        QueueADT<Integer> traversalQueue = new LinkedQueue<Integer>();
        UnorderedListADT<T> resultList = new ArrayUnorderedList<T>();
        //索引无效,返回空
        if (!indexIsValid(startIndex))
            return resultList.iterator();
        boolean[] visited = new boolean[maxCount];
        //把全部顶点设为false,白色
        for (int i = 0; i < maxCount; i++)
            visited[i] = false;
        //进入队列的为true,即访问过的,灰色
        traversalQueue.enqueue(startIndex);
        visited[startIndex] = true;
        while (!traversalQueue.isEmpty()) {
            //出队列涂黑存入resultList中
            x = traversalQueue.dequeue();
            resultList.addToRear((T) nodelist.get(x).getElement());
            //若是进入resultList的顶点还有相邻的未访问过的顶点,将其涂灰入队
            for (int i = 0; i < maxCount; i++) {
                if (hasEdge(x, i) && !visited[i]) {
                    traversalQueue.enqueue(i);
                    visited[i] = true;
                    Int++;
                }
            }
        }
        return new GraphIterator(resultList.iterator());
        }
  • 问题三:最短路径的代码理解,不知道iteratorShortestPathIndices方法和iteratorShortestPath方法的联系和区别。
  • 问题三解决:iteratorShortestPathIndices方法构建最短路径中顶点集合的迭代器,而后iteratorShortestPath方法获取迭代器并输出构成最短路径的结点。
    • iteratorShortestPath方法
    //最短路径的顶点集
    private Iterator<T> iteratorShortestPath(int startIndex, int targetIndex)
    {
        UnorderedListADT<T> resultList = new ArrayUnorderedList<T>();
        //若是索引值都无效,返回空
        if (!indexIsValid(startIndex) || !indexIsValid(targetIndex))
            return resultList.iterator();
        //it表示构成startindex和targetindex之间最短路径的顶点集,并存储在resultlist链表中,获取结点集合的迭代器对象
        Iterator<Integer> it = iteratorShortestPathIndices(startIndex, targetIndex);
        while (it.hasNext())
            resultList.addToRear((T)nodelist.get(((Integer)it.next())).getElement());
        return new GraphIterator(resultList.iterator());
    }
    • iteratorShortestPathIndices方法,构建从开始到结束的节点集合的迭代器对象
    //找到最短路径的顶点值
    private Iterator<Integer> iteratorShortestPathIndices(int startIndex, int targetIndex)
    {
        int index = startIndex;
        int[] pathLength = new int[maxCount];//路径长度数组
        int[] predecessor = new int[maxCount];//前驱结点
        QueueADT<Integer> traversalQueue = new LinkedQueue<Integer>();
        UnorderedListADT<Integer> resultList = new ArrayUnorderedList<Integer>();
        //若是索引无效或起始终点为同一索引,返回空
        if (!indexIsValid(startIndex) || !indexIsValid(targetIndex) || (startIndex == targetIndex))
            return resultList.iterator();
        boolean[] visited = new boolean[maxCount];//访问过为true,染灰
        //先标记为都没访问过,染白
        for (int i = 0; i < maxCount; i++)
            visited[i] = false;
        //将起始值入队标为访问过,染灰
        traversalQueue.enqueue(Integer.valueOf(startIndex));
        visited[startIndex] = true;
        pathLength[startIndex] = 0;//路径长度为0
        predecessor[startIndex] = -1;//前驱结点为-1位置
        //若是还有访问过的灰色,而且未找到目标索引
        while (!traversalQueue.isEmpty() && (index != targetIndex))
        {
            index = (traversalQueue.dequeue()).intValue();//出队列染黑,index储存其元素值
            //若是有其余结点与index有联系却没有被访问过的,入队染灰
            for (int i = 0; i < maxCount; i++)
            {
                if (hasEdge(index,i) && !visited[i])
                {
                    pathLength[i] = pathLength[index] + 1;//长度加一
                    predecessor[i] = index;//index为其前驱结点,predecessor中存储最短路径顶点
                    traversalQueue.enqueue(Integer.valueOf(i));//进队染灰
                    visited[i] = true;
                }
            }
        }
        //若是index不是目标索引,返回空
        if (index != targetIndex)
            return resultList.iterator();
        StackADT<Integer> stack = new LinkedStack<Integer>();
        index = targetIndex;
        stack.push(Integer.valueOf(index));//目标索引入栈
        do
        {//index不是起始索引值的元素值时,index表示其前驱结点的元素值,将结点的值依次入栈
            index = predecessor[index];
            stack.push(Integer.valueOf(index));
        } while (index != startIndex);
        while (!stack.isEmpty())
            resultList.addToRear(((Integer)stack.pop()));//栈不为空时,弹出结点添加到resultlist链表
        return new GraphIndexIterator(resultList.iterator());//resultlist中储存的就是最短路径的顶点集
    }
  • 问题四:出现从未遇到过的异常:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  • 问题四解决:有三种可能致使此类错误的状况:
    • 真实的存在内存泄漏
    • 没有为应用程序提供足够多的内存,这种状况下,能够增长JVM堆可用空间大小,或者减小程序所需内存量
    • 程序中无意的对象引用保持,使得没有明确的释放对象,以至于堆增加再增加,直到没有额外的空间。
      网上的解决办法:

      选中被运行的类,点击菜单‘run->run...’,选择(x)=Argument标签页下的vm arguments框里
      输入 -Xmx800m, 保存运行。

    但我没怎么明白具体操做,我认为个人错误多是第三种状况,因而把第一周中inkedStack方法检查了一下,发现方法size,isEmpty,和toString不完善,在其余方法的条件中调用时就有可能出错,修改以后问题解决。

代码托管

上周考试错题总结

结对及互评

  • 谭鑫20172305:谭鑫的博客中最突出的就是他的扩展学习不少,好比Tarjan算法是我没有了解的,能够说很用心了,博客总结的一直很详细,小小的建议就是但愿解释的时候能够分一下层次,一大段一大段的有点难阅读(o´ω`o)ノ

  • 王禹涵20172323:王禹涵的博客总体很好,及哦啊才问题总结详细,有一个问题就是不太明白代码问题二。

其余

关于图的概念有逻辑性,容易理解,可是问题仍是在代码理解上,我以为Java学习不能停留在知识表面,要深刻理解代码,并学会构造才是最终目的。

学习进度条

代码行数(新增/累积) 博客量(新增/累积) 学习时间(新增/累积)
目标 5000行 30篇 400小时
第一周 0/0 1/1 8/8
第二周 1163/1163 1/2 15/23
第三周 774/1937 1/3 12/50
第四周 3596/5569 2/5 12/62
第五周 3329/8898 2/7 12/74
第六周 4541/13439 3/10 12/86
第七周 1740/15179 1/11 12/97
第八周 5947/21126 1/12 12/109
第九周 7968/29094 2/14 12/121

参考

相关文章
相关标签/搜索