栈 Stack
栈的定义
栈是仅限在表尾进行插入和删除操作的线性表,又被称为后进先出的线性表,简称LIFO结构
栈可以被视为一种受限制的数组或链表
我们把允许插入和删除的一段称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
栈是一个线性表,栈底是固定的,最先进的栈只能在栈底。
栈的插入操作,叫做进栈,也称压栈、入栈,英文push
栈的删除操作,叫做出栈,也称弹栈,英文pop
进出栈的时间没有限制,请看这个例子:
3个整形数1、2、3依次进栈,可以有5种出栈次序
1进、1出、2进、2出、3进、3出
1进、2进、3进、1出、2出、3出
......
栈的抽象数据类型
ADT 栈(stack)
Data
同线性表。元素都具有相同的类型,相邻元素具有前驱和后继的关系
Operation
InitStack(*S):建立一个空栈S
DestroyStack(*S):若栈S存在,则销毁
ClearStack(*S): 将栈清空
StackEmpty(S):检查栈S是否为空
GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素
Push(*S,e):若栈S存在,插入e
Pop(S,e):删除栈顶的元素,并用e返回其值
StackLength(S):返回栈S的元素个数
endADT
栈的顺序存储(顺序栈)
栈的顺序存储是线性表顺序存储的简化,我们简称为顺序栈。
顺序表是用数组来实现的,下标为0的元素作为栈底,定义一个top变量作为栈顶(记录最后一个元素所在的下标)
当栈存在一个元素时,top = 0
空栈,top = -1
typedef struct { int data[MAXSIZE]; int top; }SqStack;
进栈操作 O(1)
int Push(SqStack *S,int e) { if(S->top == MAXSIZE - 1) //栈满 { return 0; } //S->top++; S->data[++S->top] = e; return 1; }
出栈操作O(1)
int Pop(SqStack *S,int *e){ if(s->top = -1) return 0; *e = S->data[S->top--]; //将要删除的栈顶元素赋给e //S->top--; //栈顶指针-1 return 1; }
两栈共享空间(一般用在两栈需求有相反关系时)
用一个数组存储2个栈,一个栈的栈底在下标为0处,另一个在下标为n-1处
栈1为空栈:top1=-1,栈1满了:top1 = n-1
栈2为空栈:top2=n,栈2满了:top2=0
两个栈都有东西,栈满了:top1+1=top2
typedef struct { int data[MAXSIZE]; int top1; int top2; }SqDoubleStack;
进栈操作
int push(SqDoubleStack *S,int e,int stackNumber) { if(S->top1 + 1 == S->top2) return 0 ; if(stackNumber == 1) S->data[++S->top1] = e; elseif(stackNumber == 2) S->data[--S->top2] = e; return 1; }
出栈操作
int pop(SqDoubleStack *S,int e,int stackNumber) { if(stackNumber == 1) { if(S->top1 == -1) return 1; *e=S->data[S->top1--]; } else if(stackNumber == 2) { if(S->top2 == n) return 1; *e=S->data[S->top2++]; } return 0; }
栈的链式存储
栈的链式存储结构,也称为链栈(把链表用栈的规则进行束缚)
链栈不需要头结点,也不需要考虑上限
空栈的情况:top = NULL;
typedef struct Node { int data; Node* next; }Node; typedef struct StackList { Node* top; int size; }StackList;
链栈的初始化
StackList* NewStackList() { StackList* p = malloc(sizeof(StackList)); p->top = NULL; p->size = 0; return p; }
链栈的析构
void DestroyStackList(StackList* L) { while(L->size){ Pop(L); } free(L); }
进栈操作
Status Push(StackList* L,int e) { Node* p = malloc(sizeof(Node)); p->data = e; //给新结点p添加数据域 p->next = L->top; //给新结点p添加指针域 L->top = p; //更新栈顶指针 L->size ++; //更新栈大小 return OK; }
出栈操作
Status Pop(StackList* L) { if(StackList->top = NULL) printf("This Stack is empty.\n"); return 0; //*e = L->top->data; p = L->top; L->top = L->top->next; //更新栈顶指针 free(p); //释放原栈顶指向的结点 L->size--; //更新栈大小 return OK; }
栈的应用——递归
斐波那契数列
1,1,2,3,5,8,13,21,34,......
int Fbi(int i) { if(i<2) return i == 0 ? 0 : 1; //i = 0为真则返回0,否则返回1 return Fbi(i-1)+Fbi(i-2); } int main() { int i; printf("递归显示斐波那契数列\n"); for(i=0;i<40;i++) printf("%d ",Fbi(i)); return 0; }
递归的定义
一个可以直接调用/通过一系列调用语句间接调用自己的函数被称为递归函数
栈的应用——逆波兰表示法
栈的实现方式的对比
支持操作
栈的顺序结构额外支持随机访问,但一般不会用到。栈可以被视为一种受限制的数组或链表。
时间效率
基于数组实现的栈:入栈和出栈都是在预先分配好的内存中进行,具有很好的缓存本地性,因此效率较高。
查找
顺序存储结构 O(1) //随机存取的特性:可以直接访问某个元素
单链表O(n) //顺序存取的特性:必须挨个访问元素
插入和删除
顺序存储结构 O(n) //找到要改动的位置之后后边的元素全都要动
单链表在找到位置后O(1)