堆排序与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、55

    而不是1、2、4、55

 

 

***完***

posted @ 2021-04-05 22:02  口乞厂几  阅读(407)  评论(0)    收藏  举报