การสร้างคิวและดีคิว
สร้างคิวด้วย 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)) # 2BFS ด้วยคิว
การประยุกต์ใช้คิวแบบคลาสสิกคือ การค้นหาแบบกว้างก่อน (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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ