-
快速排序和归并排序及其区别
首先在预期情况下的快速排序和归并排序时间复杂度都是O(NlogN), 在空间复杂度上,没使用临时栈的快速排序在空间上优于归并排序。
其次,在稳定性上来说,快速排序是不稳定的排序,归并排序与堆排序一样是稳定的排序,即排序后,比较值相同元素相对位置不变。如会议安排,通过比较会议优先级进行排序,若存在两个优先级相同的会议,但输入数据中前一个会议是下午开始,后一个是明天才开始,不稳定的排序可能会把明天才开始的会议放在下午的会议前面,在很多情况下,这种结果是不能被接受的。但若是对两个数组排序,那就不存在这个问题了。
再次,在并行的角度上说,二者都很容易实现分布式算法。归并排序将子序列分发下去后,需要等待其下属计算机的反馈,等得到有序子序列后,才能进行合并操作。相反快速排序却不一样,它只需告诉其下属计算机:将得到的子序列排序即可,并不需要各个计算机协同工作。
最后,值得一提的是,归并排序相比于快速排序,在面对大型数据集时显得更有效,因为归并排序并不需要一次装载全部数据(快速排序需要一次装入,选择分界值分割序列),而且快速排序需要不断切换子序列,这将增加内存分页,并大大减缓了算法的运行。
-
快速排序
快速排序是一种不稳定的排序,与堆排序相比,它也有着O(NlogN)预期的性能,但最坏的情况时间复杂度会上升到O(N**2).
算法思想:
每次选择一个分界值divider,将小于divider的放置在序列左边,大于等于divider的放置在右边,然后再分别递归解决左边,和右边
最佳的情况:每次选择的divider都处于序列中间,递归解决,我们可以发现递归树的每层的单个节点中的子序列长度在以2的指数幂递减,
最终形成的递归树的深度是logN,而每层总的元素数总是N,故每层需要处理的元素数为N-有限个divider数,N很大时可忽略不计,因此每层需处理的元素数粗略视为N,而递归树层数为logN,故最佳时间复杂度为O(NlogN)
最坏的情况:每次选择的的divider都是极限值(最大或最小值),导致左边或右边为空。这样导致的
递归结果:若第一层(次)需要递归处理元素数为N,那么接下来调用递归处理左边和右边,需要处理的元素数为N-1,
再下一层是N-2,.....1 ,0,这样递归深度就达到了N的层次,整个递归树处理元素个数为N+N-1+...+1 =(N+1)N/2,
即时间复杂度达到O(N**2)
合理的选择分界值:面对几十亿的数据,logN 大约是30,比N小得多,因此分界值divider需要合理的选择,否则很容易达到O(N**2),更重要的是会导致耗尽堆栈空间,导致程序崩溃。
*** 选择第一个元素作为divider
当数组本身有序时,或部分有序时,效果不佳
*** 在排序之前,将数组随机排列,随机排列使用线型同余发生器,时间复杂度为O(N)
当数组size很大时,会消耗一定时间
*** 比较首、中间、末三个元素,以他们的中位数作为分界值
避免了取极值的情况,但无法避免靠近极值
*** 随机待处理序列中的一个值作为分界值
由于随机性,很难遇到最坏情况
Python实现快速排序:
def quick_sort(array, start, end):
# 没有或只有一个元素直接结束
if start < 0 or start >= end:
return
# 选取第一个作为divider
divider = array[start]
low = start
high = end
# 把小值放在左边,大值放在右边,起始空位是divider==start,
# end从右往左扫描,直到遇到low。碰到小于divider的值,将之放置到low,此时空位为end
# low从左往右扫描,直到遇到end。碰到大于divider的值,将之放置到end的位置,此时空位为low
# 扫描结束后,将divider放置到low位置,使之处于“中间”位置
# 递归解决左边(start,low-1),(low+1,end)
while low < high:
# end从右往左扫描,直到遇到low。
while array[high] >= divider:
high = high - 1
if low >= high:
high = low
break
array[low] = array[high]
# 扫描结束
if low >= high:
array[low] = divider
break
while array[low] < divider:
low = low + 1
if low >= high:
low = high
break
array[high] = array[low]
# 扫描结束
if low >= high:
array[low] = divider
break
# 递归解决左边
quick_sort(array, start, low - 1)
# 递归解决左边
quick_sort(array, low + 1, end)
-
归并排序
归并排序:
和快速排序一样,归并排序也是使用一个分而治之的策略。但归并排序和快速排序不同,归并排序选择中间元素为分解点,将序列分成两等份,然后分别递归处理左右两部分。
算法思想:
归并排序可分为两部分:
第一部分为“分”:将序列分成两等份,并分别递归处理他们
第二部分为“并“:当下层递归结束返回后,我们需要将下层递归处理好的子序列”并“起来,即将两个有序子序列 合并成一个有序序列
合并完后将序列拷贝到原数组中,然后结束本层递归调用,返回到上一层递归调用,继续执行“并”,直到第一层递归调用也结束,算法结束
Python实现归并排序:
# array为原数组,array_tmp为临时数组,array_tmp由调用函数定义,降低程序内存压力
def merge_sort(array, array_tmp, start, end):
# 递归出口
if start == end:
return
mid_point = (start + end) // 2
merge_sort(array, array_tmp, start, mid_point)
merge_sort(array, array_tmp, mid_point + 1, end)
# 合并子序列start~mid_point,mid_point+1~end
left_index = start
right_index = mid_point + 1
tmp_index = start
while left_index <= mid_point and right_index <= end:
if array[left_index] <= array[right_index]:
array_tmp[tmp_index] = array[left_index]
left_index = left_index + 1
else:
array_tmp[tmp_index] = array[right_index]
right_index = right_index + 1
tmp_index = tmp_index + 1
# 将左右剩余的元素加入array_tmp
while left_index <= mid_point:
array_tmp[tmp_index] = array[left_index]
tmp_index = tmp_index + 1
left_index = left_index + 1
while right_index <= end:
array_tmp[tmp_index] = array[right_index]
tmp_index = tmp_index + 1
right_index = right_index + 1
# 将array_tmp中左右子序列合并成的新的有序序列拷贝回array,供上一层递归调用使用它(作为上一层的子序列)
for i in range(start, end + 1):
array[i] = array_tmp[i]
# 调用接口
def mergeSort(array):
# array_tmp = array会导致二者指向同一对象,合并时出错
array_tmp = [0] * len(array)
merge_sort(array, array_tmp, 0, len(array) - 1)
PS:代码clone地址:github.com:Rlyslata/DataStauct-And-Algorithm.git
路径:数组排序算法/MergeSort 数组排序算法/QuickSort