痛定思痛,开启算法之路(十二)

4.5 合并-查找问题(等价问题)

是用间接的数据结构来改善算法性能;

这里写图片描述
有两种方法可以实现如上目标:
1)利用数组存储每个分组,对于find运算只需访问一下数组即可,而对于union要费时一些;
2)利用记录存储,包括标识和指针,在union时只需改变指针指向即可,几次union之后就成为了树的集合,而对于find取决树的高度;
所以高效的合并-查找算法的思想就是要保证树的平衡(即树的高度相对最小),花一些时间保证树的平衡是值得的;
当进行union时,把较小的组的指针指向较大组的记录,重新计算合并后组的大小然后存储到根节点的相应域中(根节点不仅存储组名,还存储元素的个数);
若果union运算采用平衡法,那么树的高度就永远不会超过ln n;
这里写图片描述

    还有一种方法可以来改进合并-查找数据结构的效率,之前的算法中也有介绍过那就是路径压缩算法,他的思想很简单:
    我们从某个记录第一次的时候开始遍历其到达根节点的所有指针之后,只需要把路径上所经过的所有指针直接指向根节点就可以了;
    这样此路径上所有的经过的节点都直接指向了根节点,高度都变成了二级的非常利于find函数;
(注意第一次的路径查找是不可避免的,简化的只是此路径上所经过节点的再次find;其算法实现也非常容易,由最终记录的父节点一路向上都分别指向根节点即可)

如图:第一次由根到达节点1路径如下,之后一路向上改变父节点指针指向即可:
这里写图片描述
这里写图片描述
4.6 图

关于邻接矩阵、邻接表的一些基本概念(邻接矩阵适用于边比较多的图,使用及程序相对简单;
而邻接表适用于边比较少的图,而实际情况下图的边都比较少,所以链接表更常用),后面还会有详尽算法介绍,在此不再赘述;

第五章 基于归纳的算法设计

没有什么能比见到创造的源泉更为重要,我认为,他比创造本身更为重要。            ----莱布尼兹

数学归纳法类似于多米诺原理:为了确定所有的多米诺骨牌都会倒下,只需要验证确实推到了第一个,以及每一个骨牌倒下的时候都会使它的下一个倒下。

不需要以东拼西凑的方式来设计问题求解过程,只需要保证以下两点就够了:1)解决问题的一个小规模事例是可能的(基础事例),
    2)每一个问题的解答都可由更小规模问题的解答构造出来(归纳步骤)。

5.2 多项式求值
这里写图片描述

利用归纳法求解,无非就是缩小规模,找到小规模与大规模之间的关系,最直接的方法如下:
1)这里写图片描述,一步步由小到大、由右到左非常直观,但是效率却是非常差的:
它需要n+(n-1)+…+1=n(n+1)/2次乘法和n次加法运算;

2)改进1:在计算后一项的时候我们观察到有许多冗余的计算,即x的次幂被每次计算;若x^n的值可以由其上一项x^(n-1)得出则可以省去多次乘法运算;
有了Pn-1(x),计算x^n仅再需一次乘法,然后再用一次乘法得到(an)*(x^n),最后用一次加法完成计算;即每扩大一次规模,需额外2次乘法和一次加法;
因此最后其总效率供需2n次乘法和n次加法;

3)再次改进2:此算法利用导数的性质,Pn(x) = x*P’n-1(x)+a0;其实其看起来还是很直观的,由导数到本身只再需要一次乘法和一次加法即可;而这里写图片描述,这里的缩小规模后的形式是有所变化的:每次去掉常数项及缩小x倍(从左到右,即如下从内到外):
这里写图片描述
这里写图片描述
由以上程序代码可以很直观的得出其复杂度为:n次乘法和n次加法以及一个额外的存储空间;

第二个改进算法的例子方法3阐明了使用归纳法的灵活性。他是从左到右考虑问题的输入,而不是直觉上的方法1和2从右到左。
另一种常见的可能是对比自上而下与自下而上(当包含一颗树结构时);设计一个算法时,寻找最好的规约方式是值得的。

5.3 最大导出子图

对于这个问题的求解比较简单直接,直接附图,就不在赘述;

这里写图片描述
这里写图片描述
5.4 寻找一对一映射
这里写图片描述
归纳法,缩小规模,但是简化过的问题和初始问题应该是同样的问题:集合A与函数f的唯一条件是f把A映射到自身;

首先,1)假设:对于包含n-1个元素的集合,如何求解是已知的;
2)基础条件是显然的:若集合仅含有一个元素,则它必定映射到自己,这就是一个一对一的映射;
3)求一个包含n个元素的集合A,满足问题条件的子集S:
这里写图片描述
对于此问题的算法实现比较简单,就是借用一个数组c[i]存放映射到每个元素i的的数目,在借用一个队列存放所有映射数目为0的元素,再依次删除队列元素j同时减少j相应映射(f(j))到元素的映射数目c[f(j)];举个实例如下:首先将2,7放入队列,然后删除队首2,同时左侧的2映射到右侧的1即f(2)=1,则减少c[1]=1(最初c[1]=2):
这里写图片描述
这里写图片描述
这里写图片描述
5.5 社会名流问题

在n个人当中,一个被所有人都知道但却不知道别人的人,被定义为社会名流。转化为图论问题,使用一个有向图,每个顶点表示一个人,若A知道B则从A到B有一条边;
一个社会名流对应于图中的汇点,汇点为一个入度为n-1而出度为0的顶点;用一个n*n矩阵表示的话:

这里写图片描述
由条件知,至多只有一个社会名流;

缩小规模由n到n-1,分3种情况:1)社会名流在最初的n-1人中;2)社会名流是第n个人;3)没有社会名流;

法1:对于第一种情况容易处理:只需检验第n个人是否知道这个社会名流以及这个社会名流是否知道第n个人(提问两次即可);而对于后两种情况为了确认第n个人是否为社会名流需要提问2(n-1)个问题,而总共求和则需要n(n-1)个问题;这样问题的规模是比较大的,我们应该另寻他法;

法2:“倒推”:确认一个社会名流会很麻烦(除了问他是否知道每一个人,还要问其他每一个人是否知道他);而确认一个人不是社会名流却很简单(每次随便找2个人A和B,如A知道B即可排除A,如A不知道B即可排除B),也就是说每次2个人当中至少可以排除一人不是社会名流,则规模缩小;
其算法实现很简单,将n个人压入栈中,开始时取出两人提问问题排除一人,再次取栈顶提问,当栈空的时候则最后只剩一人,此时在验证此人是否为社会名流即可(即所有人是否知道他以及他是否知道别人),在这里用了一个二维boolean数组用于表示[i,j]第i个人是否知道第j个人,知道则为true:
这里写图片描述
5.6 分治算法:轮廓问题
这里写图片描述
这里写图片描述
问题描述很清楚,即给出了a图,由你输出其轮廓b图,如上;
数学化:对于每一座建筑 Bi 由三元组给出(Li, Hi, Ri)分别表示建筑 Bi 的左x坐标,高度和右侧x坐标;例如对于上图a的输入为:
这里写图片描述
法1:直接算法,利用归纳法在已有n-1个建筑的基础上,增加建筑Bn到轮廓中,需要他与已有轮廓贯穿相交,令Bn=(5,9,26),从左到右扫描轮廓,从左x坐标5开始,并根据其高度9判断已有的轮廓高度进行相应更新,一路向右到达右侧x坐标26;非常直观但是效率低下,最会情况下针对Bn所进行的扫描需要O(n)步,因此总步骤为n+(n-1)+…..+1=O(n^2)
这里写图片描述
法2:分治法,不是用简单的归纳原理把n-1的解扩展到n的解,而是把n/2的解扩展到n的解;
通常把问题分成几个大约相等规模的子问题会比较有效,递归关系 T(n) = T(n-1) + O(n)的解是T(n)=O(n^2),然而T(n) = T(n/2)+O(n)的解是T(n)=O(n log n);
基本上使用类似于把一栋建筑与已有轮廓合并的算法,就能把两个轮廓合并,其算法执行过程与上面一致;对于两个问题的合并其算法运行可在线性时间内完成,最坏情况下完整运行时间为O(n log n),类似于归并排序。
这里写图片描述

猜你喜欢

转载自blog.csdn.net/enjoy_endless/article/details/79231027
今日推荐