文章目录
一、栈的概念
栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(PUSH),删除则称为退栈(POP)。栈也称为先进后出表。
生活中栈的相似例子:
- 向橱柜里放取盘子(往最上面放,从最上面取)
- 子弹弹夹
栈的图示:
二、顺序栈
- 一个数组(可扩容) 接受malloc从堆内申请来的连续内存块
- top(int) 栈顶指针 既保存有效值个数,也可以理解为保存下一个元素插入位置
- stack_size 当前空间的总大小,帮助我们扩容用的
顺序栈的头文件与函数声明
#pragma once
//顺序栈的结构体声明
#define INIT_SIZE 10
typedef int ELEM_TYPE;
typedef struct Stack
{
ELEM_TYPE * base;//保存从堆内申请的一整块连续内存
int top;//栈顶指针 和书上有些区别,书上这里用的是指针类型
int stack_size;//保存栈当前可使用的最大容量
}Stack, *PStack;
//对应的栈的相关可执行函数声明:
//初始化
void Init_Stack(struct Stack* ps);
//入栈
bool Push(PStack ps, ELEM_TYPE val);
//出栈 (1.不仅需要告诉我出栈是否成功,2.还需要告诉我出栈的值是多少(借助输出参数))
bool Pop(PStack ps, ELEM_TYPE* rtval);
//获取栈顶元素(1.不仅需要告诉我此操作是否成功,2.还需要告诉我栈顶元素的值是多少(借助输出参数)
bool Top(PStack ps, ELEM_TYPE *rtval);
//获取有效值个数
int Get_length(PStack ps);
//查找
int Search(PStack ps, ELEM_TYPE val);
//打印
void Show(PStack ps);
//判空
bool IsEmpty(PStack ps);
//判满
bool IsFull(PStack ps);
//扩容 // 扩容倍数 *2
void Inc(PStack ps);
//清空
void Clear(PStack ps);
//销毁
void Destroy(PStack ps);
1.初始化顺序栈
从堆区申请一块连续空间,初始化 stack_size ,将栈顶置为0
代码如下:
//初始化
void Init_Stack(struct Stack* ps)
{
//assert
ps->base = (ELEM_TYPE *)malloc(INIT_SIZE * sizeof(struct Stack));
assert(ps->base != NULL);
if(ps->base == NULL)
{
return;
}
ps->top = 0;
ps->stack_size = INIT_SIZE;
}
2.获取有效值个数
直接返回栈顶的值就是栈的有效值个数
代码如下:
//获取有效值个数
int Get_length(PStack ps)
{
//assert
return ps->top;
}
3.判空与判满
3.1判空
如果栈顶指针的值为0的话,就说明他是一个空栈,返回true,否则返回false
//判空
bool IsEmpty(PStack ps)
{
return ps->top == 0;
}
3.2判满
当栈顶指针的值等于 stack_size是说明栈已满
//判满
bool IsFull(PStack ps)
{
//assert ps
return ps->top == ps->stack_size;
}
4.扩容
先判满,如果栈满的话,开始扩容
使用realloc函数,重新分配内存大小
扩容方式:
先看原空间后面有没有足够的空间分配,有的话直接续上。
后面如果没有足够的空间,重新找一块足够大的内存,重新分配。将原有内存数据拷贝过来,然后释放掉。
后面连续闲置空间不够,且重新找一块也找不到,则realloc失败
代码如下 :
//扩容 // 扩容倍数 *2
void Inc(PStack ps)
{
ps->base = (ELEM_TYPE *)realloc(ps->base, ps->stack_size*sizeof(Stack) * 2);
assert(ps->base != NULL);
ps->stack_size *= 2;
}
5.入栈
通过栈顶指针直接找到栈顶然后插入,如果满栈的话,扩容
//入栈
bool Push(PStack ps, ELEM_TYPE val)
{
//直接找到栈顶 进行插入(warning:一定记着插入结束后,让栈顶指针挪动一下)
//assert ps 不能满 满的话得扩容
assert(ps != NULL);
if(IsFull(ps))
{
Inc(ps);
}
//执行到这一块 肯定有格子让我们存放val
/*
ps->base[ps->top] = val;
ps->top++;
*/
ps->base[ps->top++] = val;
return true;
}
6.出栈
1.不仅需要告诉我出栈是否成功
2.还需要告诉我出栈的值是多少(借助输出参数)
首先判空,不为空的话直接使栈顶指针-1 即可(不需要重置数据)
//出栈
bool Pop(PStack ps, ELEM_TYPE* rtval)
{
//assert ps rtval
if(IsEmpty(ps))
{
return false;
}
/*
ps->top--;
*rtval = ps->base[ps->top];
*/
*rtval = ps->base[--ps->top];
return true;
}
7.获取栈顶元素
1.不仅需要告诉我此操作是否成功
2.还需要告诉我栈顶元素的值是多少(借助输出参数)
返回栈顶指针-1的值(不修改栈顶指针)
//获取栈顶元素
bool Top(PStack ps, ELEM_TYPE *rtval)
{
//assert ps rtval
if(IsEmpty(ps))
{
return false;
}
*rtval = ps->base[ps->top-1];
return true;
}
8.查找
通过for循环遍历栈内元素,返回下标
//查找
int Search(PStack ps, ELEM_TYPE val)
{
//assert
for(int i=0; i<ps->top; i++)
{
if(ps->base[i] == val)
{
return i;
}
}
return -1;
}
9.清空与销毁
9.1清空
malloc 申请来的格子不用释放 只需要将有效值个数重新赋值为0即可
也不需要修改数据
//清空
void Clear(PStack ps)
{
ps->top = 0;//清空 == 只要认为里面的值都是无效值即可 不需要修改里面的值
}
9.2销毁
malloc 申请来的格子释放掉
void Destroy(PStack ps)
{
free(ps->base);
ps->base = NULL; //避免出现野指针 并且还可以防止重复释放导致出现崩溃
ps->top = ps->stack_size = 0; //洗干净衣服 再扔掉
}
顺序栈源文件与函数实现
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "stack.h"
//初始化
void Init_Stack(struct Stack* ps)
{
//assert
ps->base = (ELEM_TYPE *)malloc(INIT_SIZE * sizeof(struct Stack));
assert(ps->base != NULL);
if(ps->base == NULL)
{
return;
}
ps->top = 0;
ps->stack_size = INIT_SIZE;
}
//入栈
bool Push(PStack ps, ELEM_TYPE val)
{
//直接找到栈顶 进行插入(warning:一定记着插入结束后,让栈顶指针挪动一下)
//assert ps 不能满 满的话得扩容
assert(ps != NULL);
if(IsFull(ps))
{
Inc(ps);
}
//执行到这一块 肯定有格子让我们存放val
/*ps->base[ps->top] = val;
ps->top++;*/
ps->base[ps->top++] = val;
return true;
}
//出栈 (1.不仅需要告诉我出栈是否成功,2.还需要告诉我出栈的值是多少(借助输出参数))
bool Pop(PStack ps, ELEM_TYPE* rtval)
{
//assert ps rtval
if(IsEmpty(ps))
{
return false;
}
/*ps->top--;
*rtval = ps->base[ps->top];*/
*rtval = ps->base[--ps->top];
return true;
}
//获取栈顶元素(1.不仅需要告诉我此操作是否成功,2.还需要告诉我栈顶元素的值是多少(借助输出参数)
bool Top(PStack ps, ELEM_TYPE *rtval)
{
//assert ps rtval
if(IsEmpty(ps))
{
return false;
}
*rtval = ps->base[ps->top-1];
return true;
}
//获取有效值个数
int Get_length(PStack ps)
{
//assert
return ps->top;
}
//查找
int Search(PStack ps, ELEM_TYPE val)
{
//assert
for(int i=0; i<ps->top; i++)
{
if(ps->base[i] == val)
{
return i;
}
}
return -1;
}
//打印
void Show(PStack ps)
{
//assert
for(int i=0; i<ps->top; i++)
{
printf("%d ", ps->base[i]);
}
printf("\n");
}
//判空
bool IsEmpty(PStack ps)
{
return ps->top == 0;
}
//判满
bool IsFull(PStack ps)
{
//assert ps
return ps->top == ps->stack_size;
}
//扩容 // 扩容倍数 *2
void Inc(PStack ps)
{
ps->base = (ELEM_TYPE *)realloc(ps->base, ps->stack_size*sizeof(Stack) * 2);
assert(ps->base != NULL);
ps->stack_size *= 2;
}
//清空(malloc 申请来的格子不用释放 只需要将有效值个数重新赋值为0即可)
void Clear(PStack ps)
{
ps->top = 0;//清空 == 只要认为里面的值都是无效值即可 不需要修改里面的值
}
//销毁(malloc 申请来的格子释放掉)
void Destroy(PStack ps)
{
free(ps->base);
ps->base = NULL; //避免出现野指针 并且还可以防止重复释放导致出现崩溃
ps->top = ps->stack_size = 0; //洗干净衣服 再扔掉
}
三、链栈
例如:入栈 1 2 3 4 5
我们只需要在头部进行插入和删除就可以完美模拟栈的运行,还有一个好处(时间复杂度很低)
所以链栈的结构体设计只需要使用单链表的结构体设计即可
链栈的头文件及函数声明
#pragma once
typedef int ELEM_TYPE;
//链栈的结构体设计只需要使用单链表的结构体设计即可(用头插和头删来模拟入栈和出站)
typedef struct LStack
{
ELEM_TYPE data;//数据域
struct LStack * next;//指针域
}LStack, *PLStack;
//对应的栈的相关可执行函数声明:
//初始化
void Init_LStack(struct LStack* ps);
//入栈
bool Push(PLStack ps, ELEM_TYPE val);
//出栈 (1.不仅需要告诉我出栈是否成功,2.还需要告诉我出栈的值是多少(借助输出参数))
bool Pop(PLStack ps, ELEM_TYPE* rtval);
//获取栈顶元素(1.不仅需要告诉我此操作是否成功,2.还需要告诉我栈顶元素的值是多少(借助输出参数)
bool Top(PLStack ps, ELEM_TYPE *rtval);
//获取有效值个数
int Get_length(PLStack ps);
//查找
struct LStack* Search(PLStack ps, ELEM_TYPE val);
//打印
void Show(PLStack ps);
//判空
bool IsEmpty(PLStack ps);
//判满 (链式结构不需要判满)
//清空
void Clear(PLStack ps);
//销毁
void Destroy1(PLStack ps);
void Destroy2(PLStack ps);
1.初始化
初始化头节点
//初始化
void Init_LStack(struct LStack* ps)
{
//assert
//ps->data;//头结点的数据域不使用
ps->next = NULL;
}
2.入栈
直接使用单链表头插法
首先通过 **malloc()**函数申请一个节点,保存val值
然后把头节点指向的next的地址赋值给 s->next
最后 把s 的地址赋值给头节点的next域
//入栈(头插)
bool Push(PLStack ps, ELEM_TYPE val)
{
//assert
//购买新节点
PLStack s = (struct LStack *)malloc(sizeof(struct LStack));
assert(s != NULL);
s->data = val;
//头部插入
s->next = ps->next;
ps->next = s;
return true;
}
3.出栈
单链表头删
- 不仅需要告诉我出栈是否成功
- 还需要告诉我出栈的值是多少(借助输出参数)
1.申请一个临时指针指向待删除节点
2.跨越指向,将待删除节点跨越过去,让p->next赋值给 ps->next
3.释放待删除节点
代码如下:
//出栈 (头删)
bool Pop(PLStack ps, ELEM_TYPE* rtval)
{
//assert
if(IsEmpty(ps))//是一个空栈的话
{
return false;
}
*rtval = ps->next->data;//将一会要出栈的值 通过rtval带出来
//1.申请一个临时指针指向待删除节点
struct LStack * p = ps->next; //因为上面判空过, 所以这里p还不为NULL
//2.跨越指向,将待删除节点跨越过去
ps->next = p->next;
//3.释放待删除节点
free(p);
return true;
}
4.判空
如果 头指针的next域为NULL 的话,说明为空栈
bool IsEmpty(PLStack ps)
{
return ps->next == NULL;
}
5.查找
通过遍历链表,找到链表内数据域与val值相同的节点,返回该节点的地址
//查找
struct LStack* Search(PLStack ps, ELEM_TYPE val)
{
//assert
for(LStack *p=ps->next; p!=NULL; p=p->next)
{
if(p->data == val)
{
return p;
}
}
return NULL;
}
6.销毁
6.1第一种销毁方法
无限头删,直到头节点的next域为NULL 即为空栈
//销毁
void Destroy1(PLStack ps)
{
//无限头删
while(ps->next != NULL)
{
LStack * p = ps->next;
ps->next = p->next;
free(p);
}
ps->next = NULL;
}
6.2第二种销毁方式
提前踢出去头节点,借助两个临时指针
使p指向第一个节点,ps->next = NULL
当p!=NULL的时候 使q = p->next; 然后释放p
然后使 p = q
此时完成一次循环
再使 p = p->next; free(q);
直到 p ==NULL 的时候结束循环;此时销毁完成
链栈的源文件与函数实现
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include "list_stack.h"
//对应的栈的相关可执行函数声明:
//初始化
void Init_LStack(struct LStack* ps)
{
//assert
//ps->data;//头结点的数据域不使用
ps->next = NULL;
}
//入栈(头插)
bool Push(PLStack ps, ELEM_TYPE val)
{
//assert
//购买新节点
PLStack s = (struct LStack *)malloc(sizeof(struct LStack));
assert(s != NULL);
s->data = val;
//头部插入
s->next = ps->next;
ps->next = s;
return true;
}
//出栈 (头删)(1.不仅需要告诉我出栈是否成功,2.还需要告诉我出栈的值是多少(借助输出参数))
bool Pop(PLStack ps, ELEM_TYPE* rtval)
{
//assert
if(IsEmpty(ps))//是一个空栈的话
{
return false;
}
*rtval = ps->next->data;//将一会要出栈的值 通过rtval带出来
//1.申请一个临时指针指向待删除节点
struct LStack * p = ps->next; //因为上面判空过, 所以这里p还不为NULL
//2.跨越指向,将待删除节点跨越过去
ps->next = p->next;
//3.释放待删除节点
free(p);
return true;
}
//获取栈顶元素(1.不仅需要告诉我此操作是否成功,2.还需要告诉我栈顶元素的值是多少(借助输出参数)
bool Top(PLStack ps, ELEM_TYPE *rtval)
{
//assert
if(IsEmpty(ps))//是一个空栈的话
{
return false;
}
*rtval = ps->next->data;
return true;
}
//获取有效值个数
int Get_length(PLStack ps)
{
//用不需要前驱操作的for循环
int count = 0;//计数器 保存有效值个数
for(LStack *p=ps->next; p!=NULL; p=p->next)
{
count++;
}
return count;
}
//查找
struct LStack* Search(PLStack ps, ELEM_TYPE val)
{
//assert
for(LStack *p=ps->next; p!=NULL; p=p->next)
{
if(p->data == val)
{
return p;
}
}
return NULL;
}
//打印
void Show(PLStack ps)
{
for(LStack *p=ps->next; p!=NULL; p=p->next)
{
printf("%d ", p->data);
}
printf("\n");
}
//判空
bool IsEmpty(PLStack ps)
{
return ps->next == NULL;
}
//判满 (链式结构不需要判满)
//清空
void Clear(PLStack ps)
{
Destroy1(ps);
}
//销毁
void Destroy1(PLStack ps)
{
//无限头删
while(ps->next != NULL)
{
LStack * p = ps->next;
ps->next = p->next;
free(p);
}
ps->next = NULL;
}
//不借助头结点,要用到两个临时指针
void Destroy2(PLStack ps)
{
//assert ps!=NULL
PLStack p = ps->next;
PLStack q = NULL;
ps->next = NULL;//将头结点提前剔出去
while(p != NULL)
{
q = p->next;
free(p);
p = q;
}
}