快速排序的时间复杂度
关于如何分析快速排序的时间复杂度自己一直不是很清楚,在看到快速排序最好,最坏,平均复杂度分析这篇文章后,作为一个小白也很快理解了,推荐给大家阅读以下。
文章中的一个主要思想就是利用递归树来描述递归算法的执行情况。
快速排序的最好情况:树的每个节点是带排序序列的中间值,递归树是平衡的,此时的性能很好。在最优的情况下,递归树的深度为(log2 n)+1仅需要递归log2n次。
快速排序最坏的情况:需要排序的序列为正序或者逆序,每次划分只得到比上一次划分少一个记录的子序列。时间复杂度为O(nn)。
由数学归纳法算出平均情况为O(nlogn)
归并排序
归并排序(merge_sort)是建立在归并排序上的一种有效的排序算法,该算法采用分而治之的思想。先使每个子序列有序,将已有序的子序列合并,得到完全有序的序列。最常用的是二路归并,即将两个有序表合并为有序表。
归并排序相比快速排序时稳定的(不会改变相等元素的顺序),速度仅次于快速排序。
算法的实现过程: 这里参照wiki百科的动图,完美展现了归并算法的实现过程。
算法的代码实现
自己仅仅依照上面对归并排序的思想的一个描述还是难以实现整个算法,还是继续参照维基百科的实现思路:
- 首先递归地将列表拆分成单元素的有序列表
- 在每次返回值后对列表进行合并并排序
代码实现过程如下。
```python
#归并排序算法代码实现
#2018-12-4
#王小灿
def merge_sort(arr):
"""输入一个无序的数组,利用归并算法从小到大排列"""
left = []
right = []
if len(arr) <= 1 :
return arr
else :
left = arr[0:int(len(arr)/2)]
right = arr[int(len(arr)/2): ]
left = merge_sort(left)
right = merge_sort(right)
return merge(left,right)
def merge(left,right) :
"""输入左右两个数组,对两个数组进行合并,返回一个有序的数组"""
arr = []
while (len(left) > 0) and (len(right) > 0):#当两个都有元素时执行
if left[0] <= right [0]:
arr.append(left[0])
left.pop(0)
else:
arr.append(right[0])
right.pop(0)
while len(left) > 0:
arr.append(left[0])
left.pop(0)
while len(right) > 0:
arr.append(right[0])
right.pop(0)
return arr
arr = [3,1,8,2,0,3,56,12,88,3,4,9]
print(str(merge_sort(arr)))
在整个代码实现过程中,遇到递归深度报错,一直在找问题,但是当排序的列表只有一个元素的时候还是在报错,应该是代码的实现有问题。采用了一个两个元素的列表对整个实现的流程进行了梳理,果然是细节错误。
另外合并的循环条件也比较巧妙。
对小白来说,一个归并算法都要搞好久,哭唧唧。
比较合并排序和快速排序
在大O表示法中,n实际上表示c*n,其中c代表固定的时间量。我们通常不考虑这个时间量,因为如果这两种算法的大O运行时间不同,这种常量将无关紧要。就拿二分法和简单查找来举例说明。
假设两种算法的运行时间包含如上所示的常量,如果我们在40亿个元素的列表中查找,二分法查找的速度还是快的多,常量没有什么影响。
但有的时候常量的影响很大,快速查找的常量比合并查找小,如果时间复杂度相同,快速查找的速度将更快。
小结
- 分而治之的思想将问题逐步分解。使用D&G处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
- 实现快速排序时,随机地选择用基准值元素。快速排序的平均运行时间为O(n log n)
- 大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在
- 比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时,O(log n )的速度比O(n)快的多