Overview
1 Partition
要点:
- pivot
- 返回值:k,x 数组的 k 位置已经是正确的顺序。
- 返回值是“随机”的。
- pivot 值在“进一步优化”的角度来说很重要,尽可能要选择中位数。
- 因为不知道中位数等,pivot 在无其它调整/优化的基本 Partition 算法中本质是随机的。
Python 语言实现 partition:
def partition(arr, left, right):
if left >= right:
return
_l = left
_r = right
pivot = arr[left]
while True:
while arr[_l] <= pivot:
_l += 1
if _l == right:
break
while arr[_r] >= pivot:
_r -= 1
if _r == left:
break
if _l >= _r:
break
arr[_l], arr[_r] = arr[_r], arr[_l]
arr[left], arr[_r] = arr[_r], arr[left]
return _r
这边的写法是基于 《算法(java)》中的实现。不同的书籍等对与 partition 的实现细节上可能略有不同,比如判断边界,退出循环的写法等,但是程序逻辑上都是一样的。
参考 stackoverflow.com 🔗 Quicksort with Python 有更简洁的写法。
def partition(array, begin, end): pivot = begin for i in xrange(begin+1, end+1): if array[i] <= array[begin]: pivot += 1 array[i], array[pivot] = array[pivot], array[i] array[pivot], array[begin] = array[begin], array[pivot] return pivot
经过测试是正确的!,可以 copy 直接使用!
2 应用
2.1 Quick Sort
要点:partition 的 k 位置是排好序的,k 位置一分为二,分别调用 quick sort 继续排序(递归)。
使用递归可以很简单地基于 partition 函数实现快速排序(不过对于非常长的数组,会存在递归深度过长的情况)
递归实现的快速排序:
def _quick_sort(arr, left, right) -> None:
if left >= right:
return None
middle = partition(arr, left, right)
_quick_sort(arr, left, middle - 1)
_quick_sort(arr, middle + 1, right)
return None
def quicksort(arr) -> None:
# 可以考虑调用 build-in function suffle 函数,随机化数组顺序。
_quick_sort(arr, 0, len(arr) - 1)
不过 Python 内置的 sorted 函数很好用,所以如果是给定的确定列表做排序,自然不会自己去实现。
相信会用到自己去写排序算法的时候,一般都是变体的应用场景。
快速算法算法的复杂度: 1.39 N l g N 1.39NlgN 1.39NlgN
2.2 第(前) K 大(小)的数
2.2.1 Partition 解法
要点:
将 partition 的返回值和 k 比较,直到等于 k 为止。注意返回值 和 k 不同,下一步要选择合适的范围继续调用 Partition
复杂度: O ( n ) O(n) O(n)
实现:
2020/07/13
因为在做 leetcode,遇到一道题,需要找到“前K大的数”,所以使用了 partition 函数。
并且做了实现,于是就在这篇 blog 上更新具体的实现如下。
def find_k_minimal_numbers(array, k):
'''in place'''
p = -1
L = 0
R = len(array) - 1
while True:
p = partition(array, L, R)
if p == k - 1:
return
else:
if p < k - 1:
L = p + 1
else:
R = p - 1
本来找到前 K 个大的数想要用堆实现,但是想来堆的实现相对于 partition 来说肯定是要复杂的。所以在数组不是非常大的情况下,partition 解法应该是优先选择。
2.2.2 大数据集 - 堆解法
要点:
- 第(前)K 大 - 最小堆;插入新的较大值,删除(挤出)最小值
- 第(前)K 小 - 最大堆;插入新的较小值,删除(挤出)最大值
复杂度: O ( n l o g k ) O(nlogk) O(nlogk)
3 关于堆
要点:
- 二叉树
- 完全二叉树性质
- 堆性质
- 显式(链表)表示方式和隐式(数组)表示方式
完全二叉树性质在隐式(数组)表示方式中的节点定位应用。
3.1 实现
要点:
结构实现
使用数组保存(节点)值。索引即节点。
使用数组的实现,需要对数组索引 0 位置使用一个占位符,因为需要从 1 开始索引。
维护一个“真实”堆的值的大小,操作函数中对此有很多需要。操作函数
插入
在数组末尾添加元素,保证完全二叉树性质;
需要一个向上交换函数,交换新的末尾元素,保证堆性质。删除(最大/小 - 顶点)
取顶点值,数组末尾与顶点交换,删除数组末尾元素(即原来的顶点),更新维护的长度值;
需要一个向下交换函数,交换前面的交换到顶点的原末尾元素,保证堆性质;
因为堆中无左右孩子大小排序一说,所以需要另外一个辅助函数,取左右还是中的最小顶点(最小堆)或是最大顶点(最大堆)返回给调用方 – 向下交换函数。O ( n ) O(n) O(n) 构建堆
Python 中考虑使用
@classmethod
3.2 应用
3.2.1 一、 堆排序 - O ( n l o g n ) O(nlogn) O(nlogn)
因为构建堆 -
O
(
n
)
O(n)
O(n) 复杂度就能得到数组最小值(正序排序,最小堆)
+
n 次 *
“删除”最小元素(正序排序)- 顶点
(
l
o
g
n
)
(logn)
(logn) ⇒
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
||
\/
O ( n ) + O ( n l o g n ) O(n) + O(nlogn) O(n)+O(nlogn) ⇒ O ( n l o g n ) O(nlogn) O(nlogn)