本文已收录至《数据结构(C/C++语言)》专栏,欢迎大家 点赞 + 收藏 + 关注 !
目录
前言
正文
栈的数据结构
栈的接口函数
栈的实现
栈的初始化函数
入栈函数
出栈函数
获取栈顶元素函数
获取栈元素个数函数
检查栈空函数
栈销毁函数
总结
前面我们介绍了线性表的两种数据结构顺序结构和链式结构,这两种结构各有优劣,但是今天我们还有另一种数据结构,他是基于线性表衍生出来的特殊线性表,那就是“栈”,关于栈大家可能比较陌生,我们计算机程序在内存上运行时就会有许多栈帧,学完了这一节,大家对栈就会有一定的了解。
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出(先进后出)的原则。
栈的操作:
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
总而言之,栈的增删查改操作只能在栈顶进行!
栈的数据结构
栈的实现一般可以使用顺序表(数组)或者链表实现,相对而言顺序表的结构实现更优一些。因为数组在尾上插入数据的代价比较小。我们总是取最优解进行实现,有兴趣的小伙伴可以尝试实现链式的栈!
// 支持动态增长的顺序栈 typedef int STDataType; typedef struct Stack { STDataType* _a;//空间指针 int _top; // 栈顶 int _capacity; // 容量 }Stack;
栈的接口函数
//动态顺序栈 // 初始化栈 void StackInit(Stack* ps); // 入栈 void StackPush(Stack* ps, STDataType data); // 出栈 void StackPop(Stack* ps); // 获取栈顶元素 STDataType StackTop(Stack* ps); // 获取栈中有效元素个数 int StackSize(Stack* ps); // 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 int StackEmpty(Stack* ps); // 销毁栈 void StackDestroy(Stack* ps);
栈的实现
对于栈,分为栈顶和栈底,可以理解为数组的首地址和数组的最后一个元素地址。
栈的初始化函数
同顺序表一样,栈也有一个指针指向栈的栈底地址(顺序表首地址),有一个栈顶指针,记录栈顶的位置,还有一个记录容量大小的变量,我们在初始化时赋予初始空间4个。
// 初始化栈 void StackInit(Stack* ps) { assert(ps);//检查栈地址是否为空 ps->_a = (STDataType*)malloc(sizeof(STDataType) * 4);//初始化申请四个空间 if (!ps->_a)//检查空间是否申请成功 { perror("malloc fail!\n"); exit(-1); } ps->_capacity = 4;//空间计数为4个 ps->_top = 0;//栈顶置为0 }
入栈函数
我们可以想象入栈就像堆叠砖块一样,一层一层向上叠,在数组中就是元素个数不断增加,top下标(指针)不断后移(变大),每次插入时如果_top与_capacity相等则扩容,我们在写栈时不需要单独写扩容函数,因为涉及扩容的只要入栈函数。所以入栈函数就是在入栈前先检查容量是否已满,满了就扩容,然后直接将数据入栈到栈顶(_top位置),然后_top自加后移即可!
// 入栈 void StackPush(Stack* ps, STDataType data) { assert(ps);//检查空指针 if (ps->_top == ps->_capacity)//扩容 { STDataType* tmp = (STDataType*)realloc(ps->_a, 2 * (ps->_capacity) * sizeof(STDataType));//扩容原来的两倍 ps->_a = tmp;//交付空间 (ps->_capacity) *= 2;//空间翻两倍 } ps->_a[(ps->_top)++] = data;//赋值栈顶-栈顶指针后移 }
出栈函数
对于元素出栈,我们只需要让栈顶指针_top自减即可,但需要检查栈是否为空,如果栈为空则终止操作。有人可能会疑惑不需要销毁被删除栈的数据吗?这里我们不需要担心,因为我们再次入栈时会覆盖该栈的数据。
// 出栈 void StackPop(Stack* ps) { assert(ps);//检查空指针 if (!StackEmpty(ps))//检查栈是否为空 { --(ps->_top);//栈顶指针后移 } }
获取栈顶元素函数
获取栈顶元素时,由于_top指针每次入栈后自加,所以_top指针每次都指向下一个待插入位置,我们只需要将_top指针减一然后通过下标访问就能得到当前的栈顶元素。在获取栈顶元素前我们仍然需要检查栈是否为空,否则会出现越界情况!如果栈为空则返回-1(或其他提示性反馈)。
// 获取栈顶元素 STDataType StackTop(Stack* ps) { assert(ps);//检查空指针 if(!StackEmpty(ps))//检查栈是否为空 return ps->_a[(ps->_top)-1]; return -1;//为空则返回-1(或其他提示性反馈) }
获取栈元素个数函数
这个就非常简单了,我们只需要返回_top即可!
// 获取栈中有效元素个数 int StackSize(Stack* ps) { assert(ps);//检查空指针 return (ps->_top);//返回_top }
检查栈空函数
检查栈是否为空,只需要判断栈顶指针_top是否为0即可,如果为空返回1,如果不为空返回0 。
// 检测栈是否为空,如果为空返回1,如果不为空返回0 int StackEmpty(Stack* ps) { assert(ps);//检查空指针 return (ps->_top == 0); }
栈销毁函数
对于栈的销毁,我们只需要free是释放栈的空间地址指针_a,然后将空间和栈顶指针置0即可。
// 销毁栈 void StackDestroy(Stack* ps) { assert(ps);//检查空指针 free(ps->_a);//释放栈空间 ps->_a = NULL;//栈空间指针置空 ps->_capacity = 0;空间变量置0 ps->_top = 0;元素个数变量置0 }
学完了栈的基础知识,我们可以发现,栈这种数据结构在实现其简单功能非常简单,但是栈的作用一般是辅助其他数据结构实现更复杂的功能的角色,所以掌握栈的基础操作也非常重要!
本次栈的知识分享就暂时先到这里啦,喜欢的读者可以多多点赞收藏和关注!
如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
博客中的所有代码合集:顺序栈
其他文章阅读推荐
数据结构初级<带头双向循环链表>_CSDN博客
数据结构初级<线性表之链表>_CSDN博客
数据结构初级<线性表之顺序表>_CSDN博客
欢迎读者多多浏览多多支持!