栈
基础
定义
栈(Stack)是一种基于先进后出(Last In, First Out,LIFO)原则的数据结构。栈可以被看作是一系列元素的集合,其中插入和删除操作仅在栈的一端(称为栈顶)进行。
基本特性:
- 后进先出(LIFO): 最后被插入到栈的元素最先被删除。这是栈最本质的特性。
- 栈顶(Top): 栈的顶部元素是唯一一个可以直接访问的元素。所有的插入和删除操作都在栈顶进行。
- 栈底(Bottom): 栈的底部是最先被插入的元素所在的位置。栈底元素是最后一个被访问到的元素。
- 插入操作(Push): 将元素放入栈的顶部。如果栈已满,这个操作可能会导致栈溢出。
- 删除操作(Pop): 从栈的顶部移除元素。如果栈为空,这个操作可能会导致栈下溢。
- 栈空间限制: 栈具有固定的大小,通常在程序运行时分配,因此栈的容量是有限的。
- 无随机访问: 栈不支持在中间位置的直接访问或修改。要访问栈中的其他元素,必须先弹出(Pop)掉位于目标元素上方的所有元素。
#include <iostream>
const int maxSize = 50;
template <class T>
class Stack {
// 栈的类定义
public:
Stack(); // 构造函数
virtual void Push(const T& x) = 0; // 新元素 x 进栈
virtual bool Pop(T& x) = 0; // 栈顶元素出栈,由 x 返回
virtual bool getTop(T& x) const = 0; // 读取栈顶元素,由 x 返回
virtual bool IsEmpty() const = 0; // 判断栈空否
virtual bool IsFull() const = 0; // 判断栈满否
virtual int getSize() const = 0; // 计算栈中元素个数
};
顺序栈(Sequential Stack)
一种基于数组实现的栈结构。它具有栈的基本特性,包括后进先出(Last In, First Out,LIFO)的原则。以下是顺序栈的一些特点和操作:
特点:
-
基于数组: 顺序栈使用数组作为底层存储结构,通过数组的索引实现对栈中元素的访问。
-
固定大小: 顺序栈在创建时需要指定一个固定的大小,即栈的容量。一旦确定,通常不会动态改变。
-
容易实现: 由于使用数组作为底层结构,顺序栈的实现相对简单,对于基本的栈操作,如进栈和出栈,时间复杂度为 O(1)。
-
随机访问: 由于底层是数组,顺序栈支持随机访问。可以直接通过索引访问栈中的元素,但通常情况下,栈操作主要集中在栈顶。
基本操作:
- 初始化: 创建一个空的顺序栈,需要指定栈的容量,一般在栈的定义时固定大小。
- 进栈(Push): 将元素压入栈顶。在数组中,相当于在栈顶索引处插入一个元素。
- 出栈(Pop): 将栈顶元素弹出。在数组中,相当于删除栈顶索引处的元素。
- 获取栈顶元素(Top): 不改变栈的状态,返回栈顶元素的值。
- 判断栈空和栈满(IsEmpty、IsFull): 分别用于判断栈是否为空和是否已满。
- 获取栈中元素个数(GetSize): 返回栈中当前元素的个数。
需要掌握进栈 出栈(++top top–) 判断栈满栈空 get_top
#include <iostream>
#include <assert.h>
const int maxStack = 20;
const int stackIncrement = 20;
template <class T>
class SeqStack {
public:
SeqStack(int sz = 50); // 构造函数,建立一个空栈
~SeqStack(); // 析构函数
void Push(const T& x); // 入栈
bool Pop(T& x); // 出栈
bool getTop(T& x); // 获取栈顶元素
bool IsEmpty() const; // 判断栈空
bool IsFull() const; // 判断栈满
int getSize() const; // 获取栈中元素个数
void MakeEmpty(); // 清空栈
friend std::ostream& operator<<(std::ostream& os, SeqStack<T>& s); // 输出栈中元素的重载操作
private:
T* elements; // 存放栈中元素的栈数组
int top; // 栈顶指针
int maxSize; // 栈最大可容纳元素个数
void overflowProcess(); // 栈的溢出处理
};
// 构造函数
template <class T>
SeqStack<T>::SeqStack(int sz) : top(-1), maxSize(sz) {
elements = new T[maxSize]; // 分配数组空间
assert(elements != NULL); // 确保分配成功
}
// 析构函数
template <class T>
SeqStack<T>::~SeqStack() {
delete[] elements; // 释放数组空间
}
// 入栈
template <class T>
void SeqStack<T>::Push(const T& x) {
if (IsFull()) {
overflowProcess(); // 如果栈满,进行溢出处理
}
elements[++top] = x; // 栈顶指针上移,将元素插入栈顶
}
// 出栈
template <class T>
bool SeqStack<T>::Pop(T& x) {
if (IsEmpty()) {
return false; // 如果栈空,返回false
}
x = elements[top--]; // 弹出栈顶元素,栈顶指针下移
return true;
}
// 获取栈顶元素
template <class T>
bool SeqStack<T>::getTop(T& x) {
if (IsEmpty()) {
return false; // 如果栈空,返回false
}
x = elements[top]; // 获取栈顶元素的值
return true;
}
// 判断栈空
template <class T>
bool SeqStack<T>::IsEmpty() const {
return (top == -1); // 如果栈中元素个数等于0,则返回true,否则返回false
}
// 判断栈满
template <class T>
bool SeqStack<T>::IsFull() const {
return (top == maxSize - 1); // 如果栈中元素个数等于maxSize,则返回true,否则返回false
}
// 获取栈中元素个数
template <class T>
int SeqStack<T>::getSize() const {
return top + 1; // 返回栈中元素个数
}
// 清空栈
template <class T>
void SeqStack<T>::MakeEmpty() {
top = -1; // 将栈顶指针置为-1,表示栈为空
}
// 输出栈中元素的重载操作
template <class T>
std::ostream& operator<<(std::ostream& os, SeqStack<T>& s) {
os << "Stack (top to bottom): ";
for (int i = s.top; i >= 0; --i) {
os << s.elements[i] << " ";
}
os << std::endl;
return os;
}
// 栈的溢出处理
template <class T>
void SeqStack<T>::overflowProcess() {
T* newElements = new T[maxSize + stackIncrement]; // 扩展空间
assert(newElements != NULL); // 确保分配成功
for (int i = 0; i <= top; ++i) {
newElements[i] = elements[i]; // 将原数组中的元素复制到新数组
}
delete[] elements; // 释放原数组空间
elements = newElements; // 将指针指向新数组
maxSize += stackIncrement; // 更新栈的最大容量
}
int main() {
SeqStack<int> myStack;
myStack.Push(1);
myStack.Push(2);
myStack.Push(3);
int topElement;
myStack.getTop(topElement);
std::cout << "Top element: " << topElement << std::endl;
myStack.Pop(topElement);
std::cout << "Popped: " << topElement << std::endl;
std::cout << "Is stack empty? " << (myStack.IsEmpty() ? "Yes" : "No") << std::endl;
myStack.MakeEmpty();
std::cout << "Is stack empty after MakeEmpty? " << (myStack.IsEmpty() ? "Yes" : "No") << std::endl;
return 0;
}
这个程序定义了一个顺序栈的类,并包含了基本的栈操作。类中使用了动态分配内存,实现了栈的扩展操作。
双栈
以下是一个简单的双栈(双端栈)的类定义,其中包含了基本的双栈操作。在这个定义中,使用一个数组实现了两个栈。
#include <iostream>
#include <cassert>
template <class T>
class DualStack {
public:
DualStack(int size = 100);
~DualStack();
// 入栈操作
void Push1(const T& item);
void Push2(const T& item);
// 出栈操作
bool Pop1(T& item);
bool Pop2(T& item);
// 获取栈顶元素
bool getTop1(T& item) const;
bool getTop2(T& item) const;
// 判断栈空
bool IsEmpty1() const;
bool IsEmpty2() const;
// 判断栈满
bool IsFull() const;
private:
T* elements;
int size;
int top1; // 栈1的栈顶指针
int top2; // 栈2的栈顶指针
void overflowProcess(); // 栈的溢出处理
};
// 构造函数
template <class T>
DualStack<T>::DualStack(int sz) : size(sz), top1(-1), top2(size) {
elements = new T[size];
assert(elements != nullptr);
}
// 析构函数
template <class T>
DualStack<T>::~DualStack() {
delete[] elements;
}
// 入栈操作(栈1)
template <class T>
void DualStack<T>::Push1(const T& item) {
if (top1 + 1 < top2) {
elements[++top1] = item;
} else {
overflowProcess();
}
}
// 入栈操作(栈2)
template <class T>
void DualStack<T>::Push2(const T& item) {
if (top2 - 1 > top1) {
elements[--top2] = item;
} else {
overflowProcess();
}
}
// 出栈操作(栈1)
template <class T>
bool DualStack<T>::Pop1(T& item) {
if (top1 >= 0) {
item = elements[top1--];
return true;
} else {
return false;
}
}
// 出栈操作(栈2)
template <class T>
bool DualStack<T>::Pop2(T& item) {
if (top2 < size) {
item = elements[top2++];
return true;
} else {
return false;
}
}
// 获取栈顶元素(栈1)
template <class T>
bool DualStack<T>::getTop1(T& item) const {
if (top1 >= 0) {
item = elements[top1];
return true;
} else {
return false;
}
}
// 获取栈顶元素(栈2)
template <class T>
bool DualStack<T>::getTop2(T& item) const {
if (top2 < size) {
item = elements[top2];
return true;
} else {
return false;
}
}
// 判断栈1是否为空
template <class T>
bool DualStack<T>::IsEmpty1() const {
return (top1 == -1);
}
// 判断栈2是否为空
template <class T>
bool DualStack<T>::IsEmpty2() const {
return (top2 == size);
}
// 判断栈是否满
template <class T>
bool DualStack<T>::IsFull() const {
return (top1 + 1 == top2);
}
// 栈的溢出处理
template <class T>
void DualStack<T>::overflowProcess() {
// 在实际情况中,可以根据需要扩展栈的大小,或者进行其他处理
std::cerr << "Stack overflow!" << std::endl;
exit(EXIT_FAILURE);
}
int main() {
DualStack<int> dualStack;
dualStack.Push1(1);
dualStack.Push1(2);
int top1Element;
dualStack.getTop1(top1Element);
std::cout << "Top element of Stack 1: " << top1Element << std::endl;
dualStack.Push2(3);
dualStack.Push2(4);
int top2Element;
dualStack.getTop2(top2Element);
std::cout << "Top element of Stack 2: " << top2Element << std::endl;
return 0;
}
这个类定义了一个双栈,可以分别对两个栈进行入栈、出栈、获取栈顶元素等操作。在构造函数中,通过一个数组实现了两个栈。请根据实际需要进行适当的修改和扩展。
bool Push(DualStack& DS, T x, int d) {
// 在双栈中插入元素x。d=0,插入第0号栈;d≠0,插入第1号栈
if (DS.t[0] + 1 == DS.t[1]) {
return false; // 栈满,函数返回
}
if (d == 0) {
DS.t[0]++; // 栈顶指针加1
} else {
DS.t[1]--;
}
DS.Vector[DS.t[d]] = x; // 进栈
return true;
}
bool Pop(DualStack& DS, T& x, int d) {
// 从双栈中弹出栈顶元素,通过x返回。d=0,从第0号栈弹栈;d≠0,从第1号栈弹栈
if (DS.t[d] == DS.b[d]) {
return false; // 栈空,函数返回
}
x = DS.Vector[DS.t[d]]; // 取出栈顶元素的值
if (d == 0) {
DS.t[0]--; // 栈顶指针减1
} else {
DS.t[1]++;
}
return true;
}
这段代码实现了在双栈中插入元素和删除栈顶元素的操作。其中,Push
函数用于插入元素,Pop
函数用于删除栈顶元素。这里使用了 d
参数来指定是对第0号栈还是第1号栈进行操作。在插入操作中,根据栈满的条件判断是否可以插入;在删除操作中,根据栈空的条件判断是否可以删除。函数返回 true
表示操作成功,返回 false
表示操作失败。
链式栈
下面是一个简单的链式栈(链栈)的类定义。链栈使用链表来实现,每个结点存储一个元素,并通过指针连接起来。
#include <iostream>
template <class T>
struct Node {
T data;
Node<T>* next;
Node(const T& item, Node<T>* link = nullptr) : data(item), next(link) {}
};
template <class T>
class LinkedStack {
public:
LinkedStack();
~LinkedStack();
void Push(const T& item); // 入栈操作
bool Pop(T& item); // 出栈操作
bool getTop(T& item) const; // 获取栈顶元素
bool IsEmpty() const; // 判断栈空
void MakeEmpty(); // 清空栈
private:
Node<T>* top; // 栈顶指针
};
// 构造函数
template <class T>
LinkedStack<T>::LinkedStack() : top(nullptr) {}
// 析构函数
template <class T>
LinkedStack<T>::~LinkedStack() {
MakeEmpty(); // 清空栈
}
// 入栈操作
template <class T>
void LinkedStack<T>::Push(const T& item) {
top = new Node<T>(item, top);
if (top == nullptr) {
std::cerr << "Memory allocation failed!" << std::endl;
exit(EXIT_FAILURE);
}
}
// 出栈操作
template <class T>
bool LinkedStack<T>::Pop(T& item) {
if (IsEmpty()) {
return false; // 栈空,出栈失败
}
Node<T>* temp = top;
item = temp->data;
top = top->next;
delete temp;
return true;
}
// 获取栈顶元素
template <class T>
bool LinkedStack<T>::getTop(T& item) const {
if (IsEmpty()) {
return false; // 栈空,获取栈顶元素失败
}
item = top->data;
return true;
}
// 判断栈空
template <class T>
bool LinkedStack<T>::IsEmpty() const {
return top == nullptr;
}
// 清空栈
template <class T>
void LinkedStack<T>::MakeEmpty() {
while (!IsEmpty()) {
Node<T>* temp = top;
top = top->next;
delete temp;
}
}
int main() {
LinkedStack<int> linkedStack;
linkedStack.Push(1);
linkedStack.Push(2);
int topElement;
linkedStack.getTop(topElement);
std::cout << "Top element of the stack: " << topElement << std::endl;
linkedStack.Pop(topElement);
std::cout << "Popped element: " << topElement << std::endl;
return 0;
}
这个链式栈的实现使用了一个链表结构,其中 Node
结构表示链表中的结点,每个结点存储一个元素和一个指向下一结点的指针。链栈通过 top
指针表示栈顶,入栈操作在链表头部插入新结点,出栈操作在链表头部删除结点。
已知有n个元素进栈,有几种出栈的可能,这个问题是一个卡特兰数问题
卡特兰数是一类在组合数学中非常重要的数列,它在许多问题中都有着重要的应用,包括括号匹配、二叉树的形状数量等。
卡特兰数的递推关系和计算公式如下:
-
递推关系:
C 0 = 1 , C n + 1 = 2 ( 2 n + 1 ) n + 2 ⋅ C n C_0 = 1, \quad C_{n+1} = \frac{2(2n + 1)}{n + 2} \cdot C_n C0=1,Cn+1=n+22(2n+1)⋅Cn -
计算公式:
下面是卡特兰数的推导过程:
递推关系的推导:
首先,我们考虑 (n + 1) 对括号的合法排列。在这些排列中,第一个右括号可以与其对应的左括号组成一对,也可以在后面的某个位置找到其对应的左括号。这就导致了递推关系的出现。
- 假设第一个右括号与其对应的左括号组成一对,那么剩下 (n) 对括号的排列数为 (C_n)。
- 假设第一个右括号在后面的某个位置找到其对应的左括号,那么剩下 (n) 对括号的排列数为 (C_n)。找到的左括号与右括号之间的括号对的排列数为 (C_k),其中 (k < n)。
因此,我们得到递推关系:
C
n
=
C
0
⋅
C
n
−
1
+
C
1
⋅
C
n
−
2
+
…
+
C
n
−
1
⋅
C
0
C_{n} = C_0 \cdot C_{n-1} + C_1 \cdot C_{n-2} + \ldots + C_{n-1} \cdot C_0
Cn=C0⋅Cn−1+C1⋅Cn−2+…+Cn−1⋅C0
将
C
0
=
1
C_0 = 1
C0=1代入,可以得到上述的递推关系。
计算公式的推导:
通过递推关系,我们可以计算卡特兰数0的计算公式。首先,我们考虑 (2n) 个位置中选择 (n) 个位置放置左括号,其组合数为 ( 2 n n ) \binom{2n}{n} (n2n)。然后,我们除以 ( n + 1 ) (n + 1) (n+1) 是因为不同位置的左括号组成的排列实际上是等价的,即 (n + 1) 种排列对应同一种合法的括号匹配。
这样,我们就得到了卡特兰数的计算公式:
$ C_n = \frac{1}{n + 1} \binom{2n}{n} $
这个公式也可以通过对阶乘的分解来进行证明。
递推
定义
若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的;若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
#include <iostream>
void hanoi(int n, char source, char auxiliary, char target) {
if (n == 1) {
std::cout << "Move disk 1 from " << source << " to " << target << std::endl;
return;
}
// Move n-1 disks from source to auxiliary peg using target peg
hanoi(n - 1, source, target, auxiliary);
// Move the nth disk from source to target peg
std::cout << "Move disk " << n << " from " << source << " to " << target << std::endl;
// Move the n-1 disks from auxiliary peg to target peg using source peg
hanoi(n - 1, auxiliary, source, target);
}
int main() {
int num_disks;
std::cout << "Enter the number of disks: ";
std::cin >> num_disks;
// Assuming three pegs are named 'A', 'B', 'C'
hanoi(num_disks, 'A', 'B', 'C');
return 0;
}
递归过程改为非递归过程
使用循环实现
单向递归是指在一个过程或函数中,它可以调用自身,但只能在一个方向上进行递归调用,通常是向下或向上,而不是同时在两个方向上进行递归调用。这意味着在递归过程中,函数只能在一个特定方向上重复调用自己,直到满足某个基本条件才会停止递归。
例如,考虑一个单向递归的阶乘函数,它可以计算一个正整数的阶乘。在这个函数中,它可以调用自身来计算下一个较小的整数的阶乘,然后将结果乘以当前的整数。这是一个向下的单向递归,因为它只能沿着较小的整数方向进行递归调用,直到达到基本条件(当输入为1时,阶乘为1)时停止。
单向递归在编程和计算中经常使用,用于解决涉及递归结构的问题,确保递归调用不会无限循环并最终达到终止条件。这有助于管理和理解递归算法的执行流程。
下面是斐波那契数列的递归和非递归形式的C++程序:
递归形式:
#include <iostream>
// 递归形式的斐波那契数列
int fibonacciRecursive(int n) {
if (n <= 1) {
return n;
} else {
return fibonacciRecursive(n - 1) + fibonacciRecursive(n - 2);
}
}
int main() {
int n;
std::cout << "Enter the value of n: ";
std::cin >> n;
std::cout << "Fibonacci(" << n << ") using recursion: " << fibonacciRecursive(n) << std::endl;
return 0;
}
非递归形式:
#include <iostream>
// 非递归形式的斐波那契数列
int fibonacciIterative(int n) {
if (n <= 1) {
return n;
}
int fibPrev = 0;
int fibCurrent = 1;
for (int i = 2; i <= n; ++i) {
int temp = fibCurrent;
fibCurrent = fibPrev + fibCurrent;
fibPrev = temp;
}
return fibCurrent;
}
int main() {
int n;
std::cout << "Enter the value of n: ";
std::cin >> n;
std::cout << "Fibonacci(" << n << ") using iteration: " << fibonacciIterative(n) << std::endl;
return 0;
}
在这两个程序中,fibonacciRecursive
函数使用递归形式计算斐波那契数列,而 fibonacciIterative
函数使用非递归形式计算。非递归形式通常更高效,因为它避免了递归调用的开销。
用栈实现
用栈实现递归要注意把后实现的优先压栈
#include <iostream>
#include <stack>
using namespace std;
// 定义一个结构体,用于表示汉诺塔的每个盘子
struct Disk {
int size;
char source, auxiliary, target;
Disk(int s, char src, char aux, char tgt) : size(s), source(src), auxiliary(aux), target(tgt) {}
};
void iterativeHanoi(int n) {
stack<Disk> s;
char source = 'A', auxiliary = 'B', target = 'C';
// 初始化栈,将最大的盘子压入栈中
s.push(Disk(n, source, auxiliary, target));
while (!s.empty()) {
Disk currentDisk = s.top();
s.pop();
if (currentDisk.size == 1) {
// 如果只有一个盘子,直接移动
cout << "Move disk " << currentDisk.size << " from " << currentDisk.source << " to " << currentDisk.target << endl;
} else {
// 将大盘子上方的所有盘子按照相反的顺序压入栈中,确保最小的盘子在栈顶
s.push(Disk(currentDisk.size - 1, currentDisk.auxiliary, currentDisk.source, currentDisk.target));
s.push(Disk(1, currentDisk.source, currentDisk.auxiliary, currentDisk.target));
s.push(Disk(currentDisk.size - 1, currentDisk.source, currentDisk.target, currentDisk.auxiliary));
}
}
}
int main() {
int n;
cout << "Enter the number of disks: ";
cin >> n;
iterativeHanoi(n);
return 0;
}
用迭代实现
在迭代过程中,计算机会反复执行一组指令,每次迭代都会基于先前的结果或状态来计算新的结果或状态。迭代通常使用循环结构来实现,允许程序多次执行相同的代码块,直到满足某个条件为止。
尾迭代(Tail Recursion)是指在递归函数中的递归调用发生在函数的最后一步操作,也就是说,在递归调用之后不再执行任何其他操作或计算,而直接返回递归调用的结果。尾迭代是递归的一种特殊形式,它具有一些特殊的性质,使得一些编程语言和编译器可以对其进行优化,减少递归调用栈的深度,从而节省内存和提高性能。
尾迭代的特点包括:
-
递归调用发生在函数的最后一步,没有后续的计算操作。
-
在尾迭代中,递归调用的结果通常直接返回,而不需要进行其他计算或操作。
-
尾迭代可以被编译器优化为迭代循环,这样可以减少函数调用栈的深度,从而避免栈溢出错误。
尾迭代的一个常见示例是尾递归函数,例如计算阶乘或斐波那契数列的尾递归版本。以下是一个计算阶乘的尾递归函数示例:
long long factorialTailRecursive(int n, long long result = 1) {
if (n <= 1) {
return result;
}
return factorialTailRecursive(n - 1, n * result);
}
在这个示例中,递归调用发生在函数的最后一步,而且结果直接返回,没有后续的操作。这使得一些编程语言的编译器能够将尾递归优化为迭代循环,避免递归调用栈的深度增加。
需要注意的是,并非所有编程语言和编译器都支持尾递归优化,因此在使用尾迭代时,要查看编程语言的规范和编译器的支持情况。尾迭代通常在函数式编程语言中更为常见,因为它与函数式编程的特性更加契合。
#include <iostream>
using namespace std;
long long fibonacciIterative(int n) {
if (n <= 1) {
return n;
}
long long prev = 0;
long long current = 1;
long long result = 0;
for (int i = 2; i <= n; ++i) {
result = prev + current;
prev = current;
current = result;
}
return result;
}
队列
队列(Queue)是一种常见的数据结构,它遵循先进先出(First-In-First-Out,FIFO)的原则。队列可以看作是一种线性数据结构,其中元素的插入和删除操作发生在两个不同的端点。先进先出 队头可出 队尾可删
const int maxSize = 50;
enum Boolean { false, true };
template <class T>
class Queue {
public:
Queue() {} // 构造函数
virtual ~Queue() {} // 析构函数
virtual void EnQueue(T x) = 0; // 新元素x进队列
virtual bool DeQueue(T& x) = 0; // 队头元素出队列
virtual bool getFront(T& x) = 0; // 读取队头元素的值
virtual bool IsEmpty() const = 0; // 判断队列空否
virtual bool IsFull() const = 0; // 判断队列满否
virtual int getSize() const = 0; // 求队列元素个数
};
循环队列(Circular Queue)
也称为环形队列,是一种特殊类型的队列数据结构,它具有固定大小的缓冲区,并且队列的头尾相连形成一个循环。这意味着当队列尾部达到缓冲区的末尾时,它会绕回到缓冲区的开头,从而实现了循环利用缓冲区的功能。循环队列常用于需要连续存储数据的应用场景,例如操作系统中的缓冲区管理、计算机网络中的数据包传输等。
以下是循环队列的类定义示例:
需要知道进出以及get_size
template <class T>
class CircularQueue {
private:
int maxSize; // 队列的最大容量
T* elements; // 存储队列元素的数组
int front; // 队头指针
int rear; // 队尾指针
public:
// 构造函数,创建一个循环队列并指定最大容量
CircularQueue(int size) {
maxSize = size + 1; // 额外一个位置用于区分队满和队空状态
elements = new T[maxSize];
front = rear = 0; // 初始化队头和队尾指针
}
// 析构函数,释放内存
~CircularQueue() {
delete[] elements;
}
// 判断队列是否为空
bool isEmpty() const {
return front == rear;
}
// 判断队列是否已满
bool isFull() const {
return (rear + 1) % maxSize == front;
}
// 获取队列的大小(元素个数)
int getSize() const {
return (rear - front + maxSize) % maxSize;
}
// 入队操作,将元素添加到队尾
bool enqueue(const T& item) {
if (isFull()) {
return false; // 队列已满,无法入队
}
elements[rear] = item;
rear = (rear + 1) % maxSize;
return true;
}
// 出队操作,将队头元素出队并返回
bool dequeue(T& item) {
if (isEmpty()) {
return false; // 队列为空,无法出队
}
item = elements[front];
front = (front + 1) % maxSize;
return true;
}
// 获取队头元素的值,但不出队
bool getFront(T& item) const {
if (isEmpty()) {
return false; // 队列为空
}
item = elements[front];
return true;
}
};
这个循环队列的类定义包括了一些常用的操作,如入队、出队、判断队列是否为空或已满、获取队列大小等。循环队列的主要特点是通过取模运算来实现头尾指针的循环,确保队列在缓冲区内部循环利用。
当进队速度快于出队速度,rear追上front,造成队满,为了区分队空条件,取当rear+1=front就认为队已满。这样不必增加其他辅助单元。
链式队列
链式队列是基于单链表的一种存储表示,
在单链表的每个结点中有两个域:data域存放队列元素的值,link域存放单链表下一个结点的地址。队列的队头指针指向单链表的第一个结点,队尾指针指向单链表的最后一个结点。这意味着队列的队头元素存放在单链表的第一个结点内,若要从队列中退出一个元素,必须从单链表中删除第一个结点,而存放着新元素的结点应插人队列的队尾,即单链表的最后一个结点后面,这个新结点将成为新的队尾。
用单链表表示的链式队列特别适合数据元素变动比较大的情形,而且不存在队列满而产生谥出的情况。另外,假若程序中要使用多个队列,与多个栈的情形一样,最好使用链式队列。这样不会出现存储分配不合理的问题,也不需要进行存储的移动。链式队列的类定义可以直接继承单链表,但因为单链表的结点在前面用了struct定义,可以直接使用,无须继承,所有在下面没有使用继承关系。
需要会找队头 会进队出队
#include <iostream>
template <class T>
struct LinkNode {
T data;
LinkNode<T>* link;
LinkNode(T value) : data(value), link(NULL) {}
};
template <class T>
class LinkedQueue {
public:
LinkedQueue() : front(NULL), rear(NULL) {}
~LinkedQueue() { makeEmpty(); }
bool EnQueue(T x);
bool DeQueue(T& x);
bool getFront(T& x);
void makeEmpty();
bool IsEmpty() { return front == NULL; }
int getSize();
friend std::ostream& operator<<(std::ostream& os, LinkedQueue<T>& Q);
protected:
LinkNode<T>* front;
LinkNode<T>* rear;
};
template <class T>
void LinkedQueue<T>::makeEmpty() {
LinkNode<T>* p;
while (front != NULL) {
p = front;
front = front->link;
delete p;
}
}
template <class T>
bool LinkedQueue<T>::EnQueue(T x) {
if (front == NULL)
front = rear = new LinkNode<T>(x);
else {
rear->link = new LinkNode<T>(x);
if (rear->link == NULL)
return false;
rear = rear->link;
}
return true;
}
template <class T>
bool LinkedQueue<T>::DeQueue(T& x) {
if (IsEmpty())
return false;
LinkNode<T>* p = front;
x = front->data;
front = front->link;
delete p;
return true;
}
template <class T>
bool LinkedQueue<T>::getFront(T& x) {
if (IsEmpty())
return false;
x = front->data;
return true;
}
template <class T>
int LinkedQueue<T>::getSize() {
LinkNode<T>* p = front;
int k = 0;
while (p != NULL) {
p = p->link;
k++;
}
return k;
}
template <class T>
std::ostream& operator<<(std::ostream& os, LinkedQueue<T>& Q) {
os << "Queue size: " << Q.getSize() << std::endl;
for (LinkNode<T>* p = Q.front; p != NULL; p = p->link)
os << p->data << " ";
os << std::endl;
return os;
}
int main() {
// Example usage
LinkedQueue<int> queue;
queue.EnQueue(1);
queue.EnQueue(2);
queue.EnQueue(3);
std::cout << queue;
int frontElement;
if (queue.getFront(frontElement))
std::cout << "Front element: " << frontElement << std::endl;
int dequeuedElement;
if (queue.DeQueue(dequeuedElement))
std::cout << "Dequeued element: " << dequeuedElement << std::endl;
std::cout << queue;
return 0;
}
优先级队列
#include <iostream>
const int DefaultPQSize = 50;
const int maxSize = 20;
template <class T>
class PQueue {
public:
PQueue(int sz = DefaultPQSize);
~PQueue() { delete[] pqelements; }
bool Insert(T x);
bool RemoveMin(T& x);
bool getFront(T& x);
void makeEmpty() { count = 0; }
bool IsEmpty() { return count == 0; }
bool IsFull() { return count == maxSize; }
int getSize() { return count; }
protected:
T* pqelements;
int count;
int maxSize;
void adjust();
};
template <class T>
PQueue<T>::PQueue(int sz) : maxSize(sz), count(0) {
pqelements = new T[sz];
if (pqelements == NULL) {
std::cerr << "Storage allocation failed!" << std::endl;
exit(1);
}
}
template <class T>
void PQueue<T>::adjust() {
T temp = pqelements[count - 1];
int j;
for (j = count - 2; j >= 0; j--) {
if (pqelements[j] <= temp)
break;
else
pqelements[j + 1] = pqelements[j];
}
pqelements[j + 1] = temp;
}
template <class T>
bool PQueue<T>::Insert(T x) {
if (count == maxSize)
return false;
pqelements[count] = x;
count++;
adjust();
return true;
}
template <class T>
bool PQueue<T>::RemoveMin(T& x) {
if (count == 0)
return false;
x = pqelements[0];
int k = 1;
for (int i = 2; i < count; i++)
if (pqelements[i] < pqelements[k])
k = i;
pqelements[0] = pqelements[k];
pqelements[k] = pqelements[count - 1];
count--;
return true;
}
template <class T>
bool PQueue<T>::getFront(T& x) {
if (count == 0)
return false;
x = pqelements[0];
return true;
}
int main() {
// Example usage
PQueue<int> priorityQueue;
priorityQueue.Insert(3);
priorityQueue.Insert(1);
priorityQueue.Insert(2);
int frontElement;
if (priorityQueue.getFront(frontElement))
std::cout << "Front element: " << frontElement << std::endl;
int removedElement;
if (priorityQueue.RemoveMin(removedElement))
std::cout << "Removed element: " << removedElement << std::endl;
return 0;
}
zz