【数据结构】--栈的实现和多种例题

【数据结构】--栈的实现和多种例题_第1张图片ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

目录

​一、栈的概念及结构

​二、栈的实现  

        1.初始化--StackInit

         2.插入--StackPush

         3.判断栈是否为空栈--StackEmpty

         4.删除栈元素--StackPop

        5.计算栈的长度--StackSize

        6.找栈顶--StackTop

        7.打印栈的数据(主函数中实现) 

        8.销毁栈--StackDestroy

​三、关于栈的典型编程题 


一、栈的概念及结构

栈:一种特殊的线性表,(线性表就是数据元素挨着放的,呈现出线性的结构)其只允许在固定的一端进行插入和删除元素操作。进行数据删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last in First Out)的原则。可以理解为一把枪打出子弹,肯定先打出的一定是后装的子弹,而新装的子弹一定是后打出的。

关于栈的两个操作:

①压栈:栈的插入操作叫作进栈/压栈/入栈,入数据在栈顶

②出栈:栈的删除操作叫作出栈。出数据也在栈顶。

                    —— 后进先出(Last In Frist Out)(也可以说先进后出)

【数据结构】--栈的实现和多种例题_第2张图片

二、栈的实现  

1.初始化--StackInit

 2.插入--StackPush

 3.判断栈是否为空栈--StackEmpty

 4.删除栈元素--StackPop

5.计算栈的长度--StackSize

6.找栈顶--StackTop

7.打印栈的数据(主函数中实现) 

8.销毁栈--StackDestroy

 里链表相对于数组没有太多优势,而数组的cpu命中率更高,数组在尾上插入数据的代价比较小。在讲双向链表的时候说过数组相对于链表的优点。所以用数组实现栈更胜一筹。还有一点就是,我们这里实现的是动态栈,因为静态栈没什么优势

【数据结构】--栈的实现和多种例题_第3张图片

首先应该了解栈应该用结构体创建,并且创建动态栈,所以这个操作跟以前通讯录的实现的操作极其相似,如果之前学会了通讯录,这里的栈的实现小菜一碟!a是用来维护栈的,通俗点讲就是动态开辟内存的头,这里的top其实就是通讯录里的size,通俗点讲就是有效元素的个数。只是对于栈结构来说你可以说是栈顶的位置,capacity当然就是这个栈最大容量了

//动态栈---更加推荐
typedef struct Stack
{
	STDataType* a;
	int top;//栈顶的位置
	int capacity;
}ST;
//静态栈
//typedef struct Stack
//{
//    STDataType _a[N];
//    int _top;
//}

1.初始化--StackInit

top初始化为0是什么意思,就意味着top最后是指向栈顶数据的下一个。(先放数据再+1)如果top初始化为-1,意味着top最后指向栈顶数据。(先+1再放数据)。这两种都可以。我们这里用top初始化为0实现

void StackInit(ST* ps)
{
	assert(ps);//传入的结构体指针不能为空
	ps->a = NULL;
	ps->top = 0;//也可以写成ps->top = -1;
	ps->capacity = 0;
}

 2.插入--StackPush

与通讯录插入的操作基本相同,值得注意的就是满了要增容的 

void StackPush(ST* ps, STDatatype x)
{
    assert(ps);
    //检查空间,满了就增容
    if (ps->top == ps->capacicy)
    {
        //第一次开辟空间容量为4,其它次容量为当前容量*2
        int newcapacity = ps->capacicy == 0 ? 4 : ps->capacicy * 2;
        //第一次开辟空间,a指向空,realloc的效果同malloc
        STDatatype* tmp = realloc(ps->a, sizeof(STDatatype) * newcapacity);
        //检查realloc
            //realloc失败
        if (tmp == NULL)
        {
            printf("realloc fail\n");
            exit(-1);
        }
            //realloc成功
        ps->a = tmp;
        ps->capacicy = newcapacity;
    }
    //插入数据 
    ps->a[ps->top] = x;
    ps->top++;
}

 3.判断栈是否为空栈--StackEmpty

运用bool,要引用头文件stdbool.h 

bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
	assert(ps);
	if (ps->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}*/
	return ps->top == 0;//这种写法很简单
}

 4.删除栈元素--StackPop

删除要注意栈为空就不能删除了

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//如果为空返回1 那么即!1为0 assert判断为假,直接报错
	// 所以你传入的这个栈不能为空栈
	//删除的数据个数不能top--;
}

 5.计算栈的长度--StackSize

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

6.找栈顶--StackTop

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//也可以写成assert(ps->top > 0);
	//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
	//因为这道题我初始化top为0,所以不用改
	//因为为空就不需要找这个栈顶了

	return ps->a[ps->top - 1];
	//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}

7.打印栈的数据(主函数中实现) 

打印栈的数据只要遍历栈就可以,知道栈为空,因为栈只能从栈顶插入和删除数据,那么我们就可以从栈顶开始打印,打印完一个栈顶,再把这个栈顶删除,使它前一个元素被看为一个栈顶

8.销毁栈--StackDestroy

 为什么需要销毁栈,因为栈中的每个元素都是动态开辟过的空间

void StackDestroy(ST* ps)
{
	assert(ps);
	if (ps->a)
	{//如果之前开辟空间成功过,则释放
    //如果之前都没有开辟过空间,即什么都没做或者开辟失败了,就不用释放了
		free(ps->a);
	}
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}


d3a9f8da5fae4e50a935197a900e7c3f.png 完整代码
这里需要三个文件

① Stack.h,用于函数的声明和类型定义

② Stack.c,用于函数的实现

③ test.c,用于测试函数

ლ(′◉❥◉`ლ)--- Stack.h

#pragma once
#include
#include
#include
#include

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;//栈顶的位置
	int capacity;
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);//找栈顶的函数
int StackSize(ST* ps);
bool StackEmpty(ST* ps);

ლ(′◉❥◉`ლ)--- Stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void StackInit(ST* ps)
{
	assert(ps);//传入的结构体指针不能为空
	ps->a = NULL;
	ps->top = 0;//也可以写成ps->top = -1;
	ps->capacity = 0;
}

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	//满了是要扩容的
	if (ps->capacity == ps->top)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);//程序异常退出
		}
		ps->capacity = newCapacity;
		ps->a = tmp;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//如果为空返回1 那么即!1为0 assert判断为假,直接报错
	// 所以你传入的这个栈不能为空栈
	//删除的数据个数不能top--;
}
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//也可以写成assert(ps->top > 0);
	//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
	//因为这道题我初始化top为0,所以不用改
	//因为为空就不需要找这个栈顶了

	return ps->a[ps->top - 1];
	//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}
void StackDestroy(ST* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
	}
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
	assert(ps);
	if (ps->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}*/
	return ps->top == 0;//这种写法很简单
}
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

ლ(′◉❥◉`ლ)--- test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

void TestStack()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	while (!StackEmpty(&st))//栈不为空
	{
		printf("%d ", StackTop(&st));//打印原栈顶
		StackPop(&st);//删除原栈顶,使它前一个元素成为新的栈顶
	}
	StackDestroy(&st);

}
int main()
{
	TestStack();
	return 0;
}

三、关于栈的典型编程题 

1、有效的括号

※ 题述:给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

① 左括号必须用相同类型的右括号闭合。

② 左括号必须以正确的顺序闭合。

示例1:
输入:s = “()”
输出:true

示例2:
输入:s = “()[]{}”
输出:true

示例3:
输入:s = “(]”
输出:false

示例4:
输入:s = “([)]”
输出:false

示例5:
输入:s = “{[]}”
输出:true

思路:不能单纯的数左括号和右括号的个数,因为有顺序,即呈现对称结构的才行。我们利用栈的先进后出的思想就可以解决这道题。我们规定1、左括号,入栈 2、右括号,跟左括号匹配。(即右括号不入栈,而是与栈中最近的栈顶匹配,在栈中的肯定都是左括号),这样就保证了所有的左括号均在栈中,想取出他只需要取栈顶,而右括号直接与栈顶匹配即可,并且注意题目中要求的括号类型只有三种即'()'  '{}'  '[]' ,那么就可以罗列出来。

1、首先要遍历传入的字符串(包含括号的字符串),要遍历到尾,判断是左括号还是右括号,左括号则入栈,右括号则与栈顶的元素即左括号匹配,匹配完后再删除这个左括号,那它前一个元素就变成了栈顶

2、右括号如何匹配的左括号,通过找栈顶top,就是把最新放进去的左括号拿出来(即栈顶),然后再跟字符串的括号比较(即与*s比较),如果不相等就直接返回假,但是在这之前一定要先销毁这个栈,因为是之前动态内存开辟的,否则s++判断下一个位置的括号,直到判断到字符串结束都匹配的话,那么这个栈应该为空,因为栈中的左括号都与右括号匹配成功,左括号应已全部出栈

3、

针对传入的字符串

问题一只有左括号呢(这种包括没有右括号的情况和右括号与左括号匹配了一部分,可最后只剩下左括号的情况)?那就没有右括号与之匹配了,所以最后要判断这个栈是否为空,如果为空就是匹配成功了,否则就只剩左括号了,左括号没有匹配的了,所以返回假就可以了。

问题二只有右括号呢?也就是意思在判断右括号之前如果就没有左括号,一定就不匹配,因为题中要求的,一定要先从左括号开始。所以在else中先判断是否为空,因为左括号用来入栈,栈中都没数据,直接返回假即可。

——————————————————————————

代码如下:注意这道题是在之前写的栈的实现的代码的基础上实现inValid函数(判断是否为有效的括号)的,如果用c++实现,就可以直接调用库,在调用库的基础上写这道题,注意用c写的时候,记得把char typedef 为 STDataType,因为这里括号都是char类型的了

#include
#include
#include
#include

typedef char STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;//栈顶的位置
	int capacity;
}ST;

void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);//找栈顶的函数
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void StackInit(ST* ps)
{
	assert(ps);//传入的结构体指针不能为空
	ps->a = NULL;
	ps->top = 0;//也可以写成ps->top = -1;
	ps->capacity = 0;
}

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	//满了是要扩容的
	if (ps->capacity == ps->top)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);//程序异常退出
		}
		ps->capacity = newCapacity;
		ps->a = tmp;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//如果为空返回1 那么即!1为0 assert判断为假,直接报错
	// 所以你传入的这个栈不能为空栈
	//删除的数据个数不能top--;
}
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	//也可以写成assert(ps->top > 0);
	//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
	//因为这道题我初始化top为0,所以不用改
	//因为为空就不需要找这个栈顶了

	return ps->a[ps->top - 1];
	//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}
void StackDestroy(ST* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
	}
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
	assert(ps);
	if (ps->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}*/
	return ps->top == 0;//这种写法很简单
}
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}
bool inValid(char* s)
{
	ST st;
	StackInit(&st);
	while (*s)
	{//循环遍历字符串,直到遍历为空
		if (*s == '('
			|| *s == '{'
			|| *s == '[')
		{
			//左括号则入栈
			StackPush(&st, *s);
			++s;
		}
		else
		{		//进了else就说明是右括号
			//遇到右括号了,但是栈里面没数据,说明前面没有左括号,不匹配,返回false
			//遇到右括号也可能前面有左括号,这样就右括号与出栈后的左括号匹配即可
			if (StackEmpty(&st))
			{
				StackDestroy(&s);
				return false;
			}
	
			//因为栈中有数据,则右括号与出栈后的左括号匹配
			STDataType top = StackTop(&st);//取栈顶,即左括号
			StackPop(&st);//删除栈顶,便于下一次取新的栈顶
			if ((*s == '}' && top != '{')
				|| (*s == ']' && top != '[')
				|| (*s == ')' && top != '('))
			{//这三种情况的任意一个成立就说明括号不匹配
				StackDestroy(&st);
				return false;
				//在终止函数之前销毁动态开辟的栈,防止内存泄漏
			}
			else
			{	//进了else就说明匹配上了,就继续++看后面的括号是否匹配
				s++;
			}
		}
	}
	//最后是否匹配成功,还要判断栈是不是空//这种情况是应对最后只有左括号的情况
	//如果栈不是空,则栈中还有左括号未出,没有匹配完,返回false
	//栈为空就说明匹配成功
	bool ret = StackEmpty(&st);
	StackDestroy(&st);
	return ret;
}

 2、栈的概念题

1、一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是 ( )

A. 12345ABCDE

B.  EDCBA54321

C.  ABCDE12345

D.  54321EDCBA

解析:B、根据栈的特点 —— 后进先出,先进后出
2、若进栈的序列为1,2,3,4,进栈过程中可以出栈,则下列不可能的一个出栈序列是 ( )

A.  1,4,3,2

B.  2,3,4,1

C.  3,1,4,2

D.  3,4,2,1

 解析C,本题需要注意的是进栈过程中可以出栈,这也导致了多种情况的出现:

进栈顺序:1 2 3 4

出栈顺序:1 2 3 4
     4 3 2 1
     … …

你可能感兴趣的:(【数据结构】知识篇+代码讲解,数据结构,开发语言,c语言,c#)