数据结构与算法(三)列表结构以及栈与队列

列表结构:要求各元素在逻辑上具有线性次序,但对其物理地址未作任何要求即动态存储策略。

数值,前驱和后继。List的私有头结点和尾节点始终存在,却对外不可见。

插入排序:前缀有序,后缀无序。将后缀元素插入到前缀中的合适位置。借助于有序序列的查找算法。稳定算法。O(n*n)

选择排序:前缀无序,后缀有序。将前缀元素插入到后缀中的合适位置。借助于无序查找最大值算法。无序查找最大值单次复杂度可降低到O(logn)。

归并排序:有序列表的二路归并排序。

基于栈结构的虚拟机。栈与队列的具体应用:每次函数调用时,会创建一帧,该帧记录返回地址,局部变量以及传入参数等,将该帧压入调用栈。若在该函数返回之前发生新的调用,则同样将新函数对应的帧压入栈顶。函数一旦运行完毕,对应的帧随即弹出,运行控制权交还给该函数上层的调用函数,并按照该帧中记录的返回地址确定在二进制程序中继续执行的位置。

对应递归:同一函数可能同时拥有多个实例,并在调用栈中各自占有一帧。这些帧的结构完全相同。在追求更高的效率场合,应该避免递归。

栈的构建:基于向量基类而派生出来的结构。

栈的应用:

1.逆序输出。实例为进制转换。含义是一类问题其结果是按照顺序计算得到的,但是却要求逆序输出,如果按照向量来保存中间结果,则需要将向量逆序输出,若用栈来保存结果,则只需依次出栈即可。如下:

2.递归嵌套:栈混洗,有A,B,S三个栈,A栈中有n个元素按某一序列。现将A栈中所有元素移到B栈中,得到另一个序列。条件操作:A出入S,S出入B,这两种操作各n次,实施出栈时栈不得为空。

每一栈混洗的序列均由栈S的n次PUSH和n次POP构成。条件,且任一前缀中的PUSH不少于POP操作,即为栈混洗。

应用:括号匹配,判断某一括号序列是否在嵌套的意义下完全匹配。方法一;分治策略,分析括号。算法流程如下:

输入:字符串括号表达式S
输出:true or false
1.删除S中不含括号的最长前缀和后缀
2.判断首尾字符是否正确
2.找到合法切分点,从首部到切分点这一段的左右括号数必须相同:
    将S分成两段再分别判断,递归的解决方式。
该算法缺点:复杂度O(n*n),难以处理多种括号的表达式。

方法二:用栈混洗来解决。若括号匹配,则属于栈混洗,即括号的排序满足PUSH和POP序列。只需检验其是否满足即可。算法如下:

输入:字符串表达式S
输出;true or false
1.for 循环扫描S:
2.遇到左括号直接进栈,遇到右括号直接出栈并与栈顶匹配,若匹配,则继续,否则返回false。
3.非括号字符忽略。
用switch格式,可处理多种括号匹配问题。

3.延迟缓冲。解决问题是,计算速度慢于扫描速度,等到有足够的缓存信息时才进行计算。栈在这里充当缓存。表达式求值问题。算法流程如下:

运算符:加减乘除,括号,头,尾字符‘\0’。
输入:字符串表达式S,前提语法正确,栈顶运算符与当前运算符的优先等级比较数组。
输出:计算值,RPN数学表达式
1.字符串去空格
2.头字符入运算符栈,运算符非空,扫描各个S各字符:
    若为运算数,则入运算数栈。添加到RPN末尾。
    若为运算符,与栈顶运算符优先级比较:
        若高于栈顶,则入栈。
        若低于栈顶,则做计算,并栈顶运算符弹出并添加到RPN末尾:
            若为一元运算符,则取出一个操作数,结果入栈。
            若为二元运算符,则取出两个操作数,结果入栈。
        若等于栈顶,则为括号或者尾部符号,弹出该运算符,扫描下一个。

3.弹出运算数的栈顶=最后的计算结果。

RPN数学表达式求值:计算效率强于常规表达式。,无需考虑运算符的优先级。
输入:RPN表达式S,假定语法正确。
输出:表达式数值
{
    栈P,存放操作数;
    while(S扫描依次){
        从S中取元素X;
        if其为操作数,则入栈P;
        else{
            从栈P中弹出所需的操作数,进行X运算,结果入栈P;
        }
    }
返回栈P的值即为结果。
}

试探回溯算法:就是暴力搜索法,求取最优解问题。多个元素构成的序列,使得某一值最低,求取序列。关键是如何快速找到解。如何保证搜索的部分不被重复搜索。八皇后问题;在N*N的棋盘上,放置八个皇后,使得彼此不攻击。水平,垂直。对角线均为其势力范围。借助栈记录查找的结果,各皇后的列号。算法如下:

输入:N*N的格子
输出:栈S,存放每一个皇后的列号。
{
    栈S,新建皇后Q(0,0),从原点出发;
    do{
        if栈S是否满足结果或者Q是否越界:
            回溯一行,试探下一列即,弹出S,Q.Y++;
        else{
            试探下一行,通过与已有皇后对比:
                找到可放置下一皇后的列;
            if存在可放置的列:
                摆上当前皇后,if达成解,则全局计数加一;
                从下一行开始,0列,试探下一皇后
            }
        }while(所有分支均穷尽)
}

关于判断皇后冲突的问题;x==q.x; y==q.y; x+y==q.x+q.y; x-y==q.x-q.y;
void placeQueen(int N){
    Stack<Queen> s;
    Queen q(0,0);
    do{
        if(s.size()>=N || q.y>=N){
            q=s.pop();q.y++;
        }else{
            while(s.find(q) && q.y<N){
                q.y++    
            }
            if(q.y<N){
                s.push(q);
                if(s.size()>=N) nSolu++;
                q.x++;q.y=0;
            }
        }
    }while(q.x>0 || q.y <N);
}

迷宫寻路:结果从起点到目标点之间找到一条通路。路径规划。用试探回溯法解决:

输入:N*N个单元格,起点坐标B和终点坐标T
输出:一条通路栈S。
条件:单元格为P,P有四种状态,可用,在栈中,墙不可用,所有方向均尝试失败后回溯过的。
P有四个方向,处于可用状态的相邻方向未知,处于失败回溯的相邻方向为noway。当然还有一个初始的状态表记录所有单元的状态。

1.if B或T的状态不可用,则返回false;
2.初始化起点B的进入方向=UNKNOWN,状态为route,入栈S。
3.do{
    取出栈顶元素,若已经到达终点则返回真。
    while(检查每一个方向){
        可用,则break;
    }
    if(所有方向均尝试过){
        则向后后退一步,出栈;
    }else{
        向前试探一步,入栈,并更新状态。
    }
}while(栈不为空)

return false;

现总结关于八皇后问题和该路径规划问题的不同点和相同点是什么?
不同点:八皇后是求出符合某一个条件的皇后位置,没有起点和终点的规定。路径规划是已知起点和终点,求出符合某一条件的路径。
相同点:均是由试探回溯策略来解决,do,while语句,栈来保存位置或路径。取初始点,判断是否成功,搜索下一个位置,若不存在则回溯,否则入栈。while判断可执行条件。

队列:先入先出原则。用列表的派生类,利用C++的继承机制实现。队列的应用:资源循环分配器,参与资源分配的客户为队列,出队,接受服务,重新入队。银行模拟业务:多个窗口,多个队列,只要有新来的顾客,只需比较其队列的size(),即可将该顾客进行入列。

猜你喜欢

转载自blog.csdn.net/u013070875/article/details/85041489