数据结构——栈、括号问题及其变式

何为数据结构?

数据结构是一种在程序中系统化管理数据集合的形式

数据结构很少单纯地表示数据集合,它通常由以下3个概念组合而成:

1.数据集合

通过对象数据的本体(例如数组和结构体等基本数据结构)保存数据集合

2.规则

保证数据集合按照一定规矩进行正确的操作、管理和保存的规则,比如按照何种顺序取出数据等条款

3.操作

“插入元素”,“取出元素”等对数据集合的操作。“查询数据的元素数”和“检查数据集合是否为空”等查询也包含在内

定义:

栈是一种数据结构,其按照最后进入栈的数据最先出栈的规则管理数据(Last In First Out,LIFO)

其中栈中最后一个元素的下一个位置称为栈顶,相应地也存在栈底,当栈顶到达预先开好的栈的空间的时候,称为栈满;当栈内没有任何元素的时候,称为栈空

一开始我们可能不理解,这样的奇怪的数据结构特点有什么作用呢?没关系,我们就先记住,后面在使用栈的时候自然就明白了

对栈的操作:

push(x):在栈顶添加元素x

pop(x):从栈顶部取出元素

isEmpty():检查栈是否为空

isFull():检查栈是否已满

另:

引用栈顶元素

检查栈中是否含有指定数据

规则:

数据中最后加入的元素最先被取出,即pop取出的元素时最后一次被push进入栈的元素

栈的实现:

1.数组实现

通过数组,我们很容易实现栈

只需要开一个长度为n的空数组,栈底即为下标0,再开一个变量top作为栈顶,top初始化为-1,此时为栈空;当想要push进一个元素进栈时,top++并插入;当top的值为n-1的时候,栈满

同时,为了方便开多个栈,我们将栈的数组和top变量整合为一个结构体

实现代码如下:

#define MAX 100
typedef struct stack{
	int nums[MAX];
	int top;
}Mystack;
//初始化
void InitiatStack(Mystack* stack) {
	stack->top = -1;
}
//进栈
void push(Mystack* stack, int x) {
	stack->top++;
	stack->nums[stack->top] = x;
}
//出栈
int pop(Mystack* stack) {
	stack->top--;
	return stack->nums[stack->top + 1];
}
//检验栈空
bool isEmpty(Mystack* stack) {
	return stack->top==-1;
}
//检验栈满
bool isFull(Mystack* stack) {
	return stack->top == MAX - 1;
}
int main() {//检验函数是否成立
	Mystack* a = (Mystack*)malloc(sizeof(Mystack));
	InitiatStack(a);
	for (int i = 0; i < MAX; i++) {
		push(a, i);
	}
	printf("是否为空:%d,是否为满:%d\n", isEmpty(a),isFull(a));
	for (int i = 0; i < MAX; i++) {
		printf("%d ", pop(a));
	}
	printf("\n是否为空:%d,是否为满:%d\n", isEmpty(a),isFull(a));
	return 0;
}
2.链表实现

观察栈的运作方式,我们发现,当元素进栈时,我们只需要关注栈顶的位置;当元素出栈时,我们只需要关注栈顶及栈顶前一个元素的位置,因此,我们可以设置一个top指针,指向栈顶节点;还有一个bottom指针,指向栈底,同时使链表中各个节点的next指针指向前一个元素即可

实现代码如下:

typedef struct stack2 {
	int val;
	struct stack2* next;
}StackNode/*栈节点*/, * LinkStackPtr;//节点指针
typedef struct LinkStack{//栈顶结构体
	LinkStackPtr top;//指向栈顶的指针
    int count;
}LinkStack;
StackNode* Initiat2() {
	StackNode* stack = (StackNode*)malloc(sizeof(StackNode));
	stack->next = NULL;//创建栈并初始化
	return stack;
}
void push2(LinkStack* p,int n) {//进栈
	LinkStackPtr new = (LinkStackPtr)malloc(sizeof(StackNode));
	new->val = n;
	new->next = p->top;
	p->top = new;
	p->count++;
}
int pop2(LinkStack* p) {//出栈(栈非空的前提)
	LinkStackPtr temp = p->top;
	p->top = p->top->next;
	int res = temp->val;
	free(temp);
	p->count--;
	return res;
}
bool isEmpty2(LinkStack* p) {//检验栈空
	return !p->top->next;
}
int main() {//检验函数是否成立
	StackNode* stack = Initiat2();//创建栈
	LinkStack* p = (LinkStack*)malloc(sizeof(LinkStack));
	p->top = stack;
	p->count = 0;//初始化栈顶指针
	printf("是否为空:%d\n", isEmpty2(p));
	for (int i = 0; i < 10; i++) {
		push2(p, i);
	}
	printf("是否为空:%d\n", isEmpty2(p));
	for (int i = 0; i < 10; i++) {
		printf("%d\n", pop2(p));
	}
	printf("是否为空:%d\n", isEmpty2(p));
}

用链表实现栈,我们可以无限制地往栈内push元素(只要内存足够容纳),因此使用起来十分方便

栈的应用示例:

1.括号问题:

引入力扣的一道题目:


其中字符串仅由{}[]()这六个元素组成

想要处理这道题目,我们就必须找到组成有效括号的特征,也就是每一个左括号都必须要有相应地右括号与之闭合,关键之处就在于他们是怎样闭合的,也就是说闭合的时候他们括住的元素也要是闭合的,如果我们这样关注多个对象来考虑,那就没完没了了

因此,我们不妨举几个字符串,看下能不能发现它们的共同特征:

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

将他们的括号高度错开,我们看下会发生什么:

(    )         (      )         (           )

 (  )           { }[ ]            [        ]

  ()          ,             ,       { }) (

我们从左到右地观察左括号,发现最影响我们判断有效括号的就是几个相同类型括号的嵌套,如何区分它们是否有效呢?

解决问题的关键就在于:从左往右遍历所有的左括号,我们会发现,最后遍历到的左括号一定最先和右括号闭合

这就类似于我们栈的运作,最后进入的元素,我们最先检验,当它成立时,就将它pop出

因此,我们只需遍历字符串,当遇到左括号的时候,将其push入栈;当遇到右括号的时候,检查栈顶是否有与之对应的左括号,若没有,则括号无效,返回false;当字符串被全部遍历完毕的时候,也就是栈中没有剩下任何元素的时候,返回true;

实现代码如下:

char pair(char a) {//为了更好地判断括号匹配,我们建立一个pair函数
    if (a == '}') return '{';
    if (a == ']') return '[';
    if (a == ')') return '(';
    return 0;
}

bool isValid(char* s) {
    int n = strlen(s);
    if (n % 2 == 1) {//当字符串元素个数为奇数的时候,括号一定无效
        return false;
    }
    int* stack=(int*)malloc(sizeof(int)*n);
    int top = -1;//建立栈数组
    for (int i = 0; i < n; i++) {//遍历字符串
        char ch = pair(s[i]);
        if (ch) {//当s[i]为右括号时
            if (top == -1 || stack[top] != ch) {//当栈为空,或者括号不匹配时
                return false;
            }
            top--;//括号匹配时,pop出栈顶的右括号
        } else {//当s[i]为左括号时
            stack[++top] = s[i];
        }
    }
    free(stack);
    return top == -1;
}
2.括号问题变式:

我们引入这样一道题目:

一开始拿到这道题目,我们可能会对水坑面积的求法无从下手,但其实这是一道括号问题的变式

水坑由\和/组成,遍历同一高度的\和/,后遇到的\一定会先与/形成水坑,而他们之间包括的面积就等于他们的下标之差,(_占了一个下标的长度,所以刚好不用管它,碰到跳过即可),也就是说,我们可以利用括号问题的方法,求出每一个高度的\和\围成的面积并push入栈,相加起来,就求出了总面积

那么我们怎么求出有多少个水坑,以及各个水坑的面积呢?

我们观察几个小面积能够合成一个水坑的大面积需要什么条件

对于一个大水坑,我们先找到其高度高的面积,那么他的长度肯定也是最长的,再往下看高度较低的面积,观察一下他们的长度关系

我们记面积s1=j1-i1,其中j1为/的下标,i1为\的下标;s2为j2-i2;s1的高度大于s2,且它们位于同一个水坑的时候,我们肯定有:i2>i1且j2<j1

我们还可以再简化,其实只要满足j2<j1即可

因此,我们需要再建立一个栈,同时记录压入的面积s和下标i(知道s和i就可以求j)

在我们建立的栈中,各部分的面积其实是按水坑顺序拍好的,一个水坑内的面积又是按由低到高排好的,因此我们可以很容易地合并每个水坑的面积并保存

实现代码如下:

typedef struct stack {
	    int left;
		int area;
		struct stack* next;
}stack2;
void StackPush(stack2** p,int left, int area) {
	stack2* new = (stack2*)malloc(sizeof(stack2));
	new->left = left;
	new->area = area;
	new->next = *p;
	*p = new;
}
int* Areas(char* slope, int* total,int*returnSize) {
	*total = 0;
	*returnSize = 0;
	int* stack1 = (int*)malloc(sizeof(int) * strlen(slope));
	stack2* tail = (stack2*)malloc(sizeof(stack2));
	tail->next = NULL;
	stack2* head = tail;
	int top1 = 0;
	for (int i = 0; i < strlen(slope); i++) {
		if (slope[i] == '\\') {
			stack1[top1++] = i;
		}
		else if (slope[i] == '/' && top1 != 0) {
			top1--;
			StackPush(&head, stack1[top1], i - stack1[top1]);
			*total += head->area;
		}
	}
	int* ans = (int*)malloc(sizeof(int) * (strlen(slope) / 2));
	int i = 0;
	while (head != tail) {
		int length = head->area + head->left;
		int LEFT = head->left;
		while (head->next->left < length&&head->next->left>LEFT) {
			stack2* p = head;
			head = head->next;
			head->area += p->area;
			free(p);
		}
		ans[i++] = head->area;
		(*returnSize)++;
		head = head->next;
	}
	return ans;
}
int main() {//检验
	char str[38] = "\\\\///\\_/\\/\\\\\\\\/_/\\\\///__\\\\\\_\\\\/_\\/_/\\";
	int* total = (int*)malloc(sizeof(int));
	int* returnSize = (int*)malloc(sizeof(int));
	int* res = Areas(str, total, returnSize);
	printf("count total:%d\n", *returnSize);
	for (int i = 0; i < *returnSize; i++) {
		printf("the %d area:%d\n", i+1,res[i]);
	}
	printf("area total:%d\n", *total);
	return 0;
}

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值