c语言 数据结构 栈的基本操作和深度理解

栈是数据结构中一类最基础的数据结构,很多人学习栈往往能很快学会,但是却不能理解栈在实际问题的一些应用条件,这篇文章主要围绕栈的结构定义和结构操作进行讲解,并且进一步讲解对栈的深度理解,提高读者对实际问题的解决能力。


函数库:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>


 数据结构=结构定义+结构操作


首先来对结构定义进行讲解:

typedef struct Stack {
	int* data;
	int size, top;
}Stack;

我们先弄懂栈是什么再去考虑上面这层代码的意思。 

我们可以将栈理解为乒乓球筒:

 假设这个筒只有一口可以打开,那么这样形成的一个装球的结构就和栈是一样的,球只能从最顶上装入,最顶上拿出,那么栈在结构实现上如何符合这样的特点呢?

1.元素的进出顺序为先进后出

2.有一个变量作为头指针永远指向栈顶元素

3.有一个表示栈空间大小的变量

由此可以解释上面的代码:

代码第2行:该结构中有一片数据域,表示栈的存储空间

代码第3行:该结构有一个变量作为头指针(top),有一个表示栈空间大小的变量(size)

结构定义完成。


接下来进行结构操作:

首先是栈的创建操作:

Stack* initStack(int n) {//参数n表示你想要创建的栈空间的大小
	Stack* s = (Stack*)malloc(sizeof(Stack));//首先为栈开辟一个空间
	s->data = (int*)malloc(sizeof(int) * n);//然后为栈的数据域单独开辟n片连续的空间
	s->size = n;//用变量size表示栈的空间大小
	s->top = -1;//用变量top表示栈顶元素的位置,由于一开始栈中没有元素,因此指向-1
	return s;//最后返回栈的地址
}

值得一提的是:为什么将top元素设置为-1呢?

原因很简单,在上面我们所开辟出来的数据域为连续的数据域,我们可以将其当作数组来处理,在数组中的第一个元素的位置可以用下标 [0] 来表示,因此当第一个元素进入时,我们只需要让top加1,就可以用 [top] 来表示栈顶元素的位置了。


然后是栈的清除操作:

void clearStack(Stack* s) {
	if (s)return;//判断栈是否为空,如果为空则不需要清除,直接返回
	free(s->data);//否则先释放数据域
	free(s);//最后释放结构域
	return;
}

栈的判空操作:

int empty(Stack* s) {
	return s->top == -1;
}//判空操作

直接返回“判断top值是否为-1”程序的真值。

栈的栈顶元素输出操作:

int top(Stack* s) {
	if (empty(s))return 0;//如果栈为空,直接结束操作
	return s->data[s->top];//如果不为空,输出数据域中以[top]为下标的元素的值
}

接下来两个操作为栈的重头戏:

入栈操作:

int push(Stack* s,int val) {//传入栈的地址和想要入栈的值
	if (s->top + 1 == s->size)return 0;//如果栈满了,则不能入栈,直接结束程序
	s->top += 1;//否则栈顶指针+1
	s->data[s->top] = val;//将值压入栈
	return 1;
}

出栈操作:

int pop(Stack* s) {
	if (empty(s))return 0;//如果为空,结束程序
	s->top -= 1;//栈顶指针-1
	return 1;
}

最后是输出栈的元素顺序操作:

void outputStack(Stack* s) {
	printf("Stack:");
	for (int i = s->top; i != -1; --i) {
		printf("%4d", s->data[i]);
	}
	printf("\n\n");
	return;
}

借用变量i依次从栈顶向栈底遍历,相当于从乒乓球筒中一个个拿球的行为。

最后附上整个程序,并借用随机程序测试:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Stack {
	int* data;
	int size, top;
}Stack;

Stack* initStack(int n) {
	Stack* s = (Stack*)malloc(sizeof(Stack));
	s->data = (int*)malloc(sizeof(int) * n);
	s->size = n;
	s->top = -1;
	return s;
}

int empty(Stack* s) {
	return s->top == -1;
}//判空操作

int top(Stack* s) {
	if (empty(s))return 0;
	return s->data[s->top];
}

int push(Stack* s,int val) {
	if (s->top + 1 == s->size)return 0;
	s->top += 1;
	s->data[s->top] = val;
	return 1;
}

int pop(Stack* s) {
	if (empty(s))return 0;
	s->top -= 1;
	return 1;
}

void clearStack(Stack* s) {
	if (s)return;
	free(s->data);
	free(s);
	return;
}

void outputStack(Stack* s) {
	printf("Stack:");
	for (int i = s->top; i != -1; --i) {
		printf("%4d", s->data[i]);
	}
	printf("\n\n");
	return;
}

int main() {
	srand(time(0));
#define MAX_OP 10
	Stack* s = initStack(5);
	for (int i = 0; i < MAX_OP; i++) {
		int op = rand() % 3, val = rand() % 100;
		switch (op) {
		case 0:
			printf("pop stack,item = %d\n", top(s));
			pop(s);
			break;
		case 1:
		case 2:
			printf("push stack , item = %d\n", val);
			push(s, val);
			break;
		}
		outputStack(s);
	}
	clearStack(s);
	return 0;
}

 栈的深度理解:

栈的结构本身是非常简单的,但是这样简单的结构能解决什么问题呢?

我们来看一道题:

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/valid-parentheses

我们先来搞懂什么是有效字符串:

{{{}}}

[[(())]][][]{}{}

(({{}}))

{[()]}[]()

以上的括号序列都是有效的,下面的是无效的:

{}{}{}{}{{{

[({))]()[][][]]]]]))))))

{{{((([[[))}}))]]))

如何用栈来解决这样的问题呢?(下面为答案,大家可以先思考一下)

1.首先读入待判断的括号序列;

2.然后从头到尾依次遍历,遇到左括号入栈,遇到右括号就将栈顶元素和该右括号判断;

3.如果判断结果为同类括号,则弹出栈顶元素;

4.如果判断结果为不同类括号,则直接退出程序,提示错误;

5.如果遍历结束之后栈为空,则提示正确。

附上程序:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

typedef struct Stack {
	char* data;
	int size, top;
}Stack;

Stack* initStack(int n) {
	Stack* s = (Stack*)malloc(sizeof(Stack));
	s->data = (char*)malloc(sizeof(char) * n);
	s->size = n;
	s->top = -1;
	return s;
}

int empty(Stack* s) {
	return s->top == -1;
}

char top(Stack* s) {
	if (empty(s))return 0;
	return s->data[s->top];
}

int push(Stack* s, char val) {
	if (s->top + 1 == s->size)return 0;
	s->top += 1;
	s->data[s->top] = val;
	return 1;
}

int pop(Stack* s) {
	if (empty(s))return 0;
	s->top -= 1;
	return 1;
}

void clearStack(Stack* s) {
	if (s)return;
	free(s->data);
	free(s);
	return;
}

void solve(char str[]) {
    int flag = 1;
    Stack* s = initStack(100);
    for (int i = 0; str[i]; i++) {
        if (str[i] == '(' || str[i] == '[' || str[i] == '{') {
            push(s, str[i]);
        }
        else {
            switch (str[i]) {
            case ')': {
                if (!empty(s) && top(s) == '(') pop(s);
                else flag = 0;
            } break;
            case ']': {
                if (!empty(s) && top(s) == '[') pop(s);
                else flag = 0;
            } break;
            case '}': {
                if (!empty(s) && top(s) == '{') pop(s);
                else flag = 0;
            } break;
            }
            if (flag == 0) break;
        }
    }
    if (flag == 0 || !empty(s)) {
        printf("error : %s\n", str);
    }
    else {
        printf("success : %s\n", str);
    }
    clearStack(s);
    return;
}

int main() {
	Stack* s = initStack(100);
	char str[100]; 
	while (~scanf("%s", str)) {//“~”需要输入三次ctrl+z才能推退出
		solve(str);
	}
	clearStack(s);
	return 0;
}

从上题的解法中可以看出栈结构对于解决实际问题的作用,我们隐隐约约能感受到栈可以解决上题一类的问题,我们能否用一句话来概括栈可以解决的问题的特点呢?

栈可以处理具有完全包含关系的问题

什么是完全包含关系?

假设下面有一个集合序列:

{{{集合1}{集合2}{集合3}}{{集合a}{集合b}{集合c}}}

在上述集合中套有两个中集合,每个中集合又包含三个小集合123,abc;假设整个大集合为一个待解决的问题,如果我们想要解决这个大问题,就需要先解决两个中集合的问题,要解决中集合的问题,就要先解决小集合的问题,此时我们可以先将每个小集合依次入栈,那么可以将栈中元素看为{”c“,”b“,”a“,”3“,”2“,”1“},我们从栈顶依次解决问题cba,321,当解决问题cba时,意味着中问题2被解决了,当解决321时,意味着中问题1被解决了,当栈为空时,意味着整个问题被解决了 。

在上述括号问题中,我们以{[()]}[]()为例子,整个序列”{[()]}[]()“为大问题”{[()]}“[]”“()”为中问题1、2、3,而中问题1“{[()]}”中又包含三个小问题,即判断“{[(”是否依次和“)]}”依次对应。

在实际的程序设计中,遇到具有完全包含关系的问题,我们可以尝试用栈来解决,而栈结构存在的意义就在于此。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C语言二叉树,和队列实验是一个经典且重要的实验项目。这个项目的目的是掌握二叉树、和队列的基本概念和操作。 首先,二叉树是一种常见的数据结构,它由节点组成,每个节点最多有两个子节点。树的根节点没有父节点,而其他节点有且仅有一个父节点。二叉树可以用于解决许多实际问题,比如表示层次结构、搜索和排序等。 在二叉树的实验中,我们需要实现一些基本操作,如创建树、插入节点、搜索节点、删除节点等。我们可以使用递归或迭代的方法来实现这些操作。此外,还可以使用前序、中序或后序遍历二叉树,以及层次遍历等方法来访问树中的节点。 其次,是一个后进先出(LIFO)的数据结构。通过,我们可以实现某些算法和数据结构,比如深度优先搜索、中缀转后缀表达式等。在的实验中,我们需要实现一些基本操作,如入、出、判断空或满等。 最后,队列是一个先进先出(FIFO)的数据结构。通过队列,我们可以实现某些算法和数据结构,比如广度优先搜索、消息传递等。在队列的实验中,我们需要实现一些基本操作,如入队、出队、判断队空或队满等。 总之,C语言二叉树,和队列实验是一个很好的练习项目,可以加强我们对这些基本数据结构理解和使用。通过完成这个实验,我们可以提高我们的编程技能,并在以后的编程实践中更好地应用这些知识。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

若亦_Royi

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值