当前位置:   article > 正文

数据结构之栈以及栈的基本操作_压栈是什么意思

压栈是什么意思

前言

栈是限定仅仅在表尾进行插入和删除操作的线性表。

在软件中,栈的这种后进先出数据结构的应用是非常普遍的。比如我们在使用浏览器时,浏览器都有一个“后退键”,你点击后可以按照访问顺序的逆序加载浏览过的网页。很多类似的软件,比如Word、Photoshop等文档或图像编辑软件中,都有撤销的操作,也是用栈这种方式来实现的。

我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何元素的栈称为空栈,栈又被称为后进先出(Last In First Out)的线性表,简称LIFO结构。

如下图:

网站的返回键:

image-20210805190707729

word里面的撤销键:

image-20210805205402128

还有很多涉及到栈的软件,这里就不一一说了,下面我们来看压栈和出栈

压栈栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈栈的删除操作叫做出栈。出数据也在栈顶。

image-20210805205231486

image-20210805205113200

动图演示进栈:

进栈

出栈12

进栈出栈的变化形式

有个问题问问大家?最先进栈的元素,是不是就只能是最后出栈呢?

答案是不一定,栈对线性表的插入和删除的位置进行了限制,并没有对插入和删除的时间进行限制,所以,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。

我们举个例子:

现在有3个数字元素1、2、3一次进栈,会有那些的出栈次序呢?

第一种:1、2、3进,再3、2、1出,出栈次序为321

第二种:1进,1出,2进,2出,3进,3出,出栈次序为123

第三种:1进,2进,2出,1出,3进,3出,出栈次序为213

第四种:1进,2进,2出,3进,3出,1出,出战次序为231

第五种:1进,1出,2进,3进,3出,2出,出栈次序为132

有没有可能是312这种出栈顺序呢?答案是不可能的,要让3先出栈,那么123必须全部进栈,那么出栈顺序必须是321。

栈的实现

我们想一下栈的实现我们是使用顺序表实现呢还是链表实现呢?其实相对而言顺序表结构的实现会更优一些,因为数组在尾上插入和删除数据的代价比较小。

栈的顺序存储结构:

image-20210805214411342

栈的链式存储结构:

进栈示意图:

image-20210805214340496

出栈示意图:

image-20210805214354152

注意:

栈的链式结构不能让尾做栈顶,因为栈的操作都在栈顶,在栈顶进行进栈和出栈时,如果让尾做栈顶,那就相当于链表的尾插尾删,而链表的尾插尾删是需要遍历找尾和尾的前一个元素的,而栈这种结构我们是无法进行遍历的,所以我们需要让链表的头做栈顶。

因为栈的顺序表结构的实现会更优一些,因为数组在尾上插入和删除数据的代价比较小,所以我们实现顺序表结构的栈


文件的创建

我们首先创建三个文件:

image-20210806155517496

Stack.h对相关头文件的包含,以及实现栈的结构和函数的声明

Stack.c对实现栈的操作的函数进行定义

test.c文件进行栈相关函数和栈功能的测试


栈结构的定义

话不多说,我们开始实现栈,首先实现栈的进栈和出栈操作,我们需要先定义栈这样的一个结构体:

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//指向动态开辟的内存的指针
	int top;
	int capacity;
}Stack;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们实现顺序表结构的栈,和博主前面讲的顺序表的实现一样,我们使用动态顺序表,这样大小灵活可变,我们这里定义一个指向动态开辟的内存的指针a,我们定义一个top来指示栈顶元素的位置,为了满足动态顺序表我们还需定义一个表示容量的变量。

下面我们来完成栈的这些操作:

// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

栈的初始化

// 初始化栈
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

image-20210806152958982

我们经过调试发现已经将栈已经初始化


入栈

// 入栈
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	//检测容量
	if (ps->capacity == ps->top)
	{
		//增容
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* temp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (temp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = temp;
		ps->capacity = newcapacity;
	}
	//操作入栈
	ps->a[ps->top] = data;
	ps->top++;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在入栈元素前,我们需要先考虑增容,仔细想想,当前top值等于我们的当前容量时我们要增容,我们在增容时,如果capacity是0时我们先开辟存储4个数据的内存,不是0时,我们就进行二倍的增容,如果开辟失败了,我们加一个判断pal->a是不是NULL,是NULL则打印错误返回,最后将capacity更新,入栈的话就简单了,因为top就是我们栈里面最后一个元素的下一个元素的下标,所以我们直接赋值即可,最后将top++。

下面我们调试一下入栈操作的代码:

image-20210806153441747

image-20210806153541126

image-20210806153609312

image-20210806153629459

发现没有问题,下面我们继续看接下来的操作:


出栈

// 出栈
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈不能为空
	ps->top--;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

首先出栈,我们得栈不能为空,所以我们这里断言一下,这个判空得函数会在下面会写。出栈我们直接top–就可以了

我们进行调试代码:

image-20210806161955981
image-20210806162020541

发现此时top已经改变。


获取栈顶元素

// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们的top是栈中最后一个元素的下一个位置的下标,所以top-1就是最后一个元素的下标,我们将它返回即可


获取栈中有效元素个数

// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

top就是我们的元素个数。返回即可


检测栈是否为空

// 检测栈是否为空,如果为空返回true结果,如果不为空返回false
bool StackEmpty(Stack* ps)
{
	return ps->top == 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5

判断栈是否为空,当ps->top==0时,我们的栈是空的,这句话是真,则返回真,ps->top!=0时,这句话为假,则返回假。

销毁栈

// 销毁栈
void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
	}
	ps->capacity = 0;
	ps->top = 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

销毁栈,如果指向存储栈中元素空间的指针不为NULL的话,我们free掉这个指针,并将他置空,防止出现野指针,最好将top以及capacity都清0。

栈的元素的打印我们是没办法进行的,因为栈只能在栈顶操作,我们不能遍历它,我们可以这样打印,但是打印出来的元素只能是逆序的。

while (!StackEmpty(&s))
{
	printf("%d ", StackTop(&s));
	StackPop(&s);
}
  • 1
  • 2
  • 3
  • 4
  • 5

image-20210806161652921

我们每次打印栈顶元素,打印为将它pop,可以打印出逆序的,但是此时栈已经空了

我们接下来来看一个栈的面试题:

括号匹配问题

点击括号匹配问题跳转到做题网站

问题描述:

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

有效字符串需满足:

1、左括号必须用相同类型的右括号闭合。
2、左括号必须以正确的顺序闭合。

示例1:

输入:s = "()"输出:true
  • 1

示例 2:

输入:s = "()[]{}"输出:true
  • 1

示例 3:

输入:s = "(]"输出:false
  • 1

示例 4:

输入:s = "([)]"
输出:false
  • 1
  • 2

示例 5:

输入:s = "{[]}"
输出:true
  • 1
  • 2

思路:

该题需要用到栈的功能,遍历字符串,遇到左括号就入栈,直到遇到右括号,然后取栈顶元素跟它比较是否匹配,不匹配返回false,匹配则继续

image-20210806175803253

我们把我们上面实现的栈的操作函数拷贝进去:

typedef char STDataType;
typedef struct StackNode
{
	STDataType *a;//存储动态开辟内存的指针
	int top;
	int capacity;
}Stack;

// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);

// 初始化栈
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	//检测容量
	if (ps->capacity == ps->top)
	{
		//增容
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* temp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (temp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = temp;
		ps->capacity = newcapacity;
	}
	//操作入栈
	ps->a[ps->top] = data;
	ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈不能为空
	ps->top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	return ps->a[ps->top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{
	return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
	assert(ps);
	if (ps->a)
	{
		free(ps->a);
		ps->a = NULL;
	}
	ps->capacity = 0;
	ps->top = 0;
}


bool isValid(char * s)
{
    Stack st;
    StackInit(&st);
    while(*s)
    {
        if(*s=='('||*s=='['||*s=='{')
        {
            StackPush(&st,*s);
            s++;
        }
        else
        {
            char ch=StackTop(&st);
            StackPop(&st);
            if((*s==')' && ch!='(')
            ||(*s==']' && ch!='[')
            ||(*s=='}' && ch!='{'))
            {
                //不匹配
                return false;
            }
            else
            {
                //匹配
                s++;
            }
        }
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121

我们按逻辑写下来然后提交发现有错误,这里我们就要看它的错误案例:

image-20210806172558756

它说只输入"["时输出的是true,但是正确答案是false,于是我们对照代码走一遍,发现我们将[入栈之后循环就结束了,返回了true,所以我们返回时不能这么写,我们应该写return StackEmpty(&st);上面循环走完,栈为空返回true,栈不为空返回false。

我们提交后还有一个错误:

image-20210806173113210

我们对照代码走一遍,发现我们栈里面还没有元素时,就取栈顶元素了,于是我们在前面加个判断:

if(StackEmpty(&st))
{
	return false;
}
  • 1
  • 2
  • 3
  • 4

当没有进栈元素时,直接返回false

我们改正后,发现可以通过了:

image-20210806173427847

此时别高兴太早,我们的代码还有问题,有什么问题呢?

是不是我们动态开辟的内存还没有释放啊,对,此时造成了内存泄漏。

所以我们最后应该这样改:

bool isValid(char * s)
{
    Stack st;
    StackInit(&st);
    bool match=true;
    while(*s)
    {
        if(*s=='('||*s=='['||*s=='{')
        {
            StackPush(&st,*s);
            s++;
        }
        else
        {
            if(StackEmpty(&st))
            {
                match=false;
                break;
            }
            char ch=StackTop(&st);
            StackPop(&st);
            if((*s==')' && ch!='(')
            ||(*s==']' && ch!='[')
            ||(*s=='}' && ch!='{'))
            {
                //不匹配
                match=false;
                break;
            }
            else
            {
                //匹配
                s++;
            }
        }
    }
    if(match==true)
    {
        match = StackEmpty(&st);
    }
    StackDestroy(&st);
    return match;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

我们设定了match来保存真假,初始为真,进去循环那里比较时如果栈为空将match改为false,并且在不匹配那里也要将match改为false,循环出来,判断match是不是为真,为真的话将判断栈是不是空赋给它,是空则match为true,不是空则match为false,最后统一销毁栈,最后返回match。

关于栈就讲解到这里,觉得可以的话点个赞再走吧,欢迎大家学习交流

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号