C/C++每日一练:编写一个栈数据结构

news2024/10/21 12:00:20

        通过编写栈(Stack)数据结构,提升对基本数据结构的理解和运用。这也是掌握更复杂数据结构与算法的基础。栈是计算机科学中的一个重要概念,经常出现在许多算法和应用中。

栈(Stack)

         栈是一种后进先出(LIFO, Last In First Out)的线性数据结构,类似于现实中的手枪的弹夹:最后放进去的子弹会第一个被射出来。这种结构在计算机科学中非常常见,广泛应用于递归、表达式求值、函数调用、深度优先搜索等算法中。

基本操作

         栈有四个基本操作:

  • Push(压栈/入栈):向栈中添加元素,通常是在栈顶位置添加。
  • Pop(弹栈/出栈):移除栈顶元素,并返回该元素。这个操作遵循“后进先出”的原则,即最后入栈的元素会最先被弹出。
  • Peek(查看栈顶元素):返回栈顶元素,但不移除它。这通常用于查看当前栈顶的内容。
  • isEmpty(栈是否为空):检查栈中是否有元素。如果栈为空,返回 true;否则返回 false。

          如下图所示:

特点

  • 后进先出(LIFO):这是栈最重要的特性。最后压入的元素最先被弹出,最早压入的元素最后被处理。
  • 受限操作:栈只允许从栈顶进行操作。即,插入和删除只能发生在栈顶,不能随机访问栈中间的元素。
  • 空间限制:在一些实现中,栈的空间可能是有限的。比如用数组实现的栈,一旦数组的空间满了,就无法再插入新的元素。

实现方式

        栈的实现方式主要有两种:数组实现链表实现

数组实现栈

        在数组实现中,栈使用一个固定大小的数组来存储元素,且用一个变量 top 记录栈顶元素的索引位置。初始时 top 为 -1,表示栈为空。

  • 优点:数组实现栈的结构简单,直接通过数组的索引操作栈,性能较好。
  • 缺点:由于数组的大小是固定的,因此栈的容量有限,若元素过多,可能会导致栈溢出,不过也可以使用动态数组解决。
链表实现栈

        在链表实现中,每个节点存储一个栈元素,并通过指针指向下一个节点。栈的栈顶为链表的头节点,每次入栈时在链表头部插入节点,出栈时删除头节点。

  • 优点:链表栈没有容量限制,可以根据需要动态扩展栈的大小。
  • 缺点:由于每个元素都需要动态分配内存,因此实现上比数组稍微复杂,而且需要手动管理内存。

题目要求

        编写一个栈数据结构,支持以下操作:

  • Push(x):将元素 x 压入栈中。
  • Pop():从栈中移除并返回栈顶元素。
  • Peek():返回栈顶元素,但不移除。
  • isEmpty():检查栈是否为空。

进阶要求

  • 使用数组或链表来实现栈(本文中用C实现数组栈,C++实现链表栈)。
  • 当栈为空时,Pop 和 Peek 操作应当返回错误或给出相应提示。

做题思路

        栈的操作基于“后进先出”(LIFO)的原则,即最后压入栈的元素最先被弹出。栈的两个核心操作是 Push(入栈)和 Pop(出栈)。为了实现栈,可以选择两种常见的数据结构:数组或链表。

  • 数组实现栈:用一个数组来存储元素,定义数组的索引0为栈底,最后新添加的元素为栈顶。并使用一个索引位置top来标记栈顶位置。入栈时top向后移动,出栈时top向前移动。
  • 链表实现栈:链表的每个节点存储一个元素,入栈时在链表头部插入节点,出栈时删除头节点。

过程解析

        分别使用数组和链表两种方式来实现栈,详细展示每种方式的代码。

数组实现栈

  1. 定义一个数组和一个变量 top 来存储栈顶的索引。
  2. 在 Push 操作中,增加 top 并插入元素。
  3. 在 Pop 操作中,减少 top 并返回栈顶元素。
  4. 在 Peek 中,直接访问 top 所指的元素。

链表实现栈

  1. 定义一个链表节点结构体。
  2. 使用链表的头节点作为栈顶,每次入栈时向链表头部插入节点。
  3. 出栈时删除链表头部节点,并更新下一个节点为栈顶。

示例代码

C 实现:数组栈

#include <stdio.h>
#include <stdbool.h>// 引入布尔类型头文件,用于定义布尔类型变量(true/false)  

#define MAX 100 // 定义栈的最大容量为100

// 栈结构体定义
typedef struct Stack {
    int items[MAX];  // 定义数组用于存储栈元素
    int top;         // 记录栈顶的索引位置,初始为-1表示空栈,同时也表示栈内的数量
} Stack;

// 初始化栈函数,将栈的 top 初始化为 -1
void initStack(Stack* s) {
    s->top = -1; // 栈顶设置为 -1,表示栈为空
}

// 检查栈是否为空
bool isEmpty(Stack* s) {
    return s->top == -1; // 如果 top 为 -1,则栈为空,返回 true
}

// 检查栈是否已满
bool isFull(Stack* s) {
    return s->top == MAX - 1; // 如果 top 达到 MAX-1,栈已满,返回 true
}

// 入栈操作
void push(Stack* s, int item) {
    if (isFull(s)) { // 如果栈满,无法再插入元素
        printf("栈满,无法入栈\n"); // 输出错误信息
        return; // 直接返回
    }
    s->items[++(s->top)] = item; // 先将 top 加1,再将新元素存储到栈顶
    printf("入栈元素: %d\n", item); // 打印入栈元素
}

// 出栈操作
int pop(Stack* s) {
    if (isEmpty(s)) { // 如果栈为空,无法弹出元素
        printf("栈为空,无法出栈\n"); // 输出错误信息
        return -1; // 返回错误值 -1 表示操作失败
    }
    return s->items[(s->top)--]; // 返回栈顶元素,栈顶指针减1
}

// 获取栈顶元素但不移除
int peek(Stack* s) {
    if (isEmpty(s)) { // 如果栈为空,无法获取栈顶元素
        printf("栈为空,无法获取栈顶元素\n"); // 输出错误信息
        return -1; // 返回错误值 -1 表示操作失败
    }
    return s->items[s->top]; // 返回栈顶的元素
}

int main() 
{
    Stack s; // 声明一个 Stack 类型的变量
    initStack(&s); // 初始化栈

    push(&s, 10); // 入栈元素 10
    push(&s, 20); // 入栈元素 20
    push(&s, 30); // 入栈元素 30

    printf("栈顶元素: %d\n", peek(&s)); // 输出当前栈顶元素
    printf("出栈元素: %d\n", pop(&s));  // 出栈,并输出弹出的元素
    printf("栈顶元素: %d\n", peek(&s)); // 再次输出栈顶元素

    return 0; // 程序正常结束
}

C++ 实现:链表栈

#include <iostream>
using namespace std;

// 定义链表节点结构体
struct Node {
    int data;       // 用于存储栈元素的值
    Node* next;     // 指向下一个节点的指针
};

// 定义栈类
class Stack {
private:
    Node* top;  // 栈顶指针,指向链表的头节点
    int size;   // 用于记录栈中元素的个数

public:
    // 构造函数,初始化栈顶为 nullptr,表示栈为空,并且初始化元素个数为 0
    Stack() {
        top = nullptr; // 栈顶指针初始化为空
        size = 0;      // 初始时栈的元素个数为 0
    }

    // 检查栈是否为空
    bool isEmpty() {
        return top == nullptr; // 如果栈顶指针为 nullptr,说明栈为空
    }

    // 入栈操作
    void push(int x) {
        Node* newNode = new Node(); // 为新元素创建一个节点
        newNode->data = x;          // 将数据存储在新节点的 data 中
        newNode->next = top;        // 新节点的 next 指向当前的栈顶
        top = newNode;              // 更新栈顶指针为新节点
        size++;                     // 栈中元素个数加 1
        cout << "入栈元素: " << x << ",当前栈大小: " << size << endl; // 输出入栈的元素和当前栈大小
    }

    // 出栈操作
    int pop() {
        if (isEmpty()) { // 如果栈为空,无法执行出栈操作
            cout << "栈为空,无法出栈" << endl; // 输出错误信息
            return -1; // 返回 -1 表示出栈失败
        }
        Node* temp = top;        // 临时保存当前栈顶节点
        int poppedValue = temp->data; // 保存栈顶的值,用于返回
        top = top->next;         // 更新栈顶指针为下一个节点
        delete temp;             // 释放原栈顶节点的内存
        size--;                  // 栈中元素个数减 1
        cout << "出栈元素: " << poppedValue << ",当前栈大小: " << size << endl; // 输出弹出的元素和当前栈大小
        return poppedValue;      // 返回弹出的栈顶元素
    }

    // 查看栈顶元素
    int peek() {
        if (isEmpty()) { // 如果栈为空,无法查看栈顶元素
            cout << "栈为空,无法获取栈顶元素" << endl; // 输出错误信息
            return -1; // 返回 -1 表示操作失败
        }
        return top->data; // 返回栈顶的值
    }

    // 获取栈中元素的个数
    int getSize() {
        return size; // 返回栈的大小
    }

    // 析构函数,释放所有节点
    ~Stack() {
        while (!isEmpty()) { // 只要栈不为空,就不断弹出栈顶元素
            pop(); // 通过 pop 操作释放每个节点的内存
        }
        top = nullptr; // 在所有节点释放后,将栈顶指针显式设为 nullptr
    }
};

int main() 
{
    Stack s; // 创建一个栈实例

    s.push(10); // 入栈元素 10
    s.push(20); // 入栈元素 20
    s.push(30); // 入栈元素 30

    cout << "栈顶元素: " << s.peek() << endl; // 输出当前栈顶元素
    cout << "当前栈大小: " << s.getSize() << endl; // 输出当前栈的大小

    cout << "出栈元素: " << s.pop() << endl;  // 出栈,并输出弹出的元素
    cout << "栈顶元素: " << s.peek() << endl; // 再次输出栈顶元素
    cout << "当前栈大小: " << s.getSize() << endl; // 输出当前栈的大小

    return 0; // 程序正常结束
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2219209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【初阶数据结构】计数排序 :感受非比较排序的魅力

文章目录 前言1. 什么是计数排序&#xff1f;2. 计数排序的算法思路2.1 绝对位置和相对位置2.2 根据计数数组的信息来确认 3. 计数排序的代码4. 算法分析5. 计数排序的优缺点6.计数排序的应用场景 前言 如果大家仔细思考的话&#xff0c;可能会发现这么一个问题。我们学的七大…

【C语言】原码 反码 补码

为什么要有原码 反码 补码的概念&#xff1f; 因为在计算机中最终只能识别机器码&#xff0c;是以 0000 0000 二进制作为表示形式&#xff0c;对于一个数&#xff0c;计算机要使用一定的编码方式进行存储&#xff0c;原码 反码 补码是机器存储一个数值的编码方式&#xff0c;最…

技术分享:A-23OH型树脂在汽车涂装废溶剂回收中的应用

在当今汽车制造业竞争激烈的环境下&#xff0c;提高生产效率、降低成本的同时&#xff0c;满足环保要求已成为各制造商追求的核心目标。水性涂料因其环保、节能等多重优势&#xff0c;在汽车涂装领域的应用日益广泛。然而&#xff0c;随之而来的喷涂废溶剂处理问题也日益凸显。…

2024年软件设计师中级(软考中级)详细笔记【7】面向对象技术(下)23种设计模式(分值10+)

目录 前言阅读前必看 第七章 面向对象技术&#xff08;下&#xff09;7.3 设计模式&#xff08;固定4分&#xff09;7.3.1 设计模式的要素7.3.2 创建型设计模式7.3.2.1 Abstract Factory&#xff08;抽象工厂&#xff09;7.3.2.2 Builder&#xff08;生成器&#xff09;7.3.2.3…

调整奇数偶数的顺序

//调整奇数偶数的顺序 //输入一个整数数组&#xff0c;实现一个函数 //使得数组中所有的奇数位于数组的前半部分&#xff0c;所有的偶数位于数组的后半部分 #include<stdio.h> void tz(int a[],int sz) {int i 0;int j 0;int q 0;int c[100] { 0 };int b[100] { 0 …

Qt第十三天:网络编程:TCP和UDP的使用

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 ❤️TCP&#xff1a; 一、创建项目&#xff0c;命名为Server&#xff0c;继承QWidget 二、添加Qt设计师…

Axure重要元件三——中继器添加数据

亲爱的小伙伴&#xff0c;在您浏览之前&#xff0c;烦请关注一下&#xff0c;在此深表感谢&#xff01; 本节课&#xff1a;中继器添加数据 课程内容&#xff1a;添加数据项、自动添加序号、自动添加数据汇总 应用场景&#xff1a;表单数据的添加 案例展示&#xff1a; 步骤…

算法: 模拟题目练习

文章目录 模拟替换所有的问号提莫攻击Z 字形变换外观数列数青蛙 总结 模拟 替换所有的问号 按照题目的要求写代码即可~ public String modifyString(String ss) {int n ss.length();if (n 1) {return "a";}char[] s ss.toCharArray();for (int i 0; i < n; i…

【华为HCIP实战课程十三】OSPF网络中3类LSA及区域间负载均衡,网络工程师

一、ABR SW1查看OSPF ABR为R4而非R3,因为R4连接骨干区域0,R3没有连接到区域0 R6查看OSPF路由: 二、查看3类LSA,由于R6不是ABR因此自身不会产生3类LSA 但是有区域间路由就可以看到3类LSA

分布式介绍

CAP理论 CAP理论是分布式架构中提出来的一种设计思想模型&#xff0c;全称是由Consistency、Availability、Partition Tolerance三个词组成。 C(Consistency&#xff0c;一致性):总能读到最新的写操作的结果A(Availability&#xff0c;可用性):每个请求都要在合理的时间内给出…

Spring Boot知识管理:跨平台集成方案

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

后渗透利用之vcenter

目录 vcenter介绍环境搭建历史漏洞版本信息1、直接访问2、请求接⼝ 打点CVE_2021_21972漏洞描述&#xff1a;POC&#xff1a; 后渗透获取vCenter后台重置密码Cookie登录创建管理员 获取虚拟机Hash分析快照挂载磁盘 获取Esxi 后台获取解密key获取数据库账号密码查询Esxi加密密码…

ESP32-IDF 分区表

目录 一、基本介绍1、配置结构体1.1 esp_partition_t1.2 esp_partition_iterator_t 2、常用 API2.1 esp_partition_find2.2 esp_partition_find_first2.3 esp_partition_get2.4 esp_partition_next2.5 esp_partition_iterator_release2.6 esp_partition_verify2.7 esp_partitio…

使用WPF写一个简单的开关控件

<Window x:Class"WPF练习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/2008"xm…

适用于 vue react Es6 jQuery 等等的组织架构图(组织结构图)

我这里找的是 OrgChart 插件; 地址: GitHub - dabeng/OrgChart: Its a simple and direct organization chart plugin. Anytime you want a tree-like chart, you can turn to OrgChart. 这里面能满足你对组织架构图的一切需求! ! ! 例: 按需加载 / 拖拽 / 编辑 / 自定义 / …

【玉米病害识别】Python+卷积神经网络算法+人工智能+深度学习+计算机课设项目+TensorFlow+模型训练

一、介绍 玉米病害识别系统&#xff0c;本系统使用Python作为主要开发语言&#xff0c;通过收集了8种常见的玉米叶部病害图片数据集&#xff08;‘矮花叶病’, ‘健康’, ‘灰斑病一般’, ‘灰斑病严重’, ‘锈病一般’, ‘锈病严重’, ‘叶斑病一般’, ‘叶斑病严重’&#x…

使用JMeter进行Spring Boot接口的压力测试

使用 Apache JMeter 对接口进行压力测试是一个相对简单的过程。以下是详细的步骤&#xff0c;包括安装、配置和执行测试计划。 1. 下载和安装 JMeter 下载 JMeter 从 JMeter 官方网站https://jmeter.apache.org/download_jmeter.cgi 下载最新版本的 JMeter。 解压缩 将下载的 …

【AIGC】ChatGPT与人类理解力的共鸣:人机交互中的心智理论(ToM)探索

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;心智理论(Theory of Mind,ToM)心智理论在心理学与神经科学中的重要性心智理论对理解同理心、道德判断和社交技能的重要性结论 &#x1f4af;乌得勒支大学研究对ChatGPT-4…

基于python+dj+mysql的音乐推荐系统网页设计

音乐网站开发 如果你在学Python&#xff0c;需相关的【配套资料工具】作为研究[doge][脱单doge] 可以后台✉私信up主&#xff0c;发送&#x1f449;关键词【音乐】 本章以音乐网站项目为例&#xff0c;介绍Django在实际项目开发中的应用&#xff0c;该网站共分为6个功能模块分…

使用开源的 Vue 移动端表单设计器创建表单

FcDesigner Vant 版是一款基于 Vue3.0 的移动端低代码可视化表单设计器工具&#xff0c;通过数据驱动表单渲染。可以通过拖拽的方式快速创建表单&#xff0c;提高开发者对表单的开发效率&#xff0c;节省开发者的时间。 源码下载 | 演示地址 | 帮助文档 本项目采用 Vue3.0 和 …