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

目录

文章目录

前言

一、顺序栈

1.顺序栈头文件及函数声明

2.初始化

3.扩容

4.入栈

5.获取栈顶元素 

 6.出栈

 7.清空与销毁

1.清空

2.销毁

8.顺序栈源文件及整体函数实现 

二、链栈 

1.链栈头文件及函数声明

2.初始化

3.结点申请

4.入栈

5.获取栈顶元素 

 6.出栈

 7.清空与销毁

1.清空

2.销毁

8.顺序栈源文件及整体函数实现 

总结
 


前言

栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈类似于子弹弹夹,入栈就像是装弹,出栈就像是退弹。

因为其是受到限制的线性表,则可以有两种实现方式,顺序表实现和链表实现,以下为实现方式:


一、顺序栈

1.顺序栈头文件及函数声明

我们可以新建头文件"stack.h"对顺序表函数声明进行保存,方便我们后期查看及使用

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define INIT_SIZE 10

typedef int Element;

typedef struct Stack
{
	Element* base;
	int top; //存在 Element *top 类型通过指针指向栈顶元素 ,此处int类型是通过数组访问
	int stackSize;
}Stack,*PStack;

//初始化栈
void Init_Stack(PStack ps);

//入栈
bool Push(PStack ps, Element val);

//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval);//rtval 是输出参数 帮助函数返回其他信息

//获取栈顶元素
bool Top(PStack ps, Element* rtval);

//获取有效元素个数
int GetSize(PStack ps);

//判空
bool IsEmpty(PStack ps);

//判满
bool IsFull(PStack ps);

//扩容函数
void Inc(PStack ps);

//清空
void Clear(PStack ps);

//销毁
void Destroy(PStack ps);

//打印
void Show(PStack ps);

2.初始化

我们需要一个栈的表头来帮助我们储存相关信息:

1.一个指向顺序栈的指针;

2.栈顶指针(实际为顺序表中下一个元素的插入位置,也是栈中有效值个数)

3.栈空间的总大小(可帮助我们扩容)

那么我们可以定义如下的一个结构体:

#define INIT_SIZE 10

typedef int Element;

typedef struct Stack
{
	Element* base;
	int top; //存在 Element *top 类型通过指针指向栈顶元素 ,此处int类型是通过数组访问
	int stackSize;
}Stack,*PStack;

接着我们实现一个初始化函数来对以上内容进行初始化:

//初始化栈
void Init_Stack(PStack ps)
{
	assert(ps != NULL);
    //为栈空间申请内存
	ps->base = (Element*)malloc(INIT_SIZE * sizeof(Element));
	if (ps->base == NULL)
	{
		exit(1);
	}
	ps->top = 0;
	ps->stackSize = INIT_SIZE;
}

 完成以上操作,我们就得到一个简单的空的顺序栈,接下来可以对其进行相关操作。

3.扩容

当我们对更多数据进行存储时,我们就需要扩充栈的大小,来进一步保存数据。

在这里以3/2倍扩容。

//扩容函数
void Inc(PStack ps)
{
	assert(ps != NULL && ps->base != NULL);
	ps->base = (Element*)realloc(ps->base, sizeof(Element)  * ps->stackSize * 2);
	if (ps->base == NULL)
	{
		exit(1);
	}
	ps->stackSize = ps->stackSize * 3 / 2;
}

封装扩容函数,可方便对数据的保存,减少为了扩容产生代码冗余。

4.入栈

对数据进行入栈,即是直接对栈中顺序表下一个空位置进行赋值操作。

//入栈
bool Push(PStack ps, Element val)
{
	assert(ps != NULL);
	if (ps == NULL)
	{
		return false;
	}
	if (IsFull(ps))//栈满需扩容
	{
		Inc(ps);
	}
	//入栈
	ps->base[ps->top] = val;
	ps->top++;//或者添加至上一步,这里因为后期理解方便

	return true;
}

5.获取栈顶元素 

这里与出栈基本相同,但是无需删除数据。

rtval是输出参数,帮助获取栈顶元素。

//获取栈顶元素   与出栈不同的是 不用减top
bool Top(PStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->base[ps->top - 1];
	return true;
}

 6.出栈

rtval是输出参数,帮助获取出栈元素。

//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval)//rtval 是输出参数 帮助函数返回其他信息
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	ps->top--;
	*rtval = ps->base[ps->top];
	return true;
}

 7.清空与销毁

1.清空

当栈中数据无需使用,我们可以对栈进行清空操作,实际即是将栈顶指针置为0;

//清空
void Clear(PStack ps)
{
	assert(ps != NULL);
	ps->top = 0;
}

2.销毁

销毁相当于进行初始化相反的操作,将向计算机申请的内存释放,将容量置为0。

//销毁
void Destroy(PStack ps)
{
	free(ps->base);
	ps->base = NULL;
	ps->stackSize = ps->top = 0;
}

8.顺序栈源文件及整体函数实现 

源文件:"stack.c"

#include "stack.h"

//初始化栈
void Init_Stack(PStack ps)
{
	assert(ps != NULL);
	ps->base = (Element*)malloc(INIT_SIZE * sizeof(Element));
	if (ps->base == NULL)
	{
		exit(1);
	}
	ps->top = 0;
	ps->stackSize = INIT_SIZE;
}

//入栈
bool Push(PStack ps, Element val)
{
	assert(ps != NULL);
	if (ps == NULL)
	{
		return false;
	}
	if (IsFull(ps))//栈满需扩容
	{
		Inc(ps);
	}
	//入栈
	ps->base[ps->top] = val;
	ps->top++;//或者添加至上一步,这里因为后期理解方便

	return true;
}

//出栈(如果出栈成功,需要告诉我出栈的值是多少,如果出栈失败,则无所谓)
bool Pop(PStack ps, Element* rtval)//rtval 是输出参数 帮助函数返回其他信息
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	ps->top--;
	*rtval = ps->base[ps->top];
	return true;
}

//获取栈顶元素   与出栈不同的是 不用减top
bool Top(PStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->base[ps->top - 1];
	return true;
}

//获取有效元素个数
int GetSize(PStack ps)
{
	return ps->top;
}

//判空
bool IsEmpty(PStack ps)
{
	return ps->top == 0;
}

//判满
bool IsFull(PStack ps)
{
	assert(ps != NULL);
	return ps->stackSize == ps->top;
}

//扩容函数
void Inc(PStack ps)
{
	assert(ps != NULL && ps->base != NULL);
	ps->base = (Element*)realloc(ps->base, sizeof(Element)  * ps->stackSize * 2);
	if (ps->base == NULL)
	{
		exit(1);
	}
	ps->stackSize = ps->stackSize * 3 / 2;
}

//清空
void Clear(PStack ps)
{
	assert(ps != NULL);
	ps->top = 0;
}

//销毁
void Destroy(PStack ps)
{
	free(ps->base);
	ps->base = NULL;
	ps->stackSize = ps->top = 0;
}

void Show(PStack ps)
{
	assert(ps != NULL);
	for (int i = 0; i < ps->top; i++)
	{
		printf("%d ", ps->base[i]);
	}
	printf("\n");
}

二、链栈 

1.链栈头文件及函数声明

我们可以新建头文件"lstack.h"对顺序表函数声明进行保存,方便我们后期查看及使用

#pragma once

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>

typedef int Element;

typedef struct LStack
{
	Element data;//数据域
	struct LStack* next;// 指针域
}LStack,*PLStack;

//初始化链栈
void Init_LStack(PLStack ps);

//购买新结点
PLStack BuyNode();

//释放结点
void FreeNode(PLStack p);

//入栈
bool Push(PLStack ps, Element val);

//出栈
bool Pop(PLStack ps, Element* rtval);

//获取栈顶元素
bool Top(PLStack ps, Element* rtval);

//获取有效数据个数
int GetSize(PLStack ps);

//判空
bool IsEmpty(PLStack ps);

//清空
void Clear(PLStack ps);

//销毁
void Destroy(PLStack ps);

//打印
void Show(PLStack ps);

2.初始化

此处链栈的基础结点与单链表基本相同,但只是相较单链表来说,其权限被限制了一部分。

typedef int Element;

typedef struct LStack
{
	Element data;//数据域
	struct LStack* next;// 指针域
}LStack,*PLStack;

接着我们实现一个初始化函数来对以上内容进行初始化:

//初始化链栈
void Init_LStack(PLStack ps)
{
	assert(ps != NULL);
	ps->next = NULL;
}

 完成以上操作,我们就得到一个简单的空的链栈,接下来可以对其进行相关操作。

3.结点申请

因为链表的结构特性,我们需要对结点进行多次申请,为了方便进行其他函数操作,并且为了优化代码,我们将结点的申请封装于一个函数

//购买新结点
PLStack BuyNode()
{
	PLStack node = (PLStack)calloc(1, sizeof(LStack));
	if (node == NULL)
	{
		exit(1);
	}
	return node;
}

4.入栈

因为单链表的特性,要实现对栈顶的操作,即是直接对链表进行头部操作即可

对数据进行入栈,即是直接对栈链表进行头插操作。

//入栈
bool Push(PLStack ps, Element val)
{
	assert(ps != NULL);
	if (ps == NULL)
	{
		return false;
	}
	PLStack newnode = BuyNode();
	newnode->data = val;
	newnode->next = ps->next;
	ps->next = newnode;
	return true;
}

5.获取栈顶元素 

这里与出栈基本相同,但是无需删除数据。

rtval是输出参数,帮助获取栈顶元素。

//获取栈顶元素
bool Top(PLStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->next->data;
	return true;
}

 6.出栈

rtval是输出参数,帮助获取出栈元素。

//出栈
bool Pop(PLStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->next->data;
	PLStack delnode = ps->next;
	ps->next = delnode->next;
	FreeNode(delnode);
	return true;
}

 7.清空与销毁

1.清空

当栈中数据无需使用,我们可以对栈进行清空操作,此处使用不断出栈的操作;

//清空
void Clear(PLStack ps)
{
	assert(ps != NULL);
	int rtval;
	while (!IsEmpty(ps))
	{
		Pop(ps, &rtval);
	}
}

2.销毁

销毁相当于进行初始化相反的操作,将向计算机申请的内存释放清空,并将链表头置为NULL。

//销毁
void Destroy(PLStack* ps)
{
	Clear(*ps);
	*ps = NULL;
}

8.顺序栈源文件及整体函数实现 

#include "lstack.h"

//初始化链栈
void Init_LStack(PLStack ps)
{
	assert(ps != NULL);
	ps->next = NULL;
}

//购买新结点
PLStack BuyNode()
{
	PLStack node = (PLStack)calloc(1, sizeof(LStack));
	if (node == NULL)
	{
		exit(1);
	}
	return node;
}

//释放结点
void FreeNode(PLStack p)
{
	free(p);
	p = NULL;
}

//入栈
bool Push(PLStack ps, Element val)
{
	assert(ps != NULL);
	if (ps == NULL)
	{
		return false;
	}
	PLStack newnode = BuyNode();
	newnode->data = val;
	newnode->next = ps->next;
	ps->next = newnode;
	return true;
}

//出栈
bool Pop(PLStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->next->data;
	PLStack delnode = ps->next;
	ps->next = delnode->next;
	FreeNode(delnode);
	return true;
}

//获取栈顶元素
bool Top(PLStack ps, Element* rtval)
{
	assert(ps != NULL);
	if (IsEmpty(ps))
	{
		return false;
	}
	*rtval = ps->next->data;
	return true;
}

//获取有效数据个数
int GetSize(PLStack ps)
{
	assert(ps != NULL);
	int num = 0;
	PLStack node = ps->next;
	while (node != NULL)
	{
		num++;
		node = node->next;
	}
	return num;
}

//判空
bool IsEmpty(PLStack ps)
{
	assert(ps != NULL);
	return ps->next == NULL;
}

//清空
void Clear(PLStack ps)
{
	assert(ps != NULL);
	int rtval;
	while (!IsEmpty(ps))
	{
		Pop(ps, &rtval);
	}
}

//销毁
void Destroy(PLStack ps)
{
	Clear(ps);
	ps = NULL;
}

//打印
void Show(PLStack ps)
{
	assert(ps != NULL);
	PLStack node = ps->next;
	for (int i = 0; i < GetSize(ps); i++)
	{
		printf("%d ", node->data);
		node = node->next;
	}
	printf("\n");
}

总结

以上就是今天要讲解的内容,本文简单介绍了栈的代码实现(包括链栈与顺序栈),栈为我们提供了数据结构的基础思想,能使我们快速便捷地处理数据的函数和方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值