《数据结构》学习笔记-第四章 栈与队列(线性序列的特例)

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(获取栈和队列中最大元素)

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 总结

看算法不难看出试探回溯的步骤如下
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值