在本次的博客当中我们来向大家介绍两个看似很新没有听过,实际上我们之前已经实现过了的数据结构——栈和队列。
🌵栈
实质上栈就是一个具有特殊要求的线性表。栈在定义上要求我们只能从一端插入和一段删除数据。举一个简单的例子:我们一次向栈中输入1,2,3,4四个数据,我们从一端输入完毕之后只能从我们输入的最后一个数据开始向外取数据。也就是说我们从栈中取数据的顺序为4,3,2,1。
栈这一结构特点在我们看来很没用,但是实质上可以帮助我们的计算机实现一些很神奇的功能,比如说括号匹配,检测等式的运算顺序等。这些特点我们在以后的计算机组成原理当中会进行详细的讲解。
我们上面说到过栈实质上就是一个经过特殊规定的线性表,所以在代码的编写方面和我们的线性表是完全相同的。我们的线性表分为两种,那么我们应该选择什么样的结构作为我们栈的存储结构呢?我们一次来分析一下:假如我们使用我们链表进行修改。我们想要在我们的链表当中插入一个数据,单链表就必须从头开始遍历知道找到链表的最后一个元素之后才可以进行出栈的删除操作。如果是双向循环链表的话,我们确实不需要遍历我们的链表了,但是我们还需要考虑到计算机的缓存的特点。
我们的计算机存在一个缓冲区。每一次读取数据都会从硬盘上面读取一连串的相邻的数据。假如我们使用链表的话,(链表是随机开辟节点,不要求节点地址连续)就会使得我们数据命中的效率下降。从这一点角度来看,我们的顺序表来改编栈就显得更胜一筹了。那么下面我们下面就来向大家介绍一下关于栈结构我们主要需要实现的函数功能:
我们需要实现的主要是以上的七个函数。 和我们使用顺序表存储数据的逻辑相同我们第一步需要创建一个结构体,进行管理我们的栈:
之后我们可以使用malloc函数向堆区开辟一段连续的空间用于存储我们入栈的数据,也就有了我们的初始化栈的函数:
当我们的栈初始化完毕之后我们就可以编写数据入栈的函数了。我们在结构体当中创建的top就是反应我们数组当中存储数据的个数,相对应的也就是我们所需要栈中插入元素的下标。所以我们想要插入数据只需要向我们的数组当中下标为top的位置进行赋值即可。
在将我们的数据插入栈中的之后我们就会让我们的 top++ 。需要提醒的是我们在向栈中插入数据的之前我们需要对栈继续检查判断我们栈是否已满,如果数据已满就不能继续插入数据,必须先进行扩容。扩容部分的代码如下:
我们可以通过调试界面进行观察我们数据的存入是否成功:
我们会发现一切正常。之后我们就可以完成下一个步骤返回栈顶元素。对于这一部分其实非常的简单我们只需要对我们的参数进行断言保证我们的参数不为NULL 之后打印我们栈当中下标为top-1的数据即可。因为当我们栈当中数据为0的时候top 0实际上对应的是我们栈中的第一个数据。所以我们可以使用top-1进行更正我们指定数据的打印。(我们也可以在刚开始将top定义成-1,这样我们的第一个数据的下标对应的就是0,但是会在其他方面出现一些问题)这一部分的代码如下:
我们可以进行测试:
接下来我们再来完成出栈的函数,再进行过栈判空操作之后我们只需要再进行 top-- 操作即可。那么我们通过下标找到的数据就是前一个,我们重新向栈中插入数据的时候可以将我们栈中删除的数据覆盖等价于删除操作。代码如下:
唯一需要我们考虑的是我们需要对栈进行判空操作,如果栈中数据为空就不能进行删除,否则会发生越界的问题。测试效果如下:
下一需要我们实现的函数为判断栈是否为空栈。我们只需要判断top是否等于0即可:
测试效果如下:
返回栈中元素个数的函数同样很简单,我们只需要返回top的值即可:
测试效果如下:
最后就来到我们销毁栈的操作了。我们需要将结构体开辟的数组释放,并将我们的top和capicity置为0即可。代码如下:
那么此上对于栈的构建也就全部结束了,在这一部分我们只是粗略的介绍了一下栈的构建思路,因为实质上的代码操作和顺序表相同,不熟悉的同学可以参考顺序表的构建方法,里面有详细有详细的讲解。接下来我们通过一道和栈的使用有关的练习题来进一步了解栈的使用方法:
我们可以构建一个栈,如果我们需要输入的数据为左括号的话就执行入栈操作,如果为右括号的话就弹出栈顶元素。如果最近的相邻的两个符号相匹配那么就继续执行检测,否则就证明该字符串非法。对于栈的构建我们可以复用我们之前所构建好的栈。根据上面的逻辑我们能够编写如下的代码:
//构建一个栈结构进行数据的存储
//如果入栈数据为 } ] )就弹出栈顶元素
//需要指定的元素进行匹配,否则就是无效字符串
typedef char DataType;
typedef struct STstack
{
DataType* data;
int top;
int capicity;
}ST;
//栈的初始化
void STInit(ST* s1)
{
assert(s1);
s1->data = (DataType*)malloc(sizeof(DataType) * 4);
s1->capicity = 4;
s1->top = 0;
}
//判断是否需要扩容
void checkcapicity(ST* s1)
{
if (s1->capicity > s1->top)
{
return;
}
else
{
//需要扩容进行扩容
DataType* tmp = (DataType*)realloc(s1->data, sizeof(DataType) * s1->capicity * 2);
if (tmp == NULL)
{
perror("malloc");
return;
}
s1->data = tmp;
s1->capicity *= 2;
}
}
//入栈函数
void STPush(ST* s1, DataType x)
{
assert(s1);
//判断是否需要扩容
checkcapicity(s1);
s1->data[s1->top] = x;
s1->top++;
}
//出栈函数
void STPop(ST* s1)
{
assert(s1);
//判断栈中是否存在数据,如果不存在数据就不可以进行删除
if (s1->top == 0)
{
printf("空栈不允许进行删除。");
return;
}
//如果不是空栈就删除栈顶元素
s1->top--;
}
//返回链表的大小
int STSize(ST* s1)
{
assert(s1);
return s1->top;
}
//判断栈为空
bool STEmpty(ST* s1)
{
assert(s1);
if (s1->top == 0)
{
return true;
}
else
{
return false;
}
}
//返回栈顶元素
DataType STTop(ST* s1)
{
assert(s1);
//判断栈中是否存在数据
assert(s1->top);
return s1->data[s1->top - 1];
}
//销毁栈
void STDestory(ST* s1)
{
assert(s1);
free(s1->data);
s1->data == NULL;
s1->top = 0;
s1->capicity = 0;
}
ST s1;
bool isValid(char* s)
{
STInit(&s1);
//将{ ( [ 压入栈中
while (*s != '\0')
{
//判断第一个输入的数据合法
if ((*s == '}' || *s == ')' || *s == ']')&&STEmpty(&s1))
{
return false;
}
if (*s == '{' || *s == '(' || *s == '[')
{
STPush(&s1, *s);
}
else
{
switch (*s)
{
case '}':
if (STTop(&s1) != '{')
return false;
break;
case ']':
if (STTop(&s1) != '[')
return false;
break;
case ')':
if (STTop(&s1) != '(')
return false;
break;
}
STPop(&s1);
}
s++;
}
//判断栈是否为空栈,如果为空栈就证明字符串合法
if (STEmpty(&s1))
{
return true;
}
else
{
return false;
}
}
测试的运行效果如下:
那么以上就是关于栈的构建的全部内容了,在下次博客当中我们会向大家介绍队列这个新的数据结构。那么感谢您的观看,再见。