特点
后进先出
单端操作 只能从栈顶插入删除
栈的定性应用
逆序输出-进制转换
递归版本
void convert(int n, stack <char> &s, int base) {
if (n < 1) return;
char digit[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
s.push(digit[n%base]);
convert(n / base, s, base);
}
栈混洗
A,B,S三个栈,A含有n个元素,B,S初始为空。只允许S.push(A.pop())弹出栈A的顶元素并随即压入栈S中,B.push(S.pop))弹出栈S的顶元素并随即压入B中,经过n次操作之后,A,S栈已为空,原A中的元素均已转入到B中,B中的一个可能的序列为A的一个栈混洗。
对于任何的1<=i <j<k<=n,...k...i...j必为“禁行”。
括号匹配
bool paren(char exp[], int lo, int hi) {
stack<char> s;
for (int i = lo; i<hi; i++) {
switch (exp[i]) {
case '(': case '{': case '[':s.push(exp[i]);
case ')':if (!s.empty() && s.top() == '(') s.pop(); else return false; break;
case ']':if (!s.empty() && s.top() == ']') s.pop(); else return false; break;
case '}':if (!s.empty() && s.top() == '}') s.pop(); else return false; break;
}
}
return s.empty();
}
延迟缓冲-表达式求值
#define N_OPTR 9//运算符总数
typedef enum {ADD,SUB,MUL,DIV,FAC,POW,L_P,R_P,EOE} Operator;//运算符集合
const char pri[N_OPTR][N_OPTR]={//运算符
/*----------------------------当前运算符-----------------*/
/* + - * / POW ! ( ) \0 */
/* + */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/*栈 - */ '>', '>', '<', '<', '<', '<', '<', '>', '>',
/*顶* */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/*运 / */ '>', '>', '>', '>', '<', '<', '<', '>', '>',
/*算 pow */ '>', '>', '>', '>', '>', '<', '<', '>', '>',
/*符 ! */ '>', '>', '>', '>', '>', '>', ' ', '>', '>',
/* ( */ '<', '<', '<', '<', '<', '<', '<', '=', ' ',
/* ) */ ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
/* \0 */ '<', '<', '<', '<', '<', '<', '<', ' ', '=',
};
bool isDigit(char a){
return a>='0'&&a<='9'?true:false;
}
void readNumber(char*& p,Stack<float>& s){
s.push((float)(*p-'0'));
while(isDigit(*(++p)))
{
float tmp=s.top()*10+(*p-'0');
s.pop();
s.push(tmp);
}
if('.'!=*p) return;//此后非小数点,书名已经完成操作数的解析
float fraction=1;
while(isDigit(*(++p)))
{
float tmp=s.top()+(*p-'0')*(fraction/=10);
s.pop();
s.push(tmp);
}
}
Operator optr2rank(char op){//由运算符转译出编号
switch(op){
case '+':return ADD;
case '-':return SUB;
case '*':return MUL;
case '/':return DIV;
case '^':return POW;
case '!':return FAC;
case '(':return L_P;
case ')':return R_P;
case '\0':return EOE;//起始符和终止符
default:exit(-1);//未知运算符
}
}
char orderBetween(char op1,char op2){//比较两个运算符之间的优先级
return pri[optr2rank(op1)][optr2rank(op2)];
}
void append(char*& rpn,float opnd){//将操作数接至末尾
int n=strlen(rpn);
char buf[64];
if(opnd!=(float)(int)opnd) sprintf(buf,".2f\0",opnd);//浮点格式,或
else sprintf(buf,"%d\0",(int)opnd);//整数格式
rpn=(char*)realloc(rpn,sizeof(char)*(n+strlen(buf)+1));//扩展空间
strcat(rpn,buf);//RPN加长
}
void append(char*& rpn,char optr){
int n=strlen(rpn);//RPN当前长度(以\0结尾,长度n-1)
rpn=(char*)realloc(rpn,sizeof(char)*(n+3));//扩展空间
sprintf(rpn+n,"%c",optr);rpn[n+2]='\0';//接入指定的运算符
}
float evalate(char* S,char* &RPN){//已剔除空白字符的表达式S求值,并转换为逆波兰式RPN
stack<float> opnd;stack<char> optr;//运算数栈,运算符栈
optr.push('\0');//尾哨兵'\0'作为头哨兵入栈
while(!optr.empty()){//在运算符非空之前逐个处理表达式中的各个字符
if(isDigit(*S)){//若当前字符为操作数,则
readNumber(S,opnd);append(RPN,opnd.top());//读入操作数,并将其接至RPN末尾
}else{//若当前字符为操作符
switch(orderBetween(optr.top(),*S)){//视其余栈顶元素的优先级操作
case '<'://栈顶元素的优先级更低时
optr.push(*S);S++;
break;
case '>'://栈顶元素更高。可实施相应的计算,并将结果重新入栈
{
char op=optr.top();optr.pop();append(RPN,op);//栈顶运算符出栈并续接至RPN末尾
if('!'==op)//若属于一元运算符
{
float pOpnd=opnd.top();opnd.pop();//只取出一个数并实施计算
opnd.push(calcu(op,pOpnd));
}else{//对于其它二元运算
float pOpnd2=opnd.top();opnd.pop();
float pOpnd1=opnd.top();opnd.pop();
opnd.push(calcu(pOpnd1,op,pOpnd2));
}
break;
}
default:exit(-1);//逢语法错误,不做处理直接退出
}
}
}
return opnd.pop();
}
当栈顶操作符大于当前操作符(例如“2*3+1”),则进行运算。‘)’大于任何操作符;
当栈顶操作符小于当前操作符(例如“1+2*3”),则将当前操作符入栈。‘(’小于任何操作符;
当栈顶操作符等于当前操作符(例如“()”),则将栈顶操作符弹出。‘(’==‘)’和‘\0’==‘\0’.
以上算法假设公式语法正确。
逆波兰数
语法规则:操作符紧邻与对应的(最后一个)操作数之后。
常用于表达式计算,因为运算符被执行的次序与其在RPN数中的次序完全一致
具体算法:凡遇到操作数,即追加到rpn,而运算符只有在从栈中弹出并执行时才被追加。
八皇后问题
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
const static int QUEENFREE = INT_MAX;
bool eightQueens(vector<int> &vi, int n ) {
vi.clear();
vi.resize(n, QUEENFREE);
int k = 0;
while (k >= 0) {
//k代表即将需要填入皇后的行数
int i = 0;
for (; i<n; i++) {//i代表在这一行填入的行数
if (islegalMove(vi, k, i)) {
//如果vi[k]不等于QUEENFREE那么就是说明这是从下一层返回来的k值
//第一次进入该层就应该满足条件就填写了,但是如果是从下一层返回来的话
//就不能填写比以前更小的值了,这也是Backtracking算法的一个特征
//是按一定循环的值循环探索适合的解。
//这里不用栈,只用k执行了差不都是栈的功能
if (vi[k] == QUEENFREE || vi[k]<i) {
vi[k] = i;
//判断已经填到了最后一行,那么就是成功了
if (k == n - 1) return true;
k++;
break;
}
}
}
if (i == n) {
//k行填写了所以可能的值都不符合要求,那么就返回上一层
//这时候需要把k行还原为原始状态,代表返回了上一层,这样方便上面判断
vi[k] = QUEENFREE;
k--;
}
}
//最后Backtracking到了根,那么就是没有解了。
return false;
}
bool islegalMove(vector<int>& vi, int row, int col) {
//判断列是否符合规定
for (int i = 0; i<vi.size(); i++) {
if (i != row&&vi[i] == col)
return false;
}
//判断对角线是否符合规定,后面的哦安定公式费点神
for (int i = 0; i<vi.size(); i++) {
if (i != row && (vi[i] - i == col - row || vi[i] + i == col + row)) {
return false;
}
}
//不用判断行了,因为默认是一行只天一个queen的
return true;
}
};
int main() {
vector<int>queen;
Solution solu;
if (solu.eightQueens(queen, 4))
for (auto x : queen) {
for (int i = 0; i<queen.size(); i++) {
if (i == x)
cout << x << "QUEEN" << "\t";
else
cout << "#" << "\t";
}
cout << endl;
}
else cout << "No Solution";
cout << endl;
system("pause");
return 0;
}
递归版本
//递归版本的八皇后
#include<iostream>
using namespace std;
#define QUEEN_MAX 20
int nSolu=0;//解的总数
int nCheck=0;//尝试的总次数
void displaySolution(int *solu,int n){//输出n*n的可行布局
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
printf((j==solu[i])?"█":"[]");
}
printf("\n");
}
cout<<nSolu<<"solution(s) found after"<<nCheck<<"check(s)\a";
printf("\n");
}
bool collide(int *solu,int k){//判断是否可以在当前行的第k列放置下一个皇后
nCheck++;
for(int i=0;i<k;i++){
if(solu[k]==solu[i]) return true;//l列
if(solu[k]-solu[i]==k-i) return true;//对角线
if(solu[k]-solu[i]==i-k) return true;//反对角线
}
return false;
}
void placeQueens(int nQueen,int k){//放置n皇后中的第k个
static int solu[QUEEN_MAX];//解法
if(nQueen<=k){//若所有皇后都已经就位,则
nSolu++;displaySolution(solu,nQueen);//输出可行解
}else//否则
for(int i=0;i<nQueen;i++){//依次
solu[k]=i;//试着将当前皇后放在(当前行的各列上
if(!collide(solu,k))//若没有冲突,则
placeQueens(nQueen,k+1);//进而考虑下一个皇后
}
}//如何回溯???
int main(){
int nQueen=8;
placeQueens(nQueen,0);//启动算法
cout<<nSolu<<"solution found after"
<<nCheck<<"check for"
<<nQueen<<"queen(s)\n";
return 0;
}
迷宫寻径
#include<iostream>
#include<stack>
#include<time.h>
using namespace std;
//迷宫寻径
typedef enum { AVAILABLE, ROUTE, BACKTRACKED, WALL } Status;//迷宫单元状态
//原始可用的,在当前路径上的,所有方向均尝试失败后回溯过得,不可使用的(墙)
typedef enum { UNKNOWN, EAST, SOUTH, WEST, NORTH, NO_WAY } ESWN;//单元的相对邻接方向
//未定 东 南 西 北 无路可通
inline ESWN nextESMN(ESWN eswn) { return ESWN(eswn + 1); }//依次转至下一邻接方向
struct Cell
{//迷宫格点
int x, y; Status status;//x坐标 y坐标 类型
ESWN incoming, outgoing;//进入 走出方向
};
int labySize;
Cell* startCell;
Cell* goalCell;
#define LABY_MAX 24//最大迷宫尺寸
Cell laby[LABY_MAX][LABY_MAX];//迷宫
void randLaby() {//生成随机的迷宫
labySize = LABY_MAX / 2 + rand() % (LABY_MAX / 2);
printf("Using a laby of size %d...\n", labySize);
for (int i = 0; i < labySize; ++i)
for (int j = 0; j < labySize; ++j)
{
laby[i][j].x = i;
laby[i][j].y = j;
laby[i][j].incoming = laby[i][j].outgoing = UNKNOWN;
laby[i][j].status = WALL;//边界格点必须是墙
}
for (int i = 1; i<labySize - 1; i++)
for (int j = 1; j<labySize - 1; j++)
if (rand() % 4) laby[i][j].status = AVAILABLE;//75%的格点为空可用
startCell = &laby[rand() % (labySize - 2) + 1][rand() % (labySize - 2) + 1];
goalCell = &laby[rand() % (labySize - 2) + 1][rand() % (labySize - 2) + 1];
startCell->status = goalCell->status = AVAILABLE;//起始格点必须可用
}
inline Cell* neighbor(Cell*cell) {//查询当前位置的相邻格点
switch (cell->outgoing) {
case EAST:return cell + LABY_MAX;//向东
case SOUTH:return cell + 1;//向南
case WEST:return cell - LABY_MAX;//向西
case NORTH:return cell - 1;//向北
default:exit(-1);
}
}
inline Cell* advance(Cell* cell) {//从当前位置转入相邻节点
Cell*next;
switch (cell->outgoing) {
case EAST:next = cell + LABY_MAX; next->incoming = WEST; break;//向东
case SOUTH:next = cell + 1; next->incoming = NORTH; break;//向南
case WEST:next = cell - LABY_MAX; next->incoming = EAST; break;//向西
case NORTH:next = cell - 1; next->incoming = SOUTH; break;//向北
default:exit(-1);
}
return next;
}
//输出某一迷宫的信息
void printLabyCell(Cell* elem) {
printf("%d->(%d,%d)->%d\n",
((Cell*)elem)->incoming,
((Cell*)elem)->x,
((Cell*)elem)->y,
((Cell*)elem)->outgoing);
}
//显示迷宫
void displayLaby() { //┘└┐┌│─
static char* pattern[5][5] = {
"┼", "┼", "┼", "┼", "┼",
"┼", " ", "┌", "─", "└",
"┼", "┌", " ", "┐", "│",
"┼", "─", "┐", " ", "┘",
"┼", "└", "│", "┘", " "
};
system("cls");
printf(" ");
for (int j = 0; j < labySize; j++)
(j < 10) ? printf("%2X", j) : printf(" %c", 'A' - 10 + j);
printf("\n");
for (int j = 0; j < labySize; j++) {
(j < 10) ? printf("%2X", j) : printf(" %c", 'A' - 10 + j);
for (int i = 0; i < labySize; i++)
if (goalCell == &laby[i][j])
printf("﹩");
else
switch (laby[i][j].status) {
case WALL: printf("█"); break;
case BACKTRACKED: printf("○"); break;
case AVAILABLE: printf(" "); break;
default: printf("%s", pattern[laby[i][j].outgoing][laby[i][j].incoming]); break;
}
printf("\n");
}//for
}
//迷宫寻径算法:在格单元s至t之间规划一条通路(如果的确存在)
bool labyrinth(Cell laby[LABY_MAX][LABY_MAX], Cell*s, Cell*t) {
if ((AVAILABLE != s->status) || (AVAILABLE != t->status)) return false;//退化情况
stack<Cell*> path;//用栈记录通路
s->incoming = UNKNOWN; s->status = ROUTE; path.push(s);//起点
do {//从起点出发不断试探,回溯,直到抵达终点,或者穷尽所有可能
displayLaby();
Cell* c = path.top();//检查当前位置(栈顶)
if (c == t) return true;//若已抵达终点,则找到一条通路;否则,沿尚未试探的方向继续试探
while (NO_WAY>(c->outgoing = nextESMN(c->outgoing)))//逐一检查所有方向
if (AVAILABLE == neighbor(c)->status) break;//视图找到尚未试探的方向
if (NO_WAY <= c->outgoing)//若所有方向都已尝试过
{
c->status = BACKTRACKED; c = path.top(); path.pop();
}//则向后回溯一部
else//否则,向前试探一步
{
path.push(c = advance(c)); c->outgoing = UNKNOWN; c->status = ROUTE;
}
} while (!path.empty());
return false;
}
int main(int argc, char *argv[])
{
/* code */
srand((unsigned int)time(NULL));//设置随机种子
randLaby();//随即生成
labyrinth(laby, startCell, goalCell) ?//启动算法
printf("\nRoute found\a\n") :
printf("\nNo route found\a\n");
system("pause");
return 0;
}
队列
特点
单端操作-从对头取出,从队尾插入
先进先出