搜索与排序—— 归并排序与快速排序

Posted 鲸骑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了搜索与排序—— 归并排序与快速排序相关的知识,希望对你有一定的参考价值。

搜索与排序(四)—— 归并排序与快速排序

本节我们来学习一下排序中效率较高的两种。

归并排序

归并排序采用分而治之策略来提高算法性能。实现原理是采用递归,不断将列表拆分成一半,直至列表为空或只有一个项,再按基本情况进行排序;一旦两半排序完成,执行合并的基本操作。

图解

一、拆分

搜索与排序(四)—— 归并排序与快速排序

二、排序与合并

搜索与排序(四)—— 归并排序与快速排序

代码

 1def mergeSort(alist):
2    if len(alist) > 1:
3        mid = len(alist) // 2
4
5        lefthalf = alist[:mid]
6        righthalf = alist[mid:]
7
8        mergeSort(lefthalf)
9        mergeSort(righthalf)
10
11        i, j, k = 0, 0, 0
12
13        while i < len(lefthalf) and j < len(righthalf):
14            if lefthalf[i] <= righthalf[j]:
15                alist[k] = lefthalf[i]
16                i += 1
17            else:
18                alist[k] = righthalf[j]
19                j += 1
20            k += 1
21
22        while i < len(lefthalf):
23            alist[k] = lefthalf[i]
24            i += 1
25            k += 1
26
27        while j < len(righthalf):
28            alist[k] = righthalf[j]
29            j += 1
30            k += 1

复杂度

将列表递归划分需要log^n次,每次排序合并需要进行n个操作。故总复杂度为O(nlog^n)。
不过需要注意的是,归并算法在递归中需要额外的空间来保存两个半部分。如果列表很大,空间占用会非常大。

快速排序

快速排序也采用了分而治之来提高效率,而且与归并算法相比,不需要额外的空间来存储。

实现原理是先选择一个枢纽值(本文选择枢纽值的较简单,采用列表首个元素为枢纽值),在从左(列表第二项)往右选择一个比枢纽值大的元素作为左标记,从右(列表末尾项)往左选择一个比枢纽值小的元素为右标记。接着交换左右标记的值。在重复寻找左右标记,直至右标记的位序小于左标记,此时交换枢纽值与右标记。

然后以交换后的枢纽值位置划分列表位两部分,对每一部分进行上诉同样的操作。

图解

一、第一轮交换

该图演示了第一轮中的全部交换过程。

二、分割列表继续局部交换。

代码

 1def quickSort(alist):
2    quickSortHelper(alist, 0, len(alist)-1)
3
4
5def quickSortHelper(alist, first, last):
6    if first < last:
7        splitpoint = partition(alist, first, last)
8
9        quickSortHelper(alist, first, splitpoint-1)
10        quickSortHelper(alist, splitpoint+1, last)
11
12def partition(alist, first, last):
13
14    leftmark = first + 1
15    rightmark = last
16
17    done = False
18
19    while not done:
20
21        while alist[leftmark] <= alist[first] and leftmark <= rightmark:
22            leftmark += 1
23
24        while alist[rightmark] >= alist[first] and rightmark >= leftmark:
25            rightmark -= 1
26
27        if rightmark < leftmark:
28            done = True
29        else:
30            alist[leftmark], alist[rightmark] = alist[rightmark], alist[leftmark]
31
32    alist[first], alist[rightmark] = alist[rightmark], alist[first]    
33
34    return rightmark

复杂度

如果交换后的枢纽值总是出现在列表中部左右,则每轮划分操作需要log^n, 每轮比较交换需要n, 复杂度位O(nlog^n)。

不过需要注意的是,交换后的枢纽值如果一直在列表左右侧的话,最坏的情况下降到递归划分需要O(n)了,总为O(n^2)。

所有枢纽值的选择很关键。有一种选择枢纽值的算法是比较列表的首项,末尾项,以及中间项三项中的中间大小值作为枢纽值。有兴趣的朋友可以自行尝试一下。


- end -

以上是关于搜索与排序—— 归并排序与快速排序的主要内容,如果未能解决你的问题,请参考以下文章

快速排序与归并排序

排序与搜索一览

重学数据结构和算法之归并排序快速排序

JavaScript 数据结构与算法之美 - 归并排序快速排序希尔排序堆排序

常用排序快速排序与归并排序

代码与算法集锦-归并排序+树状数组+快排+深度优先搜索+01背包(动态规划)