---
# System prepended metadata

title: 資料結構&演算法 5. 隊列(Queue)
tags: [資料結構&演算法]

---

# 資料結構&演算法 5. 隊列(Queue)

###### tags: `資料結構&演算法`

## 關於隊列(Queue)本篇將討論以下幾個項目
:::info
### 1. 什麼是隊列(Queue)？
### 2. 如何以 Array (陣列)實作隊列(Queue)？
### 3. 如何以循環 Array (陣列)實作隊列(Queue)？
### 4. 如何以 Linked List (鏈結串列、鏈表)實作隊列(Queue)？
### 5. Array & Linked List 實作比較
:::

---
## 測試環境：
> OS：Windows 10
> IDE：Visual Studio 2019
   
---
## 1. 什麼是隊列(Queue)？
### 簡單來說，隊列(Queue)就是個先進先出的集合
如同 Stack，Queue 也是個操作受限的集合，各種變形常見於各類底層服務，如電腦中等待 CPU 處理的各種待辦

---
## 2. 如何以 Array (陣列)實作隊列(Queue)？
### 接下來實作 Queue 的 Enqueue(加入資料) & Dequeue(取出資料)
#### 範例：
```C#
    public class QueueByArray
    {
        private int[] _IntArray;
        private int _Size = 0;
        private int _HeadIndex = 0;
        private int _TailIndex = 0;

        public QueueByArray(int arraySize)
        {
            _IntArray = new int[arraySize];
            _Size = arraySize;
        }

        public bool Enqueue(int value)
        {
            // _IntArray 已滿，無法再加入資料
            if (_HeadIndex == 0 && _TailIndex == _Size) return false;

            // _IntArray 後方已達容量上限，但前方有空位，需要將所有資料往前搬移
            if (_TailIndex == _Size)
            {
                for (var i = _HeadIndex; i < _TailIndex; ++i)
                {
                    _IntArray[i - _HeadIndex] = _IntArray[i];
                }

                _TailIndex = _TailIndex - _HeadIndex;
                _HeadIndex = 0;
            }

            _IntArray[_TailIndex] = value;

            _TailIndex++;

            return true;
        }

        public int Dequeue()
        {
            // _IntArray 內沒有資料
            if (_HeadIndex == _TailIndex) return -1;

            var result = _IntArray[_HeadIndex];

            _HeadIndex++;

            return result;
        }
    }
```
#### 操作 & 結果

![](https://i.imgur.com/7KEskxQ.png)

#### 程式實際上是這樣運作的

![](https://i.imgur.com/5cecKjT.png)

> 1. 沒有資料的時候 _HeadIndex & _TialIndex 都在第一個位置
> 2. Enqueue 時 _HeadIndex 不變，_TialIndex + 1
> 3. 再次 Enqueue 時 _HeadIndex 不變，_TialIndex + 1
> 4. Dequeue 時 _HeadIndex + 1，_TialIndex 不變

由上圖的步驟可以看出來 _HeadIndex & _TialIndex 都在不斷向後移，到最後儘管前面還有空間可以放置資料，卻因為 _HeadIndex & _TialIndex 的位置而無法使用

為了避免明明有空間卻無法 Enqueue 的狀況，我們可以在 _TialIndex 移到最後時判斷 _HeadIndex 的位置是否可以往前搬移，若 _HeadIndex 前方有 n 個空位，則將所有資料全部向前位移 n 個位置，此時時間複雜度：
```
Dequeue
    時間複雜度：O(1)
    
Enqueue
    // 沒有搬移的情況下
    最佳情況時間複雜度：O(1)
    
    // 搬移的情況下
    最壞情況時間複雜度：O(n)

    平均情況時間複雜度：O(1)
```

---
## 3. 如何以循環 Array (陣列)實作隊列(Queue)？
### 接下來實作循環 Array 的 Enqueue(加入資料) & Dequeue(取出資料)
#### 範例：
```C#
    public class QueueByCircularArray
    {
        private int[] _IntArray;
        private int _ArraySize = 0;
        private int _DataSize = 0;
        private int _HeadIndex = 0;
        private int _TailIndex = 0;

        public QueueByCircularArray(int arraySize)
        {
            _IntArray = new int[arraySize];
            _ArraySize = arraySize;
        }

        public bool Enqueue(int value)
        {
            // Array 已經滿了
            if (_ArraySize == _DataSize) return false;

            _IntArray[_TailIndex] = value;
            _TailIndex = (_TailIndex + 1) % _ArraySize;
            _DataSize++;

            return true;
        }

        public int Dequeue()
        {
            // Array 內沒有任何資料
            if (_DataSize == 0) return -1;

            var result = _IntArray[_HeadIndex];

            _HeadIndex = (_HeadIndex + 1) % _ArraySize;
            _DataSize--;

            return result;
        }
    }
```
#### 操作 & 結果

![](https://i.imgur.com/XTUmJtY.png)

#### 程式實際上是這樣運作的

![](https://i.imgur.com/0MzFYl8.png)

> 1. _HeadIndex 因為前面已經取出兩次資料所以在第三個位置，而 _TailIndex 在最後一個位置
> 2. Enqueue 時 _HeadIndex 不變，_TialIndex 則是因為前方還有兩個空位直接回到第一個位置，以循環的方式將資料放置到 queue 中

由上圖的步驟可以看出來，由於是循環的方式在指定 _HeadIndex & _TialIndex，比起原本的方式少了搬移的動作，所以此時的時間複雜度：
```
Dequeue
    時間複雜度：O(1)
    
Enqueue
    時間複雜度：O(1)
```

---
## 4. 如何以 Linked List (鏈結串列、鏈表)實作隊列(Queue)？
### 接下來實作 Linked List 的 Enqueue(加入資料) & Dequeue(取出資料)
#### 範例：
```C#
    public class QueueByLinkedlist<T>
        where T : struct
    {
        private LinkedList<T> linkedList { get; set; } = new LinkedList<T>();
        
        public bool Enqueue(T value)
        {
            linkedList.AddLast(value);
            return true;
        }

        public T? Dequeue()
        {
            var firstItem = linkedList.First?.Value;

            // 如果有值，則取出後刪除
            if (firstItem != null)
                linkedList.RemoveFirst();

            return firstItem;
        }
    }
```
#### 操作 & 結果

![](https://i.imgur.com/nRFuxPB.png)

比起 Array 來說，Linked List 實作起來相當簡單，且沒有容量限制，時間複雜度：

```
Dequeue
    時間複雜度：O(1)
    
Enqueue
    時間複雜度：O(1)
```

---
## 5. Array & Linked List 實作比較

### Linked List 跟 Array 不同，容量限制在硬體上，但對於程式來說幾乎就是個容量無限大的 Queue

### 但實務上處理 Queue 的資源有限，在大多時候待處理項目會有 Timeout 的問題，無法一直等待下去，所以 Array 有限容量反而會是個更好的選擇，在超出容量上限時直接不處理，避免還要等待到 Timeout 才知道處理失敗

---
## 總結
### 本篇使用 Array 跟 Linked List 來實作 Queue，也從 Array 的兩種實作中可以看出兩者在時間複雜度的差異，實作的難度差異不大，但在分析過時間複雜度之後就知道哪種方法會更好了

### 實務上遇到的需求遠比範例複雜得多，本篇沒有對於複雜情境多做說明，不過對應複雜情境可以使用像是 [RabbitMQ](https://www.rabbitmq.com/) 來處理

---

## 新手上路，若有錯誤還請告知，謝謝