【015】Python全栈日记-查找和排序
一、查找
1、查找概念
假设有两组数据:
int array1[]={6,4,5,3,8,7,1,2,0,9};
int array2[]={0,1,2,3,4,5,6,7,8,9};
一个有序数组,一个无序数组, 在他们之间查找某一个值的方法有什么区别呢,
对于两组数据我们都可以用最直接的方法,逐个比较直到遇到合适的值。
1、顺序查找
第一种方法从表中最后一个记录开始,逐个进行记录的关键字和给定值的比较,若某个记录的关键字和给定值比较相等,则返回返回记录所在的位置,或查找完所有记录后还没有发现符合的记录,则查找失败。我们称其为顺序查找。
通过python实现一下:
结果:
2、折半查找
当数据是一个有序的情况时,我们可以利用这样一种思路:与记录中间值比较,如果比中间值小去左边查,否则去右边查找,直到找到为止,区域内没记录时查找失败。
对于给定key值,逐步确定待查记录所在区间,每次将搜索空间减少一半(折半), 直到查找成功或失败为止。我们称其为折半查找。
通过python来实现一下:
Low和high是最小的数和最大的数的下标,mid是中间数的下标,mid取整,如果要找的数小于mid 则在下标位low和mid-1之间的数查找,如果要找的数大于mid 则在下标位mid+1和high之间的数查找,以此类推。
结果:
3、递归折半
折半查找还可以通过使用递归的方法来完成,直接通过返回给函数修改后的low或者high来完成查找。因为通过递归来循环这个过程所以不需要使用while,只需要用if限制一下不要让low大于high。
结果:
排序的分类:排序分为插入排序、选择排序、交换排序、归并排序四大类。
二、算法复杂度
在进行算法分析时,语句中的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。
定义:O(1)叫做常数阶、O(n)叫线性阶、O(n2)叫平方阶。
三、排序稳定性
稳定排序:对于关键字相等的记录,排序前后相对位置不变。
不稳定排序:对于关键字相等的记录,排序前后相对位置可能发生变化。
四、交换排序-冒泡法
冒泡排序是经典的排序方法,思想简单,操作容易,算法稳定性好。是排序的基础算法,学习它有很大的必要性。
思想:相邻记录比较,如果逆序(前大后小)则交换,这样一趟排序会使最大(最小)的记录落到最后,这称之为一趟排序。N个记录需要N-1趟排序。
1、冒泡排序算法
关键字序列 T=(21,25,49,25*,16,08),请写出冒泡排序的具体实现过程。
初态: 21,25,49, 25*,16, 08
第1趟:21,25,25*,16, 08 , 49
第2趟:21,25, 16, 08 ,25*,49
第3趟:21,16, 08 ,25, 25*,49
第4趟:16,08 ,21, 25, 25*,49
第5趟:08,16, 21, 25, 25*,49
通过画图来了解一下思路,‘
第一趟,21和25对比,不变。25和49对比,不变。49和25*对比,调换位置。49和16对比,调换位置。49和08对比,调换位置。最大数49已经被放在最后。第二趟最后就不用在和49对比。
接下来几趟和第一趟方法一致。
通过python来实现:
结果:
[1, 2, 3, 4, 5]
2、优化
我们发现每趟交换时,只需要交换那些不是挪到最后的数
对于a=[5,4,3,2,1]
第一趟,我们交换4次就可以。
第二趟,我们只需要交换3次
第三趟,我们只需要交换2次
第四趟,我们交换1次就完成了排序。
通过这个我们能发现规律:交换次数等于a的长度减去趟数。
那我们优化一下代码:
3、冒泡法复杂度分析
最好的情况当记录完全有序时,只需要n次比较,发现在这一趟中没有数据交换,则排序停止,记录已经有序,时间复杂度是 O(n) 。最坏的情况当记录完全逆序的时候,n个记录要排序n-1趟,每一趟都要交换接近n次,这样最终的时间负责度O(n2) 。
对于时间复杂度我们认为最坏的情况是本算法的时间复杂度,所以冒泡排序的时间复杂度是O(n2)。
稳定性:有排序过程中,看到25和25*在排序前后的次序未改变,因此冒泡排序是稳定排序。
因s为在最坏情况下(即倒序),所有元素的比较次数总和为(0+1+…+n-1)→O(n2)。其他情况下也要考虑移动元素的次数。 故时间复杂度为O(n2)
五、插入排序
将待排序的记录Ri,插入到已排好序的记录表R1, R2 ,…., Ri-1中,得到一个新的、记录数增加1的有序表。 直到所有的记录都插入完为止。
设待排序的记录顺序存放在数组R[1…n]中,在排序的某一时刻,将记录序列分成两部分:
◆ R[1…i-1]:已排好序的有序部分;
◆ R[i…n]:未排好序的无序部分。
显然,在刚开始排序时,R[1]是已经排好序的。
1、插入排序法
例题: 关键字序列T=(13,6,3,31,9,27,5,11),其直接插入排序的排序过程如下:
【13】, 6, 3, 31, 9, 27, 5, 11
第1趟排序: 【6, 13】, 3, 31, 9, 27, 5, 11
第2趟排序: 【3, 6, 13】, 31, 9, 27, 5, 11
第3趟排序: 【3, 6, 13,31】, 9, 27, 5, 11
第4趟排序: 【3, 6, 9, 13,31】, 27, 5, 11
第5趟排序: 【3, 6, 9, 13,27, 31】, 5, 11
第6趟排序: 【3, 5, 6, 9, 13,27, 31】, 11
第7趟排序: 【3, 5, 6, 9, 11,13,27, 31】
大概意思就是,
第一趟:我们去认定第一个数13,然后用6和他对比,比13小,所以13后移,然后把6插在原先13所在位置,然后6和13就是已经排好序的。
第二趟:用3和【6,13】对比,比13小13往后移动,比6小6后移,吧3插在6原先的位置,现在【3,6,13】已经排好序了。
以此类推……
用python来实现:
结果:
【1,2,3,4,5】
2、插入排序复杂度分析
(1) 最好情况:若待排序记录按关键字从小到大排列(正序),算法中的内循环无须执行,则一趟排序时:关键字比较次数1次,每趟排序都要移动将近1个记录,这样n个记录最终的时间复杂度是O(n)。
(2) 最坏情况:若待排序记录按关键字从大到小排列(逆序),n个记录需要n-1趟排序,最坏的情况就是完全逆序的情况,每趟排序都要移动将近n个记录,这样最终的时间复杂度是O(n2)。
(3) 稳定性:稳定排序
六、选择排序
简单选择排序是常用的排序,学习交换排序理解交换排序理念,是对其他排序有力的补充。尤其学过交换排序之后,每趟排序都要进行频繁的交换,如何改进,使用选择排序,每趟只是记住位置,最后才交换,每趟最多交换一次。
1、简单排序算法的思想
思想:选择最小的记录放在第一个位置,在剩余记录中选择最小的放在第二个位置,依次类推,直到所有记录有序。
2、简单选择排序算法:
例如:关键字序列T= (21,25,49,25*,16,08),其简单选择排序的具体实现过程如下:
原始序列: 21,25,49,25*,16,08
第1趟:08,25, 49,25*,16,21
第2趟:08,16, 49,25*,25,21
第3趟:08,16, 21,25*,25,49
第4趟:08,16, 21,25*,25,49
第5趟:08,16, 21,25*,25,49
解释一下:
第一趟:原始数列中挨个走一遍,最小的是8,然后把8和第一个数交换位置。
第二趟,第一个数已经是最小了,不用看。从第二个数开始挨个走一遍,最小的是16,把16和第二个数换位置。
以此类推……
6个数只需要5次就能排好。
用python来实现:
结果:
【1,2,3,4,5】
3、选择排序算法的时间复杂度分析
分析:
N个数排序,需要N-1趟排序,每一趟最坏的情况比较N-1次,因此最坏的情况就是接近N*N次比较。
结论:
选择排序的时间复杂度O(n2)
稳定性:不稳定排序,因为存在不相邻元素之间的交换(T= (21,25,49,25*,16,08)中的25和25*)
七、快速排序
快速排序,世界上公认的最快的排序方法,它每趟都能准确定位不止1个元素!当记录量很大的时候,而且杂乱无序时候,适合使用快速排序,因为每趟可以确定不止一个元素的位置,而且呈指数增加,所以特别快! 你一定想做一个效率高的的人,而不是只是完成任务而已。
所以学习快速排序,有很重要的意义,提高排序的速度,考虑算法的效率,做一个高效的程序员。
1、速排序思想
快速排序的思想:从待排序列中任取一个元素 (例如取第一个) 作为中心,所有比它小的元素一律前放,所有比它大的元素一律后放,形成左右两个子表;然后再对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个。此时便为有序序列了。
2、速排序算法
我们知道了快速排序的思想,那么我们用一组数字直观的观察快速排序是如何进行工作的?
以关键字序列(256,301,751,129,937,863,742,694,076,438)为例,写出执行快速算法的各趟排序结束时,关键字序列的状态。
原始序列: 256 301 751 129 937 863 742 694 076 438
第一趟: 076 129 256 751 937 863 742 694 301 438
第二趟: 076 129 256 438 301 694 742 751 863 937
第三趟: 076 129 256 301 438 694 742 751 863 937
第四趟: 076 129 256 301 438 694 742 751 863 937
我们做这种题的思路是这样的:取第一个数作为中间数,然后看最后一个数,如果最后一个数大于中间数,不动看倒数第二个数。如果最后一个数,小于中间数,把这个数放在中间数本来的位置,然后回到开头,依次进行。
也就是,满足条件的保持不动,不满足条件的挪走后,再从列表另一头进行判断。
用python实现:
结果:
【1,2,3,4,5】
3、快速排序时间复杂度的分析
分析:设每个子表的支点都在中间(比较均衡),则:
第1趟比较,可以确定1个元素的位置;
第2趟比较(2个子表),可以再确定2个元素的位置;
第3趟比较(4个子表),可以再确定4个元素的位置;
第4趟比较(8个子表),可以再确定8个元素的位置;
……
只需log2n+1趟便可排好序。
而且,每趟需要比较和移动的元素也呈指数下降(最坏为n次),加上编程时使用了交替逼近技巧,更进一步减少了移动次数,所以速度特别快。
结论:
快速排序算法的时间复杂度:O(nlog2n)。
稳定性:不稳定排序,因为有不相邻元素之间的交换。