随机快排
快排:quick sort。
- 分治算法;
- 把一个序列分成较大的和较小的2个子序列,然后递归地排列两个子序列。
步骤:
- 挑选基准值:从数列中任意挑一个元素,成为“基准”(pivot),和最后一个元素进行交换;
- 分割:重新排列数列,所有比基准值小的元素摆放在基准的前面,所有比基准值大的元素摆在基准后面(与基准相等的数可以放到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
- 递归排序子数列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
注意:
- 递归到最底部的判断条件是数列的大小是0或1,此时该数列显然已经有序。选取基准值有数种具体的方法,选取方法对排序时间性能有决定性影响(随机快排vs经典快排)。
- 随机快排时间复杂度的期望是O(NlogN)。
Python实现:
import random
def quickSort(numbers):
if len(numbers) <= 1:
return numbers
left, right, mid = [], [], []
pivot = random.choice(numbers)
for number in numbers:
if number == pivot:
mid.append(number)
elif number < pivot:
left.append(number)
else:
right.append(number)
return quickSort(left) + mid + quickSort(right)
def quick_sort(L):
return q_sort(L, 0, len(L) - 1)
def q_sort(L, left, right):
if left < right:
pivot = Partition(L, left, right)
q_sort(L, left, pivot - 1)
q_sort(L, pivot + 1, right)
return L
def Partition(L, left, right):
pivotkey = L[left]
while left < right:
while left < right and L[right] >= pivotkey:
right -= 1
L[left] = L[right]
while left < right and L[left] <= pivotkey:
left += 1
L[right] = L[left]
L[left] = pivotkey
return left
L = [5, 9, 1, 11, 6, 7, 2, 4]
print quick_sort(L)
经典快排
类似于随机快排,但是在选取基准值上有所不同。
步骤:
- 挑选基准值:选取数组的最后一个数x作为基准值;
- 分割:把整个数组划分成小于等于x和大于x两部分,将x和大于x部分数组的第一个元素交换位置,此时整个数组划分成了|小于等于x|x|大于x|三个部分,也就是这一次排序将x值排好了位置;
- 递归排序子数列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。
注意:
- 经典快排的问题是它的时间复杂度与数据状况有关。每次选择x值都是数组的最后一个数,如果数组是[5, 4, 3, 2, 1],时间复杂度就会变成O(N2)。
- 随机快排和经典快排的区别在于随机快排增加了随机性,使比较的数x具有随机性。
- 相对于随机快排的实现,经典快排只需将pivot设定为数组的最后一个值即可。
堆排序
堆排序图解Youtube
下面内容的英文链接
堆排序是通过把数组“假想成”完全二叉树的方式来进行排序。
概念
二叉树:binary tree
二叉树是指每个父节点最多有两个子节点的树。
满二叉树: full tree
满二叉树是一种特殊的二叉树。满二叉树的每个父节点要么有两个父节点,要么没有父节点。
完全二叉树:complete tree
每层结点都完全填满,在最后一层上如果不是满的,则只缺少右边的若干结点。
堆结构:heap data structure
堆是一种特殊的基于树的数据结构。如果满足以下条件,则说二叉树是堆:
- 它是一个完整的二叉树
- 大根堆(max heap):树中的所有父节点都大于子节点。
- 小根堆(min heap):树中的所有父节点都小于子节点。
由数组构建完全二叉树
- 选择列表的第一个元素作为根节点。
- 将第二个元素作为根节点的左子节点,将第三个元素作为右子节点。
- 将下两个元素作为第二级左节点的子节点。同样,将下两个元素作为第二级右节点的子节点。
- 不断重复,直到到达最后一个元素。
数组索引和完全二叉树节点的关系
完整的二叉树具有一个有趣的属性,我们可以用来查找任何节点的子节点和父节点。
如果数组中任何元素的索引为 i ,则索引2i + 1中的元素将成为元素 i 的左子节点,而索引2i + 2中的元素将成为元素i的右子节点。同样,索引 i 处任何元素的父节点都由 (i-1) / 2 的下限给出。
E.g. 索引为
4
4
4元素的父节点为
(
4
−
1
)
/
2
=
1.5
(4-1)/2=1.5
(4−1)/2=1.5 我们使用floor操作得到父节点的索引为1。
将树转化成堆
从完全二叉树开始,我们可以通过在堆的所有非叶节点上运行heapify的函数,将其修改为大根堆。简单来说就是将父节点(i)和其子节点(2i+1 & 2i+2)进行比较,将较大的节点交换到父节点的位置。如果父节点本身就比子节点大,那么不需要进行任何操作。
当树有多层的时候,使用递归的思想。
要想把任何树转化成最大堆,我们可以从下至上开始对每个子树使用heapify操作直至所有的非叶节点都被heapified过。
对于完全二叉树,非叶子节点的第一个索引由 (n / 2)- 1 给出。在它之后的所有其他节点都是叶节点,因此不需要进行heapify操作。我们从索引为 (n / 2)- 1 的节点开始heapify,然后不断heapify索引-1(对应完全二叉树向左向上)的节点,直到到达跟节点。
堆排序的步骤:
- 由于树满足max-heap的属性,因此最大的项存储在根节点上。
- 将根部元素和尾部元素互换
- 将堆的大小减小1,然后再次从 (n / 2)- 1 进行heapify操作 ,以便在根处拥有的元素值最大。
- 重复该过程,直到对列表中的所有项目进行排序。
时间复杂度:O(nlogn) 不受数据状况的影响。
*完整的图示可以看我上面的英文链接。
Python实现
def heapify(arr, n, i):
# Find the largest among root and children
largest = i
l = 2 * i + 1 # index of left node of i
r = 2 * i - 1 # index of right node of i
# l < n & r < n 确保不会越界(如果i是叶节点2i+1就会越界)
if l < n and arr[i] < arr[l]:
largest = l
if r < n and arr[largest] < arr[r]:
largest = r
# if root is the largest, swap with largest and continue heapifyinf
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
# Build heap
# 从最后一个节点开始,对整个完全二叉树进行heapify操作
# 注意我们在上面限制了l<n&r<n,所以不会对叶节点进行heapify操作
# 这里首先将原始数组变成了大根堆
for i in range(n, 0, -1):
heapify(arr, n, i)
# 这里在上面大根堆的基础上开始-1循环
for i in range(n-1, 0, -1):
# swap
arr[i], arr[0] = arr[0], arr[i]
# 互换之后,堆中就少了一个元素,所以堆的个数变了,变为i
# 此时由于堆只变了跟节点,所以只需要对根节点进行heapify
# 然后根节点又回变成值最大的点
heapify(arr, i, 0)
if __name__ == '__main__':
arr = [1,3,5,7,3,2,8,0,-1,-2]
output = heapSort(arr)
print(output)