栈:线性结构,后进先出。栈(Stack)是一种特殊的线性表(顺序表,链表)只在表尾进行删除和插入操作。
注意:对于栈来说,表尾称为栈的栈顶(top),表头称为栈底(bottom)。
栈也是线性结构的一种特例。与队列不同,他只有一个口,只能从这里读或者写数据,这个口称为栈顶(top)。栈是一种先进后出的数据结构。先进来的元素会放入栈底,而后进来的元素被放在它的上面,最后进来的元素的上面的位置,称为栈顶。
栈所提供的操作比一般的线性表要少很多,只提供:初始化、销毁、判断是否为空、求栈的长度、清空栈、将数据压入栈、将数据弹出栈、获得栈顶元素这几种操作。其中将数据压入栈、将数据弹出栈、获得栈顶元素是最重要的。有人可能觉得,将栈顶元素弹出与获得栈顶元素是不是有点重复,其实它们主要的目的在于,很多时候你只想知道当前栈顶的元素是谁,而并不想将它弹出。这样做可以简单一点。
了解了栈的基本结构和操作以后,自然而然的想到用数组来实现栈:因为前面的元素并不发生变化,只能在最后面入栈或者出栈。
因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构。
最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。
栈顶——地址较高;
栈底——地址较低。
定义一个顺序存储的栈,包含三个元素:base、top、stackSize。
typedef struct{ ElemType *base;//栈底 ElemType *top;//栈顶 int stackSize;//栈的当前可使用的最大容量 }sqStack;
或者:
typedef int ElemType; typedef struct{ ElemType data[MAXSIZE]; int top; // 用于标注栈顶的位置 int stackSize; }
其中base是指栈底的指针变量,top是指栈顶的指针变量,stackSize指栈的当前可使用的最大容量。
创建一个栈:
#define STACK_INIT_SIZE 100 initStack(sqStack *s){ s->base = (ElemType *)malloc( STACK_INIT_SIZE * sizeof(ElemType) ); if( !s->base ) exit(0); s->top = s->base; // 最开始,栈顶就是栈底 s->stackSize = STACK_INIT_SIZE; }
入栈操作:
入栈操作又叫压栈操作,就是向栈中存放数据。
入栈操作要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,直到栈满为止。
出栈操作:
出栈操作就是在栈顶取出数据,栈顶指针随之下移的操作。
每当从栈内弹出一个数据,栈的当前容量就-1。
入栈时,先将值压入栈中,再改变指针top,即将top指针加1;
出栈时,先将top指针减去1,然后再将top指针指向的值从栈中取出;
注意:top指针在开始时指向的位置其实就是将要存放数据的位置,因此它指向的区域是空的,想要将数据从栈中取出,需要首先将top指针指向该位置。
Pop(sqStack *s, ElemType *e){ if( s->top == s->base ) // 栈为空 return; *e = *--(s->top); }
栈的其它操作:清空、销毁、计算栈的当前容量等。
1、清空:清空一个栈,就是将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。
因此我们只要将s->top的内容赋值为s->base即可,这样s->base等于s->top,也就表明这个栈是空的了。
这个原理跟高级格式化只是但单纯地清空文件列表而没有覆盖硬盘的原理是一样的。
ClearStack(sqStack*s){ s->top= s->base; }
2、销毁一个栈
销毁一个栈与清空一个栈不同,销毁一个栈是要释放掉该栈所占据的物理内存空间,因此不要把销毁一个栈与清空一个栈这两种操作混淆。
DestroyStack(sqStack *s){ int i, len; len = s->stackSize; for( i=0; i < len; i++ ) { free( s->base ); s->base++; } s->base = s->top = NULL; s->stackSize = 0; }
3、计算栈的当前容量
计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s.top-s.base即可。
注意,栈的最大容量是指该栈占据内存空间的大小,其值是s.stackSize,它与栈的当前容量不是一个概念哦。
int StackLen(sqStack s){ return(s.top – s.base); //两个地址相减,返回的是存放在该地址的数据的个数 }
(指针的数学计算:++、--、相减,但是指针不能相加!)
栈的链式存储结构(栈链)
栈因为只是栈顶来做插入和删除操作,所以比较好的方法就是将栈顶放在单链表的头部,栈顶指针和单链表的头指针合二为一。
//栈的链式存储结构(栈链) teypedef struct StackNode { ElemType data; // 存放栈的数据 struct StackNode *next; } StackNode, *LinkStackPtr; teypedef struct LinkStack { LinkStackPrt top; // top指针 int count; // 栈元素计数器 } //对于栈链的Push操作,假设元素值为e的新结点是s,top为栈顶指针 Status Push(LinkStack *s, ElemType e){ LinkStackPtr p = (LinkStackPtr) malloc (sizeof(StackNode)); p->data = e; p->next = s->top; s->top = p; s->count++; return OK; } //链栈的出栈Pop操作,假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可。 Status Pop(LinkStack *s, ElemType *e){ LinkStackPtr p; if( StackEmpty(*s) ) // 判断是否为空栈 return ERROR; *e = s->top->data; p = s->top; s->top = s->top->next; free(p); s->count--; return OK; }