荷兰国旗问题
荷兰国旗问题:给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
以arr = [3,8,9,2,5,5],num = 5为例。
解决荷兰国旗问题的思路
初始化小于边界less = -1,大于边界more = len(arr) = 6,当前比较的位置cur = 0。
- 如果当前位置上的数小于 num,交换当前位置上的数与边界less后一个元素,扩大小于num的范围,less += 1,cur += 1;
- 如果当前位置上的数大于 num,交换当前位置上的数与边界more前一个元素,扩大大于num的范围,more -=1, cur 不变;
- 如果当前位置上的数等于num,less和more均不变,cur += 1。
当cur = more时,停止比较,返回。
第一步:
初始化小于边界less = -1,大于边界more = len(arr) = 6,当前比较的位置cur = 0。
第二步:比较cur = 0位置上的数与num
arr[cur] = 3,arr[cur] < num,所以,交换当前位置上的数与边界less后一个元素(此处,两者为同一元素3,可不做交换),less += 1, cur += 1。得到:less = 0,cur = 1,more = 6。
第三步:比较cur = 1位置上的数与num
arr[cur] = 8, arr[cur] > num,所以,交换当前位置上的数与边界more前一个元素,more -= 1,cur不变。得到:less = 0,cur = 1,more = 5。
第四步:比较cur = 1位置上的数与num
arr[cur] = 5, arr[cur] = num, 所以,less不变,more不变,cur += 1。得到:less = 0,cur = 2,more = 5。
第五步:比较cur = 2位置上的数与num
arr[cur] = 9, arr[cur] > num, 所以,交换当前位置上的数与边界more前一个元素,less不变,more -= 1,cur不变。得到:less = 0,cur = 2,more = 4。
第六步:比较cur = 2位置上的数与num
arr[cur] = 5, arr[cur] = num, 所以,less不变,more不变,cur += 1。得到:less = 0,cur = 3,more = 4。
第七步:比较cur = 3位置上的数与num
arr[cur] = 2, arr[cur] < num, 所以,交换当前位置上的数与边界less后一个元素,less += 1,cur += 1,more不变。得到:less = 1,cur = 4,more = 4。
第八步:
此时,cur = 4 = more,停止比较,返回数组。实现了小于num的在左边,大于num的在右边,等于num的在中间。
代码实现
def partition(arr, l, r, num):
less = l - 1 # 小于边界
more = r + 1 # 大于边界
cur = less + 1 # 当前比较位置
while cur < more:
if arr[cur] < num:
arr[less + 1], arr[cur] = arr[cur], arr[less + 1]
less += 1
cur += 1
elif arr[cur] > num:
arr[more - 1], arr[cur] = arr[cur], arr[more - 1]
more -= 1
else:
cur += 1
return arr
if __name__ == '__main__':
a = [3, 8, 9, 2, 5, 5]
print(partition(a, 0, len(a) - 1, 5))
经典快排
经典快排的思路
取数组最后一个元素作为划分值,将小于划分值的数放在左边,大于划分值的数放在右边,等于划分值的数放在中间。然后,对左右两部分继续进行上述划分。直到仅剩一个元素为止。
仍以数组arr = [3, 8, 9, 2, 5, 5]为例:
第一步:
取数组最后一个元素做划分值,即num = 5。将小于5的放在左边,大于5的放在右边,等于5的放中间。属于arr=[3,8,9,2,5,5],num = 5的荷兰国旗问题。得到小于边界less = 1,大于边界more = 4,如下图:
第二步:
对于0至less部分,取其最后一个元素做划分值;相当于是arr = [3,2],num = 2的荷兰国旗问题。得到小于边界less = -1,大于边界more = 1。
对于more至 len([3,2,5,5,9,8]) 部分,仍取其最后一个元素做划分值;相当于是arr = [9,8],num = 8的荷兰国旗问题。可得小于边界less = 3,大于边界more = 5。如下图:
第三步:
对于左边数组[2,3],其数组边界为left = 0,right = 1
less = -1,对于left到less部分,left大于less,没有元素,返回;
more = 1,对于more到len([2,3]) = 2,仅有一个元素,返回;
对于右边数组[8,9],其数组边界为left = 4,right = 5
less = 3,对于left到less部分,left大于less,没有元素,返回;
more = 5,对于more到 len([2,3,5,5,8,9]) = 6, 仅有一个元素,返回。
排序完成!
代码实现
def quick_sort(arr, l, r, num):
if l < r: # 当满足这个条件 一直做下去
less, more = partition(arr, l, r, num)
quick_sort(arr, l, less, arr[less])
quick_sort(arr, more, r, arr[r])
return arr
def partition(arr, l, r, num):
less = l - 1
more = r + 1
cur = less + 1
while cur < more:
if arr[cur] < num:
arr[less + 1], arr[cur] = arr[cur], arr[less + 1]
less += 1
cur += 1
elif arr[cur] > num:
arr[more - 1], arr[cur] = arr[cur], arr[more - 1]
more -= 1
else:
cur += 1
return less, more
if __name__ == '__main__':
arr = [3, 8, 9, 2, 5, 5]
print(quick_sort(arr, 0, len(arr) - 1, arr[-1]))
经典快排存在的缺陷
每次都使用数组最后一个元素做划分值,复杂度跟数据状况有很大的关系。
以数组 [1,2,3,4,5,6]为例,每次只排好了一个数,时间复杂度是O(n^2)。
于是,有了随机快排。
随机快排
随机快排的思路
随机选取数组中的数作为划分值,将小于划分值的数放在左边,大于划分值的数放在右边,等于划分值的数放在中间。然后,对左右两部分继续进行上述划分。直到仅剩一个元素为止。使用随机,完美地避开了数据状况问题。
代码实现
仅需在经典快排的基础上,添加一个随机函数即可。
import random, math
def quick_sort(arr, l, r, num):
if l < r:
# 随机选取l到r之间的数,与最后一个位置上的数进行交换
# 舍弃浮点数后面的小数位,并取整
random_pos = l + int(math.floor(random.random() * (r - l + 1)))
arr[r],arr[random_pos] = arr[random_pos],arr[r]
less, more = partition(arr, l, r, num)
quick_sort(arr, l, less, arr[less])
quick_sort(arr, more, r, arr[r])
return arr
def partition(arr, l, r, num):
less = l - 1
more = r + 1
cur = less + 1
while cur < more:
if arr[cur] < num:
arr[less + 1], arr[cur] = arr[cur], arr[less + 1]
less += 1
cur += 1
elif arr[cur] > num:
arr[more - 1], arr[cur] = arr[cur], arr[more - 1]
more -= 1
else:
cur += 1
return less, more
if __name__ == '__main__':
arr = [3, 8, 9, 2, 5, 5]
print(quick_sort(arr, 0, len(arr) - 1, arr[-1]))