第三章 棧和隊列
棧
棧的特性
棧是一種線性結構,這種特殊的線性結構有着最大的特點——後進先出(Last In First Out)。最後壓入棧的元素會最先被彈出。
由於棧只用在同一端進行插入和刪除,因此我們優先選擇使用順序表,因爲在順序表的末尾插入和刪除的時間複雜度都是O(1),並且操作簡單
實現
棧的實現可以基於順序表、鏈表和雙端隊列,這裏使用最簡單方法基於順序表來實現棧。
#include <iostream>
#include <assert.h>
template<class T>
class Stack
{
public:
//構造函數
Stack()
:_stack(nullptr)
,_size(0)
,_capacity(0)
{
}
//析構函數
~Stack()
{
if(_stack != nullptr)
{
delete[] _stack;
}
}
//入棧
void Push(const T& temp)
{
//尾插
//容量檢查
if(_size == _capacity)//滿了
{
size_t newCapacity = (_capacity == 0 ? 5 : 2 * _capacity);
T* stackTemp = new T[newCapacity];
for(int i = 0; i < _size; i++)
{
stackTemp[i] = _stack[i];
}
//delete空指針是完全安全的
delete[] _stack;
_stack = stackTemp;
_capacity = newCapacity;
std::cout << "Expend new capacity:" << _capacity << std::endl;
}
_stack[_size] = temp;
_size++;
}
//出棧
void Pop()
{
if(_size <= 0)
{
return;
}
_size--;
}
//棧頂元素
const T& Top()
{
assert(_size > 0);
return _stack[_size - 1];
}
//元素個數
size_t Size()
{
return _size;
}
//是否爲空
bool IsEmpty()
{
return (_size <= 0 ? true : false);
}
private:
T* _stack;//順序表
size_t _size;//長度
size_t _capacity;//容量
};
如何判斷一個序列是否爲出棧序列
一個入棧序列有很多種出棧順序,例如入棧1 2 3 4 5
,他的出棧序列可以是5 4 3 2 1
也可也是3 2 1 4 5
,那麼如何判斷一個序列是否是一個入棧序列的出棧序列呢?
這道題的思路很簡單,我們需要利用一個棧和兩個分別指向入棧序列和出棧序列的指針。當棧爲空或棧頂元素不等於當前出棧序列指針所指元素時,將入棧序列指針所指元素壓入棧,並且入棧序列指針後移;如果相同則將棧頂元素彈出並將出棧序列指針後移。如果在演算過程中還需要向棧中壓入元素而入棧序列已經被全部遍歷指針指向隊尾則可以怕判斷當前序列不是入棧序列的出棧序列;如果可以同時遍歷完入棧序列和出棧序列並且棧爲空則當前序列時一個入棧序列的出棧序列。
https://misakifx.github.io/2019/10/25/%E3%80%90DS%E3%80%91%E6%A0%88%E7%9A%84%E5%8E%8B%E5%85%A5%E3%80%81%E5%BC%B9%E5%87%BA%E5%BA%8F%E5%88%97/
棧的應用
棧有以下幾種應用:判斷括號匹配,後綴表達式,迷宮的暴力破解法等。
隊列
隊列的特點
隊列也是一種線性結構,這種線性結構的特點爲先進先出(First in First out)。由於隊列需要在隊列兩端進行插入或刪除,因此我們優先選擇鏈表來進行實現。當然使用數組實現也可以,只是數組在頭部插入和刪除元素需要ON
的時間複雜度,因此選擇鏈表更優。
實現
#include <iostream>
#include <assert.h>
template<class T>
struct QueueNode
{
QueueNode()
:_data(T())
,_next(nullptr)
{}
QueueNode(const T& data, QueueNode* next)
:_data(data)
,_next(next)
{}
T _data;
QueueNode* _next;
};
//用單向帶頭不循環鏈表實現隊列
template<class T>
class Queue
{
public:
Queue()
:_head(nullptr)
,_rear(nullptr)
,_size(0)
{
_head = new QueueNode<T>;
_rear = _head;
}
~Queue()
{
while(!Empty())
{
Pop();
}
delete _head;
_head = nullptr;
_rear = nullptr;
}
void Push(const T& data)
{
QueueNode<T>* newNode = new QueueNode<T>(data, nullptr);
_rear->_next = newNode;
_rear = newNode;
_size++;
}
bool Empty()
{
return _rear == _head;
}
void Pop()
{
QueueNode<T>* temp = _head->_next;
_head->_next = _head->_next->_next;
//隊列中只有一個元素
if(temp == _rear)
{
_rear = _head;
}
delete temp;
temp = nullptr;
_size--;
}
const T& Front()
{
assert(_head->_next != nullptr);
return _head->_next->_data;
}
const T& Back()
{
assert(_rear != _head);
return _rear->_data;
}
size_t Size()
{
return _size;
}
private:
QueueNode<T>* _head; //指向頭部結點
QueueNode<T>* _rear; //指向最後一個元素
size_t _size;
};
int main()
{
Queue<int> que;
que.Push(1);
que.Push(2);
que.Push(3);
que.Push(4);
while(!que.Empty())
{
std::cout << "size = " << que.Size() << std::endl;
std::cout << que.Front() << std::endl;
que.Pop();
}
}
size = 4
1
size = 3
2
size = 2
3
size = 1
4
以上代碼完成了隊列的基本功能。
環形隊列
環形隊列實現思路
環形隊列是一種特殊的隊列,隊列依然保證先進先出的特點,但是在邏輯結構上隊列呈一個環狀,可以保證在給定的有限空間內利用數組實現操作達到O1
的隊列。其需要用到兩個指針,一個指針head
指向向隊頭,一個指針rear
指向隊尾的後一個位置用來標記當前隊列空間的使用情況,如果隊滿則禁止繼續插入。
當插入元素時,將元素插入到隊尾指針指向的位置,然後將rear
指針後移;當彈出元素時只需將head
指針後移即可。但是要注意由於是環形隊列,因此當兩個指針走到數組末尾時需要做特殊處理讓他們重新指回到數組頭部。
但是環形隊列兩個指針的位置都是不固定的,我們又該如何判斷隊滿和隊空以及計算數組元素呢?
實現
#include <iostream>
#include <assert.h>
template<class T>
class CircleQueue
{
public:
CircleQueue(size_t capacity)
:_arr(nullptr)
,_head(0)
,_rear(0)
,_capacity(capacity)
{
assert(capacity >= 2);
_arr = new T[_capacity];
}
~CircleQueue()
{
delete _arr;
_arr = nullptr;
}
//判斷滿
bool IsFull()
{
if((_rear + 1) % _capacity == _head)
{
return true;
}
return false;
}
///判斷空
bool IsEmpty()
{
if(_head == _rear)
{
return true;
}
return false;
}
bool Push(const T& data)
{
if(IsFull())
{
return false;
}
_arr[_rear] = data;
_rear = (_rear + 1) % _capacity;
return true;
}
bool Pop()
{
if(IsEmpty())
{
return false;
}
_head = (_head + 1) % _capacity;
return true;
}
const T& Front()
{
assert(!IsEmpty());
return _arr[_head];
}
const T& Back()
{
assert(!IsEmpty());
size_t temp = (_rear + _capacity - 1) % _capacity;
return _arr[temp];
}
size_t Capacity()
{
return _capacity;
}
size_t Size()
{
return (_rear + _capacity - _head) % _capacity;
}
private:
T* _arr; //存儲環形隊列的數組
size_t _head; //指向隊頭
size_t _rear; //指向隊尾
size_t _capacity; //環形隊列的總容量,一旦確定不可再改變
};
int main()
{
CircleQueue<int> circleQueue(5);
circleQueue.Push(1);
circleQueue.Push(2);
circleQueue.Push(3);
circleQueue.Push(4);
circleQueue.Push(5);
while(!circleQueue.IsEmpty())
{
std::cout << "size = " << circleQueue.Size() << std::endl;
std::cout << circleQueue.Front() << std::endl;
circleQueue.Pop();
}
//std::cout << circleQueue.Front() << std::endl;
}
size = 4
1
size = 3
2
size = 2
3
size = 1
4
以上實現了環形隊列的基本功能。