本片要分享的内容是栈和队列,这是一种不同于链表和顺序表的新的储存数据的方式,我们先了解一下栈的内容
目录
一、栈
1.1栈的图文理解
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
画图给大家观察一下
上面的图中有以下两种操作
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
举几个例子应该会更好理解
如下图,如果我们以1,2,3,4压栈的话那出栈的顺序是什么呢
那出栈的顺序就是4,3,2,1
当然在没有要求出栈顺序的情况下也可以随时操控数据出栈,比如说这里的压栈顺序是1,2,3,4但是出栈顺序也可以是1,2,3,4,可以将1刚压进去,就出来,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,因为3出栈后在栈里的只有1,2,所以只能先出2,再出1,显然C是不对的
以上是对栈的基础图文知识,接下来整一些进阶的活,还是利用栈的增删查改来进一步加深我们对栈的理解。
1.2栈的结构
首先还是要从初始化开始,其实栈一种有两种结构,一种是静态栈
typedef int STDataType;
#define N 10
typedef struct Stack
{
STDataType _a[N];
int _top; // 栈顶
}Stack;
这种静态结构因为容量大小是写死的,不方便我们增删查改的操作,所以我们实际情况中一般不使用这种结构,而是使用动态栈的结构
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//指向栈的开始位置
int top;
int capacity;//指向栈的结束位置
}ST;
再通过图来对比观察
可以看到a指向的是栈开始的位置, capacity指向的是栈结束的位置,那top的意义是什么呢?
其实top有两种意义,他既可以指向4的位置也可以指向4的后一个位置,这就取决于你怎么初始化top;
1.3初始化栈
这里要讨论的是top如何初始化来确定top的含义
//初始化
void STInit(ST* pst)
{
assert(pst);
pst->a = 0;
pst->top = 0;
pst->capacity = 0;
}
如果我们将这里的top赋值为0,那top指向的就是top的下一个位置
为什么会指向top的下一个元素呢?
我们可以思考一下,当我们需要给空栈中添加数据的时候,top指向0时就说明已经有一个数据了
(top在0的位置)
但是我们这里只是初始化,还没有添加任何元素,所以和top指向0时是矛盾的,所以在上面的例子中,top指向的是4的下一个位置。
当然如果我们想让top指向4所在的位置我们就可以让top赋值为-1,这样top指向的位置就在上面实例中4的位置
这样将top指向-1的位置,在上面的示例中就可以表示top指向了4的位置,这时栈初始化中必须要理解的初始化的方法。
1.4销毁栈
销毁栈是非常简单的操作,我们只需要将栈中的空间free掉,让其中的数据都像初始化同样即可
代码如下
//销毁
void Destory(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
应该不难看懂
1.5压栈(入栈)
和链表、顺序表不同的是栈不能在两个方向上插入和删除,通过上面的文字和图示知道栈只有一个出口和一个入口,所以压栈只能是在栈顶。
直接放代码
//添加
void STPush(ST* pst, STDataType x)
{
if (pst->top = pst->capacity)//判断栈顶是否已经达到了容量的最大值
{
//创建新的容量,如果容量为0,则开辟四个空间的容量;如果不为0,就开辟原先容量的2倍;
//注意这里只是对容量的计数
int newCapacity = pst->capacity = 0 ? 4 : pst->capacity * 2;
//真正的使用realloc开辟数组的空间
STDataType* tmp= (STDataType*)realloc(pst->a, newCapacity * sizeof(pst->capacity));
if (tmp = NULL)
{
return;
}
pst->a = tmp;//将新的容量代替掉旧的容量
pst->capacity = newCapacity;
}
//如果栈顶没有达到最大值就赋值给a当前的位置
pst -> a[pst->top] = x;
pst->top++;
}
首先函数得有两个参数,一个是插入的位置,一个是插入的数据;
其次需要判断自己的栈顶和容量是不是相等,如果栈顶和容量相等则需要扩容,扩容完成之后不要忘记将新的容量代替掉旧的容量。
如果栈顶没有达到最大容量就直接赋值给a的当前位置。
相信不难理解。
1.6出栈
出栈函数非常简单,就是删除栈中的数据,代码如下
void STPop(ST* pst)
{
assert(pst);
assert(!STEmpty(pst));
pst->top--;
}
代码和思路都非常非常简单,对top--即可,但是需要注意的是需要判空,当数组内容删减完了之后就不能再继续删减。
1.7获取栈顶元素
通过栈的结构我们可以知道我们是通过元素的下标来访问栈的元素的,所以我们可以通过函数来获取栈顶的元素,代码如下
STDataType STTop(ST* pst)
{
assert(pst);
assert(!STEmpty(pst));
return pst->a[pst->top - 1];
}
代码和思路同样简单,首先要注意的是函数类型,访问栈顶元素那一定是我们之前typedef好的类型;
其次是判空,如果是空就用assert暴力一点断言让他报错;
如果不为空就返回a再top-1的位置的数据(注意:因为top是栈顶元素的下一个位置,所以top-1才能访问)。
1.8栈判空
使用assert断言判断指针内容是否为空是非常有效的方法,能够一目了然的知道哪里出错
bool STEmpty(ST* pst)
{
assert(pst);
if (pst->top == 0)
{
return true;
}
else
{
return false;
}
}
这里就判断top指向的位置是否可以让a来访问下下标,
如果栈顶元素是0就返回true(true为1),表示访问有效下标
如果为其他值就返回false(false为0),访问的下标无效;
1.9栈判满
非常非常简单,既然top的意义是栈顶的下一个元素,我们直接操作top来完成计数即可
bool STSize(ST* pst)
{
assert(pst);
return pst->top;
}
1.10展示栈内容
这里需要注意的是栈不能像链表和顺序表一样写Printf这样的打印函数来展现栈的内容,而是类似于一边出栈一边展示的情况。
这里直接在主函数中测试
#include"Stack.h"
int main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
while (!STEmpty(&st))
{
printf("%d ", STTop(&st));
STPop(&st);
}
Destory(&st);
return 0;
}
首先用栈的结构创建一个变量,随后初始化,再使用压栈函数,使数据入栈。
然而需要访问栈内数据时我们需要通过刚刚的获取栈顶元素来访问栈定的数据,再使用出栈函数将其弹出,这样就完成了访问栈顶元素并将其弹出栈;
再通过while循环多次访问并弹出栈,就完成了访问栈内所有元素的操作。
以上就是本篇所要分享的关于栈的内容,如果对您有所帮助,还请三联支持一下,感谢您的阅读