TOP_K問題以及堆的應用

TOP_K問題

問題簡介

找出一個列表中前k大的數

三種解決方案

  • 排序後切片,時間複雜度O(nlogn+k)

  • 簡單排序:冒泡排序爲例,時間複雜度O(kn)

  • 小根堆,時間複雜度【建堆klogk + (n-k)logk = nlogk】

時間複雜度比較

# -*- encoding: utf-8 -*-
"""
@File    : top_k.py
@Time    : 2020/1/15 12:38 下午
@Author  : zhengjiani
@Email   : [email protected]
@Software: PyCharm
"""
# 找前5個最大的,使用小根堆
# 使用內置heapq
import heapq

from leetcode.cal_time import cal_time

"""
1.取列表前k個元素建立一個小根堆,堆頂就是目前第K大的數
2.依次向後遍歷原列表,對於列表中的元素,如果小於堆頂,則忽略該元素;如果大於堆頂,則將堆頂更換爲該元素,並且對堆進行一次調整
3.遍歷列表所有元素後,倒序彈出堆頂
"""
@cal_time
def func_1():
    li = [9,5,7,8,2,6,4,1,3]
    return heapq.nlargest(5,li)

@cal_time
def func_2():
    # 排序後切片
    li = [9, 5, 7, 8, 2, 6, 4, 1, 3]
    li.sort()
    return li[:3:-1]


@cal_time
def func_3():
    li = [9, 5, 7, 8, 2, 6, 4, 1, 3]
    _bubble_sort(li, 5)
    return li[:3:-1]


# 冒泡排序,只冒前5位
def _bubble_sort(li, k):
    for i in range(k):
        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:
            break

print(func_1())
print(func_2())
print(func_3())

三種方案的時間複雜度比較

== 計算時間的裝飾器函數 ==

# -*- encoding: utf-8 -*-
"""
@File    : cal_time.py
@Time    : 2020/1/3 5:51 下午
@Author  : zhengjiani
@Email   : [email protected]
@Software: PyCharm
"""
import time
def cal_time(func):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        result = func(*args, **kwargs)
        t2 = time.time()
        print("%s running time: %s secs." % (func.__name__,t2-t1))
        return result
    return wrapper

構造堆以及堆排序

# -*- encoding: utf-8 -*-
"""
@File    : heap_sort.py
@Time    : 2020/1/14 2:22 下午
@Author  : zhengjiani
@Email   : [email protected]
@Software: PyCharm
"""
# 堆排序
# 大根堆
def sift(li,low,high):
    # li表示樹,low表示樹根,high表示樹的最後一個節點的位置
    tmp = li[low]
    i = low
    j = 2 * i + 1 # 初始j指向空位的左孩子
    # i指向空位,j指向兩個孩子
    while j <= high: # 循環退出的第二種情況:j>high,說明空位i是葉子節點
        if j+1 <= high and li[j] < li[j+1]: # 如果右孩子存在並且比左孩子大,指向右孩子
            j += 1
        if li[j] > tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else: # 循環退出的第一種情況:j位置的值都比tmp小,說明兩個孩子都比tmp小
            break
    li[i] = tmp

# 小根堆
def sift_small(li,low,high):
    # li表示樹,low表示樹根,high表示樹的最後一個節點的位置
    tmp = li[low]
    i = low
    j = 2 * i + 1 # 初始j指向空位的左孩子
    # i指向空位,j指向兩個孩子
    while j <= high: # 循環退出的第二種情況:j>high,說明空位i是葉子節點
        if j+1 <= high and li[j] > li[j+1]: # 如果右孩子存在並且比左孩子大,指向右孩子
            j += 1
        if li[j] < tmp:
            li[i] = li[j]
            i = j
            j = 2 * i + 1
        else: # 循環退出的第一種情況:j位置的值都比tmp大,說明兩個孩子都比tmp小
            break
    li[i] = tmp
    
def heap_sort_small(li):
    n = len(li)
    # 1.構造堆
    for low in range(n//2-1, -1, -1):
        sift_small(li, low, n-1)
    # 2.挨個出數
    for high in range(n-1, -1, -1):
        li[0], li[high] = li[high], li[0] # 退休棋子
        sift_small(li, 0, high-1)
        
# 構造堆,要求是完全二叉樹,整個堆最後一個元素的位置當作high
def heap_sort(li):
    n = len(li)
    # 1.構造堆
    for low in range(n//2-1, -1, -1):
        sift(li, low, n-1)
    # 2.挨個出數
    for high in range(n-1, -1, -1):
        li[0], li[high] = li[high], li[0] # 退休棋子
        sift(li, 0, high-1)

發佈了70 篇原創文章 · 獲贊 7 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章