栈的基本概念
栈的定义
栈(Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作,即先进后出。
栈顶(Top):线性表允许进行插入删除的那一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。
栈的常见基本操作
InitStack:初始化一个空栈S。
StackEmpty:判断一个栈是否为空,若栈为空则返回true,否则返回false。
Push:进栈(栈的插入操作),若栈S未满,则加入新元素使之成为新栈顶。
Pop:出栈(栈的删除操作),若栈S非空,则弹出栈顶元素。
GetTop:读栈顶元素,若栈S非空,则返回栈顶元素。
DestroyStack:栈销毁,并释放S占用的存储空间。
栈的顺序存储
设计思想
采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
若存储栈的长度为N,则栈顶位置top必须小于N。当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定位top等于-1。
栈的顺序存储结构可以描述为:
#define N 1024 //栈的最大的容量
//顺序栈的实现通过数组实现
typedef int sqstack_data_t;
typedef struct sqstack {
int top; //栈顶位置
sqstack_data_t data[N];
}ssk_node, * ssk_pnode;
顺序栈的基本操作有
//创建
ssk_pnode create_sqstack();
//出栈
sqstack_data_t pop_sqstack(ssk_pnode S);
//入栈
int push_sqstack(ssk_pnode S, sqstack_data_t data);
//判空
int empty_sqstack(ssk_pnode S);
//判满
int full_sqstack(ssk_pnode S);
//清空
int clean_sqstack(ssk_pnode S);
//销毁
int destroy_sqstack(ssk_pnode *S);
顺序栈的基本算法实现
初始化:将栈开辟在堆区
//创建
ssk_pnode create_sqstack()
{
ssk_pnode S = (ssk_pnode)malloc(sizeof(ssk_node));
if (NULL == S)
{
printf("malloc is default\n");
return NULL;
}
//printf("!!!\n");
S->top = -1; //top代表栈顶位置,栈顶就是top,而不是top-1
return S;
}
这里需要注意的是,若初始化时top等于-1,则入栈操作后,top指向栈顶元素位置,若初始化时top=0,则入栈操作后,top指向栈顶元素的下一个位置。
判空
//判空
int empty_sqstack(ssk_pnode S)
{
if (-1 == S->top)
return 0;
else
return -1;
}
判满
//判满
int full_sqstack(ssk_pnode S)
{
if (N - 1 == S->top)
return 0;
else
return -1;
}
注意,因为初始化时top我们置为了-1,则top=N-1时栈就已经存满了
入栈
//入栈
int push_sqstack(ssk_pnode S, sqstack_data_t data)
{
if (0 == full_sqstack(S))
{
printf("S is full\n");
return -1;
}
/*
S->top++;
S->data[S->top] = data;
*/
S->data[++S->top] = data;
return 0;
}
入栈的基本要点有两个:
一是在入栈时需要进行判满,若已经满栈,则不能在进行入栈 。
二是 对于top的操作,因为初始化时top置为-1,故每次入栈之前需要先对top进行自增1。
出栈
//出栈
sqstack_data_t pop_sqstack(ssk_pnode S)
{
if (0 == empty_sqstack(S))
{
printf("S is empty\n");
return -1;
}
return S->data[S->top--];
}
出栈之前注意判空,若是空栈,则没有必要进行出栈操作
清空
//清空
int clean_sqstack(ssk_pnode S)
{
if (0 == empty_sqstack(S))
{
printf("S is empty\n");
return -1;
}
S->top = -1;
return 0;
}
顺序栈的清空直接让top恢复初始的-1即可,应为顺序栈是利用数组实现的,对数组的清空就是直接让其指针指向首地址即可。
销毁
//销毁
int destroy_sqstack(ssk_pnode* S)
{
if (*S == NULL)
return -1;
free(*S);
*S == NULL;
}
销毁时需要注意的是,函数传入的是地址,销毁的目的是要释放掉主调函数中的堆区,若以值得方式传入,则并不能释放掉驻点函数的堆区空间。换言之,对于需要改变指针变量的指向的函数均需要以地址的方式传入。
栈的链式存储
链栈的定义
采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现。通常规定所有操作都是在单链表的表头进行的。
对于链栈的设计有两种模式:
1、表头做栈顶:操作时比较方便,入栈是头插,出栈是头删
2、表尾做栈顶:操作比较麻烦,因为不管是入栈出栈操作均需要去 遍历到链表的尾结点,操作比较繁琐,因此不推荐这种方式
链栈的基本结构和常用操作如下:
typedef int linkstack_data_t;
typedef struct linkstack {
linkstack_data_t data;
struct linkstack* next;
}lsk_node, * lsk_pnode;
//初始化
lsk_pnode create_linkstack();
//入栈
int push_linkstack(lsk_pnode S, linkstack_data_t data);
//出栈
linkstack_data_t pop_linkstack(lsk_pnode S);
//判空
int empty_linkstack(lsk_pnode S);
//清空
int clean_linkstack(lsk_pnode S);
//销毁
int destroy_linkstack(lsk_pnode* S);
链栈的基本操作实现
初始化
//初始化
lsk_pnode create_linkstack()
{
lsk_pnode S = (lsk_pnode)malloc(sizeof(lsk_node));
if (NULL == S)
{
printf("malloc is default\n");
return NULL;
}
S->next = NULL;
return S;
}
创建一个链栈S,作为头结点,头结点的next指向空。当然,也可以不用定义头结点,直接让S置为空,入栈后让S指向栈顶。
判空
//判空
int empty_linkstack(lsk_pnode S)
{
if (NULL == S->next)
return 0;
else
return -1;
}
因为我们使用了带头节点的链栈,所以当S->next=NULL即表示为空栈。
判满:链式存储理论上没有存满了的情况
入栈
入栈即是对链表做头插
//入栈
int push_linkstack(lsk_pnode S, linkstack_data_t data)
{
lsk_pnode new = create_linkstack();
if (NULL == new)
{
return -1;
}
new->data = data;
new->next = S->next;
S->next = new;
return 0;
}
出栈
出栈即是对链栈的头删
//出栈
linkstack_data_t pop_linkstack(lsk_pnode S)
{
if (0 == empty_linkstack(S))
{
printf("S is empty\n");
return -1;
}
lsk_pnode dele = S->next;
S->next = dele->next;
linkstack_data_t data = dele->data;
free(dele);
dele = NULL;
return data;
}
清空
注意,同顺序栈不同的是,链栈的清空需要释放掉除头结点以外的所有节点
//清空
int clean_linkstack(lsk_pnode S)
{
if (0 == empty_linkstack(S))
{
printf("S is empty\n");
return -1;
}
lsk_pnode p = S->next;
lsk_pnode q = S->next;
while (p)
{
q = q->next;
free(p);
p = q;
}
S->next = NULL;
return 0;
}
销毁
同理,销毁链栈时,也需要传入地址进行操作
//销毁
int destroy_linkstack(lsk_pnode* S)
{
if (0 == empty_linkstack(*S))
{
free(*S);
*S = NULL;
}
else
{
clean_linkstack(*S);
free(*S);
*S = NULL;
}
return 0;
}
总结
顺序栈与链栈即是分别对顺序存储与链式存储的一个升级,增加了两个限制条件,一是只能从一端插入和删除,二是先进先出。栈的用处比较广泛,例如求斐波那契数列、中缀表达式转后缀表达式等都会使用到栈。