堆排序
堆是一个特殊的完全二叉树。
挨个出数得过程要清晰。下面得问题是怎么建立一个堆?
看最后一个非叶子节点,对这个子树做一个调整,大的调整到上面,然后依次往前推。详看视频23,农村包围城市。
建立好堆之后,就可以用挨个出数来。
def sift(li, low, high): # 先写调整函数。
"""
:param li:列表
:param low:堆的根节点位置。
:param high:堆的最后一个元素的位置。
"""
i = low # i最开始指向根节点
j = 2 * i + 1 # j开始是左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j + 1] > li[j]: # 右孩子有并且比较大。
j = j + 1 # j指向右边的孩子。
if li[j] > tmp:
li[i] = li[j]
i = j # 往下看一层
j = 2 * i + 1
else: # tmp更大,把tmp放到i的位置上。
li[i] = tmp # 把tmp放到某一级领导的位置上
break
else:
li[i] = tmp
def heap_sort(li):
n = len(li)
for i in range((n - 2) // 2, -1, -1): # i表示建立堆的时候调整的部分的根的下标。
sift(li, i, n - 1) # 堆建立完成了。
for i in range(n - 1, -1, -1): # i指向当前堆的最后一个元素。
li[0], li[i] = li[i], li[0]
sift(li, 0, i - 1) # i-1是新的high。
li = [i for i in range(100)]
import random
random.shuffle(li)
print(li)
heap_sort(li)
print(li)
# 堆排序的时间复杂度为nlogn,sift调整的过程为logn,排序过程为n。和快速排序时间复杂度一样。但是实际表现快排优于堆排。
import heapq # 内置模块实现堆排序。
import random
li = list[range(100)]
random.shuffle(li)
print(li)
heapq.heapify(li) # 建立堆。
n = len(li)
for i in range(n):
print(heapq.heappop(li), end=',')
Top K问题
1、NB三人组里面全排完然后切片。
2、LOW B三人组里面,冒泡排序,排序一趟最大的数字上去了。选择排序和插入排序也类似。
3、堆排序的思想处理。
# 用堆排序的思路实现topk问题。
def sift(li, low, high): # 先写调整函数。
"""
:param li:列表
:param low:堆的根节点位置。
:param high:堆的最后一个元素的位置。
"""
i = low # i最开始指向根节点
j = 2 * i + 1 # j开始是左孩子
tmp = li[low] # 把堆顶存起来
while j <= high: # 只要j位置有数
if j + 1 <= high and li[j + 1] < li[j]: # 右孩子有并且比较小。 改成小根堆。
j = j + 1 # j指向右边的孩子。
if li[j] < tmp: # 改成小根堆。
li[i] = li[j]
i = j # 往下看一层
j = 2 * i + 1
else: # tmp更大,把tmp放到i的位置上。
li[i] = tmp # 把tmp放到某一级领导的位置上
break
else:
li[i] = tmp
def topk(li, k):
heap = li[0:k] # 取出来列表的前k个元素。
for i in range((k-2)//2, -1, -1):
sift(heap, i, k-1) # 1.建堆
for i in range(k, len(li)-1):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
# 2.遍历列表中的所有元素。
for i in range(k - 1, -1, -1): # i指向当前堆的最后一个元素。
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i - 1) # 出数
return heap
归并排序
以上过程为一次归并。
归并过程代码如下所示:
def merge(li, low, mid, high):
i = low
j = mid + 1
ltmp = []
while i<=mid and j<= high: # 只要左右两边都有数
if li[i] < li[j]:
ltmp.append(li(i))
i +=1
else:
ltmp.append(li[j])
j += 1
# while执行完,肯定有一部分没有数了。
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= mid:
ltmp.append(li[j])
j += 1
li[low:high+1] = ltmp # 切片可以写回去。
li = [2, 4, 5, 7, 1, 3, 6, 8]
merge(li, 0, 3, 7)
def merge_sort(li, low, high): # 归并排序递归的终止条件是小列表只剩一个元素。
if low < high: # 至少有两个元素,递归。
mid = (low + high) // 2
merge_sort(li, low, mid)
merge_sort(li, mid+1, high)
merge(li, low, mid, high)
递归的思想,类似于汉诺塔的操作。左边排好序,右边排好序,左边右边做归并就可以。
归并排序的时间复杂度。
归并排序不是原地排序,需要一些新的额外的空间。
nb三人组小结
快速排序倒序排序是一种极端情况。
递归需要系统空间,走了N层,空间复杂度就需要O(n)。函数栈的列表,记录函数的位置。
排序的稳定性:能保证相同数字的相对位置不变。
有顺序挨个换的都是稳定的。直接选择排序是跳着换的,因此不稳定。快速排序也是飞着换的,堆排序虽然是父亲到孩子,但是作为一个列表,也是飞着换的。python、java排序现在都用归并排序,因此比较稳定。快速排序和堆排序比较复杂,需要理解递归的部分。堆排序最难理解。