主要是通过递归的方法,快排的基本原理就是先选定一个基准,然后把数组中全部小于这个数的放到基准的前面,大于这个数的放到基准的后面。
每次递归的输出结果分成了三部分,再叠加成一个数组,分别是小于基准的数组,基准,大于基准的数组。然后再对分出来的数组不断进行快排,直到只剩下一个元素的时候,直接返回。 注意不能数组+数字,所以返回的基准要放在数组里!
算法的时间复杂度是nlogn。
时间复杂度的解释,可以知道我们快排的解决办法就是分治法,每一次根据基准划分为两部分,相当于二叉树,如下图所示,并且我们知道二叉树的深度是logn,因为每一层除了第一层等于N之外,其他都是小于N,暂且记为CN,所以总的时间复杂度是NlogN。只要不是[1,2,3,4,5]这种已经排好序的情况的最坏时间复杂度是O(N*N),这种情况可以打乱顺序来优化避免这种情况。
快排里面partition耗费的时间复杂度是O(N)。最终的O(NLOGN)其实是计算来的。算的平均情况,其实很明显 最差的情况是----每次分出来就是N-1和1个,相当于原本就是有序的序列来快排:O(N) = N + O(N-1) = N+N-1+O(N-2)= …= O(N*N)。最好的情况是O(NLGN),因为每次算的是平均划分的情况: 其实关键就是划分次数少了,从上述的N次划分降到了LOGN次划分,这就是关键之处。
满二叉树深度为logN,如何计算:
1、每一层的节点数目是上一层的2倍,也就是2,4,8,16
2、计算等比数列的和,可以得到深度为log2(N)
下面是基准划分的情况
1.最好的情况
基准划分之后,左右两边的规模都不大于n/2。这样的快排性能最好。一个是n/2(向下取整),一个是n/2-1(向上取整)。
T(n) = *T(n/2)2 + O(n) = O(nlgn)。
2.平均的情况:
平均的运行时间接近最好的情况。总代价也是O(nlgn),只要是常数比例的情况,就是O(nlgn)。
为什么?举例:
下面是1:9的情况: 从n, 9/10n, (9/10)^2n… 可以看到,最后到1,计算有多少层,故 (9/10)^d * n= 1
所以, 深度d为log10/9 n---------
设想,如果此时划分的不是9/10,而是接近于1,因为如果每次都是最坏的情况,那么其实就是1,则时间复杂度就是n,那么乘以每一层n,就是n^2
3.最坏的情况
划分之后,一个是n-1个元素,一个是0个元素。划分操作的时间复杂度是O(n),所以总和是:T(n) = T(n-1) + T(0) + O(n) = T(n-1) + O(n) = … = n×O(n) = O(n^2)。
如果划分平衡,快排的性能与归并算法一样;如果不平衡则接近插入排序。
1. 算法导论-快排
快排的期望时间复杂度以及平均时间复杂度是O(nlogn),最坏时间复杂度是O(n^2)。
后面说的随机选择快排(quick select)的期望时间复杂度以及平均时间复杂度是O(nlogn),最坏时间复杂度是O(n^2)。
注意,写的版本中,partition都是以最后一个为基准的,否则需要考虑更多的问题。所以在quick sort的过程中,也就是在搜索之前,先将随机选择的inedx与最后一个index交换值。
基本思想是选定最后一个数作为基准,然后**小于该数的交换(倘若还没有出现大于基准的数,就是与本身交换;如果出现了,就是与大于基准的数进行交换),大于该数的不动。**最后将基准交换到数组中。
def quicksort(arr, p, q):
if p < q:
if not arr[p:q]: # j+1是0的时候,J=-1会导致出错,所以需要 p<q
return []
x = arr[q]
j = p-1
for i in range(p,q):
if arr[i] <= x:
j += 1
arr[j], arr[i] = arr[i], arr[j]
arr[j + 1], arr[q] = arr[q], arr[j + 1]
quicksort(arr, j + 2, q)
quicksort(arr, p, j)
return arr
2. 算法导论
import random
def partition(A, p, r):
# 快排优化
choose_index = random.randint(p, r)
A[choose_index], A[r] = A[r], A[choose_index]
x = A[r]
i = p-1
for j in range(p, r):
if A[j] <= x:
i += 1
A[i], A[j] = A[j], A[i]
A[i+1], A[r] = A[r], A[i+1]
return i+1 # 标准的位置
# partition的作用是可以返回任意第k个已经找好位置的值的序号,并且,这个值已经在arr里的相应位置。
def quicksort(A, p, r):
if p < r: # 因为后面的减1,到了0之后,仍然继续,就是负的,此时到了list的尾部。
q = partition(A, p, r)
quicksort(A, p, q-1)
quicksort(A, q+1, r)
理解时间复杂度nlogn,栈的高度是logn,每一层处理的元素的个数是n,所以相乘是nlogn.
quick select
时间复杂度是O(N):
假设最好的情况,则每次划分是1/2
每一次的划分的时间复杂度是 N、N/2、 N/4… 1总的时间复杂度就是O(2N)
=====N*(1-1/2 ** n) / (1-1/2) = 2 * N
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
class Solution(object):
def findKthLargest(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: int
"""
# 粗心致命,一遍过 注意快排用 random来优化
def quick_select(nums, index1, index2):
def partition(nums, index1, index2):
record = index1 - 1 # 记录比index2大的
# 快排优化:随机选择,注意把随机之后的序号与最后一个交换,不能以随机的作为基准。
chooose_num = random.randint(index1, index2)
nums[index2], nums[chooose_num] = nums[chooose_num], nums[index2]
cmp_num = nums[index2]
#cmp_num = nums[chooose_num]
for i in range(index1, index2+1):
if nums[i] <= cmp_num:
record += 1
nums[record], nums[i] = nums[i], nums[record]
return record
if index1 < index2:
index = partition(nums, index1, index2)
if index > len(nums) - k:
quick_select(nums, index1, index-1)
elif index < len(nums) - k:
quick_select(nums, index+1, index2)
else:
return
return nums
quick_select(nums, 0, len(nums)-1)
return nums[len(nums) - k]