希尔排序
希尔排序是D.L.shell于1959年提出来的排序算法,在这之前(见排序1),排序算法的时间复杂度基本都是O(n^2),希尔排序是突破这个时间复杂度的第一批算法之一。
基本原理
上面讲的插入排序,在记录本身比较有序或者当记录较少时,比较高效。而希尔排序就是先使记录基本有序,然后再对全体记录进行一次插入排序。所谓基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间,像{2,1,3,6,4,7,5,8,9}这样的就可以称为基本有序了。但像{1,5,9,3,7,8,2,4,6}这样的9在第三位,2在倒数第三位就谈不上基本有序
python代码
def shell_sort(arr):
count = len(arr)
step = 2
group = int(count / step)
while group > 0:
for i in range(0,group):
j = i + group
while j < count:
k = j - group
key = arr[j]
while k >= 0:
if arr[k] > key:
arr[k+group] = arr[k]
arr[k] = key
k -= group
j += group
group = int(group/step)
return arr
希尔排序算法
考虑到是记录基本有序,可以采用增量比较法,arr[i]先不直接与arr[i+1]比较,而是与arr[i+increment]比较,这个increment就称之为增量,在python代码中用group表示。假设初始记录arr为{9,1,5,8,3,7,4,6,2},初始增量group=4
i = 0时,比较arr[0]=9与arr[4]=3的大小,因为arr[0]>arr[4],所以交换9,3位置,记录为{3,1,5,8,9,7,4,6,2}
同时比较arr[4]>arr[8],故交换,arr[0]>arr[4],交换,得到记录{2,1,5,8,3,7,4,6,9}
i= 1时,比较arr[1]=1与arr[5]=7的大小,前者小于后者,不交换
i= 2时,比较arr[2]=5与arr[6]=4的大小,前者大于后者,故交换,记录为{2,1,4,8,9,7,5,6,9}
i= 3时,比较arr[3]=8与arr[7]=6的大小,前者大于后者,故交换,记录为{2,1,4,6,3,7,5,8,9}
此时序列已经达到基本有序,接下来减少增量group=2,循环一轮后,再次缩小group=1,便可以使记录达到有序
希尔排序复杂度分析
经过上面剖析,发现希尔排序是将相隔某个"增量"的记录组成一个子序列,实现移动式的跳跃,使得效率升高,那么增量的选择就十分重要,当增量序列为2^(t+k+1)-1时,可以获得不错效果,时间复杂度是O(n^(3/2)),优于直接排序,另外希尔排序并不是一种稳定的排序算法
堆排序
之前的简单选择排序,选取最小的记录需要比较n-1次,可惜这样的操作并没有把每一趟的结果都保存下来,后面的比较中又执行了一次,因而记录的比较次数较多。如果能做到在每次选择最小记录的同时,根据比较结果对其他记录做出相应调整,那么总体的效率就会非常高了。
基本概念
堆是具有下列性质的完全二叉树:每个节点的值都大于或等于其左右孩子结点的值,称为大顶堆(下图左);或每个节点的值都小于或等于其左右孩子结点的值,称为小顶堆(下图右)
堆排序算法
将待排序的序列构成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造一个堆,这样就会得到n个元素中次小值。如此反复执行,便能得到一个有序序列了。
python代码
def heap_sort(lst):
for start in range((len(lst) - 2) // 2, -1, -1):
siftdown(lst, start, len(lst) - 1)
for end in range(len(lst) - 1, 0, -1):
lst[end], lst[0] = lst[0], lst[end]
siftdown(lst, 0, end - 1)
return lst
def siftdown(lst, start, end):
root = start
while True:
child = 2 * root + 1
if child > end: break
if child + 1 <= end and lst[child] < lst[child + 1]:
child += 1
if lst[child] > lst[root]:
lst[child], lst[root] = lst[root], lst[child]
root = child
else:
break
heap_sort(l)
归并排序(Merging Sort)
基本思想: 利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成n个有序的子序列,每个子序列的长度为1,然后两两归并,得到[n/2]个长度为2或1的有序子序列;再两两归并,......,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
排序过程
图来自《大话数据结构》
python代码
def merge_sort(arr):
if len(arr) <= 1: #子序列
return arr
mid = (len(arr) // 2)
left = merge_sort(arr[:mid])#递归的切片操作
right = merge_sort(arr[mid:len(arr)])
result = []
while len(left) > 0 and len(right) > 0:
if (left[0] <= right[0]):
result.append(left.pop(0))
else:
result.append(right.pop(0))
#j+= 1
if (len(left) > 0):
result.extend(merge_sort(left))
else:
result.extend(merge_sort(right))
return result
merge_sort(a)
复杂度分析
一趟归并需要将记录中相邻的长度为h的有序序列进行两两归并,这需要将待排序序列中的所有记录扫描一遍,因此需要耗费
O(n)时间,而由完全二叉树的深度可知,整个归并排序需要进行logn次,因此,总的时间复杂度为O(nlogn),而且这是归并排序算法中最好、最坏、平均的时间性能。空间复杂度为O(n+logn)
归并排序不存跳跃,因此算是一种稳定的排序算法