C语言数据结构篇——栈的链式存储

简介: C语言数据结构篇——栈的链式存储

初识栈


在上一节我们讲了栈的顺序存储的实现,跟顺序表基本是一个道理,这节我们来讲一下栈的链式存储。栈的链式存储,其实本质还是链表,不过是多了一些栈特有的限制(栈的特有限制和理解大家可以查看我的上一篇博客,点此链接可以直接进入:C语言数据结构篇——栈的顺序存储_Grande joie的博客-CSDN博客)。所以,有一定的链表基础,理解好栈的特点,那么实现栈的链式存储就不是很难了,下面我给大家分享一下我理解的栈的链式存储,希望对你能有所帮助。


栈的链式存储的头结点和数据节点的定义


栈链式存储的头结点和数据节点定义方法和链表的头结点以及数据节点的定义方法是一样的。头结点用于保存整个栈顶的信息,其中包括两个元素,一个是整个栈的大小,另一个就是指向第一个数据节点的指针,这样就可以通过头结点访问整个栈的同时记录整个栈的大小,如下,一个头结点就定义好了;数据节点的结构体就更不用说了,同样分为数据域和指针域,指针域保存的指针用来指向下一个数据节点(当然,这只是我自己的一种写法,并不唯一)


#include<stdio.h>
#include<stdlib.h>
#include<time.h>//下方获取随机数的time需要的工具箱
#define INFINITY 99999
typedef struct node//数据节点结构体
{
    int val;//数据
    struct node* next;//指针
}pnode;
typedef struct seqstack//头结点结构体
{
    int size;//记录栈的大小
    pnode* top;//指向栈顶元素
}stack;//别名方便使用


栈的创建


如上,头结点和数据节点就定义好了,接下来就是创建一个初始化的栈并返回创建好的,直接上代码:


stack*  initstack()//创建栈
{
    stack* istack=(stack*)malloc(sizeof(stack));//为创建的头结点分配空间
    if(istack!=NULL)//创建失败
    {
        istack->top=NULL;
        istack->size=0;
    }
    return istack;//返回创建好的头结点的地址
}


栈的各种操作


栈的定义,创建都已经完成,下面就是进行栈的各种操作:


判断栈为空


这就是我用头结点的写法实现栈的原因之一,栈的头结点记录栈的大小,而栈的大小为0或者头结点中本来应该指向第一数据节点的指针指向了空时,栈就为空了,是不是简单了许多,具体代码如下:


int isempty(stack* istack)//判断栈为空
{
    if(istack->top==NULL)
    {
        return 1;//栈为空
    }
    return 0;//栈不为空
}


获取栈顶元素


因为我们头结点中有指向栈的第一个数据节点,所以我们只需要简单的判断一下栈是否为空之后,不为空的话将栈的第一个数据节点结构体的数据域返回即可, 需要注意的是当链表为空返回时并不能返回常见的整型数字,因为有可能会跟第一个数据节点的数据域重合,出现不必要的错误。具体的代码就是下面这样了啦。


int seqstack_top(stack* istack)//获取栈顶元素
{
    if(isempty(istack)==0)//
    {
        return istack->top->val;
    }
    return INFINITY;//返回无穷大,不能返回-1,有可能栈的顶端元素就是-1
}


弹出栈顶元素


弹出栈顶元素也称为出栈,很多人会将它和获取栈顶元素混淆,其实他们最大的差别就是获取栈的元素并不会该变栈,而弹出栈的元素就会(细品)


int seqstack_pop(stack* istack)//弹出栈顶元素
{
    if(isempty(istack)==0)//栈不为空
    {
        int account=istack->top->val;获取栈顶元素
        istack->top=istack->top->next;头结点的指针指向第二个数据节点
        return account;
    }
    return INFINITY;//同理,不常用的数字即可
}


压栈 (入栈)

有出栈,自然也有入栈,入栈相比于出栈来说更复杂一点,但其实和链表的头插基本是一样的操作,不同的是头插后头结点的变化,具体的看下面的代码就可以了;


void seqstack_push(stack* istack,int x)//压栈(入栈)
{
   pnode* temp;要入栈的数据节点
   temp=(pnode*)malloc(sizeof(pnode));//为数据节点分配内存
   temp->val=x;//填充数据域
   temp->next=istack->top;//入栈的数据节点的指针域指向第一个数据节点
   istack->top=temp;//头结点的指针指向入栈的数据节点
   istack->size++;//记录栈的大小
   return;
}


栈的销毁


无论是栈的头结点或者是数据节点,他们的内存都是分配来的,所以销毁栈只需要释放他们内存就好了,这里直接看代码就好了


void seqstack_destory(stack* istack)//销毁栈
{
    if(isempty(istack)==0)
    {
        free(istack);释放了头结点的就无法找到数据节点,所以销毁头结点即可
    }
}


栈中元素的输出


因为栈定义之后可能面临多次的调用和打印输出,所以我们也可以封装一个遍历打印栈的函数方便后续使用,具体代码如下:


void seqstack_print(stack* istack)
{
    pnode* temp=istack->top;用来遍历数据节点
    for(int i=1;i<=istack->size;i++)
    {
        printf("%d ",temp->val);
        temp=temp->next;
        if(i%5==0)//这步只是为了美观
        {
            printf("\n");
        }
    }
    printf("\n");
}


完整代码


栈的基本操作也差不多完成了,我们用这些封装好的函数来写一个完整的程序吧,这里的注释可能就不是这么明白了,建议从前面看一下。代码如下:


#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define INFINITY 99999
typedef struct node
{
    int val;//数据
    struct node* next;//指针
}pnode;
typedef struct seqstack
{
    int size;//记录栈的大小
    pnode* top;//指向栈顶元素
}stack;
stack*  initstack()//创建栈
{
    stack* istack=(stack*)malloc(sizeof(stack));
    if(istack!=NULL)
    {
        istack->top=NULL;
        istack->size=0;
    }
    return istack;
}
int isempty(stack* istack)//判断栈为空
{
    if(istack->top==NULL)
    {
        return 1;//栈为空
    }
    return 0;//栈不为空
}
int seqstack_top(stack* istack)//获取栈顶元素
{
    if(isempty(istack)==0)//
    {
        return istack->top->val;
    }
    return INFINITY;//返回无穷大,不能返回-1,有可能栈的顶端元素就是-1
}
int seqstack_pop(stack* istack)//弹出栈顶元素
{
    if(isempty(istack)==0)//栈不为空
    {
        int account=istack->top->val;
        istack->top=istack->top->next;
        return account;
    }
    return INFINITY;
}
void seqstack_push(stack* istack,int x)//压栈(入栈)
{
   pnode* temp;
   temp=(pnode*)malloc(sizeof(pnode));
   temp->val=x;
   temp->next=istack->top;
   istack->top=temp;
   istack->size++;
   return;
}
void seqstack_destory(stack* istack)//销毁栈
{
    if(isempty(istack)==0)
    {
        free(istack);
    }
}
void seqstack_print(stack* istack)
{
    pnode* temp=istack->top;
    for(int i=1;i<=istack->size;i++)
    {
        printf("%d ",temp->val);
        temp=temp->next;
        if(i%5==0)
        {
            printf("\n");
        }
    }
    printf("\n");
}
int main()
{
    srand((unsigned)time(0));//以时间为种子获得随机数
    stack* istack=initstack();
    printf("请输入初始栈的容量\n");
    int m;
    scanf("%d",&m);
    for(int i=0;i<m;i++)//将随机数压栈
    {
        seqstack_push(istack,rand()%1000);//取余1000是因为我想把获取的随机数控制在1000以内
    }
    //获取栈顶元素
    printf("栈顶元素:%d\n",seqstack_top(istack));
    printf("栈中的元素\n");
    seqstack_print(istack);
    printf("\n请输入压栈的数据\n");
    int n;
    scanf("%d",&n);
    seqstack_push(istack,n);
    printf("输出压栈后的栈中元素\n");
    seqstack_print(istack);
    printf("弹出栈顶的元素后的栈中元素\n");
    seqstack_pop(istack);
    seqstack_print(istack);
    printf("销毁栈表");
    seqstack_destory(istack);
    return 0;
}


随便写点数据运行一下就是下面这个效果啦


请输入初始栈的容量

10

栈顶元素:983

栈中的元素

983 230 572 693 814

76 68 170 861 401

请输入压栈的数据

999

输出压栈后的栈中元素

999 983 230 572 693

814 76 68 170 861

401

弹出栈顶的元素后的栈中元素

983 230 572 693 814

76 68 170 861 401


大一学生,c语言学习半年,文章有什么不完善的地方还请见谅,欢迎大家对文章提出自己的看法,感谢大家的阅读。  


相关文章
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
638 1
|
7月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
160 0
栈区的非法访问导致的死循环(x64)
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
575 77
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。
|
11月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
278 11
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
475 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
11月前
|
DataX
☀☀☀☀☀☀☀有关栈和队列应用的oj题讲解☼☼☼☼☼☼☼
### 简介 本文介绍了三种数据结构的实现方法:用两个队列实现栈、用两个栈实现队列以及设计循环队列。具体思路如下: 1. **用两个队列实现栈**: - 插入元素时,选择非空队列进行插入。 - 移除栈顶元素时,将非空队列中的元素依次转移到另一个队列,直到只剩下一个元素,然后弹出该元素。 - 判空条件为两个队列均为空。 2. **用两个栈实现队列**: - 插入元素时,选择非空栈进行插入。 - 移除队首元素时,将非空栈中的元素依次转移到另一个栈,再将这些元素重新放回原栈以保持顺序。 - 判空条件为两个栈均为空。
|
11月前
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏
|
12月前
|
搜索推荐 C语言
数据结构(C语言)之对归并排序的介绍与理解
归并排序是一种基于分治策略的排序算法,通过递归将数组不断分割为子数组,直到每个子数组仅剩一个元素,再逐步合并这些有序的子数组以得到最终的有序数组。递归版本中,每次分割区间为[left, mid]和[mid+1, right],确保每两个区间内数据有序后进行合并。非递归版本则通过逐步增加gap值(初始为1),先对单个元素排序,再逐步扩大到更大的区间进行合并,直至整个数组有序。归并排序的时间复杂度为O(n*logn),空间复杂度为O(n),且具有稳定性,适用于普通排序及大文件排序场景。
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
266 9