目录
1 栈的定义和特点
栈是限定仅在表尾进行插入和删除操作的线性表。因此,对栈来说,表尾端有特殊含义,称为栈顶。不含元素的空表称为空栈。栈的修改是按后进先出的原则进行的,又被称为后进先出的线性表/
2 栈的表示和操作的实现
2.1 顺序栈的表示和实现
2.1.1 栈的顺序存储表示
顺序栈是指利用顺序存储结构实现的栈,即利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素在顺序栈中的位置。通常习惯的做法是以top=-1,即指针指向数组之外表示空栈。
2.1.2 顺序栈的定义与基本操作函数源码
//
// Created by SongBy on 2022/03/25.
//
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define TRUE 1
#define FALSE 0
#define MAXSIZE 10/*顺序栈可能达到的最大长度,存储空间初始分配量*/
typedef int Status;
typedef int ElemType;/*ElemType是栈内的元素类型,根据实际情况而定,此处假设为int*/
typedef struct {
ElemType *data;
int top;/*用于栈顶指针*/
}LinkStack;
/*操作结果:构造一个空栈S*/
Status InitStack(LinkStack *S){
S->data = (ElemType *) malloc(sizeof(ElemType) * MAXSIZE);/*申请连续的MAXSIZE长度的ElemType类型数组,并让data指向这个空间*/
if (!S->data)/*判断空间是否申请成功*/
exit(OVERFLOW);
S->top = -1;/*空栈无元素,栈顶指针指向栈外*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:栈S被销毁*/
Status DestroyStack(LinkStack *S){
free(S->data);/*释放data指向的空间*/
S->data = NULL;
S->top = -1;/*重置栈顶指针*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:栈S清为空栈*/
Status ClearStack(LinkStack *S){
S->top = -1;/*重置栈顶指针*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:若栈S为空栈,则返回true,否则返回false*/
Status StackEmpty(LinkStack S){
if(S.top!=-1)/*栈顶指针是否指向栈外*/
return FALSE;
return TRUE;
}
/*初始条件:栈S已存在*/
/*操作结果:返回S的元素个数,即栈的长度*/
int StackLength(LinkStack S){
return S.top+1;/*元素所在下标+1*/
}
/*初始条件:栈S已存在且非空*/
/*操作结果:返回S的栈顶元素,不修改栈顶指针*/
ElemType GetTop(LinkStack S){
if(S.top == -1)/*栈空*/
return ERROR;
return S.data[S.top];
}
/*初始条件:栈S已存在*/
/*操作结果:插入元素e为新的栈顶元素*/
Status Push(LinkStack *S, ElemType e){
if(S->top == MAXSIZE -1)/*栈满*/
return ERROR;
S->top++;/*栈顶指针加1*/
S->data[S->top] = e;/*将新插入元素赋值给栈顶元素*/
return OK;
}
/*初始条件:栈S已存在且非空*/
/*操作结果:删除S的栈顶元素,并用e返回其值*/
Status Pop(LinkStack *S, ElemType *e){
if(S->top == -1)/*栈空*/
return ERROR;
*e = S->data[S->top];/*将要删除的栈顶元素赋值给e*/
S->top--;/*栈顶指针减1*/
return OK;
}
/*初始条件:栈S已存在且非空*/
/*操作结果:从栈底到栈顶依次对S的每个数据元素进行访问*/
Status StackTraverse(LinkStack S){
for(int i = 0;i<=S.top;i++)/*从栈底遍历到栈顶*/
printf("%d",S.data[i]);/*此处以输出代替访问*/
return OK;
}
2.2 链栈的表示和实现
2.2.1 栈的链式存储表示
链栈是指采用链式存储结构实现的栈。通常链栈用单链表来表示。链栈的结点结构与单链表相同,在此用StackNode表示。
2.2.2 链栈的定义与基本操作函数源码
//
// Created by SongBy on 2022/03/25.
//
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
#define OVERFLOW -1
#define TRUE 1
#define FALSE 0
#define MAXSIZE 10/*顺序栈可能达到的最大长度,存储空间初始分配量*/
typedef int Status;
typedef int ElemType;/*ElemType是栈内的元素类型,根据实际情况而定,此处假设为int*/
typedef struct StackNode{
ElemType data;/*数据域*/
struct StackNode *next;/*指针域*/
}StackNode,*LinkStackPtr;/*栈结点*/
typedef struct LinkStack{
LinkStackPtr top;/*栈顶指针*/
int count;/*栈内元素计数器*/
}LinkStack;/*链栈*/
/*操作结果:构造一个空栈S*/
Status InitStack(LinkStack *S){
S->top = NULL;/*栈顶指向空*/
S->count = 0;/*栈内元素置为0*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:栈S被销毁*/
Status DestroyStack(LinkStack *S){
LinkStackPtr p;
while(S->top)/*若栈不为空则删除栈顶元素,栈指针后移*/
{
p = S->top;
S->top = S->top->next;
free(p);
}
S->count = 0;/*重置计数器*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:栈S清为空栈*/
Status ClearStack(LinkStack *S){
LinkStackPtr p;
while(S->top)/*若栈不为空则栈顶元素出栈,栈指针后移*/
{
p = S->top;
S->top = S->top->next;
free(p);
}
S->count = 0;/*重置计数器*/
return OK;
}
/*初始条件:栈S已存在*/
/*操作结果:若栈S为空栈,则返回true,否则返回false*/
Status StackEmpty(LinkStack S){
if(S.count == 0)/*栈内无元素*/
return TRUE;
return FALSE;
}
/*初始条件:栈S已存在*/
/*操作结果:返回S的元素个数,即栈的长度*/
int StackLength(LinkStack S){
return S.count;
}
/*初始条件:栈S已存在且非空*/
/*操作结果:返回S的栈顶元素,不修改栈顶指针*/
ElemType GetTop(LinkStack S){
if(S.count == 0)/*栈空*/
return ERROR;
return S.top->data;/*返回栈顶元素*/
}
/*初始条件:栈S已存在*/
/*操作结果:插入元素e为新的栈顶元素*/
Status Push(LinkStack *S, ElemType e){
LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
s->data = e;
s->next = S->top;/*把当前栈顶元素赋值给新结点的直接后继*/
S->top = s;/*将新的结点s赋值给栈顶指针*/
S->count++;
return OK;
}
/*初始条件:栈S已存在且非空*/
/*操作结果:删除S的栈顶元素,并用e返回其值*/
Status Pop(LinkStack *S, ElemType *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;
}
/*初始条件:栈S已存在且非空*/
/*操作结果:从栈底到栈顶依次对S的每个数据元素进行访问*/
Status StackTraverse(LinkStack S){
/*思路:构造一个新栈,将旧栈元素按出栈顺序入新栈,再从新栈栈顶遍历访问,即可得到从旧栈底到栈顶访问的顺序*/
LinkStack ST;/*构造新栈*/
InitStack(&ST);
LinkStackPtr p;
p = S.top;
while(p)/*旧栈元素出栈并入新栈*/
{
LinkStackPtr s = (LinkStackPtr) malloc(sizeof (StackNode));
s->data = p->data;
s->next = ST.top;
ST.top = s;
ST.count++;
p = p->next;
}
p = ST.top;
while(p)/*遍历新栈*/
{
printf("%d",p->data);
p = p->next;
}
return OK;
}
3 分析与总结
3.1 分析
3.1.1 对比一下顺序栈与链栈
时间性能:
它们在时间复杂度是一样的,均为O(1)。
空间性能:
- 顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便。
- 链栈则要求每个元素都有指针域,这同时也增加了一些内存开销,但对栈的长度无限制。
3.1.2 对比顺序栈与顺序表、链栈与链表
- 顺序栈:由于顺序栈的插入和删除只在栈顶进行,因此顺序栈的基本操作比顺序表要简单的多。
- 链栈:由于栈的主要操作是在栈顶插入和删除,显然以链表的头部作为栈顶是最方便的,而且没必要像单链表那样为了操作方便附加一个头结点。
3.2 总结
如果栈的使用过程中元素变化不可预料,有时很大有时很小,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。