C的回归基础学习——数据结构(1)栈

C的回归基础学习——数据结构(1)栈

前言

我好像忘了去补前面的内容了。。。。
这次终于来到数据结构部分。数据结构很重要,上手也只需要背点模板(有时甚至可以直接套用函数库),但是要深入研究数据结构的变形与应用真的费脑子,之前学这一模块只注意前者(所以我现在很菜),这次重学数据结构重点一定要放在后面啊。
那么,言归正传,回到这次的主题——栈

栈的实现

一句话说明栈的作用:先进后出(LIFO-last in first out)
打结构体我jio得是真的没有必要,因为栈的操作真的很简单(除非你想搞什么特别的定义),所以一般一个数组和一个栈顶指针(说是指针实际上一般是整型top)。另外,stack可以直接套用#include中的stack<数据类型>名称。

stack<int>st;
st.push(a);//入栈
st.pop(a);//将栈顶元素出栈
a = st.top();//返回栈顶元素
st.empty();//栈是空的就返回true,反之返回false

来点实战

注:习题来自leetcode

1.括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:

    左括号必须用相同类型的右括号闭合。
    左括号必须以正确的顺序闭合。
    注意空字符串可被认为是有效字符串。
输入: "()"
输出: true

输入: "()[]{}"
输出: true

输入: "(]"
输出: false

分析:

  • 基础栈的应用,遇到前括号就入栈,遇到后括号就判断是否现在栈顶为匹配的的前括号,如果不是则说明括号串无效,如果是则将这个前括号退出。记得要判断最后栈内元素是否为空,为空才完全有效。
  • 其次补充一点新东西:string。来自c++的字符串类型,而且是动态的。
    详情请见此处
    代码:
    bool is(string s) {
    	int size = s.size();
		int top = 0;
        char *stack = (char*)malloc(s.size()+1);//此处要加1,因为我定义的top是指向数组中栈顶的后一位,接下来要放元素的位置
        for(int i = 0; i < size ;i++)
        {
        	if ((s[i] == '(') || (s[i] == '[') || (s[i] == '{')) {
        		stack[top++]= s[i]; 
			} else {
				if(top == 0) return false;
				if (int(s[i] - stack[--top]) > 2 ||) return false;// 这里用了一个小技巧,所有同一类型的前后括号的ascll码相差不过2
			}
		}
		if (top != 0) return false;//判断栈是否为空,因为会出现“{”这种情况
		free(stack);
		return true;
    }//leetcode 20

2.逆波兰表达式

根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:
    整数除法只保留整数部分。
    给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9
输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: (4 + (13 / 5)) = 6
输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释: 
 ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

分析:

  • 这个其实也还好想,遇到数字就入栈,遇到符号就出两个,进行运算,在把结果入栈,搞定!
  • 其次这个地方补充一点新东西:vector(向量),其实你也可以理解为动态数组,用法与数组类似,详情请见此处
  • 还有auto是c++的一种新的定义方式,它让编译器自己根据初始化判断定义的是什么类型(为什么不用int?因为vector的遍历使用的是迭代器,不是int),简直是天使一般的存在(私以为)
    代码:
bool check(string s)
{
	if (s == "+" || s == "-" || s == "*" || s == "/")
	return true;
	return false;
}
int evalRPN(vector<string>& tokens) {
	
	int stack[10010]; //这个地方我一开始打得char,错了好几次,蠢
	int top = 0,num1,num2;
	for(auto i = tokens.begin(); i != tokens.end() ; i++)//
	{
		//可能需要考虑一下不规范的输入 ,比如除以0
		if (!check(*i)) stack[top++] = stoi(*i);
		else {
			num1 = stack[--top];
			num2 = stack[--top];
			if (*i == "+") stack[top++] = num1 + num2;				
			else if (*i == "-") stack[top++] = num2 - num1;				
			else if (*i == "*") stack[top++] = num2 * num1;				
			else if (*i == "/") stack[top++] = num2 / num1;
		}
	}
	return stack[0]; 
} //leetcode 150

3.重头戏——接雨水(单调栈)

给定n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水

分析:

  • 没错,我说过数据结构最让人难受的是变形与运用,而如何变形只有方向却没有固定答案。这就来了一道,当然这个还是相对与其他的,还是没那么BT。
  • 首先想到的肯定是最简单的暴力方法:对于每根柱子计算此处能存多少水,具体操作就是分别向左向右找到最高的柱子,然后用两者最低的柱高减去此处的柱高,时间复杂度是O(N2)
  • 上面O(N2)的复杂度中遍历每一个柱子的复杂度肯定是没法优化,那么就可以优化向左向右寻找最高柱子的过程,可以开两个数组leftMax和rightMax,leftMax[i]表示从i到最左端最高的柱子。进行预处理就可以将复杂度优化到O(N)
  • 所以我们会发现这道题的关键就是找最高的柱子。现在用栈的思路来看的话,如果我们遇到一个较低的柱子,什么事也不能做,因为可能有更低的柱子;如果出现了一个比之前高(或者等高)的柱子,那么前面的柱子就没有用了,且他可以和栈顶之后的柱子之间形成积水。那么就可得出,记当前遍历到第now个柱子,如果height[now] < stack.top(),则stack.push(now),如果height[now] >= stack.top(),则进行计算并维护栈。
  • 计算与维护:维护就是把比now低的柱子踢出去,而计算则是每次踢柱子时实际上我们得到了三根柱子:踢出的柱子(就是栈顶的柱子)s1,now,还有栈顶后面的柱子s2,把前者看作底部,后两者看作挡板,则有width=now-s2-1,depth=min(height[now],height[s2])-height[s1],此次获得的水体积就是width*depth。其实时间复杂度还是O(N),空间也是O(N),但是常数也许会小一点。
  • 最后我们看一看我们在每次维护后的stack有什么特点:栈顶元素最小,然后从顶到底依次递增,而这就是单调栈的一种情型。
    代码(栈)
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int>s;
        int ans = 0,now = 0;
        int distance,nowTop,depth;
        while (now < height.size())
        {
        	while (!s.empty() && height[now] >= height[s.top()])
			{
				nowTop=s.top();
				s.pop();
				if (s.empty()) break;
				distance = now - s.top() - 1;
				depth = min(height[s.top()] , height[now]) - height[nowTop];
				ans += distance * depth;
			} 
        	s.push(now++);
		}
		return ans;
    }
};//leetcode 42
//因为打代码之前只想出单调栈的思路,没有想到怎么实践,所以跑去看了眼题解,结果就忘不了了。。。。

后记

  • 实际上,我对于如何用栈的理解在于,怎么样找到入栈的时机和东西以及出栈的时机和东西,只要找到这个,说白了也就是用栈进行比较难的模拟罢了(也许?)
  • leetcode是真心用不惯,因为c++的STL用的少,一开始用这个很不熟练,但是学了一点后就觉得:c++真的会比c好用,特别是c++自带的东西。stack,vector,string包括我记得的queue,map都是特别方便的数据结构,关键一点是它们是动态的,c总是要声明大小或者手动动态,确实不太方便。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值