栈(Stack)和队列(Queue)

文章目录

栈和队列可以视为数组和链表的限制版本。

应用

括号匹配

问题描述:对一个字符串的左右括号进行匹配。

解题思路:遇到左括号,入栈。遇到右括号,出栈,若没元素,显然是不匹配的。字符串遍历后,如果栈里面还有元素,那么也是不匹配的。

#include <stack>
#include <string>
#include <vector>
#include <iostream>
using namespace std;

void printMatchedPairs(string expr);

int main() {
	string expr = "(a*(b+c)+d)";
	printMatchedPairs(expr);
	expr = "(a*(b+c)+d";
	printMatchedPairs(expr);
}

void printMatchedPairs(string expr) {
	stack<char, vector<char>> seq;
	for (auto& s : expr) {
		if (s == '(')
			seq.push(s);
		if (s == ')') {
			if (seq.empty()) { cout << "not matching" << endl; return; }
			else seq.pop();
		}
	}
	if (seq.empty()) cout << "matching" << endl;
	else cout << "not matching" << endl;
	return;
}

汉诺塔

问题描述:假设有n个碟子和3座塔。初始时所有碟子从大到小堆在塔1上,我们要把碟子都移动到塔2上,每次移动一个,而且任何时候都不能把大碟子压在小碟子上。

解题思路:三个塔,分为起始塔,目标塔,中转塔。每一次都是:将目标塔上n-1个砖放到中转塔,将最后一块砖放到目标塔,最后将中转塔上的砖放到目标塔。然后问题就变成了如何将目标塔上的n-1个砖放到中转塔,本质还是之前的那个思路,于是一个递归的方法就显而易见了。

#include <vector>
#include <stack>
#include <iostream>
using namespace std;

vector<stack<int, vector<int>>> tower;

void move(int, int, int, int);
int main() {	
	tower.assign(3, {});
	for (int i = 64; i >= 1; i--)
		tower[0].push(i);
	move(tower[0].size(), 0, 1, 2);
	cout << tower[1].size() << endl;
	return 0;
}

void move(int n, int x, int y, int z) {
	if (n > 0) {
		move(n - 1, x, z, y);
		tower[y].push(tower[x].top());
		tower[x].pop();
		move(n - 1, z, y, x);
	}
}

在这个问题中给我们的启发是,递归的方法适用于循环解决同一流程的问题。

在解决这一问题中,汉诺塔的移动方式则是典型的先进后出,适合用栈的数据结构来表示塔。

列车车厢重排

问题描述:
一列货运列车有n节车厢,每节车厢要停靠在不同的车站。假设n个车站从1到n编号,而且货运列车按照从n到1的顺序经过车站。车厢的编号与它们要停靠的车站编号相同。为了便于从列车上卸掉相应的车厢,必须按照从前到后、从1到n的顺序把车厢重新排列。

车厢的重排工作在一个转轨站,其中有3个缓冲轨道H1、H2和H3。开始时挂有n节车厢的火车开始在入轨道,而最后在出轨道上的顺序是从右到左,即从1~n。

问题分析:
一节节车厢进入排序流程,如果它就是我要排出去的车厢,那么就不用进入缓冲轨道,如果它不是的话那么进入缓冲轨道,且要找到比他大的,又离他最近的一个数所在的轨道。

#include <vector>
#include <stack>
using namespace std;
// 全局变量
vector<stack<int,vector<int>>> track;
int numberOfCars;		// 车厢的数量
int numberOfTracks;		// 轨道的数量
int smallestCar;		// 在缓冲轨道中编号最小的车厢
int itsTrack;			// 停靠着最小编号车厢的缓冲轨道

void outputFromHoldingTrack() {
	// 将编号最小的移出轨道
	// 从栈itsTrack中删除编号最小的车厢
	track[itsTrack].pop();
	// 删除完了之后就要在所有栈顶中找到最小的车厢和其所在轨道
	smallestCar = numberOfCars + 2;
	for(int i=1;i<=numberOfTracks;i++)
		if (!track[i].empty() && (track[i].top() < smallestCar)) {
			smallestCar = track[i].top();
			itsTrack = i;
		}
}

bool putInHoldingTrack(int c) {
	// 将车厢c移到一个缓冲轨道。返回false,当且仅当没有可用的缓冲轨道
	// 为车厢c寻找合适的缓冲轨道
	// 初始化
	int bestTrack = 0;
	int bestTop = numberOfCars + 1;
	// 扫描缓冲轨道
	for (int i = 1; i <= numberOfTracks; i++)
		if (!track[i].empty()) {
			int topCar = track[i].top();
			// 遵循原则:找到里这个数最近的,但又比它大一些的轨道
			if (c < topCar && topCar < bestTop) {
				bestTop = topCar;
				bestTrack = i;
			}
		}
		else // 如果缓冲轨道为空且没有更好的选择
			if (bestTrack == 0) bestTrack = i;
	if (bestTrack == 0) return false;
	track[bestTrack].push(c);
	if (c < smallestCar) {
		smallestCar = c;
		itsTrack = bestTrack;
	}
	return true;
}
bool railroad(int inputOrder[], int theNumberOfCars, int theNumberOfTrackers) {
	// 从初始顺序开始重排车厢
	// 如果重排成功,返回true,否则返回false

	numberOfCars = theNumberOfCars;
	numberOfTracks = theNumberOfTrackers;

	// 创建用于缓冲轨道的栈 这里是轨道数+1 目的就是考虑到了不存在的情况,如果不存在,那么我的bestTrack就是0
	track.assign(numberOfTracks + 1, {});

	int nextCarToOutput = 1; // 希望移出去的车厢号
	smallestCar = numberOfCars + 1; // 缓冲轨道中无车厢

	// 重排车厢
	for (int i = 1; i <= numberOfCars; i++)
		if (inputOrder[i] == nextCarToOutput) {
			// 如果将要排位的车厢刚好就是我要移出去的,那么直接移出去
			// 将车厢inputOrder[i]直接移出轨道
			nextCarToOutput++;
			// 移出去之后看看当前轨道里有没有也可以移出去的
			while (smallestCar == nextCarToOutput) {
				outputFromHoldingTrack();
				nextCarToOutput++;
			}
		}
		else
			if (!putInHoldingTrack(inputOrder[i]))
				return false;
	return true;

该题基于栈的数据结构,抓住离开候选轨道、进入候选轨道的条件完成了此次排序。

离线等价类问题

问题描述:输入元素、关系,将这n个元素划分为等价类。

求解策略:

  1. 第一阶段,输入数据,建立n个表以表示关系对;
  2. 第二阶段,寻找等价类,是深度优先的策略。

离线等价类本质就是每个等价类形成了一棵树,通过深度优先遍历出来。

在线等价类问题就是知道元素,知道两两之间是否等价,将这n个元素划分为等价类。

在线等价类就是通过关系一步步把这棵树搭建起来。

目的都是为了划分等价类,不过两者对于等价关系的应用不一样。

布线问题

问题介绍及思路

关键还是把握住出栈和入栈的条件。每一个线都把这个盒分了个区,所以说当这个线想要出栈时,区域内的线应该都出去了。有点类似于括号匹配问题。

配对问题通常考虑栈。

迷宫老鼠

寻找一条从入口到出口的路径。

首先把迷宫入口作为当前位置。如果当前位置是迷宫出口,那么已经找到了一条路径,寻找工作结束。如果当前位置不是迷宫出口,则在当前位置上放置障碍物,以阻止寻找过程又绕回到这个位置。

然后检查相邻位置是否有空闲。如果有,就移动到一个空闲的相邻位置,然后从这个位置开始寻找通往出口的路径。

为了方便移动,在进入新的相邻位置之前,把当前位置保存在一个栈中。

如果所有空闲的相邻位置都已经被探索果,但未能找到路径,则表明迷宫不存在此路径。

#include <iostream>
#include <stack>
#include <vector>
#include <utility>

using namespace std;
using container = stack<pair<int, int>, vector<pair<int, int>>>;
using node = pair<int, int>;
vector<vector<int>> maze = {
	{1,1,1,1,1,1,1,1,1,1,1,1},
	{1,0,1,1,1,1,1,0,0,0,0,1},
	{1,0,0,0,0,0,1,0,1,0,0,1},
	{1,0,0,0,1,0,1,0,0,0,0,1},
	{1,0,1,0,1,0,1,0,1,1,0,1},
	{1,0,1,0,1,0,1,0,1,0,0,1},
	{1,0,1,1,1,0,1,0,1,0,1,1},
	{1,0,1,0,0,0,1,0,1,0,1,1},
	{1,0,1,0,1,1,1,0,1,0,0,1},
	{1,1,0,0,0,0,0,0,1,0,0,1},
	{1,0,0,0,0,1,1,1,1,0,0,1},
	{1,1,1,1,1,1,1,1,1,1,1,1}
};



container find_path() {
	container path;
	// 设置起点和终点
	node start = { 1,1 };
	node exit = { 10,10 };
	// 初始化偏移量
	pair<int, int> offset[4];
	offset[0].first = 0; offset[0].second = 1; // right
	offset[1].first = 1; offset[1].second = 0; // down
	offset[2].first = 0; offset[2].second = -1; // left
	offset[3].first = -1; offset[3].second = 0; // up
	
	node current_location = start;
	maze[start.first][start.second] = 1;
	int option = 0;
	int lastOption = 3;

	while (current_location != exit) {
		int r, c;
		// 找无障碍的路
		while (option <= lastOption) {
			r = current_location.first + offset[option].first;
			c = current_location.second + offset[option].second;
			if (maze[r][c] == 0) break;
			option++;
		}
		// 找到了,进行更新
		if (option <= lastOption) {
			path.push(current_location);
			current_location.first = r;
			current_location.second = c;
			maze[r][c] = 1;
			option = 0;
		}
		// 没找到,进行回溯或者返回未完成的路径
		else {
			if (path.empty()) return path;
			node next = path.top();
			path.pop();
			if (next.first == current_location.first) {
				option = 2 + next.second - current_location.second;
			}
			else option = 3 + next.first - current_location.first;
			current_location = next;
		}
	}
	return path;
}

int main() {
	container path = find_path();
	while (!path.empty()) {
		cout << path.top().first << path.top().second << endl;
		path.pop();
	}
}

从该代码中,可以学到的方法有:01地图表示、用预设的偏移量来表示移动。

综上所述,栈通常用于配对问题(括号匹配、布线问题)或者针对实际应用中事物的移动情况(汉诺塔、列车重排、离线等价类问题)或者搜索问题,尤其是深度优先(迷宫老鼠)。

队列

应用

列车车厢重排

#include <iostream>
#include <queue>
#include <list>
#include <vector>
#include <deque>
#include <utility>
using namespace std;
using tracks = vector<queue<int,deque<int>>>;

tracks trs = { {},{},{} };
int next_train = 1;
pair<int, int> min_train = { 0,11 };

list<int> clear_the_rest(list<int> target);
list<int> get_trains(list<int> trains);

int main() {
	list<int> trains = { 5,8,1,7,4,2,9,6,3 };
	list<int> target = get_trains(trains);
	for (auto& t : target) cout << t << endl;
	return 0;
}

list<int> get_trains(list<int> trains) {
	list<int> target = {};
	int len = trains.size();
	for (int i = 0; i < len; i++) {
		// 弹出一节车厢
		int train = trains.back();
		trains.pop_back();
		// 判断是否可以直接驶出轨道
		if (train == next_train) {
			target.push_front(next_train);
			next_train++;
			// 驶出之后,再看看当前轨道中有没有可以续上的车厢
			target = clear_the_rest(target);
		}
		// 如果不可以,则将该车厢安排进轨道
		else {
		// 候选驶入轨道及其末尾车厢号,因为空视为车厢号为0,所以这里需要设置为-1
			pair<int, int> wait_tr = { 0,-1 };
			for (int i = 0; i < 3; i++) {
				int max;
				if (trs[i].empty()) max = 0;
				else max = trs[i].back();
				// 找到仅次于驶入车厢的最大车厢号
				if (train > max && max > wait_tr.second) {
					wait_tr.first = i;
					wait_tr.second = max;
				}	
			}
			// 实时更新最小车厢号及其位置
			trs[wait_tr.first].push(train);
			if (min_train.second > train) {
				min_train.first = wait_tr.first;
				min_train.second = train;
			}
		}
	}
	return target;
}

list<int> clear_the_rest(list<int> target) {
	while (min_train.second == next_train) {
		target.push_front(next_train);
		next_train++;
		trs[min_train.first].pop();
		
		min_train = { 0,11 };
		
		for (int i = 0; i < 3; i++) {
			if (trs[i].empty()) continue;
			if (trs[i].front() < min_train.second) {
				min_train.first = i;
				min_train.second = trs[i].front();
			}
		}
	}
	return target;
}

电路布线(Lee算法)

Lee算法是一种从网格中寻找最短路径的经典算法。

比如说,我们要寻找a和b之间的最短路径。a和b之间的最短路径需要在两个过程中确定。一个是距离标记的过程,另一个是路径标记的过程。

在距离标记过程中,从位置a开始,把从a可到达的相邻方格都标记为1,然后把标记为1的方格可到达的方格标记为2,以此类推。直到到达b或者没有可到达的相邻的方格为止。

一旦到达b,b的编号便是b与a之间的距离。之后,路径标记过程开始,从方格b开始,首先移动到比b小1的相邻位置。重复这个过程,直到到达a为止。

#include <queue>
#include <utility>
using namespace std;
using node = pair<int, int>;

bool findPath() {
	node entrance = { 1,1 };
	node exit = { 10,10 };
	
	const int size = 10;
	int grid[size + 2][size + 2];

	if (entrance == exit) {
		const int pathLength = 0;
		return true;
	}
	// 初始化偏移量
	pair<int, int> offset[4];
	offset[0].first = 0; offset[0].second = 1; // right
	offset[1].first = 1; offset[1].second = 0; // down
	offset[2].first = 0; offset[2].second = -1; // left
	offset[3].first = -1; offset[3].second = 0; // up

	// 初始化网格四周的障碍物
	for (int i = 0; i <= size + 1; i++) {
		grid[0][i] = grid[size + 1][i] = 1;
		grid[i][0] = grid[i][size + 1] = 1;
	}

	node here = entrance;
	// 0表示无障碍且未标记,1表示障碍;于是起点设置为2,只要将距离-2就可得到真实距离。
	grid[entrance.first][entrance.second] = 2; 
	int numOfNbrs = 4; // 一个方格的相邻位置数;
	node nbr;
	queue<node, deque<node>> q;
	// 距离标记
	do {
		for (int i = 0; i < numOfNbrs; i++) {
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;
			if (grid[nbr.first][nbr.second] == 0) {
				grid[nbr.first][nbr.second] = grid[here.first][here.second] + 1;
				if (nbr == exit) break;
				q.push(nbr);
			}
		}
		if (nbr == exit) break;
		if (q.empty()) return false;
		here = q.front();
		q.pop();
	} while (true);
	// 路径构造
	const int pathLength = grid[exit.first][exit.second] - 2;
	node *path = new node[pathLength];
	here = exit;
	for (int j = pathLength - 1; j >= 0; j--) {
		path[j] = here;
		for (int i = 0; i < numOfNbrs; i++) {
			nbr.first = here.first + offset[i].first;
			nbr.second = here.second + offset[i].second;
			if (grid[nbr.first][nbr.second] == j + 2) break;
		}
		here = nbr;
	}
	return true;

}
上一篇:几种个人博客搭建平台介绍


下一篇:vue首屏加载时间获取