堆排序与topk问题、NB三人组优缺点(完)
内容概要
一、topk问题与讨论
二、topk解决
三、NB三人组优缺点
四、基础算法总结
1、topk问题
topk问题简单的说,就是从无序的n个数中排序好前k个大的数
-topk问题的解决思路
要解决topk问题,有几种解决思路
一种是使用NB三人组算法将n个数都排序好,然后截取前k个数即可(python的话使用切片就好)
这样时间复杂度为O(nlogn)
另一种是使用LOWB三人组算法对n个数进行前k大数的排列(冒泡排序每次找到n个数中最大的数,循环k遍,找到前k大的数;
选择排序每次选出n个数中最大的一个,选择k遍;
插入排序维护一个k大小的有序列表,小于有序列表中最小的数的值将被忽略,大于最小数的值插入到有序列表中
)
它们的时间复杂度为O(kn)
最后一种是使用堆排序的方式(快速排序、归并排序都无法做到局部排序)
通过维护一个k大小的小根堆,之后依次遍历k+1到n的数,
如果小于小根堆的根节点(小根堆的特点之一是它的根节点一定是所有节点中最小的数),则舍去;
如果比根节点大,则替换根节点,进行向下调整。遍历完后,找到前k大的数
这种方式的时间复杂度为O(nlogk)
堆排序的方式是所有方式中最快的
-topk问题的应用
例如实时(或非实时)榜单
2、topk问题解决
解决topk问题代码
import random def sift(li, head, end): # 向下调整函数 i = head j = head * 2 + 1 tmp = li[head] while j <= end: if j+1 <= end and li[j+1] < li[j]: j += 1 if li[j] < tmp: li[i] = li[j] i = j j = j * 2 + 1 # j *= 2 + 1的写法是错误的 else: break li[i] = tmp def heap_sort(li, end): # 构建堆函数 head = (end - 1) // 2 while head >= 0: sift(li, head, end) head -= 1 def topk_sort(li, k): # topk问题解决函数 n = len(li) - 1 heap_sort(li, k-1) for i in range(k, n+1): if li[i] > li[0]: li[0] = li[i] sift(li, 0, k-1) for i in range(k-1, -1, -1): # 挨个出数 li[0], li[i] = li[i], li[0] sift(li, 0, i-1) print(li[0:k]) li = [i for i in range(100)] random.shuffle(li) # print(li) topk_sort(li, 10)
3、NB三人组优缺点
快速排列
优点:虽然三种算法的时间复杂度都是O(nlogn),但是三者速度仍然有微小区别,快排是NB三人组算法中最快的一个
缺点:快排算法可能出现最坏情况,这个情况的出现在每次要归位的数是所有剩余数中最大(或者最小的数)时,此时快速排列的时间复杂度是O(n**2)
归并排序
优点:归并排序速度第二
缺点:空间复杂度为O(n),消耗内存
堆排序
优点:可以实现部分排序
缺点:速度是三者中最慢的,代码复杂,逻辑复杂
运行时间装饰器
import time def tell_time(func): def wrapper(*args, **kwargs): start_time = time.time() res = func(*args, **kwargs) end_time = time.time() print(end_time - start_time) return res return wrapper
import random from common import * li = [i for i in range(100000)] random.shuffle(li) li1 = li.copy() # 将列表拷贝三份 li2 = li.copy() li3 = li.copy() def position(li, head, end): # 一次归位函数 tmp = li[head] while head < end: while head < end and li[end] >= tmp: end -= 1 li[head] = li[end] while head < end and li[head] <= tmp: head += 1 li[end] = li[head] li[head] = tmp return head def _quick_sort(li, head, end): # 快速排列由外到内 if head < end: mid = position(li, head, end) _quick_sort(li, head, mid-1) _quick_sort(li, mid+1, head) @tell_time def quick_sort(li, head, end): # 为了避免递归对装饰器的影响 _quick_sort(li, head, end) def sift(li, head, end): # 向下调整函数 tmp = li[head] i = head j = head * 2 + 1 while j <= head: if j+1 <= head and li[j+1] > li[j]: j += 1 if li[j] > tmp: li[i] = li[j] i = j j = j * 2 + 1 else: break li[i] = tmp @tell_time def heap_sort(li): # 堆排序 end = len(li) - 1 for i in range((end - 1) // 2, -1, -1): sift(li, i, end) for i in range(end, 0, -1): li[0], li[i] = li[i], li[0] sift(li, 0, i-1) def merge(li, head, mid, end): # 一次归并函数 i = head j = mid + 1 tmp_li = [] while i <= mid and j <= end: if li[i] <= li[j]: tmp_li.append(li[i]) i += 1 else: tmp_li.append(li[j]) j += 1 while i <= mid: tmp_li.append(li[i]) i += 1 while j <= end: tmp_li.append(li[j]) j += 1 li[head:end+1] = tmp_li def _merge_sort(li, head, end): if head < end: # 归并排序由内到外 mid = (head + end) // 2 _merge_sort(li, head, mid) _merge_sort(li, mid+1, end) merge(li, head, mid, end) @tell_time def merge_sort(li, head, end): _merge_sort(li, head, end) quick_sort(li1, 0, len(li1) - 1) merge_sort(li2, 0, len(li2) - 1) heap_sort(li3)
实操发现归并排序的效率比快排和堆排序要慢得多
3、基础算法总结
算法名称 时间复杂度——最好——平均——最坏 空间复杂度 稳定性
冒泡排序 O(n) O(n**2) O(n**2) O(1) 稳定
选择排序 O(n**2) O(n**2) O(n**2) O(1) 不稳定
插入排序 O(n**2) O(n**2) O(n**2) O(1) 稳定
快速排序 O(nlogn) O(nlogn) O(n**2) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
稳定性
稳定性指的是排序前等大的数据在排序后的相对位置不发生改变
比如5、2、4、5、1
在排序后为1、2、4、5、5
而不是1、2、4、5、5
***完***
本文来自博客园,作者:口乞厂几,转载请注明原文链接:https://www.cnblogs.com/laijianwei/p/14619693.html

浙公网安备 33010602011771号