栈
栈的基本概念
数据结构三要素——逻辑结构、数据的运算、存储结构(物理结构)
栈的定义
栈(Stack)是只允许在一端进行插入或删除操作的线性表
- 栈顶:允许插入和删除的一端
- 栈底:不允许插入和删除的一端 。固定的
- 空栈:不含任何元素的空表
特点:后进先出
(Last In First Out) 即LIFO
栈的基本操作
InitStack(&S)
初始化栈。构造一个空栈S,分配内存空间。
DestoryStack(&L)
销毁栈。销毁并释放栈S所占用的内存空间。
Push(&S,x)
进栈。若栈S未满,则将X加入是使之成为新栈顶。
Pop(&S,&x)
出栈。若栈S非空,则弹出栈顶元素,并用x返回。
GetTop(S,&x)
读栈顶元素。若栈S非空,则用x返回栈顶元素。
n个不同元素进栈,出栈元素不同排列的个数为1/n+1Cn2n。
上述公式成为卡特兰(Catalan)数,可采用数学归纳法证明。
顺序栈的定义
初始化操作
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
}SqStack;
void testStack(){
SqStack S; //声明一个顺序栈
InitStack(S);
}
//判断栈是否为空 利用top的指向判断
bool StackEmpty(SqStack S){
if(S.top == -1)
return true; //栈空
else
return false; //非空
}
进栈操作(增)
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针
}SqStack;
//新元素入栈
bool Push(SqStack &S,ElemType x){
if(S.top == MaxSize -1) //栈满,报错
return false;
S.data[++S.top];
return ture;
}
出栈操作(删)
//出栈操作的删除,数据还残留在内存中,只是逻辑上删除了
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int top;
}SqStack;
//出栈操作,将栈顶元素删除,并返回删除的值
bool Pop(SqStack &S,ElemType &x){
if(S.top == -1) //判断栈是否为空
return false;
x=S.data[S.top--];
return ture;
}
读栈顶元素操作
#define MaxSize 10
typedef struct{
ElemType data[MaxSize];
int top;
}SqStack;
//读栈顶元素
bool GetTop(SqStack S,ElemType &x){
if(S.top == -1)
return false;
x=S.data[S.top];
return true;
}
顺序栈的缺点:栈的大小不可变
共享栈
利用栈底位置相对不变的特性,可以让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸。
#define MaxSize 10
typedef struct{
int data[MaxSize];
int top0;
int top1;
}ShStack;
//初始化栈
void InitStack(ShStack &S){
S.top0 = -1;
S.top1 = MaxSize;
}
//判断栈满的条件,top0 + 1 == top1
两个栈的栈顶指针都指向栈顶元素,top0 = -1 时 0号栈为空,top1 == MaxSize 时 1号栈为空;仅党两个栈顶指针相邻(top1 - top0 == 1)时,判断为栈满。当0号栈进栈时,top0先加再赋值,1号栈进栈时top先减1再赋值;出栈时则刚好相反
共享栈是为了更有效地利用存储空间,两个栈的空间相互调节,只有在整个存储空间被沾满时才发生上溢。其存取数据的时间复杂度均为O(1) ,所以对存取效率没有什么影响。
栈的链式存储实现
采用链式存储的栈成为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。
typedef struct node{
StackElementType data;
struct node *next;
}LinkStackNode;
typedef LinkStackNode *LinkStack;
采用链式存储,便于结点的插入与删除。链栈的操作与链表类似,入栈和出栈的操作都在链表的表头进行。对于带头结点和不带头结点的链栈,具体的实现会有所不同。
链栈的进栈操作
int Push(LinkStack top,StackElementType x){
LinkStackNode *temp;
temp = (LinkStackNode *)malloc(sizeof(LinkStackNode));
if(temp == NULL)
return -1; //申请空间失败
temp->data = x;
temp ->next = temp->next;
}
链栈出栈操作
int Pop(LinkStack top,StackElementType *x){
//将栈top的栈顶元素弹出,放到x所致的存储空间中
LinkStackNode *temp;
temp = top->next;
if(temp == NULL)
return -1;
top->next = temp ->next;
*x = temp -> data;
free(temp); //释放存储空间
return 1; //表示出栈成功
}