栈简介、手写顺序栈、手写链栈和栈的应用

news2024/11/26 20:31:29

一. 简介

1. 什么是栈?

 栈是一种只能从表的一端存取数据且遵循 "先进后出"("后进先出") 原则的线性存储结构。栈也是用来存储逻辑关系为 "一对一" 数据的线性存储结构。

  C#中提供顺序栈:Stack,它不是线程安全的;如果要使用线程安全的队列,需要用:ConcurrentStack。

 分析:

  (1). 栈只能从表的一端存取数据,另一端是封闭的

  (2). 在栈中,无论是存数据还是取数据,都必须遵循"先进后出"的原则,即最先进栈的元素最后出栈

2. 一些名词

 栈顶(Top):表尾,栈中允许数据插入和删除的那一端。

 栈底(Bottom):表头,栈中无法进行数据操作的那一端。

 栈上溢(Full):栈内空间已满时,仍进行入栈操作,是一种空间不足的出错状态。

 栈下溢(Empty):栈内无数据时,仍然进行出栈操作,是一种数据不足的出错状态。

 进栈或者入栈(Push):将数据插入栈顶部。

 弹出或出栈(Pop):取出并删除栈顶部的数据。

3. 常用Api

 Push()入栈(添加数据)

 Pop()出栈(删除数据,返回被删除的数据)

 Peek()取得栈顶的数据,不删除

 Clear()清空所有数据

 Count取得栈中数据的个数

代码分享:

         {
                Console.WriteLine("--------------C#提供的Stack---------------------");
                Stack<int> s1 = new Stack<int>();
                s1.Push(1);
                s1.Push(2);
                s1.Push(3);
                s1.Push(4);
                Console.WriteLine($"元素的个数为:{s1.Count}");
                int p1 = s1.Pop();     //取出并删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p1}");
                int p2 = s1.Peek();     //取出不删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p2}");
                s1.Clear();
                Console.WriteLine($"元素的个数为:{s1.Count}");
            }

运行结果:

4. 分类

 栈是一种 "特殊" 的线性存储结构,因此栈的具体实现有以下两种方式:

 (1). 顺序栈:采用顺序存储结构可以模拟栈存储数据的特点,从而实现栈存储结构;

 (2). 链栈:采用链式存储结构实现栈结构;

PS: 两种实现方式的区别,仅限于数据元素在实际物理空间上存放的相对位置,顺序栈底层采用的是数组,链栈底层采用的是链表。

二. 顺序栈

1. 思路

 顺序栈,即栈的顺序存储结构(数组),是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。

 当top=-1时候,表示为空栈。由于顺序栈的操作位置基本在栈顶,所以,不需要查找插入和删除的位置,也不需要移动元素,因而顺序栈的基本操作要比顺序表简单的多,其基本操作时间复杂度均为O(1)。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

【文章福利】另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以点击领取

2. 手撸代码

接口

   /// <summary>
    /// 栈接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IStack<T>
    {
        int Count { get; }//元素个数
        bool IsEmpty(); //是否为空栈
        void Clear(); //清空
        void Push(T item); //入栈操作
        T Pop(); //返回栈顶数据并且出栈
        T Peek(); //取栈顶元素,不出栈
    }

实现类

 /// <summary>
    /// 顺序栈
    /// (用数组来实现)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class SeqStack<T> : IStack<T>
    {
        private T[] _array;  //底层数据用数组来存储
        private const int _defaultCapacity = 4;  //默认存储容量
        private int top = -1;  //指向栈顶元素的位置  top=-1,表示为空元素

        /// <summary>
        /// 指定容量的构造函数
        /// </summary>
        /// <param name="capacity"></param>
        public SeqStack(int capacity)
        {
            if (capacity < 0)
            {
                throw new ArgumentOutOfRangeException("栈容量不能小于0");
            }
            //指定容量小于默认容量,则采用默认容量
            if (capacity < _defaultCapacity)
            {
                capacity = _defaultCapacity;
            }
            this._array = new T[capacity];
        }
        /// <summary>
        /// 无参构造函数
        /// (初始化为默认容量)
        /// </summary>
        public SeqStack() : this(_defaultCapacity)
        {

        }

        /// <summary>
        /// 元素个数
        /// </summary>
        public int Count
        {
            get
            {
                return top + 1;
            }
        }
        /// <summary>
        /// 清空所有元素
        /// </summary>
        public void Clear()
        {
            top = -1;
        }
        /// <summary>
        /// 判断栈是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return top == -1;
        }
        /// <summary>
        /// 获取栈顶元素(不删除)
        /// </summary>
        /// <returns></returns>
        public T Peek()
        {
            if (IsEmpty())
            {
                throw new InvalidOperationException("栈下溢,栈中没有数据");
            }
            return this._array[top];
        }
        /// <summary>
        /// 出栈(删除)
        /// </summary>
        /// <returns></returns>
        public T Pop()
        {
            T data = Peek();
            top--;
            return data;
        }
        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="item"></param>
        public void Push(T item)
        {
            //当元素个数等于数组长度,则需要扩容2倍
            if (this.Count == this._array.Length)
            {
                T[] desArray = new T[this._array.Length * 2];
                //原数组copy到目标数组中
                Array.Copy(this._array, 0, desArray, 0, this.Count);
                this._array = desArray;
            }
            this._array[++top] = item;
        }
    }

调用代码

 {
                Console.WriteLine("--------------手撸顺序栈---------------------");
                IStack<int> s1 = new SeqStack<int>();
                s1.Push(1);
                s1.Push(2);
                s1.Push(3);
                s1.Push(4);
                Console.WriteLine($"元素的个数为:{s1.Count}");
                int p1 = s1.Pop();     //取出并删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p1}");
                int p2 = s1.Peek();     //取出不删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p2}");
                s1.Clear();
                Console.WriteLine($"元素的个数为:{s1.Count}");
}

运行结果:

三. 链栈

1. 思路

 链栈通常用单链表来表示,它的实现是单链表的简化。由于链栈的操作只是在一端进行,为了操作方便,把栈顶设在链表的头部。单链表获取长度需要遍历整个链表,性能很低,所以我们增加一个count属性记录元素个数。

2. 手撸代码

接口

    /// <summary>
    /// 栈接口
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IStack<T>
    {
        int Count { get; }//元素个数
        bool IsEmpty(); //是否为空栈
        void Clear(); //清空
        void Push(T item); //入栈操作
        T Pop(); //返回栈顶数据并且出栈
        T Peek(); //取栈顶元素,不出栈
    }

实现类

/// <summary>
    /// 链栈
    /// </summary>
    public class LinkedStack<T> : IStack<T>
    {
        public StackNode<T> top;  //栈顶指针
        public int count = 0;     //元素个数

        public int Count {
            get
            {
                return count;
            }     
        }
        /// <summary>
        /// 清空元素
        /// </summary>
        public void Clear()
        {
            count = 0;
            top = null;
        }
        /// <summary>
        /// 是否为空
        /// </summary>
        /// <returns></returns>
        public bool IsEmpty()
        {
            return count == 0;
        }
        /// <summary>
        /// 出栈(不删除)
        /// </summary>
        /// <returns></returns>
        public T Peek()
        {
            if (top==null)
            {
                throw new ArgumentOutOfRangeException("栈下溢,栈内没有数据");
            }
            return top.data;
        }
        /// <summary>
        /// 出栈(删除)
        /// </summary>
        /// <returns></returns>
        public T Pop()
        {
            if (top==null)
            {
                throw new ArgumentOutOfRangeException("栈下溢,栈内没有数据");
            }
            T r = top.data;
            top = top.next;
            count--;
            return r;
        }
        /// <summary>
        /// 入栈
        /// </summary>
        /// <param name="item"></param>
        public void Push(T item)
        {
            StackNode<T> newNode = new StackNode<T>(item);
            newNode.next = top;
            top = newNode;
            count++;
        }
    }

测试

  {
                Console.WriteLine("--------------手撸链栈---------------------");
                IStack<int> s1 = new LinkedStack<int>();
                s1.Push(1);
                s1.Push(2);
                s1.Push(3);
                s1.Push(4);
                Console.WriteLine($"元素的个数为:{s1.Count}");
                int p1 = s1.Pop();     //取出并删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p1}");
                int p2 = s1.Peek();     //取出不删除
                Console.WriteLine($"元素的个数为:{s1.Count},取出的数据为:{p2}");
                s1.Clear();
                Console.WriteLine($"元素的个数为:{s1.Count}");
}

运行结果

四. 应用

1. 进制转换器

(1).目标:将一个非负的十进制整数N转换成其他D进制数.

(2).原理:

 求余→转换成char→入栈→整除→继续循环,最初出栈

特别注意:int→char: char c = residue < 10 ? (char)(residue + 48) : (char)(residue + 55);

代码分享:

 public class Utils
    {
        /// <summary>
        /// 十进制N转换成D进制
        /// </summary>
        /// <param name="N"></param>
        /// <param name="D"></param>
        /// <returns></returns>
        public static string DecConvert(int N, int D)
        {
            if (D < 2 || D > 16)
            {
                throw new ArgumentOutOfRangeException("D", "只支持二进制到十六进制的转换");
            }
            Stack<char> stack = new Stack<char>();
            do
            {
                int residue = N % D;        //取余
                char c = residue < 10 ? (char)(residue + 48) : (char)(residue + 55);           
                stack.Push(c);  //进栈
                N = N / D;
            } while (N != 0);       //当除的结果为0时表示已经到最后一位了
            string s = string.Empty;
            while (stack.Count > 0)
            {
                //所有的元素出栈并压入字符串s内
                s += stack.Pop().ToString();
            }
            return s;
        }
    }

测试:

{
                Console.WriteLine("--------------进制转换---------------------");
                //十进制的365转换成八进制输出
                string result1 = Utils.DecConvert(350, 8);
                Console.WriteLine($"十进制的365转换成八进制输出:{result1}");
 }

运行结果:

2. 其它案例

(1). 高性能分页

(2). 浏览器回退功能

 我们经常使用浏览器在各种网站上查找信息。假设先浏览的页面 A,然后关闭了页面 A 跳转到页面 B,随后又关闭页面 B 跳转到了页面 C。而此时,我们如果想重新回到页面 A,有两个选择:

  • 重新搜索找到页面 A;
  • 使用浏览器的"回退"功能。浏览器会先回退到页面 B,而后再回退到页面 A。

浏览器 "回退" 功能的实现,底层使用的就是栈存储结构。当你关闭页面 A 时,浏览器会将页面 A 入栈;同样,当你关闭页面 B 时,浏览器也会将 B入栈。因此,当你执行回退操作时,才会首先看到的是页面 B,然后是页面 A,这是栈中数据依次出栈的效果。

(3). 括号匹配问题

 数编程语言都会用到括号(小括号、中括号和大括号),括号的错误使用(通常是丢右括号)会导致程序编译错误,而很多开发工具中都有检测代码是否有编辑错误的功能,其中就包含检测代码中的括号匹配问题,此功能的底层实现使用的就是栈结构。

思路:

A. 如果碰到的是左圆括号或者左大括号,直接入栈;

B. 如果碰到的是右圆括号或者右大括号,就直接和栈顶元素配对:如果匹配,栈顶元素出栈;反之,括号不匹配;

原文链接:第七节:栈简介、手撸顺序栈、手撸链栈和栈的应用 - Yaopengfei - 博客园

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

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

相关文章

【MySQL基础】如何安装MySQL?如何将MySQL设置成服务?

目录 一、MySQL的安装 1、解压配置 2、步骤安装 &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、MySQL的安装 MySQL的安装有两种方式&#xff1a;解压配置和步骤安装 1、解压配置 需提前从官网直接下载压缩包&#xff0c;进…

【MySQL篇】第二篇——库的操作

目录 创建数据库 创建数据库案例 字符集和校验规则 查看系统默认字符集以及校验规则 查看数据库支持的字符集 查看数据库支持的字符集校验规则 校验规则对数据库的影响 操纵数据库 查看数据库 显示创建语句 修改数据库 数据库删除 备份和恢复 备份 还原 注意事…

常见磁盘调度算法总结

磁盘调度算法&#x1f4d6;1. 最短寻道时间优先&#xff08;SSTF&#xff09;&#x1f4d6;2. 电梯算法&#xff08;SCAN或C-SCAN&#xff09;&#x1f4d6;3. 最短定位时间优先&#xff08;SPTF&#xff09;&#x1f4d6;4. 总结由于IO的高成本&#xff0c;操作系统在决定发送…

C语言 0 —— 计算机硬件架构及信息在计算机中的表示

当前的计算机系统&#xff0c;如Window &#xff0c;Linux&#xff0c;Mac 基本都是基于冯诺依曼的驱动架构设计的。 冯诺依曼架构输入设备先输入公式&#xff0c;给运算器&#xff0c;运算器先算 先算2*5 &#xff0c;临时放在CPU内部寄存器中&#xff0c;寄存器不够用的时候会…

vscode插件开发(四)Webview(1)

上一篇详细讲解了命令&#xff0c;这回我们一起来看一下Webview。vscode的插件其实可以分为两种&#xff0c;一种是webview插件&#xff0c;另一种是非webview插件。 webview插件的自由度很高&#xff0c;可以满足开发者的各种定制化的要求&#xff1b;而非webview插件只能使用…

我悟了!Mysql事务隔离级别其实是这样!

问题描述 ​ 最近几天在忙项目&#xff0c;有个项目是将业务收集到的数据变动&#xff0c;异步同步到一张数据表中。在测试的过程时&#xff0c;收到QA的反馈&#xff0c;说有订单的数据同步时好时坏。我怀着疑惑的表情打开了那段代码&#xff0c;它的逻辑大概是这样的&#x…

Zookeeper实现分布式锁的原理。

之前学习Redis时候&#xff0c;我们利用Redis实现了分布式锁。 黑马点评项目Redis实现分布式锁_兜兜转转m的博客-CSDN博客 为什么提出了分布式锁的概念呢&#xff1f; 因为在单体项目中&#xff0c;锁是基于JVM虚拟机实现的&#xff0c;在分布式情况下&#xff0c;JVM就不唯…

FullGC频繁,线程数持续增长排查

告警 线上应用fullgc频繁&#xff0c;收到告警 GC监控—堆内存不足 查看近12小时的监控&#xff0c;发现Survivor区一直处于 满状态、fullgc非常频繁、但没有内存溢出的现象&#xff0c;很明显是堆内存不足 GC日志分析—暂停时间并不长 因为fullgc相当频繁&#xff0c;抽…

项目管理(知识体系概述)

项目的定义:为创造独特的产品、服务或者成果进行的临时性工作。 项目的特性:1、独特的产品、服务、成果;2、临时性工作。 项目管理的目的(为了解决什么问题): 1、达成业务目标 2、满足相关方期望 3、提供项目的可预测性 4、提高项目的成果性。 5、在适当的时刻交付…

机器人运动学标定:基于考虑约束的指数积的运动学标定方法——只需要测量位置,避免冗余约束

文章目录写在前面为什么要消除归一化和正交化操作&#xff1f;只用位置而不是位姿去做标定的原因基于消除冗余约束步骤的参数辨识模型分析参考文献写在前面 基于指数积的运动学标定方法介绍&#xff1a; 机器人运动学标定&#xff1a;基于指数积的串联机构运动学标定 机器人运…

Vue表单修饰符:v-model.lazy、v-model.number、v-model.trim

表单修饰符有&#xff1a;lazy、number、trim&#xff1b;修饰符加在v-model后面&#xff1b; lazy修饰符&#xff1a; v-model的作用是双向绑定表单&#xff0c;能获取到input输入框的值&#xff0c;而且是实时获取的&#xff0c;就是当你输入框里的值发生改变就会获取到&…

【Shell 脚本速成】02、Shell 变量详解

目录 一、变量介绍 变量存取原理 二、变量定义 2.1 什么时候需要定义变量&#xff1f; 2.2 定义一个变量 定义变量举例&#xff1a; 定义变量演示&#xff1a; 2.3 取消变量 unset 2.4 有类型变量 declare declare 命令参数&#xff1a; 案例演示&#xff1a; 三…

向前迈进!走入GC世界:G1 GC原理深入解析

第零章&#xff1a;名词解释 mutator&#xff1a;应用线程 STW&#xff1a;Stop-The-World&#xff0c;指除了GC线程&#xff0c;其它所有线程全部暂停的一段时间 并发&#xff1a;指代GC线程与mutator在同一时刻执行任务 并行&#xff1a;指代多个GC线程在同一时刻执行任务…

一站式元数据治理平台——Datahub

一站式元数据治理平台——Datahub万字保姆级长文——Linkedin元数据管理平台Datahub离线安装指南 - 独孤风 - 博客园 (cnblogs.com)企业级数据治理工作怎么开展&#xff1f;Datahub这样做 - 独孤风 - 博客园 (cnblogs.com)【DataHub】 现代数据栈的元数据平台–如何与spark集成…

如何设计金融机构多场景关键应用下的存储架构

【摘要】银行、保险等金融机构存在多场景下的关键应用,如何选择适合各场景下的存储,如何设计适合业务的存储架构,显得尤为重要。本文从当前主流存储架构分析入手,提出金融机构业务场景分析与架构选型思路,以Glusterfs为例,分享如何根据业务场景的特点,有针对性的选取适合…

SQL优化

文章目录提升group by的效率分页查询优化覆盖索引子查询起始位置重定义检查 where,order by,group by后面的列尽量使用 varchar 代替 char。&#xff08;SQL 性能优化&#xff09;如果修改 / 更新数据过多&#xff0c;考虑批量进行提升group by的效率 select user_id,user_nam…

spring-security源码学习总结

由于SpringBoot 对 Security 的支持类均位于org.springframework.boot.autoconfigure.security包下&#xff0c;主要通过 SecurityAutoConfiguration 自动配置类和 SecurityProperties 属性配置来完成&#xff0c;所以需要下载springboot源码深入学习 SecurityAutoConfiguratio…

云原生边缘设备解决方案Akri on k3s初体验

作者&#xff1a; 涂家英&#xff0c;SUSE 资深架构师&#xff0c;专注 Cloud-Native 相关产品和解决方案设计&#xff0c;在企业级云原生平台建设领域拥有丰富的经验。 写在前面 k3s 是 SUSE 推出的为物联网和边缘计算构建的经过认证的 Kubernetes 发行版&#xff0c;它可以帮…

指数函数及其导函数

目录前言指数函数的导函数指数函数导函数动图绘制参考文献前言 前面我们介绍了指数函数及其基本性质以及如何在笛卡尔直角坐标系下绘制静态的指数函数图像&#xff0c;这一节&#xff0c;我们将重点讨论一下指数函数的导函数以及导函数的动态表示&#xff0c;为方便起见&#…

大数据(9f)Flink富函数RichFunction

文章目录1、概述2、示例2.1、普通函数2.2、富函数2.2.1、获取富函数的运行时上下文3、源码截取3.1、RichFunction3.2、RuntimeContext1、概述 Rich Function&#xff0c;译名富函数&#xff0c;和普通函数相比&#xff0c;多了&#xff1a;生命周期&#xff08;open和close方法…