【C数据结构】栈的实现(含完整码源)

目录

1.栈的概述

1.1 什么是栈

 1.2 栈的数据性质:LIFO

 1.3 栈的基本操作

2.栈的实现

2.1 栈的声明

2.2栈的初始化和销毁

2.3  入栈和出栈(增删)

2.4栈的判空

2.5 栈的数据数量

3.完整码源(含测试函数)

结语:栈是基于顺序表或者链表就可以实现的超简单数据结构,但是其LIFO的强大功能,又使在一些功能实现中发挥着极大的作用。

欢迎码友在评论区讨论指正。


1.栈的概述

1.1 什么是栈

栈是一种特殊的线性表,只允许在表的某一端进行数据的插入删除。

栈规定,将进行数据插入删除的这一端称为“栈顶”,另一端则称为“栈底”。

​编辑顺序表和链表可以在任意位置插入新数据,删除同理


栈:只能一端进行数据插入,删除同理

 1.2 栈的数据性质:LIFO

由于栈只能于栈顶一端进行数据的插入和删除,栈中的数据一定遵守先进后出的原则。

可以类比一摞碗进行理解,因为只能在上方放碗,所以最先取走的一定是最后放置的,最先放置的碗一定是最后被取走的。


 1.3 栈的基本操作

push(进栈/压栈/入栈):都是栈的数据插入操作,插入数据只能在栈顶。

pop  (出栈 ):栈的数据删除操作,删除数据只能在栈顶 。

图示: push操作和pop操作

**注意:栈顶是固定的一端, 但栈顶元素top是在栈内流动的,遵循先进后出原则。**

2.栈的实现

前言:栈的实现可以有两种形式:顺序表和链表。我们可以把栈和栈的接口理解为一个插入删除受限的顺序表或链表。

以顺序表为例:只需要实现一个只能在某一端插入数据的顺序表和顺序表接口,就实现了栈。

2.1 栈的声明

使用顺序表来实现栈,栈的成员与顺序表基本相同,只需要添加一个top变量用以维护栈只能在一端删插数据的功能。top是int类型。

typedef struct stack ST;
struct stack
{
	STtype* a;//该栈的指针
	int top;//栈顶(下标)
	//top指向栈顶元素(要初始化为-1,因为此时没有真正的栈顶元素)

	//这里初始化为-1,在后续的++top 入栈就能直接使用了
	//同时top+1==size(stack)
	//top==capacity时候才扩容

	//初始化为0,那么无论栈空,还是只有一个元素,top都为0。
	//同时top指向栈顶的下一个元素
	//top-1==capacity的时候就要扩容了
	int capacity;//顺序表为底层,表最大容量。
};
2.2栈的初始化和销毁

声明栈后,我们可以对栈进行初始化:也就是开辟一个栈,并标记维护栈用的两个成员变量:top和capacity。在使用结束,想要销毁一个栈时,可以进行栈的销毁:释放一个栈同时将指针置空,并标记维护栈用的两个变量。

**注意:这两个函数接口都不包括stack变量本身的申请和释放,需要初始化前自行malloc开辟空间,销毁后自行free掉空间(并置空stack指针)。**

//初始化
void InitST(ST* st)
{
	assert(st);//判空
	
	st->capacity = 20;
	st->a = (STtype*)malloc((st->capacity )* sizeof(STtype));
	st->top = -1;
		
		
}
//销毁
void DestroyST(ST* st)
{
    assert(st);//判空

	free(st->a);
	st->a = NULL;

	st->top = -1;
	st->capacity=0;

}
2.3  入栈和出栈(增删)

栈的增删只能在某一端实现,即栈顶增删。这个特性使我们在声明栈时加入变量top(可以理解为栈顶元素的下标)。

通过对top的维护,实现栈单端的数据增删。

**栈的底层使用顺序表,所以需要考虑扩容问题。**

//扩容
ST* ReallocST(ST* st)
{
	STtype* tmp = (STtype*)realloc(st->a, ((st->capacity) + 10) * sizeof(STtype));
	//先赋值给tmp,避免realloc失败
	assert(tmp);

	st->a = tmp;
	st->capacity += 10;//更新栈最大容量

	return st;
}

//数据入栈
void PushST(ST* st,STtype data)
{
	if (st->top == (st->capacity) - 1)
	{
		st = ReallocST(st);//判断是否需要扩容
	}
	
	st->top += 1;//top上移
	STtype* tmp = (st->a) + (st->top);
	*tmp = data;
	

}
//数据出栈
void  PopST(ST* st)
{
	st->top -= 1;
}

数据入栈:先判断是否需要扩容,然后将top加1得到新插入的栈顶元素下标,赋值。

数据出栈:直接将top-1,将要删除的数据的前一个元素标记为栈顶元素。这里不是实质性地删除了栈中的数据,只需要更换top的指向就能实现栈的删除功能。再下一次top指向这个被删除过的位置时候,就可以直接进行数据覆盖。是很巧妙的算法。

2.4栈的判空

已知top表示栈元素的下标,那么只有当top=-1时,栈为空栈。因为这里是为了测试栈的性质,最好用assert判断栈是否存在。

返回一个bool类型,用%d打印,为0则不是空的,为1则是空栈。

//判断栈是否为空
bool empty(ST* st)
{
	assert(st);

	if (st->top == -1)
	{
		return true;
	}
	else
		return false;
}
2.5 栈的数据数量

用top反应,直接返回top+1

//获取数据个数
int size(ST* st)
{
	int ret = (st->top) + 1;
	return ret;
}

3.完整码源(含测试函数)

//头文件

typedef int STtype;
typedef struct stack ST;
struct stack
{
	STtype* a;//该数据结构的指针
	int top;//栈顶(下标)
	
	int capacity;
};

void InitST(ST* st);
void DestroyST(ST* st);
STtype Top(ST* st);
void PushST(ST* st, STtype data);
void  PopST(ST* st);
bool empty(ST* st);
int size(ST* st);

//stack.c 函数实现

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include"stack.h"

void InitST(ST* st)
{
	assert(st);//判空
	
	st->capacity = 20;
	st->a = (STtype*)malloc((st->capacity )* sizeof(STtype));
	st->top = -1;
		
		//直接初始化栈的空间 栈顶 大小
}
//销毁
void DestroyST(ST* st)
{
	free(st->a);
	st->a = NULL;

	st->top = -1;
	st->capacity=0;

}

//扩容
ST* ReallocST(ST* st)
{
	STtype* tmp = (STtype*)realloc(st->a, ((st->capacity) + 10) * sizeof(STtype));
	//先赋值给tmp,避免realloc失败
	assert(tmp);

	st->a = tmp;
	st->capacity += 10;//更新栈最大容量

	return st;
}
//获取栈顶的数据
STtype Top(ST* st)
{
	return *((st->a) + (st->top));
}

//数据入栈
void PushST(ST* st,STtype data)
{
	if (st->top == (st->capacity) - 1)
	{
		st = ReallocST(st);//判断是否需要扩容
	}
	
	st->top += 1;//top上移
	STtype* tmp = (st->a) + (st->top);
	*tmp = data;
	

}
//数据出栈
void  PopST(ST* st)
{
	st->top -= 1;
}
//判断栈是否为空
bool empty(ST* st)
{
	assert(st);

	if (st->top == -1)
	{
		return true;
	}
	else
		return false;
}
//获取数据个数
int size(ST* st)
{
	int ret = (st->top) + 1;
	return ret;
}

//test.c

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
#include"stack.h"

void test01(ST*test)
{
	printf("测试初始化和入栈\n");
	
	InitST(test);
	PushST(test,1);
	PushST(test,2);
	for (int i = 0; i < test->capacity; i++)
	{
		printf("%d->",*((test->a)+i));
	}
	printf("\n");
}
void test02(ST*test)
{
	printf("测试出栈\n");
	PopST(test);
	for (int i = 0; i < test->capacity; i++)
	{
		printf("%d->", *((test->a) + i));
	}
	printf("\n");
	PopST(test);
	for (int i = 0; i < test->capacity; i++)
	{
		printf("%d->", *((test->a) + i));
	}
	printf("\n");
	PushST(test,10);
	for (int i = 0; i < test->capacity; i++)
	{
		printf("%d->", *((test->a) + i));
	}
	printf("\n");
	PushST(test,10);
	for (int i = 0; i < test->capacity; i++)
	{
		printf("%d->", *((test->a) + i));
	}

}
void test03(ST* st)
{
	printf("\n测试判空\n");
	bool a = empty(st);
	printf("栈现在为空:%d",a);
}
void test04(ST* st)
{
	printf("\n测试返回top\n");
	STtype a = Top(st);
	printf("栈顶元素现在是:%d",a);
}

void test05(ST* st)
{
	printf("\n测试销毁\n");
	DestroyST(st);
	printf("%p\n", st->a);
	printf("%d\n", st->capacity);
	printf("%d\n", st->top);

}

int main()
{
	ST* test = (ST*)malloc(sizeof(ST));
	test01(test);
	test02(test);
	test03(test);
	test04(test);
	test05(test);
	free(test);
	test = NULL;
	return 0;
}
结语:栈是基于顺序表或者链表就可以实现的超简单数据结构,但是其LIFO的强大功能,又使在一些功能实现中发挥着极大的作用。
欢迎码友在评论区讨论指正。
  • 63
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值