一起学数据结构(5)——栈和队列

news2025/1/24 14:51:28

1. 栈的相关定义及特点:

1. 栈的相关定义:

在正式介绍栈的定义之前,首先来回顾一下关于线性表的定义:

线性表是具有相同数据类型的n(n>0)个数据元素的有限序列,其中n为表长。n=0时,可以把线性表看作一个空表,一个典型的线性表就是26英文字母组成的序列,即:

                                                (A,B,C,D,E......)

在之前介绍线性表的文章中,解释并实现了线性表的某些功能,例如:头插、尾删、任意位置插入结点等。对于线性表而言,其相对于链表的优点有可以随机访问结点。当利用线性表对任意位置插入结点时,其时间复杂度为O(N),会过于繁琐。

在上面简要给出线性表的相关内容后,下面给出栈的基本定义:

栈(Stack)是一种特殊的线性表,但是与上面所说明的线性表不同的是,栈是一种只能在表尾进行插入、删除操作的线性表。即:
                                       Stack: (a_{1},a_{2},a_{3},a_{4},a_{5}......a_{n})

对于上面给出的栈的简要示意图,将表尾(即a_{n})称之为栈顶Top,将表头(即a_{1})称之为栈底Base,因此,上面所提到栈是一种只能在表尾进行插入、删除的数据表这一概念,在这里也可以解释为,栈是一种只能在栈顶Top进行插入、删除操作的线性表。并且,将从栈顶Top插入元素的这一操作命名为进栈,将在栈顶Top进行删除的这一操作命名为出栈。

1.2 栈的特点及相关应用:

对于上面所提到的进栈、出栈这两个操作,可以通过下面的图形进行表示:

将下面给出的图形定义为空栈

由上面给出的关于栈底、栈顶的相关定义可知,因为此时的栈为空,所以,栈底、栈顶指向同一位置。

当元素a_{1}进行入栈操作时,栈、栈底、栈顶的变化可以用下面的图形进行表示:

在元素a_{1}完成入栈后,栈底Base不变,栈顶Top指向的位置发生变化。一般来说,栈顶Top用来记录栈中完成入栈的元素个数。

如果,再向上面给出的栈中入栈两个元素a_{2},a_{3}。即:

对上面的栈进行出栈操作时,由上面给出的关于出栈的定义可知,出栈的元素顺序为:a_{3},a_{2},a_{1}

所以,栈也可以看作一个具有后进先出特点的线性表

介于栈后进先出的这一特点,栈可以用于解决许多的实际问题,例如:数制转换、括号匹配检验、表达式求值等。在文章最后会详细解释括号匹配检验问题

2. 栈的代码实现(顺序栈的实现):

2.1 栈结构创建:

采用结构体对栈的结构进行创建,其中静态的栈结构如下:
 

#define N 10;
struct Stack
{
	int arr[N];
	int top;
};

在前面实现顺序表时就提到,在采用静态方式来实现栈或者顺序表等数据结构时,由于内存大小不能进行灵活的调整,很容易就会造成内存浪费或者越界等问题。本文依旧采用动态开辟内存的方式来实现对栈结构的创建。代码如下:
 

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

其中,top表示栈顶。用于后续的插入删除等操作的实现。capacity用于表示栈中被使用的空间大小,一旦使用的空间大小达到capacity,就立刻进行扩容。

2.2 栈的初始化:

定义函数STInit用于初始化上面创建的栈的结构。其中,需要进行的操作为:

1.动态开辟一定大小的空间。或者直接将结构体中创建的指针a初始化为NULL.后续进行扩容。因为在顺序表中采用了第一种方式。所以,对于栈的初始化,采用第二种

2.初始化时,栈为空栈,所以将topcapacity初始化为0

代码如下:
 

//栈的初始化:
void STInit( ST* ps )
{
	assert(ps);

	ps->a = NULL;
	ps->top = ps->capacity = 0;

}

2.3 栈的销毁:

对于栈的销毁,同样可以分为下面几步:

1.free指针a所指向的动态开辟的空间。

2.将指针a中存储的地址改为NULL

3.将top,capacity都改为0

代码如下:

//栈的销毁:
void STDestory(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

2.4 通过栈顶向栈中插入元素:

对于通过栈顶向栈中插入元素这一功能,可以分为下面几步进行实现:

1.前面说到,为了演示扩容的第二种方式,所以在通过栈顶向栈中插入元素这一操作时,首先需要检查表示栈中已有元素数量的变量top是否与表示栈容量的变量capacity相等。若相等,则表示此时栈空间已满需要啊进行扩容。

2.在扩容完毕之后,需要将表示容量的变量capacity的大小进行更改。并且将用于扩容的指针变量中的值赋值给a

3.此时指针变量a中存储了动态开辟的空间的地址,通过ps->a[ps->top] = x来完成插入元素的目的。

4.将top+1

代码如下:
 

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? ps->capacity = 4: ps->capacity * 2;
		STDataType* newnode = (STDataType*)realloc(ps->a,sizeof(STDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("realloc");
		}
		ps->a = newnode;
		ps->capacity = newcapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

2.5 删除栈中的元素:

在上面通过栈顶向栈中插入元素的操作中,ps->a[ps->top] = x;表示,插入元素时,是通过a[ps->top]来访问数组并且进行插入的。所以,对于删除栈中的元素。只需要将top-1即可。代码如下:

void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	ps->top--;

}

2.6 探空:

用于判断此时的栈是否为空栈,所以,只需要检测ps->top == 0即可,代码如下:

bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}


2.7 求栈的长度:

对于栈的长度,也就是栈中插入了几个元素。可以通过栈顶top进行反应:
 

int size(ST* ps)
{
	assert(ps);

	return ps->top;
}

2.8 取栈顶元素:

与通过栈顶向栈中插入元素的大致思路相同,通过ps->a[top-1]达到取栈顶元素的目的,代码如下:

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	return ps->a[ps->top-1];

}

3. 与栈相关的题目解析——Leetcode.20——有效的括号:

3.1 题目解析:

题目要求在判断有效字符串时,需要满足相同类型的括号闭合,以及正确的闭合顺序。对于正确的闭合顺序这一要求,决定了题目不能使用数组来统计不同类型的括号的数量,判断相同类型阔号的数量是否为偶数来解决问题。

在栈的特点这一部分的内容中提到,栈可以看作有后进先出特点的线性表。介于这个特点可以用栈来解决此题。

具体思路如下:

1. 采用while循环对给定字符串的每个字符遍历,检测被遍历的字符是否为三个括号:‘(‘,’[’,‘{’其中之一,满足条件则将这个字符入栈。

2. 当遇到字符串为‘)’,‘]’,‘}’,时,将栈中已经记录的字符出栈,并且额外创建一个变量记录。进行匹配。如果此时遇到的字符串与出栈的字符串不满足题目中给定的关系,即不满足每个右括号都有一个对应的相同类型的左括号。则返回false。如果满足则让*s指向下一个位置。如果整体字符串都满足上述的对应关系。则返回true

例如,对于字符串"( { { [ ] } } )"

按照上面所说的步骤,首先将满足(’,’[’,‘{’其中之一,满足条件则将这个字符入栈。。此时,栈内的情况可有下面的图进行表示:

这一过程可由下面的代码实现:

while( *s)
    {
        switch(*s)
        {
            case '(':
            case '[':
            case '{':
            STPush(&ps,*s);
            break;
        }
     *s++;
    }

当遍历过程中遇到了右括号,及”] } } )",开始进行匹配,先创建一个临时变量cur用于记录出栈的元素。利用STTop取出栈顶元素记录在cur同时,为了下次循环时可以读取到栈后续的内容,需要利用STop删除这个元素。

在进行匹配时,只需要考虑匹配不成功的情况。并返回false。对于匹配不成功的情况,即左右括号不对称。可以由下面的代码表示:
 

 cur = STTop(&ps);
            STPop(&ps);
              if( (*s == '}' && cur !='{') || ((*s == ']') && (cur != '[')) || ((*s ==')'))
              && (cur != '('))
              {
                  STDestory(&ps);
                  return false;
              }
              break;

当字符串中每一个被遍历的字符都匹配成功,说明该字符串是题目要求的有效字符串。不过,再返回true之前需要考虑两个特殊情况:
1. 字符串是否只存在左括号,即‘(‘,’[’,‘{’

2. 字符串是否只存在右括号,即‘)’,‘]’,‘}’

3.字符串中左右括号的数量是否相同。

对于情况1,因为不存在右括号,所以在循环的第一部分,即入栈后,就会跳出循环,不参与后续的匹配。所以只需要利用STEmpty检测此时的栈是否为空即可。不为空则说明,左右括号数目不相同或者不存在右括号。

对于情况2.因为不存在左括号,所以在循环中经历入栈这个过程时,栈为空。只需要在入栈这个步骤结束后,检测栈是否为空即可。为空则返回false即可

对于情况3,在情况一中得到解决。

3.2 代码展示:

(注:79及79行之前的内容为栈的代码实现)

typedef char STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit( ST* ps )
{
	assert(ps);

	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

//栈的销毁:
void STDestory(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	
	if (ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? ps->capacity = 4: ps->capacity * 2;
		STDataType* newnode = (STDataType*)realloc(ps->a,sizeof(STDataType) * newcapacity);
		if (newnode == NULL)
		{
			perror("realloc");
		}
		ps->a = newnode;
		ps->capacity = newcapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	ps->top--;

}

int size(ST* ps)
{
	assert(ps);

	return ps->top;
}

bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	return ps->a[ps->top-1];

}


bool isValid(char * s){
    ST ps;
    STInit( &ps);
    char cur;
    while( *s)
    {
        switch(*s)
        {
            case '(':
            case '[':
            case '{':
            STPush(&ps,*s);
            break;
            //匹配'{'
            case'}':
             case']':
              case')':
            //检测是否存在只有右边有括号的情况
            if( STEmpty(&ps))
            {
                STDestory(&ps);
                return false;
            }
              //取栈顶元素
            cur = STTop(&ps);
            STPop(&ps);
              if( (*s == '}' && cur !='{') || ((*s == ']') && (cur != '[')) || ((*s ==')'))
              && (cur != '('))
              {
                  STDestory(&ps);
                  return false;
              }
              break;

            
        }
        *s++;
    }
    //检测是否只有左边有括号的情况,因为在匹配括号时,如果存在右括号
    //会使用STTop吸收左括号,所以,如果ret为0,则表示左括号全部吸收完。
    bool ret = STEmpty(&ps);
    STDestory(&ps);
    return ret;

}

结果如下:


 

4. 栈的代码补充:

上面给出的栈并不全面,下面给出头文件Stack.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//静态栈的创建:
//#define N 10;
//struct Stack
//{
//	int arr[N];
//	int top;
//};

//栈的动态开辟:
typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;


//栈的初始化:
void STInit(ST* ps);

//栈的销毁
void STDestory(ST* ps);

//通过栈顶向栈中插入元素
void STPush(ST* ps, STDataType x);

//删除栈中的元素:
void STPop(ST* ps);

//记录size
int size(ST* ps);

//找空
bool STEmpty(ST* ps);

//获取栈顶元素
STDataType STTop(ST* ps);


 

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

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

相关文章

图床项目进度(三)——文件展示页

前言 该项目作为一个类网盘项目&#xff0c;主要包括上传下载&#xff0c;引用&#xff0c;预览等功能。 大致功能&#xff1a; 图片预览 这里的图片预览我使用的一个插件 const state: any reactive({ image: https://pic35.photophoto.cn/20150511/0034034892281415_b…

悲观锁和乐观锁、缓存

悲观锁&#xff1a; 悲观锁的实现通常依赖于数据库提供的机制&#xff0c;在整个处理的过程中数据处于锁定状态&#xff0c;session的load方法有一个重载方法&#xff0c;该重载方法的第三个参数可以设置锁模式&#xff0c;load(object.class , int id,LockMode.?)&#xff0…

deepin 如何卸载软件

文章目录 卸载软件&#xff08;正文&#xff09; 通常来讲在官方的应用商场卸载即可。 但是呢&#xff1f; 很不幸的是&#xff0c;没能够彻底删除软件。还是能够在启动器界面上看到应用。 这时候&#xff0c;你右键卸载&#xff0c;会提示“卸载失败”。如下图&#xff1a; …

VirtualBox RockyLinux9 网络连接

有几次都是隔一段时间之后启动虚拟机&#xff0c;用第三方ssh工具就连接不上了。 简单记录一下。 1、VirtualBox设置 2、IP设置 cd /etc/NetworkManager/system-connections/ vim enp0s3.nmconnection[connection] idenp0s3 uuid9c404b41-4636-397c-8feb-5c2ed38ef404 typeet…

windows nvm 安装 以及常用的命令

1 nvm 下载 链接&#xff1a;https://github.com/coreybutler/nvm-windows/releases 可下载以下版本&#xff1a; nvm-noinstall.zip&#xff1a;绿色免安装版&#xff0c;但使用时需要进行配置。 nvm-setup.zip&#xff1a;安装版&#xff0c;推荐使用 2 安装&#xff08…

python-38-python定时任务框架

Python定时任务 Python任务调度模块 – APScheduler python调度框架APScheduler使用详解 APScheduler动态增、删、改任务 apscheduler mysql 持久化任务 APScheduler调度框架 在项目中&#xff0c;我们可能遇到有定时任务的需求。 其一&#xff1a;定时执行任务。例如每天早上 …

为什么要学习源码之Java篇

为什么学习源码 大厂面试必问。二次开发。提升代码阅读能力&#xff0c;更能输出优质代码。提升技术功底。拥抱开源社区。快速定位线上问题。 学习源码的方式 首先最重要的是学会使用。具有全局观。先对大致有个细节的了解&#xff0c;一开始不要太关注于细节。学会看注释&a…

【计算机网络】TCP传输控制协议——三次握手

文章目录 握手的流程常考考点 握手的流程 一开始&#xff0c;客户端和服务端都处于CLOSE状态&#xff0c;先是服务端监听某个端口&#xff0c;处于LISTEN状态。然后客户端主动发起连接SYN&#xff0c;之后处于SYN-SEND状态。服务端收到发起的连接&#xff0c;返回SYN&#xff0…

Spring Data Commons远程命令执行漏洞复现(CVE-2018-1273)

一、漏洞说明 Spring Data是一个用于简化数据库访问&#xff0c;并支持云服务的开源框架,包含Commons、Gemfire、JPA、JDBC、MongoDB等模块。此漏洞产生于Spring Data Commons组件&#xff0c;该组件为提供共享的基础框架&#xff0c;适合各个子项目使用&#xff0c;支持跨数据…

快速学会git版本管理——创建分支和合并分支

首先创建分支 git创建分支只需要使用switch 命令&#xff1a; git switch -c 分支名 创建分支并切换到该分支 大家看后面的括号里已经变成了dev 说明我们切换成功了。 然后想要合并分支就在 创建的分支中 进行提交修改的内容&#xff0c;还是通过&#xff1a;add 命令和co…

EasyExcel入门(最简单的读)

官网&#xff1a;EasyExcel官方文档 - 基于Java的Excel处理工具 | Easy Excel (alibaba.com) 因为暂时项目没有用到&#xff0c;所以不急&#xff0c;知道了这个技术。就想着学着用一下&#xff01; 最简单的读 先看官方文档给的用法和解释&#xff01;&#xff01;&#xff01…

开开心心带你学习MySQL数据库之第八篇

索引和事务 ~~ 数据库运行的原理知识 面试题 索引 索引(index) > 目录 索引存在的意义,就是为了加快查找速度!!(省略了遍历的过程) 查找速度是快了&#xff0c;但是付出了一定的代价!! 1.需要付出额外的空间代价来保存索引数据 2.索引可能会拖慢新增,删除,修改的速度 ~~ …

信息系统项目管理师(第四版)教材精读思维导图-第十四章项目沟通管理

请参阅我的另一篇文章&#xff0c;综合介绍软考高项&#xff1a; 信息系统项目管理师&#xff08;软考高项&#xff09;备考总结_计算机技术与软件专业技术_铭记北宸的博客-CSDN博客 本章思维导图XMind源文件 14.1 管理基础 14.2 管理过程 14.3 规划沟通管理 14.4 管理沟通 14.…

机器学习:使用PCA简化数据

文章目录 使用场景主成分分析&#xff08;Principal component analysis&#xff09;实验&#xff1a;对半导体数据&#xff08;590个特征&#xff09;进行降维处理 使用场景 我们通过电视看实况足球&#xff0c;电视显示屏有100万个像素点&#xff0c;球所占的点数为100个。人…

Collectors类作用:

一、Collectors类&#xff1a; 1.1、Collectors介绍 Collectors类&#xff0c;是JDK1.8开始提供的一个的工具类&#xff0c;它专门用于对Stream操作流中的元素各种处理操作&#xff0c;Collectors类中提供了一些常用的方法&#xff0c;例如&#xff1a;toList()、toSet()、to…

真的有线上兼职吗?推荐几个靠谱的线上兼职!

在这个互联网普及&#xff0c;信息爆炸的时代&#xff0c;线上赚钱已经成为一个热门的话题。每个人都想通过互联网赚钱&#xff0c;有些人得到钱&#xff0c;给普通人机会&#xff0c;给骗子一些机会&#xff0c;世界是两面&#xff0c;线上兼职赚钱的方式&#xff01;有好有坏…

python中的继承

要理解继承首先要有父类和子类的概念&#xff0c;可以理解成子类从父类中继承父类的属性和方法 创建父类 class Pet:def __init__(self,name,age):self.name nameself.age agedef jump(self):print(self.name"在跳")创建子类 class Cat(Pet):pass mycatCat(&quo…

HTML的有序列表、无序列表、自定义列表

目录 背景: 过程: 无序列表: 简介: 代码展示: 效果展示:​ 无序列表: 简介: 效果展示:​ 自定义列表: 简介&#xff1a; 效果展示: 总结&#xff1a; 背景: 1.有序列表&#xff08;Ordered List&#xff09;&#xff1a; 有序列表是最早的列表类型之一&#xff…

编译器02-词法分析

一&#xff1a;简述 词法分析含义&#xff1a;为了翻译语言&#xff0c;编译器把程序各种成分拆开&#xff0c;那如何拆&#xff0c;首先第一步就是将输入分解成一个个独立的单词(token)&#xff0c;这一过程叫词法分析。 二&#xff1a;单词(token)分为哪些种类 保留字…

类和对象:构造函数,析构函数与拷贝构造函数

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器…