快速排序(Quick Sort)
- 意思:取一个元素p,使元素p归位,使得列表被p分为两部分,左边都比p小,右边都比p大,递归完成排序
- 时间复杂度:O(nlogn)(partition的复杂度是O(n),一共递归logn次)
- 原地排序
- 缺点:递归有最大深度,会消耗一定的系统资源;最坏情况(倒序的情况,每次取的元素都是最大的那个数),那么每次都只有左边部分或右边部分,时间复杂度变为O(n^2)
- 尽量避免最坏情况的方法:取第一个数前,在待排序区随机取一个数,与第一个数交换位置,再开始排序
代码:
def partition(li, left, right):
temp = li[left]
while left < right:
while left < right and li[right] >= temp: #从右边找比temp小的数
right -= 1
li[left] = li[right] #把右边的值写到左边的空位
while left < right and li[left] <= temp: #从左边找比temp大的数
left += 1
li[right] = li[left] #把左边的值写到右边的空位
li[left] = temp #把temp归位
return left
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)
树
- 树是一种数据结构(比如目录结构),是一种可以递归定义的数据结构
- 树是由n个节点组成的集合:
- 如果n=0,那这是一棵空树
- 如果n>0,那存在1个节点作为树的根节点,其他节点可以分为m个集合,每个集合本身又是一棵树
树的一些概念
- 根节点:A
- 叶子节点:没有分叉的节点:B、C、H、I、P、Q、K、L、M、N
- 树的深度(高度):4,有4层
- 节点的度:分了几个叉,例如E的度为2
- 树的度:6,分叉最多的那个节点的度
- 孩子节点/父节点:E为I的父节点,I为E的子节点
- 子树:E-IJ-PQ为一个子树
二叉树
- 度不超过2的树
- 每个节点最多有2个孩子节点
- 2个孩子节点被区分为左孩子节点和右孩子节点
满二叉树和完全二叉树
- 满二叉树:每一层的节点数都达到最大值
- 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置
二叉树的存储方式
- 链式存储方式(还没学到)
- 顺序存储方式:在计算机里的顺序存储为从上到下,从左到右
- 从父节点找左孩子节点:i➡2i+1
- 从父节点找右孩子节点:i➡2i+2
- 从左孩子节点找父节点:i➡(i-1)//2
- 从右孩子节点找父节点:i➡(i-2)//2
堆
- 意思:一种特殊的完全二叉树结构
- 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
- 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
- 堆的向下调整性质:假设根节点的左右子树都是堆,但根节点不满足堆的性质,可以通过一次向下的调整来将其变成一个堆
- 构造堆:从最后一个有孩子的节点开始,从下到上,从右到左检查该节点是否及其下属是否满足正确的大小关系,农村包围城市
堆排序(Heap Sort)
- 步骤
- 建立堆
- 得到堆顶元素,为最大元素
- 去掉堆顶,将堆的最后一个元素放到堆顶,此时可通过一次向下调整使堆变有序
- 堆顶元素为第二大元素
- 重复步骤3,直到堆变空
- 时间复杂度:O(nlogn),sift()函数的复杂度是O(logn)
代码:
#调整函数-大根堆
def sift(li, low, high): #列表,堆的根节点位置,堆的最后一个元素的位置
i = low #i最开始指向根节点
j = 2 * i + 1 #j开始是i的左孩子
temp = li[low] #把堆顶存起来
while j <= high: #只要j位置有数,没有越界
if j+1 <= high and li[j+1] > li[j]: #如果右孩子有,且比左孩子大
j += 1 #把j指向右孩子
if li[j] > temp:
li[i] = li[j]
i = j 往下看一层
j = 2 * i +1
else:
break
li[i] = temp #把temp放到叶子节点上
#堆排序函数
def heap_sort(li):
n = len(li)
for i in range((n-2)//2, -1, -1): #(n-2)//2为最后一个父节点的位置
#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)
堆排序在python的内置模块——heapq
- 常用函数
- heapify(x)
- heappush(heap, item)
- heappop(heap)
堆排序的应用——topk问题
- 问题:有n个数,设计算法得到前k大的数
- 解决思路:
- 排序后切片,时间复杂度O(nlogn)
- 3种普通排序方法,排k趟就排完了,时间复杂度O(kn)
- 堆排序,时间复杂度O(nlogk)
- 取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数
- 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并对堆进行一次调整
- 遍历列表所有元素后,倒序弹出堆顶
代码:
#调整函数-小根堆
def sift(li, low, high): #列表,堆的根节点位置,堆的最后一个元素的位置
i = low #i最开始指向根节点
j = 2 * i + 1 #j开始是i的左孩子
temp = li[low] #把堆顶存起来
while j <= high: #只要j位置有数,没有越界
if j+1 <= high and li[j+1] < li[j]: #如果右孩子有,且比左孩子小
j += 1 #把j指向右孩子
if li[j] < temp:
li[i] = li[j]
i = j 往下看一层
j = 2 * i +1
else:
break
li[i] = temp #把temp放到叶子节点上
def topk(li,k):
heap = li[:k]
for i in range((k-2)//2, -1, -1): #(k-2)//2为最后一个父节点的位置
sift(heap, i, k-1)
#建堆完成
for i in range(k, len(li)):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
#遍历结束
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(li, 0, i-1)
return heap