stack与queue的介绍与使用与实现

简介: 栈(stack)是一种遵循先入后出(FILO)逻辑的线性数据结构。其只能从容器的一端进行元素的插入与提取操作。我们可以把他比作串串,我们在串肉的时候都是从底依次往上串肉,然后在吃的时候是从串顶依次向下吃,将串上的肉比作各种类型的元素(如整数、字符、对象等),串子比作适配器容器,就得到了栈这种数据结构。如图所示,我们把容器内元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。(图片取自hello算法)


stack
栈(stack)是一种遵循先入后出(FILO)逻辑的线性数据结构。其只能从容器的一端进行元素的插入与提取操作。

我们可以把他比作串串,我们在串肉的时候都是从底依次往上串肉,然后在吃的时候是从串顶依次向下吃,将串上的肉比作各种类型的元素(如整数、字符、对象等),串子比作适配器容器,就得到了栈这种数据结构。

如图所示,我们把容器内元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫作“入栈”,删除栈顶元素的操作叫作“出栈”。(图片取自hello算法)

栈的常用操作
成员函数 功能
empty() 判断栈是否为空,空返回真,不为空返回假
size() 获取栈中有效元素个数,返回值为size_t
top() 获取栈顶元素
push() 元素入栈
pop() 元素出栈
swap() 交换两个栈中的数据(可以部分也可以交换全部)
其实在c语言中就已经存在了stack,但是c++后又引入了模板,所以c++STL标准库里面就存在了stack的模板,但是要需要引用头文件#include

include

include

using namespace std;

int main()
{
stack st;

// 元素入栈
st.push(1);
st.push(3);
st.push(2);
st.push(5);
st.push(4);

//访问栈顶元素 
int top = st.top();

// 元素出栈 
st.pop(); // 无返回值

// 获取栈的长度 
int size = st.size();

// 判断是否为空 
bool empty = st.empty();
return 0;
AI 代码解读

}

栈的实现:
以下是基于deque实现栈的示例代码:

template<class T, class Con = deque<T>>
class stack
{
public:
    stack()
    {}

    void push(const T& x)
    {
        _c.push_back(x);
    }
    void pop()
    {
        _c.pop_back();
    }

    T& top()
    {
        return _c.back();
    }

    const T& top()const
    {
        return _c.back();
    }

    size_t size()const
    {
        return _c.size();
    }

    bool empty()const
    {
        return _c.empty();
    }

private:
    Con _c;

};
AI 代码解读

那么如果用链表怎么来实现呢?

下面是基于链表实现stack:

template<class T>
struct stack_node
{
    stack_node(const T& _val)
        :next(nullptr)
        , val(_val)
    {}
    stack_node* next;
    T val;
};
template<class T>
class stack
{
public:
    stack()
    {
        stackTop = nullptr;
        stkSize = 0;
    }

    ~stack()
    {
        delete[]stackTop;
        stackTop = nullptr;
        stkSize = 0;
    }

    int size()
    {
        return stkSize;
    }

    bool empty()
    {
        return size() == 0;
    }

    void push(int num)
    {
        stack_node<T>* node = new stack_node<T>(num);
        node->next = stackTop;
        stackTop = node;
        stkSize++;
    }

    int pop()
    {
        assert(!empty());
        int num = top();
        stack_node<T>* tmp = stackTop;
        stackTop = stackTop->next;
        // 释放内存
        delete tmp;
        stkSize--;
        return num;
    }

    int top()
    {
        assert(!empty());
        return stackTop->val;
    }

private:
    stack_node<T>* stackTop; // 将头节点作为栈顶
    int stkSize;        // 栈的长度
};
AI 代码解读

同样还存在着用数组来实现栈

class stack{
public:
int size()
{
return stack.size();
}

bool empty() 
{
    return stack.size() == 0;
}

void push(int num) 
{
    stack.push_back(num);
}

int pop() 
{
    int num = top();
    stack.pop_back();
    return num;
}

int top() 
{
    assert(!empty());
    return stack.back();
}
AI 代码解读

private:
vector stack;
}
};

三种实现方法的对比:
三种实现对此来说第一种实现是标准库的标准实现方法,二三都是我们自己可以通过自己思考可以想出来的实现方法,两种实现都支持栈定义中的各项操作。数组实现额外支持随机访问,但这已超出了栈的定义范畴,因此一般不会用到。

首先第三种方法的缺点之一就是如果入栈时超出数组容量,会触发扩容机制,导致该次入栈操作的时间复杂度变为0(n);

缺点二就是方法三在扩容上会存在大量的浪费空间,比如已经存在100个元素了,恰好数组正好填满,但是如果还要添加数据,链表只需要对应数据个数添加结点,但是数组要进行倍数扩容,会又大量浪费;

栈的典型应用
(取自hello算法)

浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。
queue
队列(queue)是一种遵循先入先出规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。

他就像我们打饭的时候,先去的人先打完饭,所以队列相对应就是(FIFO),

如图所示,我们将队列头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。(图片取自hello算法)

队列常用操作
成员函数 功能
empty() 判断队列是否为空
size() 获取队列中有效元素个数
front() 获取队头元素
back() 获取队尾元素
push() 队尾入队列
pop() 队头出队列
swap() 交换两个队列中的数据,可以交换部分也可以交换全部
int main()
{
/ 初始化队列 /
queue queue;

/* 元素入队 */
queue.push(1);
queue.push(3);
queue.push(2);
queue.push(5);
queue.push(4);

/* 访问队首元素 */
cout << queue.front() << endl;

/* 元素出队 */
queue.pop();

/* 获取队列的长度 */
cout << queue.size() << endl;


/* 判断队列是否为空 */
cout << queue.empty() << endl;
return 0;
AI 代码解读

}

队列的实现:
利用deque实现:

template<class T, class Con = deque<T>>

class queue
{
public:
    queue()
    {

    }

    void push(const T& x)
    {
        _c.push_back(x);
    }

    void pop()
    {
        _c.pop_front();
    }

    T& back()
    {
        return _c.back();
    }

    const T& back()const
    {
        return _c.back();
    }

    T& front()
    {
        return _c.front();
    }

    const T& front()const
    {
        return _c.front();
    }

    size_t size()const
    {
        return _c.size();
    }

    bool empty()const
    {
        return _c.empty();
    }

private:
    Con _c;

};
AI 代码解读

本人有点懒,以后有空会补充用链表来实现队列:

省略......

利用数组来实现:(全部摘自hello算法)

注意:里面有一点小优化,要注意哦

在数组中删除首元素的时间复杂度为 0(n) ,这会导致出队操作效率较低。然而,我们可以采用以下巧妙方法来避免这个问题。

我们可以使用一个变量 front 指向队首元素的索引,并维护一个变量 size 用于记录队列长度。定义 rear = front + size ,这个公式计算出的 rear 指向队尾元素之后的下一个位置。

基于此设计,数组中包含元素的有效区间为 [front, rear - 1],各种操作的实现方法如图所示。

入队操作:将输入元素赋值给 rear 索引处,并将 size 增加 1 。
出队操作:只需将 front 增加 1 ,并将 size 减少 1 。
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 0(1) 。

你可能会发现一个问题:在不断进行入队和出队的过程中,front 和 rear 都在向右移动,当它们到达数组尾部时就无法继续移动了。为了解决此问题,我们可以将数组视为首尾相接的“环形数组”。

对于环形数组,我们需要让 front 或 rear 在越过数组尾部时,直接回到数组头部继续遍历。这种周期性规律可以通过“取余操作”来实现,代码如下所示:

/ 基于环形数组实现的队列 /
class ArrayQueue {
private:
int *nums; // 用于存储队列元素的数组
int front; // 队首指针,指向队首元素
int queSize; // 队列长度
int queCapacity; // 队列容量

public:
ArrayQueue(int capacity) {
// 初始化数组
nums = new int[capacity];
queCapacity = capacity;
front = queSize = 0;
}

~ArrayQueue() {
    delete[] nums;
}

/* 获取队列的容量 */
int capacity() {
    return queCapacity;
}

/* 获取队列的长度 */
int size() {
    return queSize;
}

/* 判断队列是否为空 */
bool isEmpty() {
    return size() == 0;
}

/* 入队 */
void push(int num) {
    if (queSize == queCapacity) {
        cout << "队列已满" << endl;
        return;
    }
    // 计算队尾指针,指向队尾索引 + 1
    // 通过取余操作实现 rear 越过数组尾部后回到头部
    int rear = (front + queSize) % queCapacity;
    // 将 num 添加至队尾
    nums[rear] = num;
    queSize++;
}

/* 出队 */
int pop() {
    int num = peek();
    // 队首指针向后移动一位,若越过尾部,则返回到数组头部
    front = (front + 1) % queCapacity;
    queSize--;
    return num;
}

/* 访问队首元素 */
int peek() {
    if (isEmpty())
        throw out_of_range("队列为空");
    return nums[front];
}

/* 将数组转化为 Vector 并返回 */
vector<int> toVector() {
    // 仅转换有效长度范围内的列表元素
    vector<int> arr(queSize);
    for (int i = 0, j = front; i < queSize; i++, j++) {
        arr[i] = nums[j % queCapacity];
    }
    return arr;
}
AI 代码解读

};

队列典型应用
淘宝订单。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
各类待办事项。任何需要实现“先来后到”功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。

目录
打赏
0
1
1
0
2
分享
相关文章
『GitHub项目圈选周刊01』一款构建AI数字人项目开源了!自动实现音视频同步!
『GitHub项目圈选周刊01』一款构建AI数字人项目开源了!自动实现音视频同步!
1817 0
通义灵码用户说 | 编程智能体+MCP加持,秒查附近蜜雪冰城
通义灵码现已全面支持Qwen3,新增智能体模式,具备自主决策、环境感知、工具使用等能力,可端到端完成编码任务。支持问答、文件编辑、智能体多模式自由切换,结合MCP工具与记忆功能,提升开发效率。AI IDE重构编程流程,让开发更智能高效。
303 20
Function AI 工作流发布:以 AI 重塑企业流程自动化
本文介绍了基于函数计算 FC 打造的全新 Function AI 工作流服务,该服务结合 AI 技术与流程自动化,实现从传统流程自动化到智能流程自动化的跨越。文章通过内容营销素材生成、内容安全审核和泛企业 VOC 挖掘三个具体场景,展示了 Function AI 工作流的设计、配置及调试过程,并对比了其与传统流程的优势。Function AI 工作流具备可视化、智能性和可扩展性,成为企业智能化转型的重要基础设施,助力企业提升效率、降低成本并增强敏捷响应能力。
390 28
敏捷软件质量保证的方法与实践
本文介绍了软件质量保证(SQA)的重要性及其在敏捷开发中的实践方法。文章首先指出了传统测试方法的问题,如成本高昂和项目风险加大。为解决这些问题,文中提出了需求审核、代码审核与演练、基于会议的测试及基于风险的测试等多种实践方法。此外,文章还探讨了衡量软件质量的常见指标,如源代码行数、代码段/模块/时间段内的Bug数和代码覆盖率等。文中还详细描述了敏捷开发过程中QA的角色与活动,强调了QA需与开发人员、业务人员及客户密切协作,以确保产品质量。最后,文章指出了在敏捷开发中QA的特殊性及其对团队构成、测试阶段、工作方式等方面的影响。
248 3
敏捷软件质量保证的方法与实践
解决Git中没有小绿勾与红叉叉的问题
找到下面这个地址:\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers。后产生的,要在圈住的这几个在前面,添加空格(右击重命名加空格就能使它提到前面)关于要添加几个空格的问题,一般来说添加四个或者八个就可以。然后,重启电脑就能产生小绿勾和红叉叉的图标了。运行完重启电脑就好使。注意这一步一定要操作对,操作错误就会很多麻烦。寻找与自己电脑相配的软件版本就可以了。
268 0
Windows下使用git配置gitee远程仓库
就在前几天因为一些原因,我的电脑重装了系统,然后再重新配置git的环境的时候就遇到了一些小问题。所以我决定自己写一篇文章,以便以后再配置git时,避免一些错误操作,而导致全网搜方法,找对的文章去找对应的解决方法。下面为了演示方便就拿gitee来演示,不拿GitHub了写文章了。
126 0
Linux环境基础开发工具的使用(yum、vim、gcc、g++、gdb、make/Makefile)
本文介绍了yum 包管理工具、Vim 编辑器、gcc/g++ 编译器、gdb 调试器、编译原理及 Makefile 的使用,同时还配备了如何使用,以及图解。旨在帮助读者更好地理解和应用这些工具与技术。
72 0
Linux常见指令汇总
最常见的就是 ll (为ls -l的省略)
114 0
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
116 0
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问