1. 栈的定义
栈的定义:限定仅在表尾进行插入和删除操作的线性表。同时因为只能在表尾进行操作,所以栈又称为后进先出的线性表。见下图,浅灰色的元素后进入栈结构,出栈的时候却是第一个出去。
![5198997db13952fea34f07bc36da25da.png](https://img-blog.csdnimg.cn/img_convert/5198997db13952fea34f07bc36da25da.png)
我们针对栈结构元素的插入和删除,分别叫做“Push”压栈和“Pop”弹栈。
2. 顺序栈的插入和删除
既然栈是线性表,而线性表包括顺序表和链表,那么同样栈也包括顺序栈和链栈。顺序栈中我们定义一个top指针,其实这里只是个游标,对应着栈顶元素的下标,比如top是0,那栈顶元素下标就是0,表示只有一个0号元素,通常规定如果top等于-1,表示空栈。见下图所示,一个有5个元素空间的顺序栈结构,当top=1时,有两个元素,top=-1时,空栈,top=4时,满栈。
![8dd5b53f9e667a47d960437cd6326ab1.png](https://img-blog.csdnimg.cn/img_convert/8dd5b53f9e667a47d960437cd6326ab1.png)
那么进栈操作就很明显,只要移动我们的top游标即可,进一个top++,而删除的时候则top--
![5226473d8ee93bf650058b276de04610.png](https://img-blog.csdnimg.cn/img_convert/5226473d8ee93bf650058b276de04610.png)
3. 代码实现
那么我们就来实现下顺序栈的push和pop,以及清空栈clear
还是先定义一下数据元素,包含一个id和一个name
typedef struct DataElement
{
int id;
const char* name;
}DataElement;
接着定义顺序栈结构,包括数组元素、top指针(游标)、数组长度
typedef struct SeqStack
{
DataElement elements[MAX_SIZE]; //栈内元素数组
int top; //栈顶(数组中下标),如果为-1,表明栈是空的
int length; //当前栈的元素个数
}SeqStack;
然后我们来实现一下压栈push操作。注意:我们插入的元素下标是要将top加加后得到的值,因为top加加后才指向新的元素空间。
void PushSeqStack(SeqStack* seqStack, DataElement element)
{
if (seqStack->top == MAX_SIZE)
{
printf("满栈,压栈失败!n");
return;
}
seqStack->top++;
seqStack->elements[seqStack->top] = element;
seqStack->length++;
return;
}
接着实现弹栈pop操作,同理我们需要先将top值减减。
void PopSeqStack(SeqStack* seqStack)
{
if (seqStack->top == -1)
{
printf("空栈,弹栈失败!n");
return;
}
seqStack->top--;
seqStack->length--;
return;
}
然后实现一下初始化函数,初始化数组的长度和top指针后,直接压栈即可。
void InitSeqStack(SeqStack* seqStack, int length, DataElement* dataArray)
{
seqStack->length = 0;
seqStack->top = -1;
for (int i = 0; i < length; i++)
{
PushSeqStack(seqStack, dataArray[i]);
}
}
接着实现清空栈,直接将top赋值-1,数组长度归零即可。
void ClearSeqStack(SeqStack* seqStack)
{
seqStack->length = 0;
seqStack->top = -1;
}
然后是“是否为空”函数,也很简单,只需判断top是否为-1
int IsEmpty(SeqStack* seqStack)
{
if (seqStack->top == -1)
return 1;
return 0;
}
最后为了测试,实现一个打印顺序栈。这和遍历数组没多大区别。
void PrintSeqStack(SeqStack* seqStack)
{
if (seqStack->top == -1)
{
printf("空栈!n");
return;
}
for (int i = 0; i < seqStack->length; i++)
{
printf("%dt%sn", seqStack->elements[i].id, seqStack->elements[i].name);
}
}
4. 测试代码
接着我们测试一下,先初始化待插入的数据元素
DataElement dataArray[] =
{
{ 1, "娜美"},
{ 2, "罗宾"},
{ 3, "大和"},
{ 4, "汉库克"}
};
然后实现测试函数,这次我们动态分配顺序栈空间,然后将4个元素全部压入栈中,接着将表尾元素弹栈,最后清空栈。
void TestSeqStack()
{
SeqStack* seqStack = (SeqStack*)malloc(sizeof(SeqStack));
int length = sizeof(dataArray) / sizeof(dataArray[0]);
InitSeqStack(seqStack, length, dataArray);
printf("压栈后:n");
PrintSeqStack(seqStack);
PopSeqStack(seqStack);
printf("弹栈后:n");
PrintSeqStack(seqStack);
ClearSeqStack(seqStack);
printf("清空栈后:n");
PrintSeqStack(seqStack);
free(seqStack);
}
编译运行,可以看到,弹栈后4号元素“汉库克”(下标为3的元素)被删除了。清空后也是没问题。
![6b9cde2e03b00842b613bbe681f4e64e.png](https://img-blog.csdnimg.cn/img_convert/6b9cde2e03b00842b613bbe681f4e64e.png)
5. 总结
优点:顺序栈在插入和删除时候不需要移动元素,只需移动top指针。
缺点:顺序栈和顺序表一样,都需要预先定好数组空间,不像链表那样机动。