# 编程思想-查找排序
# 算法入门
算法(Algorithm):⼀个计算过程,解决问题的⽅法
Niklaus Wirth: “程序=数据结构+算法”
# 时间复杂度
时间复杂度:⽤来评估算法运⾏效率的⼀个式⼦
- ⼀般来说,时间复杂度⾼的算法⽐复杂度低的算法慢
- 常⻅的时间复杂度(按效率排序)
- O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n2logn)<O(n3)
快速判断算法复杂度(适⽤于绝⼤多数简单情况)
- 确定问题规模 n
- 循环减半过程 -> logn
- k层关于n的循环 -> n的k次方
# 空间复杂度
空间复杂度:⽤来评估算法内存占⽤⼤⼩的式⼦
- 空间复杂度的表示⽅式与时间复杂度完全⼀样
- 算法使⽤了⼏个变量:O(1)
- 算法使⽤了⻓度为n的⼀维列表:O(n)
- 算法使⽤了m⾏n列的⼆维列表:O(mn)
- "空间换时间"
# 递归的两个特点
- 调用自身
- 结束条件
# 顺序查找
def linear_search(data, value):
for index, item in enumerate(data):
if item == value:
return index
return -1
- 时间复杂度 O(n)
- 列表内置函数 index 的算法
# 二分查找
def bin_search(data, value):
low, high = 0, len(data) - 1
# 调整下标 low 或 high 从而问题规模不断减小
while low <= high:
mid = (low + high) // 2
if data[mid] == value:
return mid
elif data[mid] > value: # 中间值大于查找值,往小的区间缩进
high = mid - 1
else:
low = mid + 1
return -1
- 有序列表(如升序),定义合适的索引变量 low mid high
- 时间复杂度 O(logn)
# 冒泡排序
- 列表每两个相邻的数,如果前⾯⽐后⾯⼤,则交换这两个数
- ⼀趟排序完成后,则⽆序区减少⼀个数,有序区增加⼀个数
- 代码关键点:趟、⽆序区范围
- 时间复杂度:O(n2)
def bubble_sort(li):
for i in range(len(li)-1):
# 一趟操作
exchange = False # ⼀趟排序没有发⽣交换,则说明列表有序
for j in range(len(li) - i - 1): # 无序区范围
if li[j] > li[j + 1]:
li[j], li[j + 1] = li[j+1], li[j]
exchange = True
if not exchange:
return
# 选择排序
- ⼀趟排序记录最⼩的数,放到第⼀个位置
- 再⼀趟排序记录记录列表⽆序区最⼩的数,放到第⼆个位置,重复操作
- 算法关键点:有序区和⽆序区、⽆序区最⼩数的位置
- 时间复杂度:O(n2)
def select_sort(li):
for i in range(len(li) - 1):
# 取第一元素作为最小比对值
min_index = i # i 如何用,range 有开头和结尾两个参数,控制好无序区
for j in range(i+1, len(li)):
if li[j] < li[min_index]:
min_index = j
if min_index != i:
li[i], li[min_index] = li[min_index], li[i]
# 插入排序
- 初始时⼿⾥(有序区)只有⼀张牌
- 每次(从⽆序区)摸⼀张牌,插⼊到⼿⾥已有牌的正确位置
- 时间复杂度:O(n2)
def insert_sort(li):
# 取第一张牌作为有序的初始值
for i in range(1, len(li)):
tmp = li[i]
j = i - 1 # 遍历有序区
while j >= 0 and tmp < li[j]:
li[j + 1] = li[j] # 不断后移一个为 tmp 留位置
j = j - 1
li[j + 1] = tmp
# 快速排序
- 取⼀个元素p(第⼀个元素),使元素p归位
- 列表被p分成两部分,左边都⽐p⼩,右边都⽐p⼤
- 递归完成排序
# 一次归位
def partition(data, left, right):
tmp = data[left]
while left < right:
while left < right and data[right] >= tmp:
right -= 1
data[left] = data[right]
while left < right and data[left] <= tmp:
left += 1
data[right] = data[left]
data[left] = tmp
return left
# 快速排序框架
def quick_sort(data, left, right):
if left < right:
mid = partition(data, left, right)
quick_sort(data, left, mid - 1)
quick_sort(data, mid + 1, right)
- 时间复杂度:O(nlogn)
- 快速排序的问题
- 最坏情况
- 递归限制
# 堆排序
# 堆排序前传
- 完全⼆叉树:叶节点只能出现在最下层和次下层,并且最下⾯⼀层的结点都集中在该层最左边的若⼲位置的⼆叉树
- 二叉树树的顺序存储方式
- 父节点 -> 左孩子: i -> 2 * i + 1
- 父节点 -> 右孩子: i -> 2 * i + 2
- 左右孩子 -> 父节点 (i - 1) // 2
# 什么是堆
堆:⼀种特殊的完全⼆叉树结构
- ⼤根堆:⼀棵完全⼆叉树,满⾜任⼀节点都⽐其孩⼦节点⼤
- ⼩根堆:⼀棵完全⼆叉树,满⾜任⼀节点都⽐其孩⼦节点⼩
堆的向下调整性质
- 假设根节点的左右⼦树都是堆,但根节点不满⾜堆的性质
- 可以通过⼀次向下的调整来将其变成⼀个堆
堆排序过程
- 建⽴堆(农村包围城市,从后往前)
- 得到堆顶元素,为最⼤元素
- 去掉堆顶,将堆最后⼀个元素放到堆顶,此时可通过⼀次调整重新使堆有序
- 堆顶元素为第⼆⼤元素
- 重复步骤3,直到堆变空
def sift(data, low, high):
"""
一次向下调整
前置条件:左右子树都是堆,但是父节点不是最大值
目标结果:调整为堆
:param data: 列表
:param low: 堆的根节点位置
:param high: 堆最后一个元素位置
:return:
"""
i = low
j = 2 * i + 1
tmp = data[i]
while j <= high:
if j + 1 <= high and data[j + 1] > data[j] : # 右孩子存在且较大
j = j + 1
# j 位置 和 tmp 位置相比较
if data[j] > tmp:
data[i] = data[j] # i 一直是空位可以被代替,子字点上去到i位置
i = j
j = 2 * 1
else: # tmp 较大
data[i] = tmp
break
data[i] = tmp # j > high 跳出时,最后把tmp放回去
def heap_sort(data):
n = len(data)
# 建堆过程
for i in range((n - 1 - 1) // 2, -1, -1):
# i 建堆时调整的根,即low
sift(data, i, n - 1)
# 出数的过程
for i in range(n - 1, -1, -1):
# i 指向堆最后一个元素
data[0], data[i] = data[i], data[0]
sift(data, 0, i - 1) # 堆顶即data[0], data[i - 1] 待一次调整
- 时间复杂度:O(n2)
# 堆排序-topk问题
现在有n个数,设计算法得到前k⼤的数 (k < n )
解决思路:
- 排序后切⽚ O(nlogn)
- 排序LowB三⼈组 O(nk)
- 堆排序 O(nlogk)
堆排序思路
- 取列表前k个元素建⽴⼀个⼩根堆, 堆顶就是⽬前第k⼤的数
- 依次向后遍历原列表,对于列表中的元素,如果⼩于堆顶,则忽略该元素;如果⼤于堆顶,则将堆顶更换为该元素,并且对堆进⾏⼀次调整
- 遍历列表所有元素后,倒序弹出堆顶
def sift(data, low, high):
"""
一次调整为小根堆
:param data: 列表
:param low: 堆的根节点位置
:param high: 堆最后一个元素位置
:return:
"""
i = low
j = 2 * i + 1
tmp = data[low]
while j <= high:
if j + 1 <= high and data[j + 1] < data[j]: # 右孩子存在且较大
j = j + 1
# j 位置 和 tmp 位置相比较
if data[j] < tmp:
data[i] = data[j] # i 一直是空位可以被代替,子字点上去到i位置
i = j
j = 2 * i + 1
else: # tmp 较大
data[i] = tmp
break
else:
data[i] = tmp # j > high 跳出时,最后把tmp放回去
def top_k(data, k):
heap = data[0:k]
# 建堆过程 堆 heap 建堆
for i in range((k - 1 - 1) // 2, -1, -1):
# i 建堆时调整的根,即low
sift(heap, i, k - 1)
# 取剩余数一个一个与heap[0](我最小)相比较
for i in range(k, len(data)):
if data[i] > heap[0]:
heap[0] = data[i]
sift(heap, 0, k-1)
# 出数的过程
for i in range(k - 1, -1, -1):
# i 指向堆最后一个元素
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i - 1) # 堆顶即data[0], data[i - 1] 待一次调整
return heap