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

การสร้างคิวและดีคิว

สร้างคิวด้วย deque ของ Python สร้างคิวแบบวงกลม และแก้โจทย์ค่าสูงสุดของหน้าต่างเลื่อนด้วยดีคิวโมโนโทน

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

โครงสร้างข้อมูลคิว

คิว เป็นโครงสร้างข้อมูลแบบเข้าก่อนออกก่อน (FIFO) สมาชิกตัวแรกที่ใช้ enqueue จะเป็นสมาชิกแรกที่ใช้ dequeue เช่นเดียวกับแถวชำระเงินในร้านค้า การดำเนินการหลักคือ enqueue (เพิ่มที่ท้ายคิว) และ dequeue (นำออกจากหน้าคิว) ทั้งสองการดำเนินการต้องใช้เวลา O(1) เพื่อให้คิวมีประสิทธิภาพ

การใช้ลิสต์ของไพทอนเป็นคิวนั้นดูน่าสนใจแต่ไม่ถูกต้อง: list.pop(0) ใช้เวลา O(n) เพราะต้องเลื่อนสมาชิกทั้งหมดไปมา เครื่องมือที่ถูกต้องคือ collections.deque ซึ่งมี appendleft, append, popleft และ pop ที่ใช้เวลา O(1)

from collections import deque

queue = deque()

# Enqueue (add to rear)
queue.append(10)
queue.append(20)
queue.append(30)
print('Queue:', queue)          # deque([10, 20, 30])

# Peek front
print('Front:', queue[0])       # 10

# Dequeue (remove from front)
print('Dequeued:', queue.popleft())  # 10
print('Queue after:', queue)         # deque([20, 30])

คลาสคิวที่ใช้ดีค

ห่อ deque ไว้ในคลาส Queue พร้อมการดำเนินการที่มีชื่อชัดเจน เพื่อให้ตรงกับสิ่งที่ผู้สัมภาษณ์คาดหวัง ภายในคลาส enqueue จะเรียกใช้ append และ dequeue จะเรียกใช้ popleft การดำเนินการ peek จะอ่านค่า queue[0] โดยไม่ลบออก

from collections import deque

class Queue:
    def __init__(self):
        self._data = deque()

    def enqueue(self, val):
        self._data.append(val)

    def dequeue(self):
        if self.is_empty():
            raise IndexError('dequeue from empty queue')
        return self._data.popleft()

    def peek(self):
        if self.is_empty():
            raise IndexError('peek at empty queue')
        return self._data[0]

    def is_empty(self):
        return len(self._data) == 0

    def __len__(self):
        return len(self._data)

q = Queue()
q.enqueue(1); q.enqueue(2); q.enqueue(3)
print(q.peek())     # 1
print(q.dequeue())  # 1
print(len(q))       # 2

BFS ด้วยคิว

การประยุกต์ใช้คิวแบบคลาสสิกคือ การค้นหาแบบกว้างก่อน (BFS) ให้ใช้ enqueue กับโหนดราก จากนั้นตราบใดที่คิวไม่ว่าง ให้ใช้ dequeue นำโหนดออก ประมวลผลโหนดนั้น แล้วใช้ enqueue โหนดข้างเคียงที่ยังไม่เคยเยี่ยมชมเข้าไป เนื่องจากเราประมวลผลโหนดทีละระดับ BFS จึงค้นหาเส้นทางสั้นที่สุดในกราฟไม่มีน้ำหนักได้โดยธรรมชาติ คิวจะเก็บโหนดจากระดับที่อยู่ติดกันไม่เกินสองระดับเสมอ

from collections import deque

def bfs(graph, start):
    visited = {start}
    queue   = deque([start])
    order   = []
    while queue:
        node = queue.popleft()
        order.append(node)
        for neighbour in graph[node]:
            if neighbour not in visited:
                visited.add(neighbour)
                queue.append(neighbour)
    return order

graph = {0:[1,2], 1:[0,3,4], 2:[0,5], 3:[1], 4:[1], 5:[2]}
print(bfs(graph, 0))  # [0, 1, 2, 3, 4, 5]

คิววงกลม (LeetCode 622)

LeetCode 622 'ออกแบบคิววงกลม': สร้างคิวที่มีความจุคงที่และวนกลับไปยังจุดเริ่มต้น ใช้อาร์เรย์ขนาด k และตัวชี้สองตัวคือ head และ tail เพิ่มสมาชิกที่ท้ายคิว นำสมาชิกออกจากส่วนหัว และคำนวณตำแหน่งด้วยการคิดแบบโมดูโล k ตัวแปรจำนวนช่วยแยกสถานะเต็มออกจากสถานะว่าง (มิฉะนั้นทั้งสองสถานะจะมีตำแหน่งส่วนหัวเท่ากับตำแหน่งส่วนท้ายเมื่อคิดแบบโมดูโล k)

class MyCircularQueue:
    def __init__(self, k):
        self.data  = [0] * k
        self.head  = 0
        self.tail  = 0
        self.count = 0
        self.k     = k

    def enQueue(self, value):
        if self.isFull(): return False
        self.data[self.tail] = value
        self.tail  = (self.tail + 1) % self.k
        self.count += 1
        return True

    def deQueue(self):
        if self.isEmpty(): return False
        self.head  = (self.head + 1) % self.k
        self.count -= 1
        return True

    def Front(self):
        return -1 if self.isEmpty() else self.data[self.head]

    def Rear(self):
        return -1 if self.isEmpty() else self.data[(self.tail - 1) % self.k]

    def isEmpty(self): return self.count == 0
    def isFull(self):  return self.count == self.k

cq = MyCircularQueue(3)
print(cq.enQueue(1), cq.enQueue(2), cq.enQueue(3))  # True True True
print(cq.enQueue(4))   # False (full)
print(cq.Rear())       # 3
print(cq.isFull())     # True
print(cq.deQueue())    # True
print(cq.enQueue(4))   # True

ค่าสูงสุดของหน้าต่างเลื่อนด้วยดีคแบบโมโนโทนิก

LeetCode 239 'ค่าสูงสุดของหน้าต่างเลื่อน': สำหรับหน้าต่างแต่ละช่วงที่มีขนาด k ให้ค้นหาองค์ประกอบที่มีค่าสูงสุด วิธีตรวจสอบทุกองค์ประกอบใช้เวลา O(n*k) ส่วนวิธีที่ใช้เวลา O(n) ใช้ ดีคแบบลดลงโมโนโทนิก เพื่อเก็บดัชนี สำหรับองค์ประกอบใหม่แต่ละตัว ให้ลบดัชนีที่อยู่นอกหน้าต่างออกจากด้านหน้า ลบดัชนีที่มีค่าน้อยกว่าออกจากด้านหลัง (ดัชนีเหล่านั้นไม่มีทางเป็นค่าสูงสุดในหน้าต่างถัดไป) ด้านหน้าจะเก็บค่าสูงสุดไว้เสมอ

from collections import deque

def maxSlidingWindow(nums, k):
    dq     = deque()   # stores indices, decreasing values
    result = []
    for i, n in enumerate(nums):
        # Remove indices outside window
        while dq and dq[0] < i - k + 1:
            dq.popleft()
        # Remove smaller elements from back
        while dq and nums[dq[-1]] < n:
            dq.pop()
        dq.append(i)
        if i >= k - 1:
            result.append(nums[dq[0]])
    return result

print(maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3))
# [3, 3, 5, 5, 6, 7]

เหตุใดจึงใช้ดีคแทนลิสต์สำหรับคิว

list.pop(0) ของไพทอนลบสมาชิกตัวแรกในเวลา O(n) เพราะสมาชิกที่เหลือทุกตัวต้องเลื่อนไปทางซ้ายหนึ่งตำแหน่ง เมื่อมีการเพิ่ม n ครั้งและลบ n ครั้ง จะใช้เวลารวม O(n²) collections.deque เป็นลิสต์เชื่อมโยงสองทางของบล็อกขนาดคงที่ ส่วน popleft ใช้เวลา O(1) เพราะปรับเพียงตัวชี้เท่านั้น สำหรับ BFS บนกราฟที่มีโหนด 10^5 โหนด ความแตกต่างระหว่าง O(n) กับ O(n²) คือความแตกต่างระหว่าง 100 มิลลิวินาทีกับ 100 วินาที

import timeit

n = 10000

# Using list (O(n) per popleft)
list_time = timeit.timeit(
    stmt='q = list(range(n)); [q.pop(0) for _ in range(n)]',
    globals={'n': n}, number=10
)

# Using deque (O(1) per popleft)
from collections import deque
deque_time = timeit.timeit(
    stmt='q = deque(range(n)); [q.popleft() for _ in range(n)]',
    globals={'n': n, 'deque': deque}, number=10
)

print(f'List:  {list_time:.4f}s')
print(f'Deque: {deque_time:.4f}s')
print(f'Speedup: {list_time / deque_time:.1f}x')

การท่องต้นไม้ทวิภาคแบบเรียงตามระดับ (LeetCode 102)

LeetCode 102 'การท่องต้นไม้ทวิภาคแบบเรียงตามระดับ': คืนค่าของโหนดทั้งหมดทีละระดับ ใช้คิว โดยเมื่อเริ่มต้นแต่ละระดับ ให้บันทึกขนาดของคิว (ซึ่งก็คือจำนวนโหนดในระดับนั้น) ใช้ dequeue นำโหนดออกให้ครบตามจำนวนนั้น พร้อมเก็บค่าของโหนดและใช้ enqueue ลูกของโหนดเข้าไป ทำซ้ำจนกว่าคิวจะว่าง

from collections import deque

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val   = val
        self.left  = left
        self.right = right

def levelOrder(root):
    if not root:
        return []
    result = []
    queue  = deque([root])
    while queue:
        level      = []
        level_size = len(queue)
        for _ in range(level_size):
            node = queue.popleft()
            level.append(node.val)
            if node.left:  queue.append(node.left)
            if node.right: queue.append(node.right)
        result.append(level)
    return result

root = TreeNode(3, TreeNode(9), TreeNode(20, TreeNode(15), TreeNode(7)))
print(levelOrder(root))  # [[3], [9, 20], [15, 7]]

คิวลำดับความสำคัญด้วย heapq

โมดูล heapq ของ Python มี ฮีพค่าต่ำสุด (คิวลำดับความสำคัญ) ซึ่งสมาชิกที่มีค่าน้อยที่สุดจะถูกนำออกจากคิวก่อนเสมอ heapq.heappush(h, item) เพิ่มสมาชิกด้วยความซับซ้อน O(log n) และ heapq.heappop(h) นำสมาชิกที่มีค่าต่ำสุดออกด้วยความซับซ้อน O(log n) สำหรับงานอย่างอัลกอริทึมไดก์สตราและปัญหาการเลือก k อันดับแรก heapq สามารถใช้แทนคิวแบบง่ายได้

import heapq

pq = []
heapq.heappush(pq, 5)
heapq.heappush(pq, 1)
heapq.heappush(pq, 3)
heapq.heappush(pq, 2)

print('Min:', heapq.heappop(pq))  # 1
print('Min:', heapq.heappop(pq))  # 2
print('Min:', heapq.heappop(pq))  # 3

# Tasks with priorities
tasks = [(2, 'send email'), (1, 'fix bug'), (3, 'write docs')]
heapq.heapify(tasks)
while tasks:
    priority, task = heapq.heappop(tasks)
    print(f'Priority {priority}: {task}')

รูปแบบวอลเปเปอร์: คิวสำหรับการไต่คำ

LeetCode 127 'การไต่คำ': จงหาจำนวนขั้นต่ำของการแทนที่อักขระทีละหนึ่งตัวเพื่อเปลี่ยนคำหนึ่งเป็นอีกคำหนึ่ง โดยใช้ได้เฉพาะคำในพจนานุกรมเท่านั้น ให้จำลองปัญหาเป็นกราฟที่มีเส้นเชื่อมระหว่างคำซึ่งแตกต่างกันเพียงอักขระเดียว การใช้ BFS บนกราฟนี้จะหาเส้นทางที่สั้นที่สุดได้ (จำนวนขั้นต่ำน้อยที่สุด) ด้วยความซับซ้อน O(n * L²) โดย n คือขนาดพจนานุกรม และ L คือความยาวของคำ

from collections import deque

def ladderLength(beginWord, endWord, wordList):
    word_set = set(wordList)
    if endWord not in word_set:
        return 0
    queue    = deque([(beginWord, 1)])
    visited  = {beginWord}
    while queue:
        word, steps = queue.popleft()
        for i in range(len(word)):
            for ch in 'abcdefghijklmnopqrstuvwxyz':
                new_word = word[:i] + ch + word[i+1:]
                if new_word == endWord:
                    return steps + 1
                if new_word in word_set and new_word not in visited:
                    visited.add(new_word)
                    queue.append((new_word, steps + 1))
    return 0

print(ladderLength('hit', 'cog', ['hot','dot','dog','lot','log','cog']))  # 5

คิวสองปลายในฐานะคิวแบบสองด้าน

collections.deque คือ คิวสองปลาย: คุณสามารถเพิ่มและนำสมาชิกออกจากปลายทั้งสองด้านได้อย่างมีประสิทธิภาพ เมธอด appendleft และ popleft ใช้สำหรับด้านหน้า ส่วน append และ pop ใช้สำหรับด้านหลัง ทำให้โครงสร้างนี้ทำหน้าที่ได้ทั้งคิวแบบ FIFO (เพิ่มทางขวาแล้วนำออกทางซ้าย) และสแตกแบบ LIFO (ใช้ append แล้วตามด้วย pop) การหาค่าสูงสุดในหน้าต่างเลื่อนใช้ทั้งสองด้าน โดยนำดัชนีเก่าออกจากด้านซ้าย และนำค่าที่เล็กกว่าออกจากด้านขวา

from collections import deque

dq = deque([3, 4, 5])

dq.appendleft(2)   # add to front: [2,3,4,5]
dq.appendleft(1)   # add to front: [1,2,3,4,5]
dq.append(6)       # add to rear:  [1,2,3,4,5,6]

print(dq.popleft())  # 1 (from front)
print(dq.pop())      # 6 (from rear)
print(list(dq))      # [2, 3, 4, 5]

สรุป: คิว คิวสองปลาย และฮีพ

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

ตรวจสอบอย่างรวดเร็ว

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

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า collections.deque มีการเพิ่มเข้าคิวและนำออกจากคิวด้วยความซับซ้อน O(1) จึงเป็นการใช้งานคิวที่เหมาะสมใน Python BFS ใช้คิวเพื่อประมวลผลโหนดทีละระดับ และค้นหาเส้นทางที่สั้นที่สุดในกราฟที่ไม่มีน้ำหนัก และ คิวสองปลายแบบลดลงอย่างต่อเนื่องสามารถแก้ปัญหาค่าสูงสุดในหน้าต่างเลื่อนได้ด้วยความซับซ้อน O(n) โดยนำดัชนีที่ถูกครอบงำออก ต่อไปเราจะศึกษาแบบแผนสแตกโมโนโทนิกอย่างละเอียด

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

บทเรียน “การสร้างคิวและดีคิว” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “การสร้างคิวและดีคิว”

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

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

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

บทเรียน “การสร้างคิวและดีคิว” ใช้เวลานานแค่ไหน

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

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

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

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

  1. การสร้างสแตกและการประยุกต์ใช้
  2. การสร้างคิวและดีคิว
  3. รูปแบบสแตกโมโนโทน
  4. การจำลองคิวและสแตกซึ่งกันและกัน
← กลับไปที่ DSA Interview Prep