算法-2A

算法-2A

接之前的算法-1A,1B后的第三次大课

减而治之——1

在这里插入图片描述
这节课邓老师主要讲解了Decrease-and-conquer的概念,也就是减而治之,注意这里的减是减除的减,而不是简化的简,意味着把一个问题减去一部分后,得到一个更小的问题,这样一步一步得到我们问题的最终解,我们减去的部分是我们不用关心的。如果说,我们把一个问题分为两部分,其中两部分都是我们应该所关心解决的问题的话,那么这个则是下节课将讲到的分而治之的概念了。减而治之也是我们继贪心算法后提出的又一大方向的算法思想,接下来让我们一起看看吧。
在这里插入图片描述
在进入减而治之的思想之前,让我们一起来看一下一种新的数据结构,heap堆,如图所示就是一个堆,一般是完全二叉树,但不一定是满树。
在这里插入图片描述
堆的排列也很有意思,下面的逻辑结构看起来很复杂,但你完全可以像上面一样用一个数组就可以把它们存储起来,并且找到自己的左子树和右子树的元素很容易,只要自己乘二分别加一和加二就可以找到左右元素。在计算机中乘二的操作通常用左移一位来表示。
在这里插入图片描述
堆顶的性质是很特殊的,堆顶永远只有一个,那我们可以理解为它的优先级最高,那么通常我们可以用堆来实现找到一组元素中最大或最小的元素,这个复杂度是O(1)时间内,而且加入和删除操作也特别方便,是O(log n)的复杂度。堆内维持的性质是,子元素永远没有父元素的优先级高,但同一高度的元素的优先级就不一定了。
在这里插入图片描述
如图所示,当我们插入元素时,在堆的最下方最右边的子元素旁边加上新元素,然后把新元素和父元素的优先级进行比较,如果新元素的优先级高,则调换位置,然后再把新元素新位置的父元素和新元素的优先级进行比较,如此反复,则可得到一个最终的位置,并且这个操作复杂度是O(log n)的。删除元素原理相同,当堆顶被删除时,则把最下方且最右的元素拿到堆顶来,把该元素和新位置的子元素优先级进行比较,如果低于子元素,则变换位置,如此迭代到最终位置。反正最终的原则是不能违反父元素优先级高于子元素优先级这一定律。
在这里插入图片描述
这个图则就是减而治之的原理图,一个问题从n的规模在减去一部分无用的部分后达到更小的部分,当然我觉得这里不一定就是n-1的规模,更能是更小的规模。
在这里插入图片描述
接下来讲解一个二分查找的问题,在一个有序序列里,假如你想找到值为x的元素,那么怎么找呢,肯定有人想到遍历一遍,当然遍历一遍完全可以,但我们学算法的肯定应该想到有没有什么方法复杂度小于遍历一遍的复杂度n,这里我们提出二分查找。这是一种很容易想到的方法,但很多人把二分查找归并到分而治之里面去了,但其实不是的,二分查找在把原问题分为两部分时,并一部分是不会用到的,这其实是减而治之的思想。其原理就是把最小元素下标和最大元素下标相加除以2得到中间元素下标,然后用中间元素和我们要找的元素进行大小比较,如果小,则说明在左边,则把最大元素改为中间元素,反之在右边把最小元素改为中间元素加一(这里很奇妙,为什么是加一呢,仔细看代码,你会发现奥妙的),如果正好是中间元素,则我们找到了,这样的复杂度应该是log n的,这也是减而治之思想的巧妙应用。
在这里插入图片描述
第二个应用则是大名鼎鼎的选择排序,选择排序的原理如上图所示,先在n规模的数据里面遍历一遍,找到n规模里面最大的那个,然后放在最右边,这时候分为了一个n-1规模和一个1规模的两个部分,第二次在n-1的规模里再如此,则得到一个n-2规模的部分,原规模不断减小,且你发现新的这部分是天然有序的,也就是我们不用去处理,你可以理解为是减去(因为不用处理,如果需要处理则就是分而治之了),如此反复得到一个有序的集合,复杂度是n平方的。
在这里插入图片描述
这是第一次寻找最大值演示图,
在这里插入图片描述
可以看见n规模完全分为两部分,右边是有序的,不需要我们再去管的,左边是无序还在处理中的,这也是典型的减而治之的思想。

减而治之——2

在这里插入图片描述
这还是选择排序的演示图,可以看见右边开始逐渐有序,左边还是一个混沌的排列,
在这里插入图片描述
右边有序,左边混沌。
在这里插入图片描述
刚刚讲到,在我们处理选择排序时,如果一遍一遍的遍历,复杂度是n平方的,这是比较麻烦的,之前讲过的冒泡排序也是n平方的,n平方的复杂度在排序算法里面不能算很高明的算法,比较好的排序算法都可以达到nlog n的复杂度,一些极端的方法甚至可以达到n的复杂度,这些我们暂且不考虑。那么看看上面的选择排序,有什么方法可以让它达到nlog n的复杂度吗?联系我们开头讲的那种数据结构,你是不是有什么启发呢?没错,我们可以用堆来得到前面那个不断减小的规模里的极大值,这个得到的复杂度是log n的,比我们每次遍历n复杂度要小,我们得到n个,每个的复杂度是log n,总体的复杂度是nlog n的,这也是大大改进了我们的效率,数据结构其实就是为算法服务的。在这里插入图片描述
执行过程的原理图。
在这里插入图片描述
在这个演示图中,应该先执行建堆的过程,所以你可以看见左边虽然不是全部有序,但是呈现一个堆的形状,极值在堆顶,在建好堆之后,不断把堆顶放在最后,最终堆里面最后一个元素被拿出,得到一个有序的规模为n的集合,之前讲过建堆的复杂度是n
log n的,后面n次得到元素,每次为log n,总共也为nlog n,但两个在同一层次上,所以最终规模还是nlog n的复杂度。
在这里插入图片描述
接下来又是一种大名鼎鼎的排序算法,插入排序,插入排序也是n平方的复杂度,但它远远优于选择排序和冒泡排序,这个我们从两个方面理解。比如说逻辑上我们就很好理解,在我们打牌时,我们抓到一张牌放在手上,然后抓第二张,把这两张牌排好序是一件很简单的事情,再抓第三张,把第三张牌,在原来拍好的牌集合里面插进去,得到还是一个有序的序列,这是我们打牌常干的事,恰恰插入排序就是这个原理,这就是插入排序,我们很自然的打牌中会用到这种方法,其实是有道理的。我们设想,我们大牌中你会不会用选择排序的方法呢?你肯定觉得很怪,因为选择排序和冒泡排序的缺点在于,它们都必须等所有的牌发下来才能开始做排序,但在真实打牌过程中,我们总是发完牌,手上的牌也就排好序了,这是很奇妙的,如果用选择排序,那大家都要慢慢等你拿到全部牌后再去排序,那样是极慢的。所以插入排序很好的一个特性是,它是online的,也就是实时的,你发几张我就能排好序几张,再发再排。虽然也是n平方的复杂度,但实际上它的复杂度是优于选择排序和冒泡排序的。
在这里插入图片描述
这也是插入排序的演示图,第一张有序,然后把第二张再排好序,,第三张再在前面两张里排好序,前面有序的规模越来越大,后面无序的规模越来越小,最终得到一个有序的序列。
在这里插入图片描述
第四种排序方式来了,这也是极好的排序方式,时间复杂度是很小的n*log n的,也就是我们的快速排序。在一个无序序列中,通常把第一个元素作为中间元素,然后把比它小的放前面,比它大的放后面,然后前后是两个新的集合,虽然不是完全有序,但有一定的性质,就是前面任何一个元素都小于后面任何一个元素,然后再把这两个新的集合来分别执行上面的操作,如此往复,一致到最后的集合里面只有自己一个元素了。
在这里插入图片描述
这里提出一个快速查找的概念,在上面的二分查找里,有一个前提,是有序的集合,如果无序集合想要找到第几小的元素,这种情况如果排个序,很快就可以根据下标知道它排列第几,但这个是很浪费的一个行为,因为我们可能并不想知道其它元素排在第几,但我们还是做到了,相当于做了一些无用功,那么有什么方法可以省去吗?这里有一个和快速排序很像的办法,你就假定这个是第一个元素就是第k小的元素,然后比它小的放前面,大的放后面,最终看前面有多少个元素,假如前面的少于k个,那么说明我们猜错了,那么前面一部分元素就不用管了,在后面一部分元素里,又选第一个作为第k小的元素,然后比它小的放前面,大的放后面,每次比较前后有多少个元素来知道这是不是第k小的元素,这样的操作看起来很麻烦,其实是很精明的,这样的方法,最坏的时候,复杂度也才会和排序一样,最好的情况,远远优于进行一次排序,排序做了很多浪费的工作,如果只想查询这么一次的话。
在这里插入图片描述
这是一份伪代码,详细代码可以看我另一篇博客快速排序
和这里不一样的是,我博客里面的终止条件是if(left>=right)return;

减而治之——3

在这里插入图片描述
最后一节课里,邓老师用了一整节课的时间解决了,最短路径问题。最短路径问题是相当经典的算法问题,它的解决方案由图灵奖获得者迪杰斯特拉提出,故这一算法也叫迪杰斯特拉算法。在n个点中,我们想知道某一个点到其它点的最短路径,比如我们就在某一个点,我们到其它任一点的最短路径是多少,迪杰斯特拉算法就是解决这样一个问题。
在这里插入图片描述
像插入排序时,我们自然的在打牌中应用到一样,在现实生活中,我们也有这样很自然的办法可以轻松得到我们想要的最优解,列如图右,我们用物理方法,按比例做出一个简单的模型,在我们想知道A点到B点间的最短路径时,只要一只手拎起A点,一只手拎起B点,然后慢慢绷直,中间绷直的线段就是我们的最短路径,很神奇,可能算法上我们要考虑很多问题,但是实际上我们只有两只手就可以快速得到最短路径。
在这里插入图片描述
实际上,我们的算法就是模拟这个过程,假如我们把模型放在桌子上,捏住我们的起点,然后慢慢往上拉,拉的过程中,不断有点被拉上去,,最终得到起始点到所有点的最短路径。
在这里插入图片描述
在更新最短路径过程中,更新“线”的时候,你会发现这和之前解决最小支持树的Prim算法很像,但其实是有差距的,更新算法并不一致。可能有一个点到已经被拉起来的点线段最短,但其实这时候,可能其它没被拉起来的点但已经悬空的线很长,贴在桌面上的那部分更短,所以这里和prim算法不一致。
在这里插入图片描述
随着新点被拉上去,其它桌面上的点到空中的点的最短路径,可能随着新点上去而被更新路径。
在这里插入图片描述
演示过程,虚线是桌面,红色的线,是悬空会被考虑的线。
在这里插入图片描述
随着C点上去,有的路径被更新,S到D点和A到E点就是和Prim算法的区别,Prim会选A到E,但是这时我们选S到D。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面三图是迪杰斯特拉的伪代码,详细代码可看我另一篇博客迪杰斯特拉算法模板

在这里插入图片描述
在要得到剩下点到起点最短距离时,我们还可以用heap的方法来得到,因为堆顶的极值性质,得到最小很是方便,但这里要注意,随着新的点例如C点被拉上去,我们要更新这个堆,因为有的最短路径随着C点被拉上去而产生新的更短的路径。
在这里插入图片描述
更新例子,在6那个点没有被拉上去之前,起点到下面的下面那个点需要16个单位,但随着6那个点被拉上去,产生了新的最短路径,那么下面的下面那个点别更新,
在这里插入图片描述
现在,只需要15个单位了。迪杰斯特拉算法有些复杂,不能靠几句话就可以完全理解,所以希望读者结合其它资料一起理解,而且实现该算法也很麻烦,需要多多练习。

尾声

十过其三,坚持可贵。

猜你喜欢

转载自blog.csdn.net/qq_42529477/article/details/105978053
今日推荐