« 上一篇下一篇 »

数据结构<六>——栈

栈(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