數據結構: 隊列 講解

數據結構: 隊列 講解

一、概念:

先進者先出,這就是 隊列。

把它想象成排隊買票,先來的先買,後來的人只能站末尾,不允許插隊。

隊列跟棧非常相似,支持的操作也很有限,最基本的操作也是兩個:

  • 入隊 enqueue(),放一個數據到隊列尾部;
  • 出隊 dequeue(),從隊列頭部取一個元素;

所以,隊列跟棧一樣,也是一種操作受限的線性表數據結構

二、順序隊列和鏈式隊列:

跟棧一樣,隊列可以用數組來實現,也可以用鏈表來實現。

  • 用數組實現的隊列叫作順序隊列
  • 用鏈表實現的隊列叫作鏈式隊列

1.順序隊列:

隊列,需要兩個指針,一個head 指針,指向隊頭;一個tail 指針,指向隊尾

class SqQueue(object):
    """順序隊列"""

    def __init__(self, size):
        """
        size:指定隊列的大小
        """
        self.data = list(None for _ in range(size + 1))
        self.max_size = size + 1
        self.head = 0
        self.tail = 0
        self.length = 0

    def get_length(self):
        """獲取隊列的長度"""
        return self.length

    def is_full(self):
        """判斷隊列是否滿了"""
        if (self.tail + 1) % self.max_size == self.head:
            return True
        return False

    def is_empty(self):
        """判斷隊列是否爲空"""
        if self.head == self.tail:
            return True
        return False

    def en_queue(self, element):
        """進入隊列, 從隊尾插入"""
        if self.is_full():
            raise IndexError('Queue is full!')
        else:
            self.data[self.tail] = element
            self.tail = (self.tail + 1) % self.max_size
            self.length += 1

    def de_queue(self):
        """出隊列, 從對頭取出"""
        if self.is_empty():
            raise IndexError('Queue is empty!')
        else:
            del_element = self.data[self.head]
            self.data[self.head] = None
            self.head = (self.head + 1) % self.max_size
            self.length -= 1
            return del_element

    def show_queue(self):
        """顯示隊列元素, 從隊首開始顯示"""
        if self.is_empty():
            raise IndexError('Queue is empty!')
        else:
            j = self.head
            while j != self.tail:
                print(self.data[j])
                j = (j + 1) % self.max_size


queue = SqQueue(5)
for i in range(5):
    queue.en_queue(i)

queue.show_queue()
print('----------')
queue.de_queue()
queue.show_queue()

如圖所示

  1. 當a、b、c、d依次入隊之後,隊列中的 head 指針,指向下標爲 0 的位置,tail 指針指向下標爲 4 的位置。

  2. 當調用兩次出隊操作後,隊列中 head 指針指向下標爲 2 的位置, tail 指針仍然指向下標爲 4 的位置。

隨着不停的進行入隊、出隊操作,head 和 tail 都會持續往後移動。當tail 移動最右邊,即使數組中還有空間,也無法繼續往隊列中添加數據了。

  • 如果使用數據遷移,每次進、出隊都相當於刪除數組下標爲 0 的數據,要搬移整個隊列中的數據,這樣出隊操作的時間複雜度從原來的O(1) 變爲了 O(n)

  • 優化思路,在出隊時,不用搬移數據,如果沒有空閒空間,在入隊時,在集中出發依次數據的搬移。

2.鏈式隊列:

鏈表的實現,我們同樣需要兩個指針:head 指針和 tail 指針。它們分別指向鏈表的第一個結點和最後一個結點

class Node(object):
    """節點"""
    def __init__(self, data=None):
        self.data = data
        self.next = None


class LkQueue(object):
    """鏈式隊列"""

    def __init__(self):
        self.front = Node()
        self.rear = Node()
        self.length = 0

    def get_length(self):
        """獲取長度"""
        return self.length

    def is_empty(self):
        """判斷是否爲空"""
        if self.length == 0 :
            return True
        return False

    def en_queue(self, elem):
        """入隊操作"""

        tmp = Node(elem)
        if self.is_empty():
            self.front = tmp
            self.rear = tmp
        else:
            self.rear.next = tmp
            self.rear = tmp
        self.length += 1

    def de_queue(self):
        """出隊操作"""
        if self.is_empty():
            raise ValueError("LKQueue is empty!")
        else:
            del_elem = self.front.data
            self.front = self.front.next
            self.length -= 1
            return del_elem

    def show_queue(self):
        """顯示隊列"""
        if self.is_empty():
            raise ValueError("LKQueue is empty!")

        j = self.length
        tmp = self.front
        while j > 0:
            print(tmp.data)
            tmp = tmp.next
            j -= 1


lkq = LkQueue()
for i in range(5):
    lkq.en_queue(i)

lkq.show_queue()
print('------------------')
lkq.de_queue()
lkq.show_queue()

如圖所示

3.循環隊列:

我們把首尾相連,扳成一個

如圖所示

  1. 隊列的大小爲8,當前 head = 4,tail = 7;

  2. 當一個新元素 a 入隊時,我們放入小標 爲 7 的位置;

  3. 這時,我們不把 tail 更新爲 8,而是在環中後移一位,到下標 0 的位置;

  4. 在有一個元素 b 入隊時, 我們將 b 放入下標爲 0 的位置,然後tail 加 1 更新爲 1 ;

  5. 在 a,b 依次入隊後,循環隊列中元素就變成下面這樣;

代碼實現

class CircularQueue(object):
    """循環隊列"""

    def __init__(self, size):
        """設置隊列長度爲size"""
        
        self.queue = [""] * size
        self.max_size = size
        self.start = -1
        self.end = -1

    def is_full(self):
        """檢查循環隊列是否已滿"""
        if (self.end + 1) % self.max_size == self.start:
            return True
        return False

    def is_empty(self):
        """檢查循環隊列是否爲空"""
        if self.start == -1 and self.end == -1:
            return True
        return False

    def front(self):
        """從隊首獲取元素, 如果隊列爲空, 返回 -1"""
        return -1 if self.is_empty() else self.queue[self.start]

    def rear(self):
        """獲取隊尾元素, 如果隊列爲空, 返回 -1"""
        return -1 if self.is_empty() else self.queue[self.end]

    def en_queue(self, element):
        """向循環隊列插入一個元素, 如果成功插入返回 True"""
        if not self.is_full():
            self.start = 0
            self.end = (self.end + 1) % self.max_size
            self.queue[self.end] = element
            return True
        return False

    def de_queue(self):
        """從循環隊列中刪除一個元素, 如果成功刪除, 返回True"""
        if not self.is_empty():
            if self.start == self.end:
                self.start, self.end = -1, -1
            else:
                self.start = (self.start + 1) % self.max_size
            return True

        return False


queue = CircularQueue(8)
for i in range(8):
    queue.en_queue(i)

4.阻塞隊列:

阻塞隊列就是在隊列基礎上增加了阻塞操作

簡單理解

  • 就是在隊列爲的時候,從隊頭取數據會被阻塞。因爲此時還沒有數據可取,直到隊列中有了數據才能返回;
  • 如果隊列已經滿了,那麼插入數據的操作就會被阻塞,直到隊列中有空閒位置後再插入數據,然後再返回;

如圖所示

也可以想到"生產者" 跟 “消費者” 的個數,來提高數據的處理效率。

如圖所示

在多線程的情況下,有多個線程同時操作隊列,這個時就會存在線程安全的問題,如何實現一個線程安全隊列呢?

5.併發隊列:

線程安全的隊列,我們叫做併發隊列.

最簡單方式是直接在 enqueue()、dequeue() 方法上加鎖,但是鎖粒度大併發度會比較低,同一時刻僅允許一個存或者取操作。實際上,基於數組的循環隊列,利用 CAS 原子操作,可以實現非常高效的併發隊列。

參考資料:

《數據結構與算法之美》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章