几个定义先写在前面:
#define MAXSIZE 20
typedef int SElemType;/* SElemType类型根据情况而定,这里设为int */
typedef enum{ FALSE = 0, TRUE = 1 }Bool;
栈
,一种常见的数据结构。它有许多应用,比如网页左上角的“前进”和“返回”,(也就是撤销上一步操作)等等。
它长什么样呢?
它很像一个数组,但不同的是,它的数据只能从一个开口进出。
比如,我们有ABCD四个元素,我们先让他们依次入栈,即压栈(push),再让他们依次出栈,即弹栈(pop),那么最终得到的顺序将变为DCBA。
这又张图可供参考。
当然,我们也可以让它以ABCD的顺序近,并且以ABCD的顺序出。
怎么做?我们可以在把A压栈后,直接弹栈,再把B压栈,再直接弹栈……
也就是说压一个,弹一个,那么最终的出栈顺序自然是ABCD了。
这是一个用c语言写的
顺序栈
的结构体:
typedef struct Stack
{/* 顺序栈结构 */
SElemType data[MAXSIZE];
int top;
}SqStack;
这是关于顺序栈的一些基本操作:
Bool ClearStack(SqStack* s)
{ /* 把S置为空栈 */
s->top = -1;
return TRUE;
}
Bool StackEmpty(SqStack* s)
{ /* 若栈S为空栈,则返回TRUE,否则返回FALSE */
if (s->top == -1)
return TRUE;
else
return FALSE;
}
Bool GetTop(SqStack* s,SElemType* e)
{ /* 若栈不空,则用e返回S的栈顶元素,并返回TRUE;否则返回FALSE */
if (s->top == -1)
return FALSE;
else
*e = s->data[s->top];
return TRUE;
}
int StackLength(SqStack* s)
{ /* 返回S的元素个数,即栈的长度 */
return s->top + 1;
}
Bool visit(SElemType e)
{
printf("%d ", e);
return TRUE;
}
Bool StackTraverse(SqStack* s)
{ /* 从栈底到栈顶依次对栈中每个元素显示 */
int i;
i = 0;
while (i <= s->top)
{
visit(s->data[i++]);
}
printf("\n");
return TRUE;
}
当然最重要的还是压栈和弹栈操作了。
顺序栈压栈:
Bool Push(SqStack* s, SElemType e)
{ /* 插入元素e为新的栈顶元素 */
if (s->top == MAXSIZE - 1) /* 栈满 */
{
return FALSE;
}
s->top++; /* 栈顶指针增加一 */
s->data[s->top] = e; /* 将新插入元素赋值给栈顶空间 */
return TRUE;
}
顺序栈弹栈:
Bool Pop(SqStack* s, SElemType* e)
{/* 若栈不空,则删除S的栈顶元素,用e返回其值,并返回TRUE;否则返回FALSE */
if (s->top == -1)
return FALSE;
*e = s->data[s->top];/* 将要删除的栈顶元素赋值给e */
s->top--; /* 栈顶指针减一 */
return TRUE;
}
“共享栈”
但其实顺序栈有时候并不完美,因为如果其中元素过少,那就会有很大的空间浪费。毕竟数组空间是开死的。
那如何更好的利用空间呢?
我们可以这么做:把两个栈口对口“接”起来,形成一个只有一个栈顶,但有两个栈底的变型栈。而且它的栈顶在中间摆动,位置不定。
这样,当其中一个栈空着不怎么用时,第二个栈就变“大”了。
下面是关于共享栈的c语言结构体:
typedef struct DoubleStack
{/* 两栈共享空间结构 */
SElemType data[MAXSIZE];
int top1; /* 栈1栈顶指针 */
int top2; /* 栈2栈顶指针 */
}SqDoubleStack;
下面是关于共享栈的压栈弹栈操作:
共享栈压栈:
Bool PushDouble(SqDoubleStack* s, SElemType e, int stackNumber)
{/* 插入元素e为新的栈顶元素 */
if (s->top1 + 1 == s->top2)//栈已满
{
return FALSE;
}
if (stackNumber == 1)
s->data[++s->top1] = e;/* 先top1+1,再给数组元素赋值 */
else if (stackNumber == 1)
s->data[--s->top2] = e;/* 先top2-1,再给数组元素赋值 */
return TRUE;
}
共享栈弹栈:
Bool PopDouble(SqDoubleStack* s, SElemType* e, int stackNumber)
{
if (stackNumber == 1)
{
if (s->top1 == -1) /* 栈1已经是空栈 */
return FALSE;
*e = s->data[s->top1--];/* 将栈1的栈顶元素出栈 */
}
else if (stackNumber == 2)
{
if (s->top2 == MAXSIZE) /* 栈2已经是空栈 */
return FALSE;
*e = s->data[s->top2++];/* 将栈2的栈顶元素出栈 */
}
return TRUE;
}
链栈
但是说到底,顺序栈都有一个空间是死的,不够灵活的弊端。
怎么才能想有多少空间,就有多少空间呢?
当然是链式存储。将栈结构和链式存储结合起来,也就构成了链栈。
这样,我们在压栈时,就申请一块新的结点;弹栈时,就释放一块结点。每次空间开辟都用malloc去实现,空间大小也就灵活起来了。
值得注意的是,我们的栈顶,也就是top指针指向的位置,是链表头。
下面是关于链栈的c语言结构体:
typedef struct _StackNode
{/* 链栈节点 */
SElemType data;
struct _StackNode *next;
}StackNode,*LinkStackPtr;
typedef struct LinkStack
{/* 链栈结构 */
LinkStackPtr top;
int count;//节点数
}LinkStack;
下面是关于链栈的压栈弹栈操作:
链栈压栈:
Bool PushLink(LinkStack* s, SElemType e)
{/* 插入元素e为新的栈顶元素 */
LinkStackPtr ls = (LinkStackPtr)malloc(sizeof(StackNode));
ls->data = e;
ls->next = s->top;/* 把当前的栈顶元素赋值给新结点的直接后继 */
s->top = ls; /* 将新的结点s赋值给栈顶指针*/
s->count++;
return TRUE;
}
链栈弹栈:
Bool PopLink(LinkStack* s, SElemType* e)
{/* 若栈不空删除S的栈顶元素,用e返回其值,并返回TRUE;否则返回FALSE */
LinkStackPtr p;
if (LinkStackEmpty(s))//空栈
return FALSE;
*e = s->top->data;
p = s->top;/* 将栈顶结点赋值给p */
s->top = s->top->next;/* 使得栈顶指针下移一位,指向后一结点*/
free(p); /* 释放结点p */
s->count--;
return TRUE;
}
这儿有一个有趣的网站,可以实现链栈的可视化压栈弹栈操作。
(在网页上面菜单栏选择栈)
https://visualgo.net/zh/list
好了就到这里了,谢观。
(:з)∠)