文章目录
- 前言
- 一、栈的特征
- 二、栈的实现
- 1、栈的设计
- 2、栈的初始化和销毁
- 3、元素的入栈和出栈
- 4、返回栈顶元素
- 三、栈的应用
前言
在学习完链表之后,接下来就要了解另外的两个常用的线性数据结构,栈和队列。
一、栈的特征
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。==栈中的数据元素遵守后进先出LIFO(Last In First Out)==的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
二、栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
1、栈的设计
我们可以直接使用一个定长数组来设计栈,然后每次插入和删除数据都在数组的末尾进行尾插和尾删。该设计和顺序表中的静态顺序表的设计一致,就是栈只允许在栈顶进行元素的删除和插入。
#define N 10
typedef int STDataType;
typedef struct Stack
{
STDataType a[N];
int top; //栈顶
}ST;
但是使用定长数组设计时,如果进入栈的数据太多还需要扩容,进入栈的数据太少时又浪费空间,所以还可以使用和动态顺序表一致的设计,即使用动态开辟的数组。
typedef int STDataType;
typedef struct Stack
{
STDataType* data;
int top; //栈顶
int capacity; //用来记录动态数组的大小,当栈满时再开辟空间。
}ST;
2、栈的初始化和销毁
栈的初始化就是将创建的ST结构体中用来存数据的动态数组的指针先置为NULL,避免其为野指针,将动态数组的大小capacity置为0。然后将top先置为0或-1。
当将top置为0时,当要入栈时,就先将数组中下标为top的元素中放入数据,然后top再++,此时下标为top-1的元素就为栈顶元素,top表示了栈中的元素。
当将top置为-1时,当有元素要入栈时,就需要先将top++,然后再将数组中下标为top的元素中放入数据。因为top为-1时指向的是栈底的下面,需要先将top++,然后才能在栈底放入第一个元素。此时top指向的就是栈顶元素,当要返回栈顶元素时直接将数组中下标为top的元素返回即可,但是要计算栈是否为空时,就需要判断top==-1。
下面实现栈的操作是按照top刚开始设为0而实现的。即真正的栈顶是top下面的那个元素,此时top指向的元素数据为空。所以当返回栈顶元素时,需要将下标为top-1的元素返回。top的值刚好就是栈中元素的值,所以判断栈空时就用top==0来判断。
栈的初始化。
void StackInit(ST* ps)
{
assert(ps);
ps->data = NULL;
ps->top = 0;
ps->capacity = 0;
}
栈的销毁就是将动态申请的数组的内存释放。
void StackDestory(ST* ps)
{
assert(ps);
//将动态数组申请的空间都释放。
free(ps->data);
ps->data = NULL;
ps->top = 0;
ps->capacity = 0;
}
3、元素的入栈和出栈
元素入栈就相当于在数组的尾部插入元素。但是元素在入栈前要判断一下此时栈是否满了,输入满了就需要再将栈扩容。
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//当栈的容量不够时
if (ps->top == ps->capacity)
{
//当动态数组的大小为0时,就申请4个空间;当动态数组大小不为0时,就说明栈满了,需要扩容。
int newCapacity = (ps->capacity == 0) ? 4 : (ps->capacity * 2);
//给动态数组申请空间,用来存放数据
STDataType* tmp = (STDataType*)realloc(ps->data, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->data = tmp;
ps->capacity = newCapacity;
}
//将数据存到数组的末尾,然后栈顶向后移。
ps->data[ps->top] = x;
ps->top++;
}
元素出栈就相当于将数组末尾的数据删除,但是在出栈前要判断此时栈是否为空,如果为空就没有元素可以出栈。
判断栈空的函数。
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top <= 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;
}
出栈的函数。
void StackPop(ST* ps)
{
assert(ps);
//出栈时要检查栈中有没有元素,有元素才可以出栈,没有元素就不可以出栈
assert(!StackEmpty(ps));
ps->top--;
}
4、返回栈顶元素
返回栈顶元素就是将此时栈顶的元素返回,因为此时top指向的是下一个要存数据的位置,此位置当前还没有数据,所以当返回栈顶元素时,实际返回的是数组下标为top-1的元素的值。
STDataType StackTop(ST* ps)
{
assert(ps);
//出栈时要检查栈中有没有元素,有元素才可以出栈,没有元素就不可以出栈
assert(!StackEmpty(ps));
return ps->data[(ps->top)-1];
}
三、栈的应用
经过上面的介绍我们发现栈的实现与顺序表的实现类似,差别就是栈只允许在末尾进行插入和删除。那么栈这个数据结构怎么应用呢?我们可以通过一个经典题来体会栈的特点。
题目
题目链接
题目分析
该问题就可以使用栈来解决,遍历字符串,当遇到左括号 ( [ { 时,就将这些左括号进行压栈,当遇到右括号 ) ] }时,就取出栈顶元素来与当前括号相匹配,如果两个括号类型一致就将该左括号出栈,并且访问字符串的下一个括号。如果两个括号匹配不一致,则返回false。当匹配到字符串末尾时,此时栈也为空,说明这些左括号和右括号都匹配,此时返回true。
代码
这个题中用到了我们上面写的一些栈的操作,需要将这些对应的函数都放到代码里面,这样提交时才不会报错。
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
//静态数组实现栈
//#define N 10
//typedef int STDataType;
//typedef struct Stack
//{
// STDataType a[N];
// int top;
//}ST;
//动态数组实现栈
typedef char STDataType;
typedef struct Stack
{
STDataType* data;
int top;
int capacity;
}ST;
//栈的初始化
void StackInit(ST* ps);
//检查栈空
bool StackEmpty(ST* ps);
//栈的销毁
void StackDestory(ST* ps);
//元素入栈
void StackPush(ST* ps, STDataType x);
//元素出栈
void StackPop(ST* ps);
//返回栈顶元素
STDataType StackTop(ST* ps);
void StackInit(ST* ps)
{
assert(ps);
ps->data = NULL;
ps->top = 0;
ps->capacity = 0;
}
bool StackEmpty(ST* ps)
{
assert(ps);
/*if (ps->top <= 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;
}
void StackDestory(ST* ps)
{
assert(ps);
//将动态数组申请的空间都释放。
free(ps->data);
ps->data = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//当栈的容量不够时
if (ps->top == ps->capacity)
{
//当动态数组的大小为0时,就申请4个空间;当动态数组大小不为0时,就说明栈满了,需要扩容。
int newCapacity = (ps->capacity == 0) ? 4 : (ps->capacity * 2);
//给动态数组申请空间,用来存放数据
STDataType* tmp = (STDataType*)realloc(ps->data, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->data = tmp;
ps->capacity = newCapacity;
}
//将数据存到数组的末尾,然后栈顶向后移。
ps->data[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
//出栈时要检查栈中有没有元素,有元素才可以出栈,没有元素就不可以出栈
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
//出栈时要检查栈中有没有元素,有元素才可以出栈,没有元素就不可以出栈
assert(!StackEmpty(ps));
return ps->data[(ps->top)-1];
}
bool isValid(char * s){
ST st;
//创建一个栈并且初始化。
StackInit(&st);
//遍历括号字符串
while(*s!='\0')
{
//当为左括号时,就进行压栈
if((*s=='(') || (*s=='[') || (*s=='{'))
{
StackPush(&st,*s);
s++;
}
else
{
//如果为右括号,并且此时栈已空,就说明括号不匹配,
if(StackEmpty(&st))
{
StackDestory(&st);
return false;
}
//取出栈顶元素
char top = StackTop(&st);
//并且将栈顶元素进行出栈
StackPop(&st);
//如果两个括号匹配就访问下一个字符
if((top=='(' && *s==')') || (top=='[' && *s==']') || (top=='{' && *s=='}'))
{
s++;
}
else
{
//如果两个括号不匹配就返回false
StackDestory(&st);
return false;
}
}
}
//如果字符串访问完后,此时栈中还有左括号,说明括号不匹配
if(!StackEmpty(&st))
{
StackDestory(&st);
return false;
}
StackDestory(&st);
//当字符串访问完,且此时栈为空,说明括号都匹配,
return true;
}