目录
冒泡排序 O(n^2)
列表每两个相邻的数,如果前面比后面大,则交换这两个数;(每趟排序把最大的数推到无序区的最后面,成为有序区的第一个数,指针最终停在无序区的倒数第二个数)
一趟排序完成后,则无序区减少一个数,有序区增加一个数。(共需要N-1趟)
def bubble_sort(li):
for i in range(len(li) - 1): # i表示第i趟
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
exchange是标志位,当一趟结束后没有发生交换则说明已经排序完成,不需要继续循环
选择排序 O(n^2)
每趟遍历找到最小的数放在前面/后面的有序区,代码把有序区放到列表最前面
def select_sort_simple(li):
li_new = []
for i in range(len(li)):
min_val = min(li)
li_new.append(min_val)
li.remove(min_val)
return li_new
def select_sort(li):
for i in range(len(li) - 1): # i是第i趟
min_loc = i # 标记为无序区第一个位置
for j in range(i + 1, len(li)):
if li[j] < li[min_loc]:
min_loc = j
li[i], li[min_loc] = li[min_loc], li[i] # 与无序区第一个位置交换
return li
插入排序 O(n^2)
(插牌)初始时有序区只有一个数,每次从无序区取出一个数,插到有序区的正确位置
def insert_sort(li):
for i in range(1, len(li)): # i表示摸到的牌的下标,第一个数自动归入有序区不需要遍历
tmp = li[i]
j = i - 1 # j指的是手里的牌的下标
while j >= 0 and li[j] > tmp:
li[j + 1] = li[j] # 下标为j的牌右移一位
j -= 1 # 下标j左移一位
li[j + 1] = tmp # 当手里的牌没有摸到的牌大时,把摸到的牌放在该张牌右边
快速排序 O(nlogn)
归位:当p左边的数字都比p小,右边的数字都比p大,成为p归位完成
1. 首先取一个元素p使p归位,最开始left指针指向列表第一个数,right指针指向列表最后一个数,当left<right时,运行partition函数。将列表第一个数存放到tmp中,如果right指针指向的数比tmp大,right指针向左走一位,直到right指针指向的数比tmp小,此时把right指针指向的数放到left指针的位置;如果left指针指向的数比tmp小,则left指针向右走一位,直到left指针指向的数比tmp大,把left指针指向的数放到right指针的位置(这个位置的数之前被放到了没有移动前的left指针的位置),以此类推。当left指针和right指针相遇时结束循环,把tmp放到此时两个指针共同指向的位置上。
2. 第一个元素归位后位置为mid,递归mid左右两个子列表完成排序。
在这个过程中相当于先把left位置的元素取到tmp中,right指针找到一个比tmp小的元素a放到left的位置,left指针再找到一个比tmp大的元素b放到right的位置,最后把tmp填回两个指针共同指向的位置。
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):
tmp = li[left]
while left < right: # 循环终止条件是left和right的指针碰到一起
while left < right and li[right] >= tmp: # 右边的数大于等于tmp的话往右移一位
right -= 1 # 最终right指向一个小于tmp的数
li[left] = li[right] # 小于tmp的数放到左边
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left]
li[left] = tmp # 把tmp归位
return left
quick_sort(li, 0, len(li) - 1)
堆排序 O(nlogn)
1. 建立堆
2. 得到堆顶元素为最大元素
3. 去掉堆顶,将堆最后一个元素放到堆顶,通过一次调整使堆重新有序
4. 堆顶元素为第二大元素
5. 重复以上步骤直到堆变空
也就是说在已经有了一个堆结构的前提下进行排序。
堆:(大根堆)任意节点比其孩子节点大
def sift(li, low, high):
"""
调整父节点和子节点之间的大小关系(不递归)
:param li:
:param low: 根节点的位置
:param high: 堆的最后一个元素的位置
:return:
"""
i = low # 指向根节点
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: # 上一个if是挑选孩子中更大的一个与父节点比较
li[i] = li[j]
i = j # 移到下一层
j = 2 * i + 1
else: # tmp更大,把tmp放到i的位置上
break
li[i] = tmp # 循环结束(因为j位置没有节点或者tmp更大)后把tmp放回去
def heap_sort(li):
n = len(li)
for i in range((n - 2) // 2, -1, -1): # 从最后一个非叶节点开始反向遍历
sift(li, i, n - 1)
# 构造堆完成了
for i in range(n - 1, -1, -1): # li中i以后的节点为有序区,避免更高的空间复杂度
li[0], li[i] = li[i], li[0]
sift(li, 0, i - 1)
另外可以通过内置模块import heapq,使用heapify(x)构造堆,使用heappush(heap,item)将item推入堆元素,使用heappop(heap)从小到大输出堆元素
topk问题解决思路:
1. 取列表前k个元素建立一个小根堆,堆顶就是目前第k大的数
2. 遍历原列表,对于列表中的元素,小于堆顶则忽略该元素,大于堆顶则将堆顶更换为该元素,然后进行一次调整
3. 遍历列表所有元素后倒序弹出堆顶
def topk(li, k):
heap = li[0:k]
for i in range((k - 2) // 2, -1, -1):
sift(heap, i, k-1)
# 构建堆完成了
for i in range(k, len(li) - 1):
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(heap, 0, i-1)
# 出数
return heap
归并排序 O(nlogn)
假设目前列表分两段有序,将其合并为一个有序列表称为一次归并。将列表分解成元素,递归合并两个有序列表。
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)
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
希尔排序
分组插入排序算法
1. 取一个整数d1 = n/2,将元素分为d1个组,每组相邻元素之间距离为d1,在组内进行直接插入排序;
2. 取第二个整数d2 = d1/2,重复上述分组排序过程,直到di = 1,即所有元素在同一组内进行直接插入排序。
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序,最后一趟排序使得所有数据有序。时间复杂度比较复杂,和选取的gap序列有关。
def insert_sort_gap(li, gap):
for i in range(gap, len(li)): # i表示摸到的牌的下标
tmp = li[i]
j = i - gap # j指的是手里的牌的下标
while j >= 0 and li[j] > tmp:
li[j + gap] = li[j] # 下标为j的牌右移gap位
j -= gap # 下标j左移gap位
li[j + gap] = tmp # 当手里的牌没有摸到的牌大时,把摸到的牌放在该张牌右边
def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d //= 2
计数排序 O(n)
已知列表中的数范围在0到100之间
def count_sort(li, max_count=100):
count = [0 for _ in range(max_count + 1)]
for val in li: # val是序列里的值
count[val] += 1
li.clear()
for val, ind in enumerate(count): # 值为val的元素有ind个
for i in range(ind):
li.append(val)
桶排序(当数据范围比较大的时候对计数排序的改进)
def bucket_sort(li, n=100, max_num=10000):
buckets = [[] for _ in range(n)] # 创建桶
for val in li:
i = min(val // (max_num // n), n - 1) # i表示val放到几号桶里
buckets[i].append(val)
# 可以全都放到桶里再排序,也可以放到桶里的过程中顺便排序
# 这里选第二种
for j in range(len(buckets[i]) - 1, 0, -1): # 从后往前遍历
if buckets[i][j] < buckets[i][j - 1]:
buckets[i][j], buckets[i][j - 1] = buckets[i][j - 1], buckets[i][j]
else:
break
sorted_li = []
for buc in buckets:
sorted_li.extend(buc) # 连接列表
return sorted_li
基数排序
利用多关键字排序思想,先比较数字的最高位(本质上还是桶排序)
def radix_sort(li):
max_num = max(li) # 最大值的位数
it = 0 # 迭代次数
while 10 ** it <= max_num:
buckets = [[] for _ in range(10)]
for val in li:
digit = (val // 10 ** it) % 10
buckets[digit].append(val)
# 分桶完成
li = []
for buc in buckets:
li.extend(buc)
# 把数写回li
it += 1
return li