一、定义
-
先进先出 (First In First Out)
-
按照到达的顺序来释放元素
-
所有的插入在表的一端(队首)进行,所有的删除都在表的另一端(队尾)进行
-
二、实现
1、创建
int S[n+1]; //S为最多能够容纳n个元素的队列 int head=0, tail=0; //head表示队首的位置,tail表示队尾的位置,队列为空时,head和tail位于同一位置;队列满时,(tail + 1)%(n + 1)==head。
2、判断是否为空
bool emp(){ if (tail==head) return true; else return false; }
3、判断是否为满
bool ful(){ if ((tail + 1)%(n + 1)==head){ return true; } return false; }
4、进队
//x进队 bool push(int x){ if (ful()){ return false; } S[tail]=x; tail = (tail + 1)%(n + 1); return true; }
5、出队
bool pop(){ if (emp()) { return false; } head = (head - 1)%(n + 1); return true; }
三、应用
1、农夫过河
(1)问题内容
“人狼羊菜”乘船过河:只有人能撑船,船只有两个位置(包括人),狼羊、羊菜不能在没有人时共处。
(2)思路
用状态位向量(x_1,x_2,x_3,x_4)表示物体的位置,x_1,x_2,x_3,x_4分别代表人、狼、羊、菜的位置,物体在起始处时,x_i=0;在对岸时,x_i=1。问题变为从状态0000(整数0)出发,寻找全部由安全状态构成的状态序列,以状态1111(整数15)为最终目标。状态序列中每个状态都可以从前一状态通过农夫(可以带一样东西)划船过河的动作到达。序列中不能出现重复状态。
利用广度优先搜索,采用队列做辅助结构,把下一步有可能达到的状态都放在队列中,然后顺序取出对其分别处理,处理过程中再把下一步的状态放在队列中,……。由于队列的操作按照先进先出原则,因此只有前一步的所有情况都处理完后才能进入下一步。
-
广度优先搜索:搜索该步的所有可能状态,再进一步考虑后面的各种情况——队列应用
-
深度优先搜索:沿某一状态走下去,不行再回头——栈应用
(3)代码
a. 确定每个物体的位置
bool farmer(int status) { return ((status & 0x08) != 0); } bool wolf(int status) { return ((status & 0x04) != 0); } bool sheep(int status) { return ((status & 0x02) != 0); } bool cabbage(int status) { return ((status & 0x01) != 0); }
b. 安全状态的判断
bool safe(int status) // 返回true:安全,false:不安全 { if ((goat(status) == cabbage(status)) && (goat(status) != farmer(status))) return(false); // 羊吃白菜 if ((goat(status) == wolf(status)) && (goat(status) != farmer(status))) return(false); // 狼吃羊 return(true); // 其它状态为安全 }
c.广度优先搜索
-
定义一个整数队列moveTo,它的每个元素表示一个可以安全到达的中间状态。
-
还需要定义一个数据结构记录已被访问过的各个状态,以及已被发现的能够到达当前这个状态的路径。用顺序表route 的第i 个元素记录状态i是否已被访问过。
-
若route[i] 已被访问过,则在这个顺序表元素中记入前驱状态值; -1表示未被访问
-
route 的大小(长度)为16
-
void solve() { int movers, i, location, newlocation; vector<int> route(END+1, -1); // 记录已考虑的状态路径 queue<int> moveTo; // 准备初值 moveTo.push(0x00); route[0]=0; while (!moveTo.empty() && route[15] == -1) { status = moveTo.front(); moveTo.pop(); for (movers = 1; movers <= 8; movers <<= 1) { // 农夫总是在移动,随农夫移动的也只能是在农夫同侧的东西 if (farmer(status) == (bool)(status & movers)) { // 随农夫移动以后的状态 newstatus = status ^ (0x08 | movers); // 安全的,并且未考虑过的走法 if (safe(newstatus) && (route[newstatus] == -1)) { route[newstatus] = status; moveTo.push(newstatus); } } } } // 反向打印出路径 if (route[15] != -1) { cout << "The reverse path is : “ << endl; for (int status = 15; status >= 0; status = route[status]) { cout << "The status is : “ << status << endl; if (status == 0) break; } } else { cout << "No solution.“ << endl; }
2、双向队列、单调队列
双向队列:一端既可以有元素入队,又有元素出队的队列 单调队列:队列中元素单调递增或递减的队列
给定一个长度为n(n<=10^6)的数组。有一个大小为k的滑动窗口从数组的最左端移动到最右端。你可以看到窗口中的k个数字。窗口每次向右滑动一个数字的距离。
输入 输入包括两行。 第一行包括n和k,分别表示数组的长度和窗口的大小。 第二行包括n个数字。
输出 输出包括两行。 第一行包括窗口从左至右移动的每个位置的最小值。 第二行包括窗口从左至右移动的每个位置的最大值。
思路:维护单调队列
int main(){ int n,k; cin >> n >> k; vector<int> max, min; int ar[n]; \\STL库中的双端队列 deque<int> Q, P; \\记录输入的数组 for (int i=0; i<n; ++i){ int t; cin >> ar[i]; } for (int i=0; i <n; ++i){ if (Q.empty()){ Q.push_back(i); } else { \\随着窗口移动,先进入的元素滑出窗口 if (i - k >= Q.front()){ Q.pop_front(); } \\维护一个单调递增的队列 while (!Q.empty() && ar[Q.back()]<=ar[i]){ Q.pop_back(); } Q.push_back(i); } \\在已经形成长度为k的窗口时,记录每次移动时窗口中的最大值 if (i>=k-1){ max.push_back(ar[Q.front()]); } } for (int i=0; i <n; ++i){ if (P.empty()){ P.push_back(i); } else { if (i - k >= P.front()){ P.pop_front(); } while (!P.empty() && ar[P.back()]>=ar[i]){ P.pop_back(); } P.push_back(i); } if (i>=k-1){ min.push_back(ar[P.front()]); } } for (int i=0; i<min.size(); ++i){ if (i+1<min.size()){ cout << min[i] << " "; } else { cout << min[i] << endl; } } for (int i=0; i<max.size(); ++i){ if (i+1<max.size()){ cout << max[i] << " "; } else { cout << max[i]; } } }