三大中级算法
- 难度 ★★
- 算法复杂度O(nlogn)
- 一般情况下排序时间: 快速排序< 归并排序 < 堆排序
快速排序
: 缺点极端情况下效率低堆排序
: 缺点在快的排序算法中相对慢归并排序
: 缺点要有额外内存空间
快速排序 ★★
quick Sort
算法复杂度: O(nlogn) <n乘logn>
思路
每趟取第一个元素,让列表被该元素分为两部分,左边的比他小,右边比他大, 重复用每次得来的第一个元素递归
问题1:
递归py有最大深度问题999次!(虽然可以设置)
问题2:
最坏的情况,如果列表本身是一个倒序列表,那么效率相对较低
(解决方案:随机快排,一开始就随机取一个数放在最左边开始排)
def quick_sort(li, left, right):
"""
快速排序
"""
if left < right: # 至少两个元素
mid = partition(li, left, right)
quick_sort(li, left, mid-1)
quick_sort(li, mid+1, right)
def partition(li, left, right):
"""
将下标为0的元素为参照物,左边的放比他小的,右边放比他大的
"""
tmp = li[left] # 第一个位置
while left < right: # 一直循环
while left < right and li[right] >= tmp: # 右边开始找比tmp 小的数放到左边的空位
right -= 1 # 往左走一步
li[left] = li[right] # 把右边的值写到左边空位
# 有可能所有数都比自己大
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left] # 把左边的值写到右边的空位上
li[left] = tmp # 把tmp归位
return left # 或者返回right都行
li_test = [3, 2, 7, 1, 6, 9, 8, 4]
quick_sort(li_test, 0, len(li_test)-1)
print(li_test)
堆排序 ★★
算法复杂度: O(nlogn) <n乘logn>
前提
二叉树的知识储备!!
效率:
快排的时间复杂度优于堆排
def sift(li, low, high):
"""
调整堆
:param li: 列表
:param low: 堆的根节点
:param high: 堆的最后一个元素位置
:return:
"""
i = low # i最开始指向的父
j = 2 * i + 1 # 堆顶左孩子
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 # 把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_test = [3, 2, 7, 1, 6, 9, 8, 4]
heap_sort(li_test)
print(li_test)
堆排序py 模块
import heapq # q : queue 优先列队
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=',')
堆排序 topk问题 【常用】
问题:比如热搜网,有n个数,取前k打的数(排序好的)
思路
- 取列表前k个元素建立小根堆,堆顶是目前第k大的数
- 依次向后面遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素,反之换为该元素,并进行一次调整
- 遍历素有元素后,倒序弹出堆顶
import random
def sift(li, low, high):
"""
调整为小根堆
:param li: 列表
:param low: 堆的根节点
:param high: 堆的最后一个元素位置
:return:
"""
i = low # i最开始指向的父
j = 2 * i + 1 # 堆顶左孩子
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 # 把tmp放到叶子节点上
def top_key(li, k):
heap = li[0: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):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i-1)
# 3.出数
return heap
li = list(range(1000))
random.shuffle(li)
print(top_key(li, 10)) # 测试 前10数
归并排序 ★★
算法复杂度: O(nlogn) <n乘logn>
空间复杂度: O(n)
思路
假设两个列表已有序,那么将他们合并在一起,将列表越分越小,直到分为一个元素
def merge(li, low, mid, high):
"""
:param li: 列表
:param low: 左列表第一个元素
:param mid: 左列表最后一个元素,那么右列表第一个就是mid+1
:param high: 右列表最后一个元素
:return:
"""
i = low # 第一段第一个元素
j = mid + 1 # 第二段的第一个元素
tmp_list = []
while i <= mid and j <= high: # 必须左右有数
if li[i] < li[j]:
tmp_list.append(li[i])
# 移动后箭头必须移动一位,因为已经把小的值提出到临时list中
i += 1
else:
tmp_list.append(li[j])
j += 1
# while 执行完,说明有一段执行完了,剩下的接到tmp中即可
# 分别判断一下那个还有数
while i <= mid:
tmp_list.append(li[i])
i += 1
while j <= high:
tmp_list.append(li[j])
j += 1
li[low:high+1] = tmp_list # 写回去
def merge_sort(li, low, high):
if low < high: # 至少有2个元素,递归
mid = (low + high) // 2 # 整除2
merge_sort(li, low, mid)
merge_sort(li, mid+1, high)
merge(li, low, mid, high)
li_test = [3, 2, 7, 1, 6, 9, 8, 4]
merge_sort(li_test, 0, len(li_test)-1)
print(li_test)