0Pricing
DSA Interview Prep · บทเรียน

heapq ของ Python และเทคนิคฮีปสูงสุด

ใช้ heapq.heappush/heapq.heappop กลับเครื่องหมายค่าเพื่อจำลองฮีปสูงสุด และใช้ heapq.nlargest/heapq.nsmallest สำหรับการค้นหาค่าสูงสุด k อันดับอย่างรวดเร็ว

heapq ของ Python และเทคนิคฮีปสูงสุด เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

ภาพรวมโมดูล heapq ของไพธอน

โมดูล heapq ของไพธอนมีฮีปต่ำสุดที่สร้างอยู่บนรายการไพธอนทั่วไป ต่างจากคลาสฮีปโดยเฉพาะ ตรงที่ heapq ทำงานกับรายการที่มีอยู่แล้วในตำแหน่งเดิม ฟังก์ชันของโมดูลนี้ได้แก่ heapify สำหรับสร้างฮีปในเวลา O(n), heappush สำหรับเพิ่มสมาชิกในเวลา O(log n), heappop สำหรับนำค่าต่ำสุดออกในเวลา O(log n) และการดำเนินการเพิ่มแล้วนำออกหรือแทนที่เพื่อประสิทธิภาพแบบรวม

import heapq

# heapq operates on plain Python lists
heap = []
heapq.heappush(heap, 5)
heapq.heappush(heap, 2)
heapq.heappush(heap, 8)
heapq.heappush(heap, 1)

print('Heap array:', heap)          # internal array (not sorted!)
print('Peek min:', heap[0])         # O(1) min access
print('Pop min:', heapq.heappop(heap))  # 1
print('Next min:', heap[0])         # 2

# heapify: turn any list into a heap in O(n)
data = [9, 4, 7, 1, 3, 6, 2]
heapq.heapify(data)
print('Heapified:', data, '| min:', data[0])

สร้างฮีปสูงสุดด้วยการเปลี่ยนเครื่องหมายของค่า

heapq ของไพธอนมีเฉพาะฮีปต่ำสุด หากต้องการจำลอง ฮีปสูงสุด ให้เปลี่ยนเครื่องหมายของ values ทั้งหมดก่อนทำการเพิ่ม และเปลี่ยนเครื่องหมายอีกครั้งเมื่อนำค่าออก วิธีนี้ใช้ได้เพราะฮีปจัดลำดับตามค่าที่จัดเก็บไว้ และการเปลี่ยนเครื่องหมายจะกลับลำดับดังกล่าว อย่าลืมเปลี่ยนเครื่องหมายทั้งสองด้านเสมอ กล่าวคือ เปลี่ยนเครื่องหมายก่อน push และเปลี่ยนเครื่องหมายหลัง pop การลืมขั้นตอนใดขั้นตอนหนึ่งเป็นข้อผิดพลาดที่พบบ่อยในการสัมภาษณ์

import heapq

max_heap = []
for val in [5, 1, 8, 3, 9, 2]:
    heapq.heappush(max_heap, -val)  # negate on push

print('Max-heap internal:', max_heap)  # all negated

# Pop in descending order:
results = []
while max_heap:
    results.append(-heapq.heappop(max_heap))  # negate on pop
print('Sorted descending:', results)  # [9, 8, 5, 3, 2, 1]

# Common pattern: top-k largest
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
k = 3
heap = []
for x in data:
    heapq.heappush(heap, -x)
print('Top', k, ':', [-heapq.heappop(heap) for _ in range(k)])

heapq.nlargest และ nsmallest

heapq.nlargest(k, iterable) และ heapq.nsmallest(k, iterable) จะคืนค่าสมาชิกที่มากที่สุดหรือเล็กที่สุดจำนวน k รายการ ทั้งสองฟังก์ชันมีความซับซ้อน O(n log k) ซึ่งมีประสิทธิภาพมากกว่าการเรียงลำดับทั้งหมดที่มีความซับซ้อน O(n log n) เมื่อ k มีขนาดเล็กกว่า n มาก ภายในฟังก์ชันจะใช้ฮีปขนาด k เมื่อ k ใกล้เคียงกับ n ไพธอนจะเปลี่ยนไปใช้การเรียงลำดับทั้งหมด ใช้ฟังก์ชันเหล่านี้สำหรับการค้นหาสมาชิก k อันดับแรกเพียงครั้งเดียว โดยไม่ต้องดูแลฮีปถาวร

import heapq

data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 7]

# Top 3 largest:
print(heapq.nlargest(3, data))   # [9, 8, 7]
# Top 3 smallest:
print(heapq.nsmallest(3, data))  # [1, 1, 2]

# With a key function:
words = ['banana', 'apple', 'cherry', 'date', 'elderberry']
print(heapq.nlargest(2, words, key=len))   # ['elderberry', 'banana']
print(heapq.nsmallest(2, words, key=len))  # ['date', 'apple']

# Note: when k ~ n, use sorted() instead:
# sorted(data)[-k:]  or  sorted(data, reverse=True)[:k]

ฮีปกับทูเพิลสำหรับคีย์ที่ซับซ้อน

เมื่อสมาชิกในฮีปต้องใช้ คีย์เปรียบเทียบแบบกำหนดเอง ให้จัดเก็บสมาชิกเหล่านั้นเป็นทูเพิล (priority, data) heapq ของไพธอนจะเปรียบเทียบทูเพิลทีละสมาชิก จึงเปรียบเทียบลำดับความสำคัญก่อน หากลำดับความสำคัญเท่ากัน ระบบจะเปรียบเทียบสมาชิกตัวที่สอง ซึ่งอาจทำให้เกิดข้อผิดพลาดหากข้อมูลไม่สามารถเปรียบเทียบกันได้ รูปแบบที่ปลอดภัยที่สุดคือเพิ่มตัวนับที่ไม่ซ้ำกันเป็นตัวตัดสินกรณีเสมอ เพื่อไม่ให้ต้องเปรียบเทียบสมาชิกข้อมูลโดยตรง

import heapq
import itertools

# Pattern: (priority, counter, item)
# Counter ensures unique tiebreaker, avoids comparing items
counter = itertools.count()
heap = []

def push_task(priority, task):
    heapq.heappush(heap, (priority, next(counter), task))

push_task(3, 'low priority task')
push_task(1, 'high priority task')
push_task(2, 'medium priority task')
push_task(1, 'another high priority')

while heap:
    pri, cnt, task = heapq.heappop(heap)
    print(f'P{pri}: {task}')
# Output in priority order: P1, P1, P2, P3

heapq.merge: การผสานออบเจ็กต์วนซ้ำที่เรียงลำดับแล้ว

heapq.merge(*iterables) จะผสานออบเจ็กต์วนซ้ำที่เรียงลำดับแล้วหลายรายการแบบทำงานเมื่อจำเป็น ให้เป็นผลลัพธ์เดียวที่เรียงลำดับแล้ว โดยไม่โหลดข้อมูลทั้งหมดไว้ในหน่วยความจำ วิธีนี้เทียบเท่ากับการผสานแบบ k ทางโดยใช้ฮีปต่ำสุดขนาด k และใช้ในอัลกอริทึมการเรียงลำดับภายนอก ฟังก์ชันนี้คืนค่าตัววนซ้ำ ดังนั้นสมาชิกจึงถูกสร้างทีละรายการ เหมาะอย่างยิ่งสำหรับชุดข้อมูลขนาดใหญ่หรือสถานการณ์ที่มีการส่งข้อมูลต่อเนื่อง

import heapq

# Merge multiple sorted lists efficiently
sorted_lists = [
    [1, 5, 9],
    [2, 6, 8],
    [3, 4, 7]
]

# heapq.merge takes sorted iterables and returns a merged sorted iterator
merged = list(heapq.merge(*sorted_lists))
print('Merged:', merged)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# The k-way merge manually (educational version):
def merge_k_sorted(lists):
    heap = []
    for i, lst in enumerate(lists):
        if lst:
            heapq.heappush(heap, (lst[0], i, 0))
    result = []
    while heap:
        val, list_idx, elem_idx = heapq.heappop(heap)
        result.append(val)
        if elem_idx + 1 < len(lists[list_idx]):
            heapq.heappush(heap, (lists[list_idx][elem_idx+1], list_idx, elem_idx+1))
    return result

print('Manual k-way:', merge_k_sorted(sorted_lists))

รูปแบบการลบแบบขี้เกียจสำหรับฮีป

เมื่อต้องการ remove สมาชิกใด ๆ จากฮีปแต่ไม่ทราบดัชนี ให้ใช้ การลบแบบขี้เกียจ โดยทำเครื่องหมายสมาชิกที่ถูกลบไว้ในเซตแยกต่างหาก แล้วข้ามสมาชิกเหล่านั้นเมื่อทำการนำค่าออก วิธีนี้มีความซับซ้อน O(log n) โดยเฉลี่ยตลอด และหลีกเลี่ยงความยุ่งยากในการติดตามดัชนี นี่เป็นแนวทางมาตรฐานในอัลกอริทึมดิกสตราที่มีรายการซ้ำ และในการจำลองตัวจัดตารางงาน

import heapq

class LazyHeap:
    def __init__(self):
        self._heap = []
        self._removed = set()

    def push(self, task):
        heapq.heappush(self._heap, task)

    def remove(self, task):
        self._removed.add(task)  # mark as removed

    def pop(self):
        while self._heap:
            task = heapq.heappop(self._heap)
            if task not in self._removed:
                return task
        return None

lh = LazyHeap()
for t in [5, 1, 8, 3, 2]:
    lh.push(t)
lh.remove(1)  # 'delete' 1 lazily
lh.remove(8)  # 'delete' 8 lazily
results = [lh.pop() for _ in range(3)]
print(results)  # [2, 3, 5] -- 1 and 8 skipped

สมาชิกที่มากเป็นอันดับ k ในสตรีมข้อมูล

สมาชิกที่มากเป็นอันดับ k ในสตรีมข้อมูล (LeetCode #703) จะรักษาฮีปต่ำสุดขนาด k ไว้ รากของฮีปจะเป็นสมาชิกที่มากเป็นอันดับ k จากข้อมูลที่พบจนถึงตอนนั้นเสมอ เมื่อมีตัวเลขใหม่เข้ามา ให้ push ตัวเลขนั้น และหากขนาดฮีปเกิน k ให้ pop ค่าต่ำสุดออก รากจะเป็นสมาชิกที่มากเป็นอันดับ k เสมอ เพราะในฮีปมีสมาชิกที่มากกว่ารากอยู่พอดี k-1 รายการ

import heapq

class KthLargest:
    def __init__(self, k, nums):
        self.k = k
        self.heap = []
        for num in nums:
            self.add(num)

    def add(self, val):
        heapq.heappush(self.heap, val)
        if len(self.heap) > self.k:
            heapq.heappop(self.heap)  # remove smallest
        return self.heap[0]  # kth largest = root of min-heap

# k=3, initial=[4,5,8,2]
kl = KthLargest(3, [4, 5, 8, 2])
print(kl.add(3))   # 4 (top 3: 8,5,4 -- kth=4)
print(kl.add(5))   # 5 (top 3: 8,5,5 -- kth=5)
print(kl.add(10))  # 5 (top 3: 10,8,5 -- kth=5)
print(kl.add(9))   # 8 (top 3: 10,9,8 -- kth=8)

ค้นหาคู่ k คู่ที่มีผลรวมต่ำที่สุด

ค้นหาคู่ k คู่ที่มีผลรวมต่ำที่สุด (LeetCode #373) ใช้ฮีปต่ำสุดเพื่อสร้างคู่ตามลำดับ เริ่มต้นด้วยคู่ทั้งหมด (nums1[0], nums2[j]) สำหรับทุกค่า j จากนั้น pop ค่าต่ำสุด และสำหรับคู่ที่นำออกมา (nums1[i], nums2[j]) ให้ push (nums1[i+1], nums2[j]) ซึ่งเป็นผู้สมัครถัดไปจากคอลัมน์เดิมของรายการ nums2 นี่เป็นรูปแบบที่พบบ่อยสำหรับการสร้างคู่หรือผลคูณตามลำดับด้วยฮีป

import heapq

def k_smallest_pairs(nums1, nums2, k):
    if not nums1 or not nums2:
        return []
    heap = []
    # Initialize with pairs (nums1[0], nums2[j])
    for j in range(min(k, len(nums2))):
        heapq.heappush(heap, (nums1[0] + nums2[j], 0, j))
    result = []
    while heap and len(result) < k:
        total, i, j = heapq.heappop(heap)
        result.append([nums1[i], nums2[j]])
        if i + 1 < len(nums1):
            heapq.heappush(heap, (nums1[i+1] + nums2[j], i+1, j))
    return result

print(k_smallest_pairs([1,7,11], [2,4,6], 3))
# [[1,2], [1,4], [1,6]]

ตัวจัดตารางงานด้วยฮีปสูงสุด

ตัวจัดตารางงาน (LeetCode #621) ถามหาเวลาต่ำสุดในการ schedule งาน n รายการ โดยมีช่วงพักจำนวน n ช่วงระหว่างงานประเภทเดียวกัน ให้ใช้ฮีปสูงสุดของความถี่งาน ในแต่ละช่วงเวลา ให้เลือกงานที่พร้อมใช้งานและมีความถี่สูงสุด ลดจำนวนของงานนั้นลงหนึ่ง แล้วนำงานไปไว้ในช่วงพัก ประมวลผลงาน k=n+1 รายการต่อรอบ หรือเติมช่วงเวลาว่าง วิธีแบบละโมบนี้ที่ใช้ฮีปสูงสุดจะให้คำตอบที่เหมาะที่สุด

import heapq
from collections import Counter

def least_interval(tasks, n):
    freq = Counter(tasks)
    heap = [-f for f in freq.values()]  # max-heap (negated)
    heapq.heapify(heap)
    time = 0
    while heap:
        cycle = n + 1
        temp = []
        for _ in range(cycle):
            if heap:
                temp.append(heapq.heappop(heap))
        for f in temp:
            if f + 1 < 0:  # still tasks remaining
                heapq.heappush(heap, f + 1)
        # Add full cycle or remaining tasks if queue empty
        time += cycle if heap else len(temp)
    return time

print(least_interval(['A','A','A','B','B','B'], 2))  # 8
print(least_interval(['A','A','A','B','B','B'], 0))  # 6

ฮีปในอัลกอริทึมดิกสตรา

คิวลำดับความสำคัญในอัลกอริทึมดิกสตรา สร้างด้วยฮีปต่ำสุด จัดเก็บทูเพิล (distance, node) และประมวลผลโหนดที่ยังไม่เยี่ยมชมซึ่งอยู่ใกล้ที่สุดก่อนเสมอ เมื่อดึงโหนดออกมาแล้วพบว่าระยะทางของโหนดนั้นมากกว่าเส้นทางที่สั้นที่สุดที่ทราบอยู่ในปัจจุบัน ซึ่งเป็นรายการเก่าจากการลบแบบขี้เกียจ ให้ข้ามรายการนั้น วิธีนี้ไม่จำเป็นต้องใช้การดำเนินการลดคีย์ และทำให้การใช้งานเรียบง่าย โดยยังคงความซับซ้อน O((V + E) log V)

import heapq

def dijkstra(graph, start):
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    heap = [(0, start)]  # (distance, node)
    while heap:
        d, u = heapq.heappop(heap)
        if d > dist[u]:   # stale entry, skip
            continue
        for v, w in graph[u]:
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                heapq.heappush(heap, (dist[v], v))
    return dist

graph = {
    'A': [('B', 4), ('C', 1)],
    'B': [('D', 1)],
    'C': [('B', 2), ('D', 5)],
    'D': []
}
print(dijkstra(graph, 'A'))  # {'A':0,'B':3,'C':1,'D':4}

จัดเรียงสตริงใหม่ด้วยฮีปสูงสุด

จัดเรียงสตริงใหม่ (LeetCode #767) กำหนดให้จัดเรียงสตริงใหม่เพื่อไม่ให้อักขระที่อยู่ติดกันสองตัวเหมือนกัน ให้ใช้ฮีปสูงสุดของ (-frequency, char) ในแต่ละขั้นตอน ให้ pop อักขระที่มีความถี่สูงสุด หากอักขระก่อนหน้าเป็นตัวเดียวกับอักขระที่มีความถี่สูงสุด ให้ pop อักขระที่มีความถี่สูงสุดเป็นอันดับสองแทน วิธีแบบละโมบนี้ทำให้มั่นใจว่าอักขระที่มีข้อจำกัดมากที่สุดจะถูกวางให้เร็วที่สุดเท่าที่ทำได้

import heapq
from collections import Counter

def reorganize_string(s):
    freq = Counter(s)
    heap = [(-f, c) for c, f in freq.items()]
    heapq.heapify(heap)
    result = []
    prev_freq, prev_char = 0, ''
    while heap:
        freq, char = heapq.heappop(heap)
        result.append(char)
        # Push back the previous character if still remaining
        if prev_freq < 0:
            heapq.heappush(heap, (prev_freq, prev_char))
        prev_freq, prev_char = freq + 1, char  # decrement freq (less negative)
    result_str = ''.join(result)
    # Verify no adjacent duplicates
    return result_str if len(result_str) == len(s) else ''

print(reorganize_string('aab'))   # 'aba'
print(reorganize_string('aaab'))  # '' (impossible)

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์เขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้เกี่ยวกับส่วนติดต่อการเขียนโปรแกรมของ โมดูล heapq ของไพธอน ซึ่งรวมถึง heapify, heappush, heappop, nlargest, nsmallest และ merge การจำลอง ฮีปสูงสุด ด้วยการเปลี่ยนเครื่องหมายของค่า และ รูปแบบการใช้ฮีปที่พบบ่อยในการสัมภาษณ์ เช่น การประมวลผลสตรีมเพื่อหาสมาชิก k อันดับแรก สมาชิกที่มากเป็นอันดับ k ในสตรีม ตัวจัดตารางงาน และอัลกอริทึมดิกสตรา บทถัดไปเราจะจัดการกับมัธยฐานจากสตรีมข้อมูลและการผสานแบบ k ทาง

คำถามที่พบบ่อย

บทเรียน “heapq ของ Python และเทคนิคฮีปสูงสุด” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “heapq ของ Python และเทคนิคฮีปสูงสุด” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “heapq ของ Python และเทคนิคฮีปสูงสุด”

ใช้ heapq.heappush/heapq.heappop กลับเครื่องหมายค่าเพื่อจำลองฮีปสูงสุด และใช้ heapq.nlargest/heapq.nsmallest สำหรับการค้นหาค่าสูงสุด k อันดับอย่างรวดเร็ว คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “heapq ของ Python และเทคนิคฮีปสูงสุด” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. คุณสมบัติฮีปและการแทนด้วยอาร์เรย์
  2. สร้างฮีป เลื่อนเข้า และเลื่อนออกตั้งแต่ต้น
  3. heapq ของ Python และเทคนิคฮีปสูงสุด
  4. มัธยฐานจากกระแสข้อมูลและการผสาน k ทาง
← กลับไปที่ DSA Interview Prep