邓俊辉 《数据结构》学习笔记-第四章 栈与队列(自用)
1.Stack栈(FILO先进后出)
1.1 接口与实现
从基类继承的接口:size()empty()
自定义的接口:push()入栈pop()出栈top()取顶
基于vector实现stack:以向量首端为栈底,向量末端为栈顶!
这样出栈的操作直接remove最后一个元素,如果颠倒过来的话,每取出一个元素整个向量都要前移
template <class T> class vectorStack:public vector<T>{
public:
void push(T const & e){this->insert(size(),e);}//入栈
T pop(){return this->remove((size())-1);}//出栈
T& top(){return (*this)[(size())-1];}//取顶
};
基于List实现stack:以列表末端为底顶,首端为栈顶!
这样出栈的操作只需要remove列表的第一个元素,如果颠倒过来的话,每取出一个元素都要遍历整个列表
template <class T> class listStack:public list<T>{
public:
void push(T const & e){insertBefore(first(),e);}//入栈
T pop(){return remove(first());}//出栈
T& top(){return *(first());}//取顶
};
1.2 应用
1.2.1 逆序输出(递归深度和输出长度不易预知)
conversion进制转换
void convert(Stack<char> & s,__int64 n,int base){//进制转换
static char digit[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
while(n>0){
s.push(digit[n%base]);
n/=base;
}
}
main(){
Stack<char> S; convert(S,n,base);
while(!S.empty()) printf("%C",S.pop());//逆序输出
}
1.2.2 递归嵌套(针对相似性问题,分支位置和嵌套深度不固定)
1.parenthesis括号匹配
下列算法假设表达式中只有 '(‘ 或 ‘)’
bool paren(const char exp[],int lo,int hi){//括号是否匹配
Stack<char> s;
for(int i=lo;i<hi;i++)
if('('==exp[i]) s.push(exp[i]);
else if(!s.empty()) s.pop();
else return false;
return !s.empty();
}
2.permutation栈混洗
什么是栈混洗呢?
对于长度为n的输入序列,可能的得到的栈混洗种数sp(n)=?
当n=3时,<1,2,3]栈混洗种数为5种,但是全排列却有6种,少了一种![3,1,2>
只要不存在312则可判断是否为栈混洗,甄别它的算法可以直接模拟栈混洗
3.合法的括号匹配和合法的栈混洗有着对应关系,即n个序列有多少种栈混洗,则n个括号所对应的合法表达式就有多少种
1.2.3 延迟缓冲(预读足够长后,方可处理前缀)
infix:中缀表达式
float evaluter(char *s,char * &RPN){//中缀式求值,这里的RPN一开始不明白,后来知道是s的RPN:即为逆波兰表达式
vectorStack<float> opnd;//运算数栈
vectorStack<char> optr;//运算符栈
optr.push('\0');//先将尾哨兵推入栈中
while(!optr.empty()){
if(isdijit(*s)){ //若为操作数,存在读入连续数字的情况
readNumber(s,opnd); //则读入操作数
append(RPN,opnd.top());
}else
switc(orderBetween(optr.top(),*s)){//分别处理
case '<'://栈顶运算符优先级低
optr.push(*s);s++;break;
case '='://优先级相等(当前运算符为右括号或者是尾哨兵)
optr.pop();s++;break;//脱括号,接收下一个字符
case '>':{//运算时机到了
char op=optr.pop();//对应出栈的运算符
append(RPN,op);
if('!'==op)//一元运算符
opnd.push(calcu(op,opnd.pop()));
else{//二元运算符
float opnd2=opnd.pop(),opnd1=opnd.pop();//注意运算顺序!先弹出的是运算符后面的数
opnd.push(calcu(opnd1,op,opnd2));
}
break;
}
}
}
return opnd.pop();
}
下图是优先级表
1.2.4 栈式计算(基于栈结构的特定计算模式)
postfix :Reverse Polish Notation逆波兰表达式(RPN)
infix->postfix的转换方法(注意!操作符的位置可能会变,但是操作数不会!):
(1)手工转换
(2)算法转换(在中缀表达式求值的同时,完成RPN转换)
2.Queue队列(FIFO先进先出)
2.1 接口与实现
从基类继承的接口:size()empty()
自定义的接口:
只能在队尾插入(查询)enqueue()插入rear()查询尾部元素
只能在队头删除(查询)dequeue()取出front()查询头部元素
扩展接口:getMax()
实现
//以列表首端为队列头,末端为队列尾
template <class T> class queue:public list<T>{
public:
void enqueue(T const &e){insertAfter(this->last(),e);}//入队
T dequeue(){return remove(first());}//出队
T & front(){return first()->data;}//队首
};
2.2 应用(都是现有的代码,打出来练练手)
2.2.1 资源循环应用
RoundRobin{//资源分配器
Queue Q(clients);//参与资源分配的所有客户排成队列
while(!ServiceClosed()){//在服务关闭之前,反复地
e=Q.dequeue();//令队首的客户出队,并
serve(e);//接受服务,然后
Q.enqueue(e);//重新入队
}
}
2.2.2 银行模拟服务
参数:nWin:窗口(队列)数目,servTime://营业时长
struct Customer{//顾客类
int window;//所属窗口
ubsigned int time;//服务时长
};
void simulate(int nWin,int servTime){
Queue<Customer>* windows=new Queue<Customer>[nWin];
for(int now=0;now<servTime;now++){//在下班之前,每隔单位时间
Customer c;c.time=1+rand()%50;//一位新顾客到达,其服务时长随机指定
c.window=bestWindow(windows,nwin);//找出最佳服务窗口
windows[c.window].enqueue(c);//新顾客加入对应的队列
for(int i=0;i<nWin;i++)//分别检查
if(!Windows[i].empty())//各非空队列
if(--windows[i].front().time<=0)//队首顾客接受服务
windows[i].dequeue();//服务完毕则出列,由后继顾客接替
}
delete [] windows;//释放所有类
}
3.补充
3.1 Steap+Queap(获取栈和队列中最大元素)
3.1.1 Steap(Stack+Heap=push+pop+getMax)
S.pop();P.pop();
S.push(e);P.push(max(e,P.top()));
3.1.2 Queap(Queue+Heap=enqueue+dequeue+getMax)
Q.dequeue();P.dequeue();
Q.enqueue(e);P.enqueue(e);
for(x=P.rear();x&&x->key<=e);x=x->pred)
x->key=e;
3.2 Probe-backtrack试探回溯法
试探:从0开始,逐渐增加候选解长度
剪枝回溯:一旦发现注定失败,则收缩至前一长度
并继续试探,就这样循环往复直至到全部试探完毕
3.2.1 8queens八皇后(完整c++代码)
写完才意识到应该用栈来实现的,怎么搞成vector了,我真傻,真的
首先得先了解八皇后是什么?
可以看到红色部分是一个皇后的势力范围,在这些地方都不能有其他皇后的存在!
所以在n*n的棋盘上只能有n个皇后且每行只有一个!明确了这些之后开始动脑子了
先画一个只有自己才能看懂的四皇后的一种解法流程图(hahah,意思就是太乱了)
然后再开始不停试探,回溯剪枝,直到m=N时,一个解法就出来了。
现在开始编写程序
首先我们需要一个皇后类,来干嘛?记录她的行列号并实现判断她与其他皇后的位置是否安全
参考别人的java代码,用c++实现了!改了一下午,哎,这个是链接
class Queen{//皇后类
friend int setPositionY(int,vector<int>,int);
friend void placeQueen(int);
private:
int positionX,positionY;//记录皇后的位置
public:
Queen(int pox,int poy):positionX(pox),positionY(poy){}//构造函数
Queen(){}//默认构造函数
bool isSafe(Queen q){//判断两个皇后是否安全
return //安全返回true,不安全返回false
!(positionX==q.positionX||//在同一行
positionY==q.positionY||//在同一列
positionX+positionY==q.positionX+q.positionY||//45°线
positionX-positionY==q.positionX-q.positionY);//-45°线
}
};
然后我们编写放置皇后在合适的位置的代码
int setPositionY(int N,vector<int> QueenDone,int poy){//为皇后找合法的位置
Queen *q,*q_exist;
bool flag;//创建一个标记来记录是否有合适的位置
for(int i=poy;i<N;i++){//遍历该行的所有位置
q=new Queen(QueenDone.size(),i);//为每一个格子 都创建一个新皇后这样就可以做比较啦 ,同行不同列
flag=true;
for(int x=0;x<QueenDone.size();x++){//和之前的每个皇后都要比较
q_exist=new Queen(x,QueenDone[x]);//创建之前存在的皇后,从第0行开始
if(!(q_exist->isSafe(*q))){//不安全
flag=false;
break;//跳出循环 ,列数+1
}
}
if(flag){
return q->positionY;//返回找到的列数
}
}
return -1;//都执行完了还没找到,查找失败
}
最后是主要代码
void placeQueen(int N){
int solution=1;//解法d的数量
int positionY=0;//皇后的列数
int next_positionY=0;//在当前行,但是列数是下一列数开始寻找合适的位置
vector<int> QueenDone;//存放已经找到的皇后的列坐标
Queen *queen;//定义一个皇后,从她开始吧
while(true){
if(QueenDone.empty()){
queen=new Queen(0,positionY);
QueenDone.push_back(queen->positionY);
positionY++;//这里怎么理解?就是可能回溯到后来,QueenDone直接为空了!就是连第一个皇后都不合法,列数+1
}
else{//已经有皇后在啦
if(QueenDone.size()==N){//已经遍历完一组了!有解了
cout<<"第"<<solution++<<"组解:";
for(vector<int>::iterator iter=QueenDone.begin();iter!=QueenDone.end();++iter){
cout<<*iter<<" ";
}
cout<<endl;
//从头开始,即第一个皇后换下一个位置!
next_positionY=QueenDone[QueenDone.size()-1]+1;
QueenDone.pop_back();
}
else{
int judge=setPositionY(N,QueenDone,next_positionY);
if(judge==-1){//没找到合适的位置
next_positionY=QueenDone[QueenDone.size()-1]+1;//则,列数+1
QueenDone.pop_back();
if(QueenDone.size()==0){//当求到最后一组解时,可能会回溯皇后在(0,0)的情况,此时QueenDone为空
if(next_positionY<N){//继续右移
QueenDone.push_back(next_positionY);
next_positionY=0;
}
else break;// 即next_positionY=N时,遍历完毕!
}
}
else{//找到合适的位置了!
QueenDone.push_back(judge);//把结果插入QueenDone
next_positionY=0;//行数+1,列数置0
}
}
}
}
}
把老师的示例代码打一遍
仍然是先构建皇后类
struct Queen{//皇后类
int x,y;//坐标
Queen(int xx=0,int yy=0):x(xx),y(yy){};
bool operater==(Queen const & q){//重载判等操作符
return(x==q.x)//行
||(y==q.y)//列
||(x+y==q.x+q.y)
||(x-y==q.x+q.y)//对角线
}
bool operator!=(Queen const & q){return !(*this==q);}
};
八皇后通用算法
void placeQueen(int N){
int nSolu;//计数器
stack<Queen> solu;Queen q(0,0);//从原点开始
do{
if(N<solu.size()||N<=q.y){//出界,则
q=solu.pop();
q.y++;//回溯且列数加1
}
else{//否则
while((q.y<N)&&(0<=solu.find(q)))//通过与已有的皇后对比,利用查找接口
q.y++;//尝试找到可摆放下一皇后的列
if(N>q.y){//若存在可摆放的列
solu.push(q);//摆上皇后
if(N<=solu.size())//遍历一组解完成
nSolu++;//计数
q.x++;q.y=0;//转入下一行,从第0行开始试探下一皇后
}
}
}
while((0<q.x)||(q.y<N));//直到所有分支都被检查或剪枝
3.2.2 maze迷宫寻径
顾名思义就是走迷宫呗
先放个链接在这里
首先创建迷宫单元
struct Cell{//迷宫单元
int x,y;//坐标
Status status;//类型
ESWN incoming,outgoing; //进入、走出方向
};
相关类型用枚举
//状态:可用、在当前路径上、所有方向均尝试失败后回溯、不可使用(墙)
typedef enum{AVAILABLE,ROUTE,BACKTRACKED,WALL} Status;
//单元的相对邻接方向
typedef enum{UNKNOWN,EAST,SOUTH,WEST,NORTH,NO_WAY} ESWN;
//依次转至下一邻接方向
inline ESWN nextESWN(ESWN eswn){
return ESWN(eswn+1);
}
算法
bool labyrinth(Cell Laby[MAX][MAX],Cell *s,Cell *t){
stack<Cell*> path;//用栈记录通路
s->incoming=UNKNOWN;s->status=ROUTE;path.push(s);//从起点出发
do{//不断试探回溯,直到抵达终点,或穷尽所有可能
Cell *c=path.top();
if(c==t) return true;//找到通路
while(NO_WAY>(c->outgoing=nextESWN(c->outgoing)))//查找另一
if(AVAILABLE==neighbor(c)->status) break;//尚未试探的方向
if(NO_WAT<=c->outgoing){//若所有方向都已尝试过,则向后回溯
c->status=BACKTRACKED;
c=path.pop();
}
else{//否则向前试探
path.push(c=advance(c));
c->outgoing=UNKNOWN;
c->status=ROUTE
}
}while(!path.empty());
return false;
}
3.2.3 总结
看算法不难看出试探回溯的步骤如下