数据结构与算法——栈、队列、堆

这是我第一次写blog,试一试,想总结一下自己学的东西,也希望自己能坚持下来。
目前打算写一些算法的东西,为明年夏令营的机试做准备。以后如果有空,还会继续写一些计算机专业课(操作系统、网络、数据库等)方面的东西,以及NLP和ML相关的内容。大二学生,自身水平有限,请多多指教。

2019.7.14

引言

qwq 嘻嘻第一次写blog,说在前面,我觉得做算法这玩意儿,每次都是:盯着题目看了一大天,你不一定能想出来。想出来的一般都是BF,时间复杂度都很高,TLE反正是过不了样例。哎,艰难,还有就算写出来了,总有一些奇奇怪怪的样例过不了。所以哎多写代码趴。practice makes perfect
栈:后进先出的线性表(LIFO),递归、逆序
队列:先进先出的线性表(FIFO),缓冲、BFS、层次遍历
堆:优先级队列
单调栈/队列:原理简单,使用较难,很重要
这部分内容没有什么太多的知识,重点在于如何灵活应用

STL

需要掌握基本的STL的用法
1.stack

#include<stack>
stack<datatype> s;  //create
s.push(x);          //push data
datatype y = s.top();//peek value
s.pop();            //delete , no return-value
s.empty();          //return bool
s.size();          //return int 

2.queue

#include<queue>
queue<datatype> q;  //create
q.push(x);                 //push
datatype head = q.front();  //first data
datatype tail = q.back();  //last data
p.pop();  //delete
p.empty();       //return bool
p.size();       //return int

3.priority_queue

#include<queue>
priority_queue<datatype> pq;   //create
pq.push(x);                 //push
datatype y = pq.top();  //first data,only in this way
pq.pop();  //delete
pq.empty();       //return bool
pq.size();       //return int

在c++中,pq默认为最大堆(数值越大,优先级越高)
4.deque

#include<deque>
deque<datatype> d;
d.push_back(x);   
d.push_front(x);
d.pop_back();
d.pop_front();
x = d.front();
x = d.back();

1.括号匹配

对于给定字符串,判断括号是否匹配

流程: 扫描一遍字符串,如果有左括号就压栈。遇到右括号,检测栈顶,如果匹配就弹出,不匹配则算法结束。最后检测栈是否为空,不为空则说明不匹配。算法实现很简单,注意判断边界条件。
在实现上,其实可以不借助栈,使用变量进行模拟即可。
相关题目:括号匹配 括号分数

2.表达式求值

中缀转后缀

#include<iostream>
#include<string>
#include<stack>

using namespace std;
string test(string str)
{
	string ptr = "";
	stack<char> s;
	for(int i = 0;i < str.size();i++)
	{
		if(str[i] >= '0'&& str[i] <= '9')  //如果是数值,直接输出
		{
			ptr += str[i];
		}
		else               //如果是操作符
		{
			if(s.empty())        // (1)栈为空直接压栈
			{
				s.push(str[i]);
			}
			else
			{
				if(str[i] == '(')     //(2)左括号直接压栈
				s.push(str[i]);
				else if(str[i] == ')')          //(3)右括号则弹栈至左括号
				{
					while(s.top() != '(')
					{
						ptr += s.top();
						s.pop();
					}
					s.pop();  //将左括号弹出
				}
				//(4)对于运算符,压入栈时,其栈顶的运算符优先级低于自身
				else if(str[i] == '+' || str[i] == '-')  
				{
					while(!s.empty() && s.top() != '(')
					{
						ptr += s.top();
						s.pop();
					}
					s.push(str[i]);
				}
				else if(str[i] == '*' || str[i] == '/')
				{
					while(!s.empty() && s.top() != '(' &&
					     (s.top() == '/' ||s.top() == '*'))
					{
						ptr += s.top();
						s.pop();
					}
					s.push(str[i]);
				}
			}
		}
	}
	while(!s.empty()) //如果操作符栈还有操作符
	{
		ptr += s.top();
		s.pop();
	}
	return ptr;
}
int main()
{
	string s = "1+((2+3)*4)-5";
	cout<<test(s);
}

3.判断出栈序列合法性

对于给定的入栈序列s1和出栈序列s2,判断s2是否合法
关键在于:出栈序列的元素s2[i]之后比他小的元素,一定是按照降序排列的
我们通过一个栈来模拟,判断序列的合法性
不断将入栈序列元素压入,每次压入一个元素,检查栈顶与指向出栈序列的元素比较,如果相等弹出,指针后又移。最后判断栈是否为空,为空则合法

bool test(string s1,string s2)
{
	if(s1.size() != s2.size())
	return false;
	
	stack<char> s;
	int top = 0;
	
	for(int i = 0;i < s1.size();i++)
	{
		s.push(s1[i]);
		while(!s.empty() && s.top() == s2[top])
		{
			s.pop();
			top++;
		}
	}
	if(!s.empty())
	return false;
	return true;
}

队列

主要是在BFS的时候用得多一点

单调栈

顾名思义,栈中的元素是单调的
单调递增/减栈:从栈底向上元素单调递增/减
用途:寻找比当前元素下一个更小/大的前后元素以及其位置

void test(vector<int>&num)  //实现一个严格单调递增栈
{
	stack<int> s;
	for(int i = 0;i < num.size();i++)
	{
		while(!s.empty()&&s.top() > num[i])
		s.pop();
		s.push(num[i]);
	}
}

1.bad hair day
题目大意:对于数组每一个元素,计算其到右边第一个比他大的元素的步数-1
使用暴力搜索的时间复杂度:O(n2)

int test(vector<int>&num)  //O(n)
{
	stack<int> s;
	int sum = 0;
	for(int i = 0;i < num.size();i++)
	{
		while(!s.empty() && num[s.top()] <= num[i])  //构造一个单调递减栈
		s.pop();
		sum += s.size();
		s.push(i);
	}
	return sum;
}

2.next greater number 1

vector<int> test(vector<int>&num1,vector<int>&num2)
{
	stack<int> s;
	vector<int> index;
	if(num2.size() == 0)
	  return index;
	map<int,int> p;   //value--index
	for(int i = 0;i < num1.size();i++)
	{
		while(!s.empty() && num1[s.top()] < num1[i])  //单调递减栈
		{
			p[num1[s.top()]] = i;   //栈顶遇到第一个比他大的元素num1[i]
			s.pop();
		}
		s.push(i);
	}
	for(int i = 0;i < num2.size();i++)
	index.push_back(p[num2[i]] == 0 ? -1 :num1[p[num2[i]]]);
	return index;
}
/*执行用时 :8 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗 :9.5 MB, 在所有 C++ 提交中击败了35.96%的用户*/

next greater number 2
题目变化在于是循环数组,只需要将数组放大一倍即可
3.largest rectangle in histogram
题目大意:对于直方图,找出最大矩形面积
实际上,我们需要找到元素的第一个比他小的值

int test(vector<int>&num)
{
	int ans = 0;
	stack<int> s;
	num.push_back(0);  //这里有个技巧,在数组末尾加入0是为了最后清空栈
	for(int i = 0;i < num.size();i++)
	{
		while(!s.empty()&&num[s.top()] > num[i]) //严格单调递增 
		{
		   //弹出一个元素,计算以该元素为基准的max值 
			int bar = num[s.top()];
			s.pop();
			//如果弹出后栈为空,说明该bar可以左扩张到左边界
			//如果弹出后栈不为空,说明该元素是目前区间最大的,
			//不能再继续左扩展 
			//弹栈元素的最大右扩张值是当前while第一个弹出的bar 
			int index = s.empty() ? -1 : s.top();
			ans = max(ans,bar * (i - index - 1));
		}
		s.push(i);
	}
	return ans;
}
/*执行用时 :12 ms, 在所有 C++ 提交中击败了96.98%的用户
内存消耗 :10.6 MB, 在所有 C++ 提交中击败了35.50%的用户*/

4.maximal rectangle
这道题和上一个类似,只需要计算出每一个数组的高度,然后调用上面的函数即可

单调队列

入队:这个和单调栈差不多,为了维护单调性,如果队尾元素大于将要入队的元素,则队尾元素出队,一直到队尾元素小于/大于将要入队的元素。
出队:被动出队(为了维护单调性,从队尾出队)、主动出队(和普通队列一样)

void test(vector<int>&num)
{
	//实现一个严格单调递增队列
	deque<int> d;
	for(int i = 0;i < num.size();i++)
	{
		while(!d.empty() && num[d.back()] > num[i])//被动出队 
		d.pop_back();
		d.push_back(i); //入队 
	} 
}

1.sliding windows
题目大意:给定一个数组和一个窗口尺寸k,让这个窗口在数组上移动(从0开始),每移动一个元素得到一个子数组,求每个子数组的最大值、最小值.
暴力搜索:O(nk),采用单调队列:O(n)
运行流程
对于数组:1,3,-1,-3,5,3,6,7 设窗口尺寸k=3,来计算最小值
(1)1入队,[1],min[0] = 1
(2)3入队,满足单调递增,[1,3],min[1] = 1
(3)-1入队,弹出1,3,[-1],min[2] = -1
(4)-3入队,弹出-1,[-3],min[3] = -3
(5)5入队,满足 单调递增,[-3,5],min[4] = -3
(6)3入队,弹出5,[-3,3],min[5] = -3
(7)6入队,满足单调递增,[-3,3,6],i - d.front() + 1 > k,-3出队,[3,6],min[6] = 3
(8)7入队,满足单调递增,[3,6,7],min[7]=3
最后min[k-1:n-1] = [-1,-3,-3,-3,3,3]

void test(vector<int>&num,int k)
{
	deque<int> d;
	vector<int> max,min;
	for(int i = 0;i < num.size();i++) 
	{
	//构造一个单调递增队列
		while(!d.empty() && num[d.back()] > num[i]) //不满足单调性出队
		d.pop_back();
		d.push_back(i);
		while(i - d.front() + 1 > k) //大于窗口尺寸
		d.pop_front();
		min.push_back(num[d.front()]);
	}
	d.clear();
	for(int i = 0;i < num.size();i++)
	{
		while(!d.empty() && num[d.back()] < num[i])
		d.pop_back();
		d.push_back(i);
		while(i - d.front() + 1 > k)
		d.pop_front();
		max.push_back(num[d.front()]);
	}
	for(int i = k-1 ;i < num.size();i++)
	{
		cout<<min[i]<<" ";
	}
	cout<<endl;
	for(int i = k-1 ;i < num.size();i++)
	{
		cout<<max[i]<<" ";
	}
}

单调队列最主要的应用(我的理解):在区间中求最值
单调队列还可以应用于DP进行优化
sliding Windows problem set
未完待续。。。

猜你喜欢

转载自blog.csdn.net/qq_42028241/article/details/95900413