Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> C#

Deque Class ใน C# - คิวสองด้าน (Double-Ended Queue)

Deque (Double-Ended Queue) คืออะไร?

Deque (อ่านว่า "เด็ค") ย่อมาจาก Double-Ended Queue เป็นโครงสร้างข้อมูลที่อนุญาตให้เพิ่มและลบข้อมูลได้จาก ทั้งสองด้าน** (Front และ Back)

ใน .NET Framework ไม่มีคลาส Deque<T> ตัวจริง (จนถึง .NET 8) แต่สามารถใช้ LinkedList<T> ซึ่งเป็น Doubly-Linked List ทำหน้าที่เป็น Deque ได้เต็มที่

ทำไม LinkedList<T> จึงเป็น Deque?

  • เป็น Doubly-Linked List มีโหนดชี้ไปข้างหน้าและข้างหลัง
  • มี First (Front) และ Last (Back) nodes
  • เพิ่ม/ลบที่ทั้งสองปลายได้ O(1)

เมธอดหลักของ LinkedList<T> (ทำหน้าที่ Deque)

เมธอด คำอธิบาย Time Complexity
AddFirst(T) เพิ่มที่ด้านหน้า (Front) O(1)
AddLast(T) เพิ่มที่ด้านหลัง (Back) O(1)
RemoveFirst() ลบและคืนค่าด้านหน้า O(1)
RemoveLast() ลบและคืนค่าด้านหลัง O(1)
First / Last ดูค่าด้านหน้า/หลัง (ไม่ลบ) O(1)
Clear() ล้างข้อมูลทั้งหมด O(n)
Contains(T) ตรวจสอบว่ามีค่าหรือไม่ O(n)
ToArray() คัดลอกเป็นอาร์เรย์ O(n)

ตัวอย่างการใช้งาน LinkedList<T> เป็น Deque

using System;
using System.Collections.Generic;

public class DequeDemo
{
    public static void Main()
    {
        // ใช้ LinkedList เป็น Deque
        var deque = new LinkedList();
        
        // เพิ่มที่ด้านหลัง (Back) - เช่น Queue
        deque.AddLast(10);
        deque.AddLast(20);
        deque.AddLast(30);
        Console.WriteLine("หลัง AddLast 10,20,30: " + string.Join(", ", deque));
        // 10, 20, 30
        
        // เพิ่มที่ด้านหน้า (Front) - เช่น Stack
        deque.AddFirst(5);
        deque.AddFirst(1);
        Console.WriteLine("หลัง AddFirst 5,1: " + string.Join(", ", deque));
        // 1, 5, 10, 20, 30
        
        // ดูค่าที่ปลายทั้งสอง (ไม่ลบ)
        Console.WriteLine("Front (First): " + deque.First.Value);  // 1
        Console.WriteLine("Back (Last): " + deque.Last.Value);    // 30
        
        // ลบจากด้านหน้า
        int front = deque.First.Value;
        deque.RemoveFirst();
        Console.WriteLine("RemoveFirst: " + front); // 1
        Console.WriteLine("ตอนนี้: " + string.Join(", ", deque));
        // 5, 10, 20, 30
        
        // ลบจากด้านหลัง
        int back = deque.Last.Value;
        deque.RemoveLast();
        Console.WriteLine("RemoveLast: " + back); // 30
        Console.WriteLine("ตอนนี้: " + string.Join(", ", deque));
        // 5, 10, 20
        
        // ตรวจสอบค่า
        Console.WriteLine("Contains 10: " + deque.Contains(10)); // True
        Console.WriteLine("Contains 99: " + deque.Contains(99)); // False
        
        // แปลงเป็น Array
        int[] array = deque.ToArray();
        Console.WriteLine("ToArray: " + string.Join(", ", array));
    }
}

ผลลัพธ์

หลัง AddLast 10,20,30: 10, 20, 30
หลัง AddFirst 5,1: 1, 5, 10, 20, 30
Front (First): 1
Back (Last): 30
RemoveFirst: 1
ตอนนี้: 5, 10, 20, 30
RemoveLast: 30
ตอนนี้: 5, 10, 20
Contains 10: True
Contains 99: False
ToArray: 5, 10, 20

การ Implement Deque เอง (Custom Deque)

หากต้องการ Deque แบบ Array-based (Circular Buffer) สำหรับประสิทธิภาพหน่วยความจำดีกว่า:

public class ArrayDeque
{
    private T[] _array;
    private int _head = 0;
    private int _tail = 0;
    private int _count = 0;
    
    public ArrayDeque(int capacity = 8)
    {
        _array = new T[capacity];
    }
    
    public void AddFirst(T item)
    {
        if (_count == _array.Length) Resize();
        _head = (_head - 1 + _array.Length) % _array.Length;
        _array[_head] = item;
        _count++;
    }
    
    public void AddLast(T item)
    {
        if (_count == _array.Length) Resize();
        _array[_tail] = item;
        _tail = (_tail + 1) % _array.Length;
        _count++;
    }
    
    public T RemoveFirst()
    {
        if (_count == 0) throw new InvalidOperationException("Deque is empty");
        T item = _array[_head];
        _array[_head] = default;
        _head = (_head + 1) % _array.Length;
        _count--;
        return item;
    }
    
    public T RemoveLast()
    {
        if (_count == 0) throw new InvalidOperationException("Deque is empty");
        _tail = (_tail - 1 + _array.Length) % _array.Length;
        T item = _array[_tail];
        _array[_tail] = default;
        _count--;
        return item;
    }
    
    private void Resize()
    {
        var newArray = new T[_array.Length * 2];
        // คัดลอกข้อมูล...
    }
    
    public int Count => _count;
}

กรณีการใช้งาน (Use Cases)

  • Sliding Window algorithms - เพิ่ม/ลบปลายทั้งสอง
  • Palindrome checking - เปรียบเทียบหน้า-หลัง
  • Browser History - Back/Forward navigation
  • Undo/Redo - เพิ่มท้าย ลบหัว/ท้าย
  • Work Stealing Queue - ใน Parallel Programming

สรุป

  • Deque = Double-Ended Queue (เพิ่ม/ลบ ทั้งหน้าและหลังได้)
  • ใน .NET ใช้ LinkedList<T> แทน (มี First/Last, AddFirst/Last, RemoveFirst/Last)
  • Time Complexity: O(1) สำหรับการเพิ่ม/ลบที่ปลาย
  • มีเมธอด: Clear, Contains, ToArray, Synchronized
  • เหมาะสำหรับ Sliding Window, Undo/Redo, Browser History