数据结构—三种结构的栈的实现
昨天学习了数据结构中的栈,其实说白了,栈和队列都是对插入和删除数据元素有具体要求的线性表,所以在掌握了前面的线性表的知识后,很容易就可以理解这种数据结构的实现模式。
栈:是限定仅在表尾进行插入和删除操作的线性表。
其过程可以理解为网页的打开与后退,当你打开一个网页时,这是你最新打开的界面,显示在最上层,这里其实就类似于插入的操作。但当你要后退到上一个网页时,你退出了当前界面,回到了之前打开的界面,这其实就类似于删除的操作。
特别的,我们把对栈的插入叫做压栈,对栈的删除叫做弹栈。
接下来,我要介绍三种栈的结构的实现。
1.栈的顺序存储
说到顺序存储,我们一定能想到数组。栈的顺序存储其实就是用数组来实现的,但是我们还需要一个值来表示栈顶的位置,因为压栈和弹栈的操作都是在栈顶实现的,所以我们引用了top这个变量,当为空栈时,top = -1;其他时候top等于栈顶元素的下标。
以下为栈的顺序存储的代码实现:
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int date[MAX];
int top;
}SqStack;
SqStack *InitStack(SqStack *stack){ //初始化栈,申请内存空间,并给top赋-1
stack = (SqStack *)malloc(sizeof(SqStack));
stack->top = -1;
printf("初始化成功!\n");
return stack;
}
void push(SqStack *stack){
int index = stack->top+1;
if(index == MAX-1){ //满栈返回
printf("目前栈满!\n");
return;
}
printf("请输入要压栈的数据:");
scanf("%d",&stack->date[index]);
stack->top++; //压栈成功,栈顶标记加一
printf("压栈成功!\n");
}
void pop(SqStack *stack){
printf("%d\n",stack->top);
int index = stack->top;
if(index == -1){ //空栈返回
printf("目前为空栈!\n");
return;
}
printf("要弹栈的数据为:%d ",stack->date[index]);
stack->top--; //弹栈成功,栈顶标记减一
printf("弹栈成功!\n");
}
void print_stack(SqStack *stack){ //遍历栈内数据
printf("%d\n",stack->top);
int index = stack->top;
if(index == -1 ){
printf("目前为空栈!\n");
return;
}
for(int i = 0; i <= index; i++){
printf("第%d个数据为:",i+1);
printf("%d\n",stack->date[i]);
}
}
int main(){
SqStack *stack;
stack = InitStack(stack);
int choice = -1;
while(choice != 0){
printf("1. 压栈\n");
printf("2. 弹栈\n");
printf("3. 输出\n");
scanf("%d",&choice);
switch(choice){
case 1: push(stack);
break;
case 2: pop(stack);
break;
case 3: print_stack(stack);
break;
}
}
return 0;
}
2.两栈共享空间
有没有可能有这种情况:两个同类型的栈,我们各自为他们开辟了数组空间,但是有可能第一个栈已经满了,再压栈就溢出了,而另一个栈还有很多存储空间空闲。所以为了最大限度地利用其事先开辟的存储空间进行操作,我们可以两栈共享空间。
所谓两栈共享空间,其实就是指事先开辟一个数组,其两端分别作为栈顶,即top1和top2,top1 = -1 表示 1栈空栈,top2 = MAX 表示 2栈空栈。**top1从前向后累加元素,top2从后往前累加元素,直到两指针相遇,也就是**top1+1 == top2时,表示栈满。
以下为两栈共享空间的代码实现:
#include<stdio.h>
#include<stdlib.h>
#define MAX 20
typedef struct{
int date[MAX];
int top1;
int top2;
}doublestack;
doublestack *Initstack(doublestack *stack){ //初始化两栈数组
stack = (doublestack *)malloc(sizeof(doublestack)); //令top1 = -1,top2 = MAX
stack->top1 = -1;
stack->top2 = MAX;
return stack;
}
void push(doublestack *stack){ //选择压1栈或2栈
int index;
while(1){
if(stack->top1 + 1 == stack->top2){
printf("目前栈满!无法继续压栈\n");
return;
}
int choice;
printf("请选择压1栈或压2栈?\n");
scanf("%d",&choice);
if(choice == 1){
index = ++stack->top1;
printf("输入压1栈的数据:");
scanf("%d",&stack->date[index]);
printf("压栈成功!\n");
return;
}
else if(choice == 2){
index = --stack->top2;
printf("请输入压2栈的数据:");
scanf("%d",&stack->date[index]);
printf("压栈成功!\n");
return;
}
else{
printf("输入错误!");
continue;
}
}
}
void pop(doublestack *stack){
int index;
while(1){
int choice;
printf("请选择弹1栈或弹2栈?\n");
scanf("%d",&choice);
if(choice == 1){
index = stack->top1;
if(index == -1){
printf("1栈为空栈!\n");
continue;
}
printf("要弹栈的数据为:%d",stack->date[index]);
stack->top1--;
printf("弹栈成功!\n");
return;
}
else if(choice == 2){
index = stack->top2;
if(index == MAX){
printf("2栈为空栈!\n");
continue;
}
printf("要弹栈的数据为:%d",stack->date[index]);
stack->top2++;
printf("弹栈成功!\n");
return;
}
else{
printf("输入有误!\n");
continue;
}
}
}
void print_stack(doublestack *stack){
int index;
index = stack->top1;
if(index == -1)
printf("1栈为空栈!\n");
else{
printf("1栈:\n");
for(int i = 0; i <= index; i++){
printf("第%d个数据为:",i+1);
printf("%d\n",stack->date[i]);
}
}
index = stack->top2;
if(index == MAX)
printf("2栈为空栈!\n");
else{
printf("2栈:\n");
for(int i = MAX-1; i >= index; i--){
printf("第%d个数据为:",MAX-i);
printf("%d\n",stack->date[i]);
}
}
return;
}
int main(){
doublestack *stack;
stack = Initstack(stack);
int choice = -1;
while(choice != 0){
printf("1. 压栈\n");
printf("2. 弹栈\n");
printf("3. 输出\n");
scanf("%d",&choice);
switch(choice){
case 1: push(stack);
break;
case 2: pop(stack);
break;
case 3: print_stack(stack);
break;
}
}
return 0;
}
3.栈的链式存储结构
不管是栈顺序存储结构还是两栈共享空间,因为都是通过定义数组来实现,所以总会有栈满的情况,这时,我们想起来线性表中的单链表可以实现动态分配内存空间,基本不存在栈满的情况。
因为只能在栈顶进行压栈和弹栈,所以此处不需要头节点。当空栈时,栈结构中的栈顶指针指向NULL,并且count记录了数据元素的个数。
以下是栈的链式存储结构的代码实现:
#include<stdio.h>
#include<stdlib.h>
typedef struct STACKNODE{ //栈结构的单链表
int date;
struct STACKNODE *next;
}stacknode,*linkstack;
typedef struct LinkStack{ //栈结构
linkstack top;
int count;
}link;
link *Initstack(link *stack){ //初始化栈,给栈顶指针赋值NULL
stack = (link *)malloc(sizeof(struct LinkStack));
stack->count = 0;
stack->top = NULL;
return stack;
}
void push(link *stack){
linkstack pnew = (linkstack)malloc(sizeof(stacknode));
printf("请输入压栈数据:");
scanf("%d",&pnew->date);
pnew->next = stack->top;
stack->top = pnew;
stack->count++;
printf("压栈成功!\n");
}
void pop(link *stack){
linkstack temp;
if(stack->top == NULL){
printf("目前为空栈!\n");
return;
}
printf("你要弹栈的数据为:%d",stack->top->date);
temp = stack->top;
stack->top = stack->top->next;
free(temp);
stack->count--;
printf("弹栈成功!\n");
}
void print_stack(link *stack){
linkstack temp;
if(stack->top == NULL){
printf("目前为空栈!\n");
return;
}
temp = stack->top;
int index = 1;
while(temp != NULL){
printf("第%d个数据:",index++);
printf("%d\n",temp->date);
temp = temp->next;
}
}
int main(){
link *stack;
stack = Initstack(stack);
int choice = -1;
while(choice != 0){
printf("1. 压栈\n");
printf("2. 弹栈\n");
printf("3. 输出\n");
scanf("%d",&choice);
switch(choice){
case 1: push(stack);
break;
case 2: pop(stack);
break;
case 3: print_stack(stack);
break;
}
}
}
比较三种栈的结构,如果栈的使用过程中元素变化不可预料,有时很小,有时很大,那么最好是用链栈。反之,如果它的变化在可控范围之内,使用顺序栈会好一点。