【数据结构】栈(顺序存储、链式存储)
前言
栈(stack)是限定仅在表尾插入和删除操作的线性表。我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表。
栈的插入操作,叫做进栈,也称为压栈、入栈。
栈的删除操作,叫做出栈,也称为弹栈。
栈的顺序存储结构
顺序存储就是用数组来实现的栈,是一种特殊的线性表,可以简称为顺序栈。
栈的结构定义如下:
#define MAXSIZE (100) //栈的最大存储数据量 typedef struct STACK { int data[MAXSIZE]; //用于存储数据的数组 int top; //栈顶指针 }SqStack;
进栈操作如下:
(1)检查是否满栈,满栈则退出并返回错误;
(2)栈顶指针top
向上移动一位,将要插入的数据赋给栈顶。
typedef enum { FALSE = 0, TRUE }bool; bool Push(SqStack *S, int data) { if (S->top == MAXSIZE - 1) //满栈 { return FALSE; } S->top++; //栈顶指针加一 S->data[S->top] = data; //将数据赋给栈顶 return TRUE; }
出栈操作如下:
(1)检查是否为空栈,若是则退出并返回错误;
(2)将栈顶数据赋给*data
(函数参数的传递是必须要用指针的,C++最好用引用),栈顶指针向下移动一位。
bool Pop(SqStack *S, int *data) { if (S->top == -1) //空栈 { return FALSE; } *data = S->data[S->top]; //将栈顶数据赋给data S->top--; //栈顶指针减一 return TRUE; }
验证一下:
int main(void) { SqStack S; S.top = -1; int data1; for (int i = 0; i < 10; i++) { Push(&S, i); //向栈中放入0-9的数 } while (Pop(&S, &data1)) //取出栈中所有数据,并打印 { printf("%d ", data1); } return 0; }
两栈共享空间
将数组的两端作为两个栈的栈底,栈顶向中间靠拢,那么相比定义两个数组,可以提高空间的利用率。
栈的结构定义如下:
typedef struct DOUBLESTACK { int data[MAXSIZE]; int top1; // 栈1栈顶指针 int top2; // 栈2栈顶指针 }SqDoubleStack;
进栈操作如下:
(1)当top1
和top2
相邻时,满栈,退出并返回错误;
(2)StackNo
是选择将数据放入栈1还是栈2。
bool Push_Double(SqDoubleStack *S, int data, int StackNo) { if (S->top1 + 1 == S->top2) //满栈 { return FALSE; } if (1 == StackNo) // 判断是放入那个栈中 { S->top1++; S->data[S->top1] = data; } else { S->top2--; S->data[S->top2] = data; } return TRUE; }
出栈操作如下:
(1)根据StackNo
判断从栈1或栈2出栈;
(2)判断栈1或栈2是否为空栈,若为空,则返回错误,否则将栈顶元素赋给*data
,并向栈底移动top
指针。
bool Pop_Double(SqDoubleStack *S, int *data, int StackNo) { if (1 == StackNo) { if (-1 == S->top1) //栈1是空栈 { return FALSE; } *data = S->data[S->top1]; S->top1--; } else { if (MAXSIZE == S->top2) //栈2是空栈 { return FALSE; } *data = S->data[S->top2]; S->top2++; } return TRUE; }
测试程序:
int main(void) { SqDoubleStack DS; /* 栈顶指针初始化 */ DS.top1 = -1; DS.top2 = MAXSIZE; int data1; int data2; for (int i = 0; i < 10; i++) { Push_Double(&DS, i, 1); Push_Double(&DS, -i, 2); } printf("Stack1:\n"); while (Pop_Double(&DS, &data1, 1)) { printf("%d ", data1); //打印栈1内所有数据 } printf("\nStack2:\n"); while (Pop_Double(&DS, &data2, 2)) { printf("%d ", data2); //打印栈2内所有数据 } return 0; }
栈的链式存储结构
线性表有顺序存储和链式存储结构,栈是一种特殊的线性表,那么也有链式存储结构,简称链栈。
将链表的头指针作为栈的栈顶指针,因为有栈顶指针,就不需要链表的头结点了。
链栈的结构定义如下:
typedef struct StackNode //结点 { int data; struct StackNode *next; }StackNode; typedef struct LinkStack //栈顶指针和结点数 { StackNode *top; int count; }LinkStack;
进栈操作如下:
(1)申请一个新结点,存储进栈数据;
(2)将新结点插入到链表的最前端,使其next
指向原先的top
结点,最后使top
指向新结点。
bool Push_Link(LinkStack *S, int data) { StackNode *SNode = (StackNode *)malloc(sizeof(StackNode)); //申请一个结点大小的内存 SNode->data = data; //将数据赋给结点 /* 将新结点插入链表中 */ SNode->next = S->top; S->top = SNode; S->count++; //链表长度+1 return TRUE; }
出栈操作如下:
(1)检查是否为空栈,若是,则返回错误,否则将栈顶指针指向的数据赋给*data
;
(2)删除栈顶结点:定义一个指针p,指向栈顶结点,将栈顶指针top
指向下一个结点,删除原来的栈顶结点。
bool Pop_Link(LinkStack *S, int *data) { if (S == NULL) { return FALSE; } *data = S->top->data; //将栈顶数据赋给*data StackNode *p; p = S->top; //定义一个指针p,指向栈顶结点 S->top = S->top->next; //将栈顶指针指向下一个结点 free(p); //删除原来的栈顶结点 S->count--; return TRUE; }
测试程序:
int main(void) { /* 链栈定义并初始化 */ LinkStack LS; LS.count = 0; LS.top = NULL; int data1; /* 依次进栈0-9 */ for (int i = 0; i < 10; i++) { Push_Link(&LS, i); } /* 出栈并打印所有元素 */ printf("链栈(长度:%d):\n", LS.count); while(LS.count > 0) { Pop_Link(&LS, &data1); printf("%d ", data1); } return 0; }
顺序栈和链栈的时间复杂度是一样的,都是O(1)
,如果栈内的元素数量变化小且可控,可以使用顺序栈,链栈因为每个结点都有指针域,增加了一定的内存开销;如果栈内的元素数量不可控,最好使用链表,根据实际需求变化长度,不会造成大量的空间浪费。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~