【数据结构】栈(顺序存储、链式存储)

前言

栈(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;
}

栈例程1



两栈共享空间

将数组的两端作为两个栈的栈底,栈顶向中间靠拢,那么相比定义两个数组,可以提高空间的利用率。

共享栈

栈的结构定义如下:

typedef struct DOUBLESTACK
{
int data[MAXSIZE];
int top1; // 栈1栈顶指针
int top2; // 栈2栈顶指针
}SqDoubleStack;

进栈操作如下:

(1)当top1top2相邻时,满栈,退出并返回错误;

(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;
}

栈例程2



栈的链式存储结构

线性表有顺序存储和链式存储结构,栈是一种特殊的线性表,那么也有链式存储结构,简称链栈。
将链表的头指针作为栈的栈顶指针,因为有栈顶指针,就不需要链表的头结点了。

链栈

链栈的结构定义如下:

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;
}

栈例程3


顺序栈和链栈的时间复杂度是一样的,都是O(1),如果栈内的元素数量变化小且可控,可以使用顺序栈,链栈因为每个结点都有指针域,增加了一定的内存开销;如果栈内的元素数量不可控,最好使用链表,根据实际需求变化长度,不会造成大量的空间浪费。

posted @   若悲浪  阅读(613)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示