第五话·数据结构必看之·栈 真的这么简单?

Posted kikokingzz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第五话·数据结构必看之·栈 真的这么简单?相关的知识,希望对你有一定的参考价值。

 🌕写在前面


  • 🍊博客主页:kikoking的江湖背景
  • 🎉欢迎关注🔎点赞👍收藏⭐️留言📝
  • 🌟本文由 kikokingzz 原创,CSDN首发!
  • 📆首发时间:🌹2021年12月06日🌹
  • 🆕最新更新时间:🎄2021年12月06日🎄
  • ✉️坚持和努力一定能换来诗与远方!
  • 🙏作者水平很有限,如果发现错误,请留言轰炸哦!万分感谢感谢感谢!



 目录

🔥1.栈的逻辑结构

🍊1.1 线性表

🍊1.2 栈的数学性质

✅例题1·卡特兰数

🔥2.栈的物理结构

🍊2.1 栈的顺序存储结构

🍓顺序栈的优点

🍊2.2共享栈

🍓共享栈的优点

🍊2.3 栈的链式存储结构

🍓链栈的优点

🔥3.用单链表实现链栈的一系列操作

🍊3.1链栈的定义

🍊3.2初始化

🍓选择带哨兵位头结点的单链表

🍊3.3判空操作

🍊3.4销毁

🍊3.5进栈

🍊3.6出栈

🍊3.7读栈顶元素

🔥4.用动态数组实现顺序栈的一系列操作

🍊4.1顺序栈的定义

🍊4.2初始化

🍊4.3判空操作

🍊4.4销毁

🍊4.5进栈

🍊4.6出栈

🍊4.7读栈顶元素

🔥栈的应用1·括号匹配

 ✅思路

🖥算法演示

🍓代码实现

🔥栈的应用2·中缀表达式求值

🍊中缀表达式转后缀表达式方法

🍊后缀表达式求值的计算机实现

🍊中缀表达式转后缀表达式的机器计算

🌟中缀表达式求值

✅普通法——将上述两种方法按次序进行

✅深度结合法 

🍊中缀表达式转前缀表达式方法​

🍓前缀表达式的机器计算


🐉老样子,学习一种数据结构要按照怎么样的顺序学习呀?

L要从它的逻辑结构-->物理结构(存储结构)-->执行的操作来进行分析

✨✨✨我是分割线✨✨✨

🔥1.栈的逻辑结构


🍊1.1 线性表

栈(Stack)是只允许一端进行插入或删除操作的线性表


🍊1.2 栈的数学性质

 常常以选择题形式考察,出栈顺序是否合法


✅例题1·卡特兰数

列举检验:

✨✨✨我是分割线✨✨✨

🔥2.栈的物理结构


🍊2.1 栈的顺序存储结构

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素同时附设一个指针(top)指示当前栈顶元素的位置。

🍓顺序栈的优点

1.偶尔增容,偶尔增一下,而链栈每次都要申请结点

2.插入数据相比链栈更高效


🍊2.2共享栈

🍓共享栈的优点

1.节省存储空间

2.降低发生上溢的可能


🍊2.3 栈的链式存储结构

·采用链式存储的栈称为链栈

🍓链栈的优点

1.便于多个栈共享共享存储空间提高效率

2.不存在栈满上溢的情况

3.和顺序栈相比,它可以动态地分配存储空间,方便扩容

 ✨✨✨我是分割线✨✨✨

🔥3.用单链表实现链栈的一系列操作


🍊3.1链栈的定义

typedef int ElemType;

typedef struct LStackNode //定义一个结构体类型的结点

	ElemType x; //数据域
	struct LStackNode* next; //指针域
LS; //用LS代替LStackNode结点

🍊3.2初始化

🍓选择带哨兵位头结点的单链表

·原因:可见,用单链表实现入栈、出栈基本上都是对应于头插和头删为了方便起见,采用带哨兵位的头结点的单链表!

void LStackInit()

	LS* phead = (LS*)malloc(sizeof(LS));//申请一个哨兵位的头结点
	phead->next = NULL; //初始化时,哨兵位头结点指向NULL
	phead->data = 0;//哨兵位头结点的数据域为0

🍊3.3判空操作

int LStackEmpty(LS* phead)//判空

	assert(phead);
	return phead->next == NULL ? 1 : 0; 
	//如果phead指向空,则说明链栈为空

🍊3.4销毁

void LStackDestroy(LS* phead)//销毁

	assert(phead);
	LS* cur = phead->next;
	while (cur)
	
		LS* next = cur->next;
		free(cur);
		cur = next;
	
	phead->next = NULL;

🍊3.5进栈

void LStackPush(LS* phead ,ElemType x)

    assert(phead);
	LS* newnode= (LS*)malloc(sizeof(LS));//动态申请一个新结点
	newnode->data = x;//新结点的数据域放x
	newnode->next = phead->next;
	phead->next = newnode;

🍊3.6出栈

void LStackPop(LS* phead)

    assert(phead);
	LS* cur = phead->next;
	if (cur)//如果哨兵位头结点后面有结点
	
		LS* next = cur->next;
		free(cur);
		phead->next = next;
	
	return;

🍊3.7读栈顶元素

ElemType LStack(LS* phead)

	assert(phead);
	assert(!LStackEmpty(phead));//栈非空才可以向下进行
	return (phead->next)->data;

✨✨✨我是分割线✨✨✨

🔥4.用动态数组实现顺序栈的一系列操作



🍊4.1顺序栈的定义

typedef int STDataType;
typedef struct Stack //这是一个动态数组Stack

	STDataType* a;//指针a指向数组的首元素地址
	int top;//栈顶
	int capacity;//栈的容量
ST;//用ST来简便代替struct Stack 

🍊4.2初始化

void StackInit(ST* pst)

	assert(pst);//断言结构体不为空
	pst->a = (STDataType*)malloc(sizeof(STDataType) * 4);
    //动态申请存放4个元素的空间
	//好处是之后扩容可以直接扩容
	pst->top = 0;//指向栈顶元素的top指向0
	pst->capacity = 4;//栈的容量为4

🍊4.3判空操作


🍊4.4销毁

void StackDestroy(ST* pst)//销毁栈

	assert(pst);//结构体不为空才往下进行
	free(pst->a);//销毁栈
	pst->a = NULL;//将原本指向栈底的指针置空
	pst->capacity = pst->top = 0;//栈的容量和top的值都置0

🍊4.5进栈

void StackPush(ST* pst, STDataType x)

	assert(pst);
	if (pst->top == pst->capacity)//判断是否需要增容
	
		STDataType* tmp = (STDataType*)realloc(pst->a,sizeof(STDataType) * pst->capacity * 2);//用tmp指向新申请的一片空间
		if (tmp == NULL)
		
			printf("realloc fail\\n");//增容失败
			exit(-1);//结束整个程序
		
		pst->a = tmp;//栈底指针a指向新空间
		pst->capacity *= 2;//栈的容量扩增两倍
	
	pst->a[pst->top] = x;//在top指向的位置插入元素
	pst->top++;//top指向下一个可以插入的位置

🍊4.6出栈

void StackPop(ST* pst)//出栈操作

	assert(pst);
	assert(!StackEmpty(pst));//如果栈为空就不进行下去了
	pst->top--;//将指向栈顶的top减1

🍊4.7读栈顶元素

STDataType StackTop(ST* pst)

	assert(pst);
	assert(!StackEmpty(pst));//如果为空就不进行下去了
	return pst->a[pst->top - 1];//返回一个栈顶元素
    //因为top一直指向下一个元素插入的位置,所以当前栈顶元素的位置应当是top-1

✨✨✨我是分割线✨✨✨

🔥栈的应用1·括号匹配


20. 有效的括号https://leetcode-cn.com/problems/valid-parentheses/

 ✅思路

1.每出现一个右括号,就消耗一个左括号

2.遇到左括号就入栈

3.遇到右括号,就“消耗”一个左括号


🖥算法演示

✅正常模式:

❌错误模式:

由于C语言的库不够完整,因此下面所使用到的栈,调用了上面预先建立的顺序栈的程序


🍓代码实现

bool isValid(char * s)
    ST st;
    StackInit(&st);//记得要初始化它
    while(*s)
    
        //左括号入栈
        //右括号找最近的左括号匹配
        if(*s=='['||*s=='('||*s=='')
        
            StackPush(&st,*s);
            ++s;//继续判断下一个题目给的括号
        
        else
        
            if(StackEmpty(&st))//栈为空,说明没有前括号
            
                StackDestroy(&st);//记得要先销毁再return
                return false;
            
            char top=StackTop(&st);//读取栈顶元素
            //如果不匹配就return false
            if((top=='['&&*s!=']')
            ||(top=='('&&*s!=')')
            ||(top==''&&*s!=''))
            
                StackDestroy(&st);//记得要先销毁
                return false; 
            
            else
            
                StackPop(&st);//将栈顶元素出栈
                ++s;//继续判断下一个题目给的括号
            
        
    

    bool ret = StackEmpty(&st);//判断此时栈是否空
    //如果栈不空,说明有多余的左括号没有得到匹配
    StackDestroy(&st);//记得要销毁

    return ret;//最后栈空返回true,不空返回false

✨✨✨我是分割线✨✨✨

🔥栈的应用2·中缀表达式求值


前情须知:

上述中括号就是界限符,界限符同时也限制的运算的先后次序,计算机如何识别界限符是很难的,这时候,一个波兰数学家提出:可以不同界限符也能无歧义地表达 

🍊中缀表达式转后缀表达式方法

 


🍊后缀表达式求值的计算机实现


🍊中缀表达式转后缀表达式的机器计算


🌟中缀表达式求值

✅普通法——将上述两种方法按次序进行

Step1.将中缀表达式转换为后缀表达式

Step2.使用后缀表达式计算

✅深度结合法 

·使用两个栈来进行


 🍊中缀表达式转前缀表达式方法


🍓前缀表达式的机器计算

以上是关于第五话·数据结构必看之·栈 真的这么简单?的主要内容,如果未能解决你的问题,请参考以下文章

多线程必看之JAVA线程并发辅助类

第四话·数据结构必看之·单链表就这?

第五话-依赖倒转原则

Android程序猿必看之《终端应用开发指南》

零基础必看之数学建模索引

计算机网络必看之·你确定了解传输层吗?