用C语言实现一个栈

用C语言实现一个栈

一、准备工作

1.什么是栈?

栈(Stack),是一种线性数据结构。那线性结构是什么呢?线性结构就是数据元素排成像一条线一样的结构,每个元素只有前后两个方向的关系,比如我们前几期内容讲过的顺序表单链表双向循环链表都属于线性结构。但栈的特殊性在于它的操作受限,只允许在固定的一端进行插入和删除元素操作,比如一摞盘子,只能从最上面拿或放。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守 后进先出——LIFO(Last In First Out)的原则
栈的示意图

2.实现一个栈,我们需要些什么?

(1)数组

实现一个栈,我们往往使用数组,因为数组在内存中分配连续的地址空间,使得栈顶元素的访问无需遍历中间的结点,并且数组实现的栈在操作的过程中时间复杂度为O(1),代码也比较简洁。在这里,提到了数组,不难联想到顺序表,其实二者类似,只不过栈只能在栈顶插入删除数据,而顺序表不受限制。当然,栈也分为静态栈和动态栈,由于静态栈开辟的数组空间是固定的,不够灵活,因此,我们往往采用动态栈:通过动态内存开辟的方式,让数组的容量跟着需求来。
栈的数组

(2)top

top指的是栈顶元素的位置,当栈中没有插入数据时,top为-1,待插入数据后,top+1变为0,刚好和数组的下标相对应(第一个数据下标为0),每次插入数据后,栈顶位置都会变化,要+1。
top

(3)capacity

capacity:表示当前数组能够存多少个数据,指的是栈的容量。(注意:这里的容量不是指数组有多少个字节的空间,而是能够存储数据 的个数,因此,我们可以有这样的公式:数组空间的大小(单位:字节)=数组所存储的单个数据大小 * capacity
size:表示当前数组已经存了多少个数据。

结构体

我们现在有了一个数组,top(整型)和capacity(整型)。它们虽然数据类型不同,但却紧密联系,共同实现一个栈,因此,我们可以将他们放入一个结构体中(如图)。

二、栈的实现(代码+分析)

1.初始化

结构体Stack告诉我们实现栈需要的一些内容,但是它只是一张图纸,就像房屋的设计图一样,告诉我们哪里是客厅,哪里是厨房,却不能实实在在地住进去。因此,我们需要对顺序表进行初始化(代码如下)。

void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = (DataType*)malloc(sizeof(DataType) * 4);//先开辟4个数据的空间
	if (ps->a == NULL)//基本操作,检查动态内存开辟是否成功
	{
		perror("malloc fail");
		return;
	}
	ps->top = -1;//top指的是栈顶元素的位置,现在栈中没有插入数据,因此top为-1,待插入数据后,top+1变为0,刚好和数组的下标相对应(第一个数据下标为0)
	ps->capacity = 4;//表示初始化后栈中能存4个数据
}

void StackInit(Stack* ps)是初始化函数,我们发现这里传的是结构体的指针,而不是结构体本身。为什么呢,因为形参只是实参的一份临时拷贝,出了函数的作用域后就销毁了,形参在函数里再怎么变化对实参都没有什么影响,想要改变实参,应当传它的地址。一个经典的例子就是写一个交换a,b值的函数时,传地址才有效,道理是一样的。

2.入栈

入栈操作和顺序表的尾插操作类似,比较简单,需要注意的是top要+1后再插入数据,代码如下:

void StackPush(Stack* ps, DataType x)
{
	assert(ps);
	if (ps->top + 1 == ps->capacity)//检查是否需要扩容,top是数组的下标位,我们都知道,数组的下标位是比数组实际数据个数少1的,因此需要top+1
	{
		DataType* tmp = (DataType*)realloc(ps, sizeof(DataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		ps->a = tmp;//tmp指向的是调整后内存的起始地址
	}
	ps->capacity *= 2;//经过扩容,容量变为原来的两倍
	ps->top++;//现在是插入数据,栈顶的下标位要+1了
	ps->a[ps->top] = x;//在栈顶处插入数据
}

3.判断栈中是否存在有效数据

当栈为空时,其特点就是栈顶的下标位为-1,这个函数是为后面的出栈函数服务,代码如下:

bool StackEmpty(Stack* ps)//判断栈中是否存在数据,如果为空,则返回1,如果不为空,则返回0
{
	assert(ps);
	return ps->top == -1;//当栈为空时,其特点就是栈顶的下标位为-1
}

4.栈顶数据出栈

出栈操作和顺序表的尾删操作类似,需要注意的是,assert里面的内容为0(或者NULL)时会生效,进行断言,如果现在想让它生效,则要(!StackEmpty(ps))为0,则StackEmpty(ps)为1,此时就是栈中数据为空的情况,代码如下:

void StackPop(Stack* ps)//栈顶数据出栈
{
	assert(ps);
	assert(!StackEmpty(ps));//assert里面的内容为0(或者NULL)时会生效,如果现在想让它生效,则要(!StackEmpty(ps))为0,则StackEmpty(ps)为1,此时就是栈中数据为空的情况
	ps->top--;//所谓出栈,就是将栈顶的下标位-1,这样在取出元素的时候就取不到已经出了栈顶数据了
}

5.获取栈顶元素

DataType StackTop(Stack* ps)//查看栈顶的元素
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top];
}

6.查看栈中数据个数

int StackSize(Stack* ps)//查看一下栈中有几个元素
{
	assert(ps);
	return ps->top + 1;//数组下标位+1为元素个数
}

7.栈的销毁

void StackDestroy(Stack* ps)//栈的销毁
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;
}

三、使用头文件——声明与定义分离

1.头文件——Stack.h

在栈中,我们为了实现它的基本功能,写了很多的函数,如果我们将结构体,函数的声明,函数的定义,以及函数的调用测试写在同一个源文件中,会大大降低代码的可读性和可维护性;因此,我们可以将结构体以及一些函数的声明放在头文件Stack.h中,代码如图:
Stack.h

2.源文件——Stack.c

在源文件Stack.c中,我们存放函数具体实现的代码,在第一行应加上 #include"Stack.h",代码如下:

#include"Stack.h"

void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = (DataType*)malloc(sizeof(DataType) * 4);//先开辟4个数据的空间
	if (ps->a == NULL)//基本操作,检查动态内存开辟是否成功
	{
		perror("malloc fail");
		return;
	}
	ps->top = -1;//top指的是栈顶元素的位置,现在栈中没有插入数据,因此top为-1,待插入数据后,top+1变为0,刚好和数组的下标相对应(第一个数据下标为0)
	ps->capacity = 4;//表示初始化后栈中能存4个数据
}

void StackPush(Stack* ps, DataType x)
{
	assert(ps);
	if (ps->top + 1 == ps->capacity)//检查是否需要扩容,top是数组的下标位,我们都知道,数组的下标位是比数组实际数据个数少1的,因此需要top+1
	{
		DataType* tmp = (DataType*)realloc(ps, sizeof(DataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("malloc fail");
			return;
		}
		ps->a = tmp;//tmp指向的是调整后内存的起始地址
	}
	ps->capacity *= 2;//经过扩容,容量变为原来的两倍
	ps->top++;//现在是插入数据,栈顶的下标位要+1了
	ps->a[ps->top] = x;//在栈顶处插入数据
}

bool StackEmpty(Stack* ps)//判断栈中是否存在数据,如果为空,则返回1,如果不为空,则返回0
{
	assert(ps);
	return ps->top == -1;//当栈为空时,其特点就是栈顶的下标位为-1
}

void StackPop(Stack* ps)//栈顶数据出栈
{
	assert(ps);
	assert(!StackEmpty(ps));//assert里面的内容为0(或者NULL)时会生效,如果现在想让它生效,则要(!StackEmpty(ps))为0,则StackEmpty(ps)为1,此时就是栈中数据为空的情况
	ps->top--;//所谓出栈,就是将栈顶的下标位-1,这样在取出元素的时候就取不到已经出了栈顶数据了
}

DataType StackTop(Stack* ps)//查看栈顶的元素
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top];
}

int StackSize(Stack* ps)//查看一下栈中有几个元素
{
	assert(ps);
	return ps->top + 1;//数组下标位+1为元素个数
}

void StackDestroy(Stack* ps)//栈的销毁
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;
}

3.源文件——Test.c(运行测试)

栈是不支持遍历的,没有StackPrint函数。因为栈的主要目的是提供高效的压栈(push)和弹栈(pop)操作,这两种操作的时间复杂度都是O(1)。如果允许遍历,可能需要访问中间元素,这样就会破坏栈的LIFO原则。比如,如果用户可以随便访问栈中间的元素,那栈就不再遵循严格的后进先出规则了。

#include"Stack.h"

int main()
{
	Stack ST;
	StackInit(&ST);

	StackPush(&ST, 1);
	StackPush(&ST, 2);

	printf("%d ", StackTop(&ST));
	StackPop(&ST);

	StackPush(&ST, 3);
	StackPush(&ST, 5);
	
	printf("%d ", StackTop(&ST));
	StackPop(&ST);

	StackPush(&ST, 9);
	
	while (!StackEmpty(&ST))
	{
		printf("%d ", StackTop(&ST));
		StackPop(&ST);
	}

	StackDestroy(&ST);

	return 0;
}

运行结果如图:
测试结果

本期总结+下期预告

本期内容完成了栈的C语言实现,下期将为大家带来队列相关的内容!

感谢大家的关注,我们下期再见!
在这里插入图片描述

### 回答1: 好的,您想要用C语言实现一个的逆转程序。可以使用以下代码: ``` #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 // 定义的最大容量 int stack[MAX_SIZE]; // 定义一个 int top = -1; // 定义顶指针 // 判断是否为空 int is_empty() { return top == -1; } // 判断是否已满 int is_full() { return top == MAX_SIZE - 1; } // 入 void push(int data) { if (is_full()) { printf("已满,无法入!"); exit(1); } stack[++top] = data; } // 出 int pop() { if (is_empty()) { printf("已空,无法出!"); exit(1); } return stack[top--]; } // 递归实现的逆转 void reverse_stack() { if (is_empty()) { return; } int data = pop(); reverse_stack(); push(data); } int main() { int length, data; printf("请输入的长度:"); scanf("%d", &length); printf("请输入中的数据:"); for (int i = 0; i < length; i++) { scanf("%d", &data); push(data); } printf("原始:"); for (int i = 0; i <= top; i++) { printf("%d ", stack[i]); } printf("\n"); printf("逆转后的:"); reverse_stack(); for (int i = 0; i <= top; i++) { printf("%d ", stack[i]); } return 0; } ``` 运行程序后,输入的长度和中的数据,程序将逆转中的数据并输出结果。 ### 回答2: 要用C语言实现一个的逆转程序,首先需要了解的基本概念和实现方式。 是一种具有后进先出(LIFO)特性的数据结构,可以通过数组或链表来实现。在C语言中,我们可以使用数组和指针来模拟的行为。 具体实现程序的思路如下: 1. 创建一个,用数组或链表来表示,并声明一个指针变量top,用于指向顶元素的位置。 2. 读入待逆转的数据,并依次将其入。可以使用循环来实现,每次读入一个数据项,将其压入顶,并更新top指针。 3. 当输入结束后,中保存了所有待逆转的数据。接下来,需要从中依次取出元素,即实现逆转操作。可以使用另一个数组或链表来保存逆转后的数据。 4. 遍历,每次从顶取出一个元素,并将其存放到逆转数组或链表中,同时更新top指针。这一步需要使用循环来实现,直到中的元素全部取出。 5. 逆转后的数据已经保存在逆转数组或链表中,可以按需进行后续的操作。例如,将其打印输出或进行其他处理。 需要注意的是,的逆转操作会改变中的元素顺序,因此在实际应用中需要谨慎使用。 综上所述,以上是使用C语言实现一个的逆转的一种方法。具体实现过程中,还可以根据应用场景做适当调整。 ### 回答3: 要实现一个的逆转程序,可以使用C语言的数组和指针操作。首先,定义一个的结构体,包含一个元素数组和一个顶指针。然后,创建一个函数来实现的逆转。 函数的实现思路如下: 1. 创建一个临时数组来保存逆转后的元素。 2. 将中的元素依次出,并保存到临时数组中。 3. 将临时数组中的元素逐个入实现的逆转。 4. 返回逆转后的。 下面是一个示例的C语言代码实现: ```c #include <stdio.h> #define MAX_SIZE 100 typedef struct { int elements[MAX_SIZE]; int top; } Stack; // 初始化 void initStack(Stack* stack) { stack->top = -1; } // 判断是否为空 int isEmpty(Stack* stack) { return stack->top == -1; } // 判断是否已满 int isFull(Stack* stack) { return stack->top == MAX_SIZE - 1; } // 出 int pop(Stack* stack) { if (isEmpty(stack)) { printf("Error: Stack is empty.\n"); return -1; } else { return stack->elements[stack->top--]; } } // 入 void push(Stack* stack, int value) { if (isFull(stack)) { printf("Error: Stack is full.\n"); } else { stack->elements[++stack->top] = value; } } // 的逆转 Stack reverseStack(Stack* stack) { Stack tempStack; initStack(&tempStack); while (!isEmpty(stack)) { int element = pop(stack); push(&tempStack, element); } return tempStack; } int main() { Stack stack; initStack(&stack); push(&stack, 1); push(&stack, 2); push(&stack, 3); Stack reversedStack = reverseStack(&stack); printf("Reversed Stack: "); while (!isEmpty(&reversedStack)) { printf("%d ", pop(&reversedStack)); } return 0; } ``` 以上代码实现一个的逆转程序。使用了定义的结构体,实现的初始化、判断是否为空或已满、入和出等基本操作函数。在`reverseStack`函数中,利用临时的实现的逆转。在`main`函数中,演示了如何使用该程序逆转中的元素并输出。 这个程序可以根据具体需求进行修改和扩展,例如添加容错处理和其他辅助函数等等。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值