C语言_数据结构总结6:链式栈

纯c语言代码,不涉及C++

顺序栈的实现,欢迎查看这篇文章:C语言_数据结构总结5:顺序栈-CSDN博客

0. 结构单元

#include<stdio.h>

#include<stdlib.h>

typedef int ElemType;

typedef struct Linknode {

ElemType data; //数据域

struct Linknode* next; //指针域

}LinkStack;

1.1 初始化(返回头结点) 推荐

cs 复制代码
LinkStack* InitLinkStack1() {
	LinkStack* L = (LinkStack*)malloc(sizeof(LinkStack));  //定义一个头结点(栈顶结点)
	if (L == NULL) {
		printf("内存分配失败\n");
		return NULL;
	}
	L->next = NULL;
	return L;
}

1.2 初始化方法2(不返回头结点指针) 不推荐

cs 复制代码
void InitLinkStack2(LinkStack** L) {
	*L = (LinkStack*)malloc(sizeof(LinkStack));  //定义一个头结点
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return;  //提前结束该函数
	}
	(*L)->next = NULL;
}

首先:在链式栈的初始化操作中,返回头结点的指针是一种常见且推荐的做法,但并不是绝对必须的,

推荐返回头结点指针的原因:
1.方便后续操作

返回头结点的指针可以让调用者方便地对链式栈进行后续操作。因为链式栈的各种操作(如入栈、出栈、获取栈顶元素等)都需要通过头结点来访问栈中的元素,调用者持有头结点的指针后,就可以直接将其作为参数传递给这些操作函数

2.符合模块化设计原则

将初始化操作设计为返回头结点指针,使得初始化函数的功能更加独立和清晰。调用者可以明确知道初始化函数会返回一个指向链式栈头结点的指针,便于在不同的代码模块中复用该函数。

2. 1 判空方法1 (采用指针传递) 推荐

cs 复制代码
int LinkStackEmpty1(LinkStack* L) {
	return L->next == NULL;
}

2.2 判空方法2 (采用值传递) 不推荐

cs 复制代码
int LinkStackEmpty2(LinkStack L) {
	return L.next == NULL;
}

**首先:**从语法和功能角度来看,传入 LinkStack L 是可行的。

因为判空操作仅仅是读取栈的信息,不涉及对栈结构的修改,所以即使采用值传递,也能正确完成判空的逻辑。

在上述代码中,LinkStackEmpty2 函数接收的是 LinkStack 类型的变量,通过判断其 next 指针是否为 NULL 来确定栈是否为空。

不建议值传递(传入LinkStack L)的原因(其它类似操作以此类推):
1. 内存开销较大

当使用值传递(传入 LinkStack L)时,会在函数调用时复制整个 LinkStack 结构体。

对于链式栈来说,虽然结构体本身可能不大,但复制操作仍然会带来额外的内存开销。而使用指针传递(传入 LinkStack* L),只需要复制一个指针,其内存开销远远小于复制整个结构体。

2. 性能问题

值传递的复制操作会消耗一定的时间,尤其是在结构体较大或者频繁调用判空函数的情况下,会对程序的性能产生影响。指针传递则避免了这种复制操作,能够提高程序的执行效率。

3. 代码一致性

在链式栈的其他操作(如入栈、出栈等)中,通常需要修改栈的结构,因此需要使用指针传递。为了保持代码的一致性,建议在判空操作中也使用指针传递,这样可以让代码更加统一和易于理解。

3. 入栈

cs 复制代码
int push(LinkStack* L, ElemType value) {
	LinkStack* s = (LinkStack*)malloc(sizeof(LinkStack));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = L->next;
	L->next = s;
	return 0;  //入栈(插入成功)
}

4. 出栈

value:用于存储出栈元素的值,是一个指向元素类型的指针

cs 复制代码
int pop(LinkStack* L, ElemType* value) {
	if (LinkStackEmpty1(L))
	{
		printf("栈空,无法出栈!\n");
		return -2;
	}
	LinkStack* p = L->next;
	*value = p->data;
	L->next = p->next;
	free(p);
	return 0;  //出栈成功
 }

5. 获取栈顶元素

cs 复制代码
int gettopValue(LinkStack* L, ElemType* value) {
	if (LinkStackEmpty1(L))
	{
		printf("栈空,无法获取元素!\n");
		return -2;
	}
	LinkStack* p = L->next;
	*value = p->data;  // 合并:*value = L->next->data
	return 0;  //获取栈顶信息成功
}

6. 打印栈内元素

cs 复制代码
void printLinkStack(LinkStack L) {
	if (LinkStackEmpty1(&L))
	{
		printf("栈空,无法获取元素!\n");
		return;
	}
	LinkStack* p = L.next;
	printf("栈中的元素(从栈顶到栈底)为: ");
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n-----------------------------------------------\n");
}

7.销毁

cs 复制代码
void destroyLinkStack(LinkStack* L) {
	LinkStack* p = L;
	while (p != NULL) {
		LinkStack* s = p;
		p = p->next;
		free(s);
	}
}

8.测试

cs 复制代码
int main() {
	//第二种初始化方式
	LinkStack* L1;  // 定义一个头指针
	InitLinkStack2(&L1);  // 初始化时,将该头指针指向了头结点


	//第一种初始化方式
	LinkStack* L = InitLinkStack1();
	if (L == NULL)
	{
		return -1;  
	}

	printLinkStack(*L);  // 栈空,无法获取元素!

	// 测试进栈操作
	push(L, 11);
	push(L, 22);
	push(L, 33);
	printLinkStack(*L);  // 栈中的元素(从栈顶到栈底)为: 33 22 11

	// 获取栈顶元素
	ElemType value;
	if (gettopValue(L,&value) == 0)
	{
		printf("当前栈顶元素为:%d\n", value);  // 当前栈顶元素为:33
	}

	// 测试出栈操作
	if (pop(L, &value) == 0) {
		printf("出栈的元素为:%d\n", value);  // 出栈的元素为:33
	}
	printf("元素出栈后,");
	printLinkStack(*L);  // 元素出栈后,栈中的元素(从栈顶到栈底)为: 22 11

	// 销毁栈
	destroyLinkStack(L);

	return 0;
}

9. 完整代码

cs 复制代码
//链式栈的基本实现
//头结点的下一个结点是栈顶元素
#include<stdio.h>
#include<stdlib.h>

typedef int ElemType;
typedef struct Linknode {
	ElemType data;  //数据域
	struct Linknode* next;  //指针域
}LinkStack;

// 操作1------初始化(返回头结点) 推荐
LinkStack* InitLinkStack1() {
	LinkStack* L = (LinkStack*)malloc(sizeof(LinkStack));  //定义一个头结点(栈顶结点)
	if (L == NULL) {
		printf("内存分配失败\n");
		return NULL;
	}
	L->next = NULL;
	return L;
}

// 操作1.1------初始化(不返回头结点指针) 不推荐
void InitLinkStack2(LinkStack** L) {
	*L = (LinkStack*)malloc(sizeof(LinkStack));  //定义一个头结点
	if (*L == NULL)
	{
		printf("内存分配失败!\n");
		return;  //提前结束该函数
	}
	(*L)->next = NULL;
}

// 操作2------判空(采用指针传递) 推荐
int LinkStackEmpty1(LinkStack* L) {
	return L->next == NULL;
}

// 操作2.1------判空(采用值传递) 不推荐
int LinkStackEmpty2(LinkStack L) {
	return L.next == NULL;
}

// 操作3------入栈
int push(LinkStack* L, ElemType value) {
	LinkStack* s = (LinkStack*)malloc(sizeof(LinkStack));
	if (s == NULL)
	{
		printf("内存分配失败!\n");
		return -2;
	}
	s->data = value;
	s->next = L->next;
	L->next = s;
	return 0;  //入栈(插入成功)
}

// 操作4------出栈,类似于链表的头删法
// value:用于存储出栈元素的值,是一个指向元素类型的指针
int pop(LinkStack* L, ElemType* value) {
	if (LinkStackEmpty1(L))
	{
		printf("栈空,无法出栈!\n");
		return -2;
	}
	LinkStack* p = L->next;
	*value = p->data;
	L->next = p->next;
	free(p);
	return 0;  //出栈成功
 }

// 操作5------获取栈顶元素
int gettopValue(LinkStack* L, ElemType* value) {
	if (LinkStackEmpty1(L))
	{
		printf("栈空,无法获取元素!\n");
		return -2;
	}
	LinkStack* p = L->next;
	*value = p->data;  // 合并:*value = L->next->data
	return 0;  //获取栈顶信息成功
}

// 操作6------打印栈
void printLinkStack(LinkStack L) {
	if (LinkStackEmpty1(&L))
	{
		printf("栈空,无法获取元素!\n");
		return;
	}
	LinkStack* p = L.next;
	printf("栈中的元素(从栈顶到栈底)为: ");
	while (p != NULL) {
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n-----------------------------------------------\n");
}

// 操作7------销毁栈
void destroyLinkStack(LinkStack* L) {
	LinkStack* p = L;
	while (p != NULL) {
		LinkStack* s = p;
		p = p->next;
		free(s);
	}
}


int main() {
	//第二种初始化方式
	LinkStack* L1;  // 定义一个头指针
	InitLinkStack2(&L1);  // 初始化时,将该头指针指向了头结点
	
	//第一种初始化方式
	LinkStack* L = InitLinkStack1();
	if (L == NULL)
	{
		return -1;  
	}

	printLinkStack(*L);  // 栈空,无法获取元素!

	// 测试进栈操作
	push(L, 11);
	push(L, 22);
	push(L, 33);
	printLinkStack(*L);  // 栈中的元素(从栈顶到栈底)为: 33 22 11

	// 获取栈顶元素
	ElemType value;
	if (gettopValue(L,&value) == 0)
	{
		printf("当前栈顶元素为:%d\n", value);  // 当前栈顶元素为:33
	}

	// 测试出栈操作
	if (pop(L, &value) == 0) {
		printf("出栈的元素为:%d\n", value);  // 出栈的元素为:33
	}
	printf("元素出栈后,");
	printLinkStack(*L);  // 元素出栈后,栈中的元素(从栈顶到栈底)为: 22 11

	// 销毁栈
	destroyLinkStack(L);

	return 0;
}

10. 运行截图

本人菜鸟一只,文章如有问题,欢迎评论区指正!

相关推荐
ianozo24 分钟前
数据结构--【栈与队列】笔记
数据结构·笔记
Tomorrow'sThinker32 分钟前
Python零基础学习第三天:函数与数据结构
开发语言·windows·python
---yx89897832 分钟前
数字人系统源码---v10技术五大底层架构链路全局开发思路
算法·架构·数字人·数字人源码·数字人系统
元媛媛35 分钟前
Python - 轻量级后端框架 Flask
开发语言·python·flask
xiao--xin39 分钟前
LeetCode100之二叉搜索树中第K小的元素(230)--Java
java·算法·leetcode·二叉树·树的统一迭代法
路飞雪吖~41 分钟前
数据结构 && 常见的排序算法
数据结构·算法·排序算法
手握风云-1 小时前
Java数据结构第二十一期:解构排序算法的艺术与科学(三)
数据结构·算法·排序算法
钢板兽1 小时前
Java后端高频面经——Spring、SpringBoot、MyBatis
java·开发语言·spring boot·spring·面试·mybatis
爱吃柠檬呀1 小时前
《C陷阱与缺陷》读书笔记(一)
c语言·开发语言·算法·《c陷阱与缺陷》·编写程序
行码棋1 小时前
【Python】omegaconf 用法详解
开发语言·python