排序
快速排序 时间复杂度:O(nlogn)
思路
总体思路
将第一个元素并使之归位(使用partition函数), 随后列表被分为两部分且左边均比该元素小,右边均比该元素大,随后左右两边均进行递归
partition函数
从列表left处开始找比tmp值小的元素,找到后放入空位置,
随后从列表right处开始找比tmp值大的元素,找到后放入空位置直到left=right
代码实现
partition函数
每次找到后输出一次列表,最终right与left相等时再输出一次列表
def partition(li,left,right): #left right表示列表需要操作的范围
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp: #列表right处开始找比tmp值小的元素
right -= 1
li[left] = li[right]
print(li, 'r')
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left]
print(li, 'l')
li[left] = tmp #循环结束时 left和right相等
return left
li = [5,7,4,6,3,1,2,9,8]
print('原列表:',li)
print(partition(li,0,len(li)-1))
主函数
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)
return li
li = [5, 7, 4, 6, 3, 1, 2, 9, 8]
print(quick_sort(li,0,len(li)-1))
堆排序 时间复杂度:O(nlogn)
相关知识点
二叉树:度不超过2的树
满二叉树:一个每一层结点数达到最大的二叉树
完全二叉树:满二叉树最后一层从右到左减少叶子结点
图a满二叉树 图b完全二叉树
大根堆:每一个父节点都比其孩子结点大
小根堆:每一个父节点都比其孩子结点小
思路
首先化成大根堆,需要用到向下调整函数(除去根节点外是两个大根堆,此函数可以把根节点放入组成一个包含所有元素的大根堆)
然后把根节点(9)拿出,将最后一个元素(3)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将3放入
然后把根节点(8)拿出,将最后一个元素(3)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将3放入
然后把根节点(7)拿出,将最后一个元素(2)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将2放入
然后把根节点(6)拿出,将最后一个元素(1)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将1放入
然后把根节点(5)拿出,将最后一个元素(0)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
然后把根节点(4)拿出,将最后一个元素(0)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
然后把根节点(3)拿出,将最后一个元素(1)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将1放入
然后把根节点(2)拿出,将最后一个元素(0)放到根节点的位置
然后此时除去根节点以外是两个大根堆,然后使用向下调整函数,将0放入
然后拿出1,结束
向下调整函数
代码实现
把2进行向下调整
def sift(li,low,high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
"""
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] #如果父节点小,li[j]变为父节点,然后开启下一层
i = j
j = 2 * i + 1
else:
li[i] = tmp #tmp已经是最大的 则直接变为父节点
break
else:
li[i] = tmp #如果j已经>high 直接结束
return li
li = [2,9,7,8,5,0,1,6,4,3]
print(sift(li,0,len(li)-1))
主函数
思路
首先从最后一个元素及其父节点开始,最后一个元素下标为n-1,则其父节点为(i-1)//2 不论是左节点还是右节点
依次下去
代码实现
def heap_sort(li):
n = len(li)
for i in range((n-2)//2,-1,-1): #第i个节点对应的父节点为(i-1)//2 不论是左节点还是右节点
sift(li,i,n-1)
#完成建堆
for i in range(n-1,-1,-1):
li[0],li[i] = li[i],li[0] #high对应的元素与堆顶元素交换
sift(li,0,i-1) #i-1是新的high
return li
li = [2,9,7,8,5,0,1,6,4,3]
print(heap_sort(li))
python内置函数
python存在关于堆的内置模块
import heapq
import random
li = list(range(10))
random.shuffle(li)
print('原列表为:',li)
heapq.heapify(li) #建堆
print('小根堆为:',li) #默认建的是小根堆
n = len(li)
print(heapq.heappop(li)) #每次抛出一个最小的
print('排序列表为:')
for i in range(n-1):
print(heapq.heappop(li),end=',')
堆排序之topk问题
问题描述
现有n个数,设计算法得到前k大的数(k<n)
思路
首先取前k个元素建立一个小根堆,此时堆顶就是目前第k大的数,然后依次向后遍历原列表,对于每个元素,如果小于堆顶,则忽略,若大于,则将堆顶更换成该元素,并且对堆进行一次调整。
原列表取前k个元素建立一个小根堆
对于0,0<1,忽略
对于7,7>1,代替堆顶1并进行一次向下调整
对于2,2>3,忽略
对于4,5>3,代替堆顶3并进行一次向下调整
对于5,5>4,代替堆顶4并进行一次向下调整,结束
代码实现
#建立小根堆
def sift(li,low,high):
"""
:param li: 列表
:param low: 堆的根节点位置
:param high: 堆的最后一个元素的位置
:return:
"""
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] #如果父节点小,li[j]变为根节点,然后开启下一层
i = j
j = 2 * i + 1
else:
li[i] = tmp #tmp已经是最大的 则直接变为父节点
break
else:
li[i] = tmp #如果j已经>high 直接结束
return li
def topk(li,k):
heap = li[0:k]
for i in range((k-2)//2,-1,-1):
sift(li,i,k-1)
#1.完成建堆
for i in range(k,len(li)):
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
import random
li = list(range(10))
random.shuffle(li)
print('原列表为:',li)
print('前5大的数为:',topk(li,5))
归并排序 时间复杂度:O(nlogn) 空间复杂度:O(n)
总的思路
百度上的一张图就可以解释
归并
思路
首先说明归并的思想,即一个列表中前后两段均为排好序的序列:
3,6,8,10, 2,4,7,11
依次从两段中取元素进行比较并放入新的列表
第一次: 2 3,6,8,10, 4,7,11
第二次: 2,3 6,8,10, 4,7,11
第三次: 2,3,4 6,8,10, 7,11
第四次: 2,3,4,6 8,10, 7,11
第五次: 2,3,4,6,7 8,10, 11
第六次: 2,3,4,6,7,8 10, 11
第七次: 2,3,4,6,7,8,10 11
第八次: 2,3,4,6,7,8,10,11
代码实现
#归并函数
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 <= high:
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)
print(li)
代码实现
首先把列表分成两部分,对每一部分依次分解,直到low>=hjgh,此时列表已经被分成两个排好序的部分,在对这两个排好序部分进行归并
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)
li = [2,4,5,7,1,3,6,8]
merge_sort(li,0,len(li)-1)
print(li)
总结
1.三种排序算法 时间复杂度都是O(nlogn)
2.一般情况下,就运行时间而言:
快速排序<归并排序<堆排序
3.缺点
快速排序:极端条件下排序效率低 (倒序)
归并排序:需要额外的内存开销
堆排序:在这三分钟较慢