数据结构-栈与队列

基础

定义

栈(Stack)是一种基于先进后出(Last In, First Out,LIFO)原则的数据结构。栈可以被看作是一系列元素的集合,其中插入和删除操作仅在栈的一端(称为栈顶)进行。

基本特性:

  1. 后进先出(LIFO): 最后被插入到栈的元素最先被删除。这是栈最本质的特性。
  2. 栈顶(Top): 栈的顶部元素是唯一一个可以直接访问的元素。所有的插入和删除操作都在栈顶进行。
  3. 栈底(Bottom): 栈的底部是最先被插入的元素所在的位置。栈底元素是最后一个被访问到的元素。
  4. 插入操作(Push): 将元素放入栈的顶部。如果栈已满,这个操作可能会导致栈溢出。
  5. 删除操作(Pop): 从栈的顶部移除元素。如果栈为空,这个操作可能会导致栈下溢。
  6. 栈空间限制: 栈具有固定的大小,通常在程序运行时分配,因此栈的容量是有限的。
  7. 无随机访问: 栈不支持在中间位置的直接访问或修改。要访问栈中的其他元素,必须先弹出(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)的原则。以下是顺序栈的一些特点和操作:

特点:

  1. 基于数组: 顺序栈使用数组作为底层存储结构,通过数组的索引实现对栈中元素的访问。

  2. 固定大小: 顺序栈在创建时需要指定一个固定的大小,即栈的容量。一旦确定,通常不会动态改变。

  3. 容易实现: 由于使用数组作为底层结构,顺序栈的实现相对简单,对于基本的栈操作,如进栈和出栈,时间复杂度为 O(1)。

  4. 随机访问: 由于底层是数组,顺序栈支持随机访问。可以直接通过索引访问栈中的元素,但通常情况下,栈操作主要集中在栈顶。

基本操作:

  1. 初始化: 创建一个空的顺序栈,需要指定栈的容量,一般在栈的定义时固定大小。
  2. 进栈(Push): 将元素压入栈顶。在数组中,相当于在栈顶索引处插入一个元素。
  3. 出栈(Pop): 将栈顶元素弹出。在数组中,相当于删除栈顶索引处的元素。
  4. 获取栈顶元素(Top): 不改变栈的状态,返回栈顶元素的值。
  5. 判断栈空和栈满(IsEmpty、IsFull): 分别用于判断栈是否为空和是否已满。
  6. 获取栈中元素个数(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个元素进栈,有几种出栈的可能,这个问题是一个卡特兰数问题
卡特兰数是一类在组合数学中非常重要的数列,它在许多问题中都有着重要的应用,包括括号匹配、二叉树的形状数量等。

卡特兰数的递推关系和计算公式如下:

  1. 递推关系:
    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

  2. 计算公式:
    在这里插入图片描述

下面是卡特兰数的推导过程:

递推关系的推导:

首先,我们考虑 (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=C0Cn1+C1Cn2++Cn1C0
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;
}

递归过程改为非递归过程

image-20231112145007192

使用循环实现

单向递归是指在一个过程或函数中,它可以调用自身,但只能在一个方向上进行递归调用,通常是向下或向上,而不是同时在两个方向上进行递归调用。这意味着在递归过程中,函数只能在一个特定方向上重复调用自己,直到满足某个基本条件才会停止递归。

例如,考虑一个单向递归的阶乘函数,它可以计算一个正整数的阶乘。在这个函数中,它可以调用自身来计算下一个较小的整数的阶乘,然后将结果乘以当前的整数。这是一个向下的单向递归,因为它只能沿着较小的整数方向进行递归调用,直到达到基本条件(当输入为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 函数使用非递归形式计算。非递归形式通常更高效,因为它避免了递归调用的开销。

用栈实现

用栈实现递归要注意把后实现的优先压栈

image-20231112140306172
#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;
}

image-20231112144434026

用迭代实现

在迭代过程中,计算机会反复执行一组指令,每次迭代都会基于先前的结果或状态来计算新的结果或状态。迭代通常使用循环结构来实现,允许程序多次执行相同的代码块,直到满足某个条件为止。

尾迭代(Tail Recursion)是指在递归函数中的递归调用发生在函数的最后一步操作,也就是说,在递归调用之后不再执行任何其他操作或计算,而直接返回递归调用的结果。尾迭代是递归的一种特殊形式,它具有一些特殊的性质,使得一些编程语言和编译器可以对其进行优化,减少递归调用栈的深度,从而节省内存和提高性能。

尾迭代的特点包括:

  1. 递归调用发生在函数的最后一步,没有后续的计算操作。

  2. 在尾迭代中,递归调用的结果通常直接返回,而不需要进行其他计算或操作。

  3. 尾迭代可以被编译器优化为迭代循环,这样可以减少函数调用栈的深度,从而避免栈溢出错误。

尾迭代的一个常见示例是尾递归函数,例如计算阶乘或斐波那契数列的尾递归版本。以下是一个计算阶乘的尾递归函数示例:

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

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值