栈(stack)是限定仅在表尾(栈顶)进行插入和删除操作的线性表。
允许插入和删除的一端称为栈顶,另一端称为栈底。
以下代码为公有代码:
typedef intStatus;// 函数执行状态 #define OK1 #define ERROR0 #define TRUE1 #define FALSE0 typedef int SElemType;
一、栈的顺序存储
定义一个top变量来指示栈顶元素在数组中的位置。当栈存在一个元素时,top等于0,因此空栈的判定条件为 top == -1。
1、结构定义
#define MAXSIZE 5 // 栈的结构定义 typedef struct { SElemType data[MAXSIZE]; int top;// 用于栈顶指针 }SqStack;
2、进栈操作
// 插入元素e为新的栈顶元素 Status Push(SqStack *S, SElemType e) { if(S->top == MAXSIZE - 1)// 栈满 { return ERROR; } S->top++;// 栈顶指针加1 S->data[S->top] = e;// 将新元素值赋给栈顶空间 return OK; }
算法分析:
(1)进栈前先判断栈是否已满,判定条件是top指针是否等于栈的总空间大小减1;
(2)栈顶指针加1;
(3)把新元素值赋给栈顶空间;
(4)返回正确。
3、出栈操作
// 若栈不空,则删除S的栈顶元素,用e返回其值 Status Pop(SqStack *S, SElemType *e) { if(S->top == -1)// 判断栈是否为空 return ERROR; *e = S->data[S->top];// 把要删除的栈顶元素值赋给e S->top--;// 栈顶指针减1 return OK; }
算法分析:
(1)出栈前先判断栈是否为空,根据上文所述,栈为空的条件是top == -1;
(2)把要删除的栈顶元素的数据赋给e;
(3)top指针减1;
(4)返回正确。
4、两栈共享空间
为了最大限度的使用存储空间,可以使两个栈共享同一个数组空间。关键思路是:两个栈分别在数组的两端,向中间靠拢。当然,使用这种模式的前堤是两个栈的数据类型相同。限于篇幅,这里不做具体介绍。
栈的顺序结构比较简单,其实就是线性表的变化而已,下面我们来看栈的链式存储结构
二、栈的链式存储
对于链栈来说,通常是不需要头结点的,而把栈顶放在单链表的头部(插入与删除只能在栈顶),所以栈顶指针就指向链表头部。对于空栈来说,链表原定义是头指针指向空,所以链栈的空就是top = NULL。
1、结构定义
typedef struct StackNode { SElemType data; struct StackNode *next; }StackNode, *LinkStackPtr; typedef struct LinkStack { LinkStackPtr top; int count; }LinkStack;
2、进栈操作
// 插入元素e为新的栈顶元素 Status Push(LinkStack *S, SElemType e) { LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode)); s->data = e; s->next = S->top;// 把当前的栈顶元素赋值给新结点的直接后继 S->top = s;// 将新的结点s赋值给栈顶指针 S->count++; return OK; }
算法分析:
(1)、先创建一个元素空间s;
(2)、把新元素赋给s的数据域;
(3)、把当前的栈顶元素赋给s的直接后继;
(4)、栈顶指针指向新结点;
(5)、计数器加1;
(6)、返回正确。
3、出栈操作
// 若栈不空,则删除S的栈顶元素,用e返回其值 Status Pop(LinkStack *S, SElemType *e) { LinkStackPtr p; if(StackEmpty(*S))// 判断栈是否为空 return ERROR; *e = S->top->data; p = S->top;// 将栈顶结点赋值给p S->top = S->top->next;// 栈顶指针后移,指向后一结点 free(p);// 释放结点p的空间 S->count--; return OK; }
算法分析:
(1)、出栈之前先判定栈是否为空,由前文所述,栈为空的条件是top = NULL,所以StackEmpty()函数定义如下:
// 若栈为空,返回正确,否则返回错误 Status StackEmpty(LinkStack S) { if(S.top == NULL)// 栈为空的条件即头指针为NULL return TRUE; else return FALSE; }
(2)、把当前栈顶元素的数据赋给e;
(3)、把栈顶结点暂存于p;
(4)、top指针后移,指向后一结点;
(5)、释放p指向的空间;
(6)、计数器减1;
(7)、返回正确。
4、栈的整栈创建
// 堆栈的整栈创建 void StackCreated(LinkStack *S, int e) { int i; StackInit(S);// 初始化一空栈 srand(time(0));// 初始化随机数种子 for(i = 0; i < e; i++) { Push( S, (rand() % 100 + 1) );// (rand() % 100 + 1)--随机生成100以内的数字 } }
算法分析:
(1)、首先初始化一空栈,根据前文所述,StackInit()定义如下
// 初始化一个空栈 Status StackInit(LinkStack *S) { S->top = NULL;// 链栈为空即top = NULL S->count = 0;// 计数器初始化为0 return OK; }
(2)、初始化随机数种子;
(3)、循环:调用Push()函数,进栈随机数。
总结:如果栈在使用过程中元素变化不可预料,有时很小,有时很大,那么最好是用链栈,反之,如果它的变化在可控范围内,使用顺序栈会更好一些。
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚集于我们要解决的问题核心。
附件:栈的顺序结构与链式结构实现(C语言) STACK.zip