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

คุณสมบัติฮีปและการแทนด้วยอาร์เรย์

ทำความเข้าใจโครงสร้างต้นไม้ทวิภาคสมบูรณ์ที่จัดเก็บเป็นอาร์เรย์ หาสูตรดัชนีของโหนดแม่และลูก และมองเห็นการดำเนินการเลื่อนขึ้นและเลื่อนลง

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

ฮีปคืออะไร

ฮีปคือต้นไม้ทวิภาคแบบสมบูรณ์ชนิดพิเศษที่มีคุณสมบัติของฮีป: ในฮีปต่ำสุด parent ทุกโหนดมีค่าน้อยกว่าหรือเท่ากับโหนดลูก และในฮีปสูงสุด parent ทุกโหนดมีค่ามากกว่าหรือเท่ากับโหนดลูก คุณสมบัตินี้รับประกันว่าองค์ประกอบที่มีค่าน้อยที่สุดหรือมากที่สุดจะอยู่ที่รากเสมอ ทำให้เข้าถึงองค์ประกอบปลายสุดได้ในเวลา O(1) ฮีปคือโครงสร้างข้อมูลเบื้องหลังคิวลำดับความสำคัญ

# Min-heap example:
#         1
#        / \
#       3   2
#      / \ / \
#     7  4 5  6
# Every parent <= its children
# Root (1) is always the minimum

# Max-heap example:
#         9
#        / \
#       7   8
#      / \ / \
#     3  4 5  6
# Every parent >= its children
# Root (9) is always the maximum
print('Heap property: parent dominates all descendants')

โครงสร้างต้นไม้ทวิภาคแบบสมบูรณ์

ฮีปถูกจัดเก็บเป็นต้นไม้ทวิภาคแบบสมบูรณ์ — ทุกระดับจะถูกเติมเต็ม ยกเว้นระดับสุดท้ายที่อาจเติมไม่เต็ม โดยเติมจากซ้ายไปขวา โครงสร้างนี้ทำให้สามารถใช้การแทนด้วยอาร์เรย์ที่เรียบง่าย โดยไม่สิ้นเปลืองพื้นที่และไม่ต้องใช้ตัวชี้ คุณสมบัติความสมบูรณ์รับประกันว่าความสูงของฮีปจะเป็น floor(log₂ n) เสมอ จึงรับประกันการดำเนินการ push และ pop ในเวลา O(log n)

# Complete binary tree properties:
# 1. All levels filled except possibly the last
# 2. Last level filled from LEFT to right
# 3. For n nodes: height = floor(log2(n))

# NOT complete (last level not left-filled):
#     1
#    / \
#   2   3
#        \
#         4  <- right child without left sibling

# Valid complete binary tree with 4 nodes:
#     1
#    / \
#   2   3
#  /
# 4
print('Complete BT: height = floor(log2(n)) always')

การแทนฮีปด้วยอาร์เรย์

โครงสร้างต้นไม้ทวิภาคแบบสมบูรณ์ทำให้สามารถจัดเก็บฮีปในอาร์เรย์ที่ไม่มีตัวชี้ใด ๆได้ สำหรับโหนดที่ดัชนี i (เริ่มนับดัชนีจาก 0) parent ของโหนดนั้นอยู่ที่ (i-1) // 2 โหนดลูกด้านซ้ายอยู่ที่ 2i+1 และโหนดลูกด้านขวาอยู่ที่ 2i+2 การคำนวณด้วยจำนวนเต็มนี้ใช้แทนการท่องผ่านตัวชี้ และทำให้ฮีปใช้แคชได้ดีเป็นอย่างมาก

# Array representation (0-indexed):
# Index:  0  1  2  3  4  5  6
# Array: [1, 3, 2, 7, 4, 5, 6]
# Tree:        1          (index 0)
#             / \         
#            3   2        (indices 1, 2)
#           / \ / \       
#          7  4 5  6      (indices 3,4,5,6)

# Index formulas (0-based):
def parent(i):      return (i - 1) // 2
def left_child(i):  return 2 * i + 1
def right_child(i): return 2 * i + 2

heap = [1, 3, 2, 7, 4, 5, 6]
print('Parent of index 3:', parent(3), '-> value', heap[parent(3)])
print('Left child of 1:', left_child(1), '-> value', heap[left_child(1)])

การเลื่อนขึ้น: คืนสภาพฮีปหลังการแทรก

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

def sift_up(heap, i):
    while i > 0:
        p = (i - 1) // 2  # parent index
        if heap[p] > heap[i]:  # min-heap: parent should be smaller
            heap[p], heap[i] = heap[i], heap[p]
            i = p
        else:
            break  # heap property restored

# Demonstrate: insert 0 into an existing min-heap
heap = [1, 3, 2, 7, 4, 5, 6]
heap.append(0)  # add at end
print('Before sift-up:', heap)
sift_up(heap, len(heap) - 1)
print('After sift-up:', heap)  # 0 should bubble to root

การเลื่อนลง: คืนสภาพฮีปหลังการนำออก

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

def sift_down(heap, i, n):
    while True:
        smallest = i
        l = 2 * i + 1  # left child
        r = 2 * i + 2  # right child
        if l < n and heap[l] < heap[smallest]:
            smallest = l
        if r < n and heap[r] < heap[smallest]:
            smallest = r
        if smallest == i:
            break  # already in correct position
        heap[i], heap[smallest] = heap[smallest], heap[i]
        i = smallest

heap = [1, 3, 2, 7, 4, 5, 6]
# Pop min: move last to root, then sift-down
heap[0] = heap[-1]
heap.pop()
print('After move last to root:', heap)
sift_down(heap, 0, len(heap))
print('After sift-down:', heap)  # valid min-heap again

การสร้างฮีปจากอาร์เรย์: อัลกอริทึมของฟลอยด์

การแทรกองค์ประกอบ n รายการทีละรายการแบบตรงไปตรงมาใช้เวลา O(n log n) อัลกอริทึม heapify ของฟลอยด์สร้างฮีปในเวลา O(n) ด้วยการใช้การเลื่อนลงกับโหนดที่ไม่ใช่ใบทุกโหนด โดยเริ่มจากโหนดที่ไม่ใช่ใบโหนดสุดท้าย (ดัชนี n//2 - 1) แล้วย้อนกลับไปยังราก โหนดใบเป็นฮีปที่ถูกต้องอยู่แล้ว ดังนั้นเราจึงต้องปรับแก้เฉพาะโหนดภายในเท่านั้น นี่คือเหตุผลที่งานทั้งหมดรวมกันได้เป็น O(n) แทนที่จะเป็น O(n log n)

def build_heap(arr):
    n = len(arr)
    # Start from last non-leaf node: index n//2 - 1
    for i in range(n // 2 - 1, -1, -1):
        sift_down(arr, i, n)
    return arr

arr = [5, 3, 8, 1, 9, 2, 7]
print('Before:', arr)
build_heap(arr)
print('After (min-heap):', arr)  # root should be 1

# Why O(n)? Most nodes are near the bottom (leaves).
# Level k from bottom has ~n/2^k nodes, each needing
# at most k swaps. Sum = n * sum(k/2^k) = O(n).

การเรียงลำดับแบบฮีปโดยใช้ฮีปในอาร์เรย์

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

def sift_down_max(arr, i, n):
    while True:
        largest = i
        l, r = 2*i+1, 2*i+2
        if l < n and arr[l] > arr[largest]: largest = l
        if r < n and arr[r] > arr[largest]: largest = r
        if largest == i: break
        arr[i], arr[largest] = arr[largest], arr[i]
        i = largest

def heap_sort(arr):
    n = len(arr)
    # Build max-heap
    for i in range(n // 2 - 1, -1, -1):
        sift_down_max(arr, i, n)
    # Extract elements one by one
    for end in range(n - 1, 0, -1):
        arr[0], arr[end] = arr[end], arr[0]  # move max to end
        sift_down_max(arr, 0, end)

arr = [5, 3, 8, 1, 9, 2, 7]
heap_sort(arr)
print(arr)  # [1, 2, 3, 5, 7, 8, 9]

ฮีปต่ำสุดกับฮีปสูงสุด

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

import heapq

# Python heapq is a MIN-HEAP
min_heap = []
heapq.heappush(min_heap, 5)
heapq.heappush(min_heap, 1)
heapq.heappush(min_heap, 3)
print('Min-heap min:', heapq.heappop(min_heap))  # 1

# Simulate MAX-HEAP by negating values
max_heap = []
for val in [5, 1, 3]:
    heapq.heappush(max_heap, -val)  # negate on push
print('Max-heap max:', -heapq.heappop(max_heap))  # 5 (negate on pop)

# For tuples: heapq sorts by first element
print(min_heap, max_heap)

สรุปความซับซ้อนของการดำเนินการฮีป

การดำเนินการทั้งหมดในฮีปมีพื้นฐานมาจากการเลื่อนขึ้นและการเลื่อนลง ซึ่งทั้งสองอย่างใช้เวลา O(log n) push: append + การเลื่อนขึ้น = O(log n) pop: สลับรากกับตัวสุดท้าย + การเลื่อนลง = O(log n) peek: เข้าถึงดัชนี 0 = O(1) การสร้างฮีป: O(n) โดยใช้อัลกอริทึมของฟลอยด์ การเรียงลำดับแบบฮีป: O(n log n) ความซับซ้อนเหล่านี้ทำให้ฮีปเป็นโครงสร้างที่เหมาะอย่างยิ่งเมื่อจำเป็นต้องหาค่าต่ำสุดหรือค่าสูงสุดของคอลเลกชันที่เปลี่ยนแปลงอยู่เสมอซ้ำ ๆ

# Heap complexity summary:
# Operation     | Time       | Space
# --------------|------------|-------
# Push          | O(log n)   | O(1)
# Pop (min/max) | O(log n)   | O(1)
# Peek          | O(1)       | O(1)
# Build from n  | O(n)       | O(1) in-place
# Heap sort     | O(n log n) | O(1)
# nlargest(k,n) | O(n log k) | O(k)

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

รูปแบบการใช้ฮีปในทางปฏิบัติสำหรับการสัมภาษณ์

ฮีปใช้แก้ปัญหาในการสัมภาษณ์ได้หลายประเภทด้วยรูปแบบร่วมกัน นั่นคือรักษาคิวลำดับความสำคัญของตัวเลือก k รายการขณะประมวลผลองค์ประกอบ n รายการแบบสตรีม องค์ประกอบที่พบบ่อยที่สุด k อันดับแรก จุด k จุดที่ใกล้จุดกำเนิดที่สุด และตัวจัดตารางงาน ล้วนใช้รูปแบบนี้ ให้สังเกตเมื่อพบโจทย์ว่า “กำหนดสตรีมของรายการ n รายการ ให้รักษา k รายการที่ดีที่สุดไว้” — กรณีนี้ต้องใช้ฮีปขนาด k เสมอ ทำให้ใช้เวลารวม O(n log k)

import heapq

# Top-K closest points to origin using a max-heap of size k
def k_closest(points, k):
    # Use max-heap (negate distance) of size k
    heap = []
    for x, y in points:
        dist = -(x*x + y*y)  # negate for max-heap
        heapq.heappush(heap, (dist, x, y))
        if len(heap) > k:
            heapq.heappop(heap)  # remove farthest
    return [[x, y] for _, x, y in heap]

points = [[1,3], [-2,2], [5,8], [0,1]]
print(k_closest(points, 2))  # 2 closest to origin

ข้อแลกเปลี่ยนระหว่างฮีปกับอาร์เรย์ที่เรียงแล้ว

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

# Trade-off comparison:
# Structure     | insert  | delete_min | search | range_query
# --------------|---------|------------|--------|------------
# Min-heap      | O(logn) | O(logn)    | O(n)   | O(n)
# Sorted array  | O(n)    | O(n)       | O(logn)| O(logn+k)
# BST (balanced)| O(logn) | O(logn)    | O(logn)| O(logn+k)
# Hash map      | O(1)    | O(1)       | O(1)   | O(n)

# Interview heuristic:
# 'Find minimum repeatedly from dynamic collection' -> HEAP
# 'Binary search or range query' -> sorted array or BST
# 'Fast lookup by key' -> hash map
print('Heap = dynamic collection with priority access')

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

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

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้: คุณสมบัติของฮีปและโครงสร้างต้นไม้ทวิภาคแบบสมบูรณ์ การแทนด้วยอาร์เรย์พร้อมสูตรดัชนีของ parent และโหนดลูก และการเลื่อนขึ้นและการเลื่อนลงในฐานะส่วนประกอบพื้นฐานของการดำเนินการทั้งหมดในฮีป รวมถึงการสร้างฮีปด้วยอัลกอริทึมของฟลอยด์ในเวลา O(n) ต่อไปเราจะลงมือสร้าง heapify และศึกษาโมดูล heapq ของไพธอน

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

บทเรียน “คุณสมบัติฮีปและการแทนด้วยอาร์เรย์” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “คุณสมบัติฮีปและการแทนด้วยอาร์เรย์”

ทำความเข้าใจโครงสร้างต้นไม้ทวิภาคสมบูรณ์ที่จัดเก็บเป็นอาร์เรย์ หาสูตรดัชนีของโหนดแม่และลูก และมองเห็นการดำเนินการเลื่อนขึ้นและเลื่อนลง คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

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

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

บทเรียน “คุณสมบัติฮีปและการแทนด้วยอาร์เรย์” ใช้เวลานานแค่ไหน

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

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

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

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

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