1、希尔排序 :
希尔排序(Shell Sort)是插入排序的一种。也叫做缩小增量排序,是直接插入排序算法的一种改进版,是一个非稳定的排序算法.他的原理是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰好被分成一组,算法便是终止了。
希尔排序的基本思想:
将数组列在一个表中并对列分别进行插入排序,重复这个过程,不过每次用更长的列(步长更长了,列数更少了)来进行。最后整个表只有一列了。将数组转换至表是为了更好的理解这个算法,算法本身还是使用数组进行排序 。
这个算法在时间复杂度上面来说的话:
1、最优时间复杂度:根据步长序列的不同而不同。
2、最坏时间复杂度:O(n**2) (“**” 代表的是平方)
3、稳定性:不稳定。
这个算法中! 当我们的步长取得好的话!我们的时间复杂度是可以得到提升的 !
希尔排序的实现:
#coding=utf8
def Shell_sort(alist):
"""希尔排序"""
n = len(alist)
gap = n // 2 #代表的步长
#gap变化到0之前,插入算法执行的步骤
while gap >=1:
#插入算法,与普通的插入算法的区别就是gap步长
for j in range(gap.n):
i = j
while i > 0:
if alist[i] < alist[i - gap]:
alist[i] , alist[i - gap] = alist[i - gap],alist[i]
i -= gap
else:
break
#开始缩短步长
这就是一个shell_sort的实现,
希尔排序的时间复杂度:
直接看代码分析时间复杂度,我们没办法分析,那么我们直接取特殊的情况! 就是当我们的gap=1的时候,我们会发现这个希尔排序就是一个插入排序,也就是希尔排序最坏时间复杂度为O(n**2),最优的复杂度是根据gap来决定的!取决于gap的取值,那么到底我们的gap如何取到最优呢!?那么我们就需要数学知识了!
2、快速排序:
1、快排第一种运行底层原理:
假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(基准数就是拿一个数字做我们的参考数)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列:
3 1 2 5 4 6 9 7 10 8
在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以做到这点吗?
方法就是使用到我们的快排(快排的执行逻辑):分别别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边,相当于两个指针。刚开始的时候让 i 指向序列的最左边(即i=1),指向数字6。让 j 指向序列的最右边(即=10),指向数字。
因为此处设置的基准数是最左边的数,所以需要让 j 先出动,这一点非常重要(请自己想一想为什么)。j 一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来 i 再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。然后两个交换。什么时候停止呢?当我们的i 和 j 重合的时候!两者那个位置就是基数6应该在的位置。
这个时候基数6的左边都是小于6的数,基数6的右边都是大于6的数字,此时基数6的左右两边分别是两个队列!在持续上面的步骤就行,直到完全结束排序。
2、快排第二种运行原理:
假如我们有一组数据, 54 ,26 ,93 , 17 , 77 , 31 , 44 , 55 , 20 那么我们还是采用两个指针,第一个指针指向的是这一组数据的第一个,称为low指针 ,第二个指针指向这一行数的最后一个 元素,我们称为high指针。
第一步:我们找一个中间值! 就第一个元素吧! 我们使用一个变量保存这个值! Mid_value = 54 。 那么low此时指向的是空!第二步:然后往左移动最后的high这个指针,判断这个列表的high这个指针指向的值是比54大?还是比54小呢?,
如果比这个中间值54小的话,那就将这个值保存在 low 这个指针下,然后让low += 1 ,促使low这个指标向右移动,执行第三步! 此刻high这个指向此刻为空!
如果high比这个中间值大!直到找到这个比中间值54小的值。进行交换值以后!在执行下一步。
第三步:然后我们开始往右移动low指针,如果发现 alist[low] 这个值比54小的话,那么我们就让low += 1 ,如果发现下一个值比54这个值大!那么我们就将这个值存储到上一步的high指向的那个位置,然后让high -= 1,促使他向左移动!,然后在重复第二步骤!第三步。直到两个指针重合!第一次结束。
第四步:当找到重合点以后,那么就让重合的位置等于那个中间值54.
此时54左边的都比54小,54右边的都比54小!然后分别对这两个数组再次进行这样的快排操作。
快排实现1:
#coding=utf-8
#first代表的是alist这个列表的第一个元素的下标
#last代表的是alist这个列表的最后一个元素的下标
def quick_sort(alist,first,last):
"""快速排序"""
if first >= last:
return
mid_value = alist[first] #这个是我们的中间值
low = first #其中的开始元素的指针
high = last #其中这是最后一个元素的指针
#6、外层循环的判断。 其实就是一个不断调用的情况 !
#7、这里面出现的情况,low和high的值是相等的! ,我们尽量将54都放在其中的一边去处理。
# 只需要最后在判断的时候进行判断一下就行!想要放在哪一边都行
while low < high:
#开始移动
#1、第一步首先移动high这个元素。判断low什么时候停止移动
while low < high and alist[high] >= mid_value:
high -= 1
# 2、如果发现其中一个比中间值mid_value小的话,那么就会不满足上面的条件,这时候我们就需要交换两个元素。
list[low] = alist[high]
#3、移动玩两个元素之后我们就需要去移动low,然后对low进行判断了
#9、 low += 1
#4、这边是在移动low这个元素。
while low > high and alist[low] > mid_value:
low += 1
alist[high] = alist[low]
#9、 high -= 1
#10、从循环退出时Low是跟high相等的!
alist[low] = mid_value
#11、此时整个循环执行了一次!下面该对循环过后的左右两边的小的队列进行排序了。左边的列表式 alist[:low-1] ,右边的列表式alist[low+1:]
quick_sort(alist , first , low - 1) #递归使用左边的列表进行quick_sort进行排序 ,然后产生的两个子序列再次进行quick_sort进行排序 。下面同理。
quick_sort(alist , low + 1, last)
#5、然后上面的两个步骤是交叉调用的!这时候我们需要外层有一个循环来控制。
#8、我们还要考虑一个问题! 当我们在移动的时候!出现low和high相等的情况的时候,我们发现low和high完美的错过了!
#我们要怎么样保证在low和high相等的情况下保证程序的退出呢?? 看我们的第9步的操作,我们需要将这两个指针在相遇的时候不进行操作。
#那就将两个指针的移动放再循环中!不影响最终的结果。
快速排序的最优时间复杂度是 O(nlogn) ,最坏的时间复杂度是O(n**2) , 快排是不稳定的!
如何计算最优的时间复杂度,假设我们的数组有九个元素! 那就相当于, 9 /2/2/2 =1 ,9除以多少个2才能到1呢? 那么相当于!2的多少次方是9呢? 2**n = 9 ? 这个时候对数换算! 也就是n = log以2为底9的对数。 现在我们不知道列表有多少个,那么就是 n = log以2为底n的对象代表我们的复杂度, 那么我们在找的时候也需要执行n次! 所以是O(nlogn)。