【数据结构】栈 (顺序栈+链栈)

一、栈的概念

栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。栈具有记忆作用,对栈的插入与删除操作中,不需要改变栈底指针。
栈是允许在同一端进行插入和删除操作的特殊线性表。允许进行插入和删除操作的一端称为栈顶(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;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shang_Jianyu_ss

感谢大哥

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值