爱恨交织的算法恨只因爱的太深--算法思想篇
Posted Answer.AI.L
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爱恨交织的算法恨只因爱的太深--算法思想篇相关的知识,希望对你有一定的参考价值。
壹、前 言
自从意识到算法的重要性之后,LZ对算法可谓是又爱又恨,爱是因为对算法学起来真的很有意思,
如果你以前有对数学这门学科情有独钟过的话,在接触了算法之后,你会自然而然地对算法产生爱慕之情。。。
之所以恨,是恨自己能力有限,算法太难,因为太爱所以会恨。。。
学生时代,学校开数据结构这门课的时候,至今悔恨当初把课堂时间都给了睡眠。。。直到校招面试时,很多企业
都把算法提到台面时,回回都怒吃闭门羹。。。被问的哑口无言。。。LZ才下意识明白到了时间都去哪儿了??我啥就
这么差劲呢。。。大学的四年时光就这么毫无意义地流逝。。。
算法对于逻辑思维能力的要求比较苛刻。。。一个问题的实现算法可以有很多种,,而我们需要做的就是写出一个
令人赏心悦目的算法实现,不管从代码的结构上,还是运行性能上,,我们都要尽可能地做到最好。。。
解决同一个问题,,有的人可能扬扬洒洒写上上百行的代码,,而有的人却是言简意赅的十来行就能达到同样的目的。。。
算法之所以这么吸引着我,,就是因为他解决问题的多样性。。。
贰、排 序 的 概 念
在计算机程序开发过程中,对一组数据元素或记录按照某个关键字进行排序,排序完成的序列可用于快速查找相关记录的过程。
由概念可以知道排序的目的是用于快速查找。
叁、如 何 衡 量 排 序 算 法 的 优 劣
可以根据三个指标来衡量一个排序算法的优劣性:
1):时间复杂度:分析关键字的比较次数和记录的移动次数;
2):空间复杂度:分析排序算法中需要多少的辅助内存;
3):稳定性:若两个关键字A和B的关键字值相等,但排序后A、B的先后次序保持不变,
则称这种排序算法是稳定的;否则就是不稳定的。
肆、排 序 的 分 类
1、内部排序:整个排序过程中不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
2、外部排序:若参与排序的数据元素非常多,数据库非常大,计算机无法把整个排序过程放在内存中完成,
必须借助于外部存储器(如磁盘等)。
大分类下的子分类:
1、内部排序算法分类:
3):插入排序(直接插入排序、折半插入排序、Shell排序)
4):归并排序
5):桶式排序
6):基数排序
2、外部排序算法分类:
1):多路归并排序
将原文件分解成多个能够一次性装入内存的部分,分别把每一部分调入内存完成排序,
接下来再对多个有序的子文件进行归并排序。
即:将大文件分批调入内存中来进行排序,排序之后还要将结果输出到外存储器,待
原文件所有记录都处理完毕后,最后再将以上分组排序好的结果进行两组两组地
归并排序。
注:在内存容量允许的条件下,每组中包含的记录越多越好,这样可以减少合并的次数。
伍、内 部 排 序 算 法 思 想
第1趟:程序将记录定位在第1个数据上,用第1个依次和后面的每个数据进行比较,如果第1个数据大于后面的某个数据,
交换他们……依次类推,直到与最后一个数据比较完之后,这组数据中的最小数据被选出,它被排在第1位。
第2趟:程序将记录定位在第2个数据上,用第2个数据依次和后面的所有数据进行比较,如果第2个数据大鱼后面的某个数据,
交换他们……依次类推,直到与最后一个数据比较完之后,这组数据中的第2小数据被选出,它被排在第2位。
(注意:此时即第2趟排序中第1个数据不参与比较)
后面依次类推……
第1趟:将索引0~n-1处的全部数据建大顶堆,就可以选出这组数据的最大值,然后将大顶堆的根节点与这组数据的最
后一个节点交换,就使得这组数据的最大值排在最后。
第2趟:将索引0~n-2处的全部数据建大顶堆,就可以选出这组数据(0~n-2个数据,不包含第1趟已经选出的最大值)
的最大值(注意第1趟中的最大值已被选出并不参与本趟比较,此处的最大值指的是0~n-2个数据中的最大值),
将第2趟中的大顶堆的根节点与这组数据(0~n-1个数据,包含第1趟已经选出的最大值)的倒数第2个节点交换,
就使得这组数据的第2大值排在倒数第2位。
后面依次类推……
概念解释说明:
大顶堆:将一组数据元素按照他们原有顺序排成一棵完全二叉树,并且树中所有节点的值都大于等于其左右子节点的值。
小顶堆:将一组数据元素按照他们原有顺序排成一棵完全二叉树,并且树中所有节点的值都小于等于其左右子节点的值。
第1趟:依次比较第0和1、1和2、2和3……n-2和n-1索引的元素,即相邻两个数据进行比较,如果前一个数据大于后一个
数据,交换他们。经过第1趟排序之后,最大的元素就排在了最后。(第1趟有n个数据进行了比较)
第2趟:依次比较第0和1、1和2、2和3……n-3和n-2索引的元素,如果前一个数据大于后一个数据,交换他们。
(注意:第2趟比较的数据中不包含第1趟中已经选出的最大数据,即只有n-1个数据进行比较)。
经过第2趟排序之后,第2大的元素排在了倒数第2位。
后面依次类推……
第1趟:从排序序列中任取一个值作为分界值(一般取第1个数据)。
定义一个指针i,指向左边第一个元素(即索引下标0。若分界值取第1个数据,那指针i的索引所对应的元素值为分界值),
用于找出序列中比分界值小的元素;
再定义一个指针j,指向右边第一个元素(即索引下标为:n-1),用于找出序列中比分界值大的元素;
拿指针j指向的索引所对应的元素和分界值比较,
(1):如果小于分界值,则把指针j指向的元素赋给此时指针i所指向的索引;把目光切换向指针i,
然后向后移动指针i(此时指针i指向的索引下标为1),把指针i指向的索引元素和分界值进行比较,
[1]:如果小于分界值,则继续向后移动指针i,直到遇到指针i所指向的元素大于分界值;
[2]:如果大于分界值,则把指针i指向的索引元素赋给此时指针j所指向的索引;切换为指针j移动,
(2):如果大于分界值,则向前移动指针j,此处指针j指向索引下标为(n-2),
依次类推,直到指针j所指向的元素小于分界值。
后面的依次类推(递归)。。。
注意点:指针i只负责找比分界值小的元素,指针j只负责找比分界值大的元素;
一旦指针i指向的元素比分界值大,则把该元素赋给此时指针j所指向的索引,
此时指针i不动,对指针j进行操作,向前移动指针j;
一旦指针j指向的元素比分界值小,则把该元素赋给此时指针i所指向的索引,
此时指针j不动,对指针i进行操作,向后移动指针i;
对于有n个元素的一组数据来说,直接插入排序要进行n-1趟插入操作。具体步骤如下:
第1趟:将第2个数据与前面的有序序列进行一一对比,此时前面的有序序列只有1个数据。如果
依次类推……
第n-1趟:将第n个数据与前面的有序序列进行一一对比,先与第n-1个数据比较,如果第n个数据比第n-1个数据小,
则将第n-1个数据向后移1位,再把第n个数据的值插入到序号为n-1的位置上,否则不对第n-1个数据进行移动;
再与第n-2个数据进行比较,若第n个数据比第n-2个数据小,则将第n-2~n-1个数据全部向后移1位,
再把第n个数据的值插入到序号为n-2的位置上,否则不对第n-2~n-1的数据进行移动;
依次类推……
最后与第1个数据数据进行比较,若第n个数据比第1个数据小,则将1~n-1个数据全部向后移1位,
再把第n个数据的值插入到序号为1的位置上,否则不对第1~n-1个数据进行移动。
注意:在每趟比较之前,需把第n个数据存储在一个变量中,防止整体移位时导致第n个数据丢失。
折半排序是对直接插入排序进行了优化。
直接插入排序中:当第n-1趟需要将第n个元素插入到前面的1至n-1个元素的序列中时,他总是会从第n-1个元素开始,
逐一地比较每一个元素(比较顺序为:n-1,n-2,……,1),直到找到他的位置为止。。。这个显然没有利用到前面1至n-1个
元素已经是有序序列这个特点。。。而折半插入排序就是对于这个进行了简单的优化。。。折半插入的具体操作如下:
操作1:计算1至n-1元素的中间点(假设为他们中间点的元素为m),然后就拿第n个元素和m进行比较,如果第n个元素大,
就直接在m~n-1的半个范围内搜索;反之,就在1~m的半个范围内搜索。。。这就是折半的大致思想。。。
操作2:在半个范围内搜索时,再按照操作1中的步骤再次进行折半搜索。。。直到确定了第n个元素的插入位置为止。。。
Shell排序是对直接插入排序进行了简单改进:它通过加大插入排序中元素之间的间隔,并在这些有间隔的元素中进行插入排序,
从而使数据项大跨度地移动。当这些数据项排过一趟序后,Shell排序算法减少数据项的间隔再进行排序,依次进行下去。
进行这些排序时的数据项之间的间隔被称为增量。习惯上用字母h来表示增量。
举例说明:
待排序列:-30 -16 21* 23 9 -49 21 30* 30
第一趟排序:h = 9 / 2 = 4 (其中9为带排序列的元素个数)
排序后:-30 -49 21* 23 9 -16 21 30* 30
第二趟排序:h = 4 / 2 = 2
排序后:-30 -49 9 -16 21* 23 21 30* 30
第三趟排序:h = 2 / 2 = 1
排序后:-49 -30 -16 9 21* 21 23 30* 30
归并排序是将长度为n的无序序列看成是n个长度为1的有序子序列,首先做两两合并,得到n/2个长度为2的有序子序列,
再做两两合并……不断重复这个过程,最终得到一个长度为n的有序序列。。。
归并排序算法的关键是在于"合并",合并的具体操作如下(假设:A和B为待合并的两有序序列):
操作1:定义变量i,i初始化值为0;
操作2:定义变量j,j初始化值为0;
操作3:拿A序列中的i索引处元素和B序列中的j索引处元素进行比较,将较小的那个元素复制到一个临时数组中:;
操作4:如果i索引处元素小,i++;如果j索引处元素小,j++;
操作5:不断地重复上面4个步骤,即可将A、B两个有序序列中的数据元素复制到临时数组中去。知道A、B中任意的
一个序列中的所有元素都被复制到临时数组中。最后,再将另一个有序序列中剩余的元素全部复制到临时数组中,
合并完成,再将临时数组中的数据复制回去即可。。。
桶式排序不再是一种基于比较的排序方法,这种排序方法需要待排序列满足如下两个特征:
1):待排序列的所有值处于一个可枚举的范围内;
2):待排序列所在的这个可枚举范围不应该太大,否则排序开销太大。
待补充。。。
基数排序不是一种常规的排序方法,它更多地像是一种排序方法的应用,基数排序必须依赖于另外的排序方法。基数排序总体思路是
将待排序的数据拆分成多个关键字进行排序,也就是说,基数排序的实质是多关键字排序。
举例说明(最低位优先法):
待排序列:192 221 13 23
第1轮比较个位:221 192 13 23
第2轮比较十位:13 23 221 192
第3轮比较百位:13 23 192 221
附加说明:对于在基数排序时选择的关键字排序,选择哪种排序方式更加合适呢?答案是--桶式排序
依据桶式排序的两个特征,且对于多关键字拆分出来的子关键字,他们一定位于0-9这个可枚举的范围内,
这个范围也不大,因此用桶式排序效率非常好。
陆、结 束 语
算法是一个很奇妙的东西,越是不会就越喜欢,因为它足够迷人,足够吸引着程序员的你去一探究竟。。。
虽然卤煮在这条道路上还是个菜鸟,如果不是一名算法工程师,平时也不会接触到太多算法知识,但是好奇心强的卤煮就是喜欢算法。
希望能和大家一起交流交流算法心得,或是各种专业知交流,毕竟技多不压身。。。好啦,今天就到这里了。。。
父亲节,各位博友别忘了给父亲打个电话,问个好。那是我们一生的榜样,无可替代。。。父亲节快乐!!!
如果你觉得博文写的不错,就点下【推荐一下】或【打赏】卤煮一杯奶茶吧!!!
以上是关于爱恨交织的算法恨只因爱的太深--算法思想篇的主要内容,如果未能解决你的问题,请参考以下文章