Çift Uçlu Kuyruk (Deque)

Hem önden hem sondan O(1) ekleme ve çıkarma yapılabilen iki uçlu kuyruk.

Önce bunlara göz at

#Nasıl çalışır?

Deque (double-ended queue, iki uçlu kuyruk), elemanların hem önden hem sondan eklenip çıkarılabildiği bir veri yapısıdır. Yığın (stack) ile kuyruğun (queue) birleşimi gibi düşünebilirsin.

Yalnızca sona ekleyip önden çıkarırsan kuyruk, yalnızca tek uçtan ekleyip çıkarırsan yığın gibi davranır. Bu esneklik onu birçok algoritmanın temel yapı taşı yapar.

Genellikle dairesel dizi (circular buffer) ya da çift yönlü bağlı listeyle gerçeklenir; böylece dört işlem de O(1) sürer.

#Kendin dene

$ deque --interactive

Deque iki uçlu bir kuyruktur: hem öne hem sona ekleyip hem önden hem sondan çıkarabilirsin.

Eleman: 3/8GeziliyorYeniÇıkarılıyorBulundu / okunan

#Temel işlemler

pushFront(x)
x değerini en öne ekler.
pushBack(x)
x değerini en sona ekler.
popFront()
Öndeki elemanı çıkarır ve döndürür.
popBack()
Sondaki elemanı çıkarır ve döndürür.
peekFront() / peekBack()
Uçtaki elemanı çıkarmadan okur.

#Örnek kod

Aynı algoritma 8 dilde. Her program derlenip çalıştırılarak test edildi ve hepsi aynı çıktıyı veriyor.

class Deque:
    """Circular buffer: both ends wrap around, so nothing is ever shifted.

    (The standard library already has one: collections.deque.)
    """

    def __init__(self, capacity: int) -> None:
        self._items = [0] * capacity
        self._front = 0
        self._size = 0

    def push_front(self, value: int) -> None:
        if self._size == len(self._items):
            raise OverflowError("deque is full")
        self._front = (self._front - 1) % len(self._items)
        self._items[self._front] = value
        self._size += 1

    def push_back(self, value: int) -> None:
        if self._size == len(self._items):
            raise OverflowError("deque is full")
        self._items[(self._front + self._size) % len(self._items)] = value
        self._size += 1

    def pop_front(self) -> int:
        if self._size == 0:
            raise IndexError("deque is empty")
        value = self._items[self._front]
        self._front = (self._front + 1) % len(self._items)
        self._size -= 1
        return value

    def pop_back(self) -> int:
        if self._size == 0:
            raise IndexError("deque is empty")
        self._size -= 1
        return self._items[(self._front + self._size) % len(self._items)]

    def __len__(self) -> int:
        return self._size


d = Deque(8)
d.push_back(1)
d.push_back(2)
d.push_front(0)       # [0, 1, 2]
print(d.pop_back())   # 2
print(d.pop_front())  # 0
print(len(d))         # 1

#Karmaşıklık

İşlemSüre
pushFront / pushBackO(1)
popFront / popBackO(1)
Ortadaki bir elemana erişmekO(1) dizi / O(n) liste
BellekO(n)

Dairesel dizide front indeksi geriye, arka indeks ileriye kayar; hiçbir eleman taşınmaz. Dizi dolunca kapasite iki katına çıkarılır, bu yüzden ekleme amortize O(1) olur.

#Ne zaman kullanılır?

Kayan pencere (sliding window) maksimumu, 0-1 BFS, palindrom kontrolü, geri al/yinele geçmişi ve iş çalma (work stealing) zamanlayıcıları deque kullanır. Python'da collections.deque, C++'ta std::deque, Java'da ArrayDeque hazır gerçeklemelerdir.

#Alıştırma

Soruları cevaplamak ve ilerlemeni kaydetmek için giriş yap.

Kolay

1/3

Deque'de hangi uçlardan eleman eklenip çıkarılabilir?

  • 1Hem önden hem sondan
  • 2Yalnızca sondan
  • 3Yalnızca önden
  • 4Yalnızca ortadan

Orta

1/1

Boş deque: pushBack(1), pushBack(2), pushFront(0), popBack(). Hangi değer çıkar?

  • 12
  • 20
  • 31
  • 4Hiçbiri

Zor

1/1

Kayan pencere maksimumu (sliding window maximum) probleminde deque neden kullanılır?

  • 1Pencereden çıkanlar önden, işe yaramayan küçükler sondan O(1) atılabildiği için
  • 2Elemanları otomatik sıraladığı için
  • 3Arama O(1) olduğu için
  • 4Daha az bellek kullandığı için