1.栈的定义
栈是只允许在一端进行
插入
或删除
的线性表
。
注意栈是个操作受限的线性表
栈顶
:只允许插入或者删除
栈底
:固定的,不允许进行插入或删除的另一端
空栈
:不含任何数据元素的栈
栈又被称为后进先出的线性表,简称为LIOF(last in first out)
其特殊之处在于限制了这个线性表的插入和删除位置,他始终只在当前栈顶进行操作。
栈的插入操作:也叫做进栈,也叫做压栈与入栈。
栈的删除操作:也叫做出栈,有时也被称为弹栈。
数学性质: n
个不同的元素进栈,出栈元素的不同排列的个数为 N=C(2n,n)/(n+1)
。上述公式也是灰常经典的 卡特兰数
,可采用的数学归纳法去证明。
其原理:
卡特兰数的前几项:1,1,2,5,14,42,132,429,1430,4862…
令h(0)=1,h(1)=1,卡特兰数满足递推式:
h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)
例如:h(2)=h(0)h(1)+h(1)h(0)=11+11=2
h(3)=h(0)h(2)+h(1)h(1)+h(2)h(0)=12+11+21=5
个人感觉顺序栈这块出题也就在卡特兰数出了其他没啥难度
基本操作
同时栈这个逻辑结构还分为了顺序栈,共享栈和链栈三种实现方式
栈的顺序存储结构及实现:
采用顺序春促结构的栈称之为
顺序栈
,他利用一组地址连续的存储单元存放到自栈底至栈顶的数据元素,同时附设了一个指针指示当前栈顶元素的位置。
栈的ADT定义:
#define MAXISZIE 20
typedef int ElemType;
typedef struct {
//用数组存储顺序栈中元素
ElemType data[MAXISZIE];
int top;//栈顶指针,从0到maxsize-1,-1表空
//也可以时1到maxsize,0表空
}SeqStack,*PSeqStack;
顺序栈的清空:
//清空顺序栈
void Clear(PSeqStack S) {
//检查空指针
if (S == NULL)
return;
S->top = -1;//采取王道书上的设法
memset(S->data, 0, sizeof(ElemType) * MAXISZIE);
}
顺序栈的销毁:
//顺序栈的销毁
void DestoryStack(PSeqStack S) {
//由于时静态的不需要free
Clear(S);
return;
}
顺序栈的初始化:
//顺序栈的初始化
void InitStack(PSeqStack S) {
Clear(S);//清空
}
顺序栈的判满操作:
//判断顺序栈是否已经满了,返回1表示满0表未满
int IsFull(PSeqStack S) {
if (S == NULL)
return 0;
if (S->top == MAXISZIE - 1)
return 1;
return 0;
}
顺序栈的判空:
//抽象出判空方法便于后面使用
int IsEmpty(PSeqStack S) {
if (S == NULL)
return 0;
if (S->top == -1)
return 1;
return 0;
}
顺序栈的入栈(重点):
//元素入栈.0->失败,1->成功后面也是如此
int Push(PSeqStack S, ElemType* e) {
//日常判空操作
if ((S == NULL) || (e == NULL))
return 0;
if (IsFull(S) == 1) {
printf("顺序栈已满,不能插入。");
return 0;
}
//这时侯如果前面条件满足就可以放入元素咯
//首先分析一下栈顶指针要加一,逻辑层面向上指
S->top++;
memcpy(&S->data[S->top], e, sizeof(ElemType));
return 1;
}
顺序栈的出栈(重点):
//出栈
int Pop(PSeqStack S, ElemType* e) {
if ((S == NULL )||( e == NULL)) {
return 0;
}
if (S->top == -1) {
printf("栈为空");
return 0;
}
//接下来就是成功的情况
//删除元素首先就是要让指针-1然后逻辑上清楚
//首先就是要接受一下数据
memcpy(e, &S->data[S->top], sizeof(ElemType));
S->top--;
return 1;
}
得到栈顶元素(重点):
//获取栈顶元素
//其实本质上就是吧栈顶指针指向的值赋值给e
//成功-1失败-0
int GetTop(PSeqStack S, ElemType* e) {
if ((S == NULL) || (e == NULL)) {
return 0;
}
//如果栈为空同样没有元素哦
if (IsEmpty(S) == 1) {
printf("展位空");
return 0;
}
//开找!
memcpy(e, &S->data[S->top], sizeof(ElemType));
return 1;
}
查询顺序栈的长度:
//查找顺序栈的长度
int Length(PSeqStack S) {
if (S == NULL)
return 0;
return S->top + 1;//因为时从-1开始的当然要加1啦
}
顺序栈的打印:
//打印所有的元素
void PrintStack(PSeqStack S) {
if (S == NULL)
return;
//判断一下栈为空也无法打印
if (IsEmpty == 1) {
return;
}
int count;
for (count = 0; count<=S->top; ++count) {
printf("s[%d],value=%d\n", count, S->data[count]);
}
}
介绍完了顺序结构接下来我们来讲讲 栈
的 链式存储
简称为 链栈
栈的顺序存储结构(链栈)及实现:
链栈的优点是采用的是链式存储,从而便于结点的插入和删除。其操作和链表类似,入栈和出栈操作都是在链表的表头进行。这里为了方便操作采取了带头节点的方式。
链栈的ADT:
typedef int ElemType;
typedef struct SNode {
ElemType data;
struct SNode* next;
}SNode,*LinkStack;
链栈的初始化:
//链栈的初始化,分配头节点,返回头节点
SNode* InitStack() {
SNode* head = (SNode*)malloc(sizeof(SNode));
if (head == NULL)
return NULL;
head->next = NULL;
return head;
}
链栈的清空:
//清空链栈
void Clear(LinkStack S) {
if (S == NULL)
return;
SNode* tmp;
SNode* tmp2 = S->next;//保护头节点
while (tmp2 != NULL) {
tmp = tmp2->next;//保存头节点跟着的第二个结点
free(tmp2);//删除头节点跟着的第一个结点
//吧第二个结点赋值给tmp2
tmp2 = tmp;
}
S->next = NULL;
return;
}
链栈的销毁:
//销毁链栈
void DestroyStack(LinkStack S) {
//销毁的意义是销毁链栈所有的结点包括头节点
//一关于链的遍历马上想到while
SNode* tmp;
while (S != NULL) {
//循环s=null时跳出去
tmp = S->next;
free(S);//释放当前结点
S = tmp;//保存当前结点
}
return;
}
判空:
//判空
int IsEmpty(LinkStack S) {
if (S == NULL)
return 0;
if (S->next == NULL)
return 1;
return 0;
}
入栈(重点):
//元素入栈.成功返回1失败-0
int Push(LinkStack S, ElemType* e) {
if ((S == NULL) || (e == NULL))
return 0;
//创建结点
SNode* tmp = (SNode*)malloc(sizeof(SNode));
if (tmp == NULL)
return 0;
memcpy(&tmp->data, e, sizeof(ElemType));
tmp->next = S->next;
S->next = tmp;
return 1;
}
出栈(重点):
//元素出栈
int Pop(LinkStack S, ElemType* e) {
if ((S == NULL) || (e == NULL))
return 0;
//记得判断一下是否为空
if (S->next == NULL)
{
printf("栈为空");
return 0;
}
//因为头节点没赋值所以要指向真正的第一个数据结点
memcpy(e, &S->next->data, sizeof(ElemType));
//删去头节点接的结点
SNode* tmp = S->next;
S->next = tmp->next;
free(tmp);
return 1;
}
获取栈顶元素:
//获取栈顶元素
int GetTop(LinkStack S, ElemType* e) {
if ((S == NULL) || (e == NULL))
return 0;
if (S->next == NULL) {
return 0;
}
memcpy(e, &S->next->data, sizeof(ElemType));
return 1;
}
打印链表:
//打印链栈中的所有元素
void PrintStack(LinkStack S) {
if (S == NULL)
return;
if (S->next == NULL)
return;
SNode* tmp = S->next;
int count = 0;
while (tmp!=NULL)
{
printf("S[%d],value=%d\n", count++, tmp->data);
tmp = tmp->next;
}
}
链表长度:
//求链表长度
int Length(LinkStack S) {
if (S == NULL)
return NULL;
SNode* tmp = S->next;
int len = 0;
while (tmp != NULL) {
len++;
tmp = tmp->next;
}
return len;
}
还有一个值得一提的栈的存储结构就是——共享栈
共享栈的实现:
其利用了栈底位置相对不变的特性,可以让两个顺序栈共享同一个数组空间,因为两种相同类型的数组空间极有可能照成一个满而另外一个空间还有许多的情况,所以我们完全可以用一个数组来放入两个栈的方法来更加有效的利用存储空间。
其关键的思路就是讲两个数组的指针设置在数组的两端,向两端放入数据的同时两边的栈顶指针分别向中间靠拢
知道了关键思路就可以开始动手实现了
共享栈的ADT:
typedef int ELemType;
//定义结构
typedef struct {
ELemType data[MAXSIZE];
int top1;
int top2;//栈2的栈顶指针
}SqDoubleStack;
共享栈的初始化:
void InitStack(SqDoubleStack* S) {
S->top1 = -1;
S->top2 = MAXSIZE;
return;
}
共享栈的清空:
//清空
int ClearStack(SqDoubleStack* S) {
S->top1 = -1;
S->top2 = MAXSIZE - 1;
return 1;
}
入栈:
//共享栈的插入
int Push(SqDoubleStack* S, ELemType* e, int StackNumber) {
if (S->top1 + 1 == S->top2) {
//说明栈满了
return 0;
}
if (StackNumber == 1) {
//说明是栈1入栈元素
S->top1++;
memcpy(&S->data[S->top1], e, sizeof(ELemType));
}
if (StackNumber == 2) {
S->top2--;
memcpy(&S->data[S->top2], e, sizeof(ELemType));
return 1;
}
}
出栈:
//共享栈的删除
int Pop(SqDoubleStack* S, ELemType* e, int StackNumber) {
if (StackNumber == 1) {
if (S->top1 == -1)
return 0;//说明栈1空了删了个寂寞
memcpy(e, &S->data[S->top1], sizeof(ELemType));
S->top1--;
}
else if (StackNumber==2) {
if (S->top2 == MAXSIZE)
return 0;
memcpy(e, &S->data[S->top2], sizeof(ELemType));
S->top2--;
}
return 1;
}
遍历:
//懒得写了但是感觉写了会很好理解这个结构的特殊
/*
首先就是搞个计数器先是从链表1遍历,计数器设置位0,不断加1同时输出值知道等于是top1指针停下,注意数组中的data[count++],输出玩直接++就是下一个值大于同top1后停止输出因为栈1到顶了
第二遍历就是遍历共享栈2,从top2指针开始不断++知道count=maxsize出循环因为maxsize对于数组来说已经越界啦
*/
判空:
//判空
int IsEmpty(SqDoubleStack S) {
if (S.top1 == -1 && S.top2 == MAXSIZE - 1) {
return 1;
}
else {
return 0;
}
}
其他的操作都较为简单就不写了重点看看这上面几个理解一下就行