精华内容
下载资源
问答
  • 提出一种全局优化算法,用于相似不变地在一场景中匹配一个形状。该算法采用支撑树来表示形状,匹配问题被转化成在目标点集中定位这棵树的问题。通过最小化边的空间变换同一个全局空间变换之间的差别,树的每条边的...
  • 论文研究-求解大规模VCVRP问题的快速动态规划算法.pdf, 车辆路径问题是一类典型的组合优化问题, 大部分研究都只考虑车辆能力固定的情形, 实际中受货物形状特性及客户...
  • 针对栅格环境下存在任意形状的静态障碍物问题,提出了结合二分搜索法的牛耕式全覆盖路径规划算法,该算法可以加速寻找下一个未覆盖空间的初始位置,提高了覆盖的效率。对该算法在多种室内环境中进行仿真,仿真结果...
  • 基于正则波兰表达式(NPE)表示,提出了一种形状曲线相加算法来处理软模块之间的组合运算,可获得每个布图解下最优的布图实现。通过回溯算法来确定每个模块的位置及形状,并将它们集成到模拟退火算法的流程之内。...
  • 工序A是做出形状,规定在A类机器才能做 工序B是画纹,只有B类机器才能做 工序C是缝合成球,只有C类机器才能做 这里有3个工序,每个工序用时不同,同个工序做不同的玩具时用时也不一样 这里有3类机器,...
  • 在机器人技术中,Vector Field Histogram(VFH,向量场直方图)是Johann Borenstein和Yoram Koren在1991年提出的一种实时路径规划算法。VFH通过所谓的直方图网格利用机器人环境的统计表示,因此非常重视处理来自...
  • 研究了一种基于人工蜂群算法的煤矿救灾机器人全局路径规划算法。该算法首先进行环境建模,然后根据环境信息特点,巧妙结合人工蜂群算法获得机器人全局优化路径。该路径规划方法具有建模方便、算法简单以及不局限于障碍...
  • 为快速准确地计算时间序列数据相似度,引入快速动态时间规划距离(fast dynamic time warping,FDTW),提出了基于FDTW的模糊C均值算法和模糊C中心点聚类算法。FDTW通过对数据序列进行拉伸和压缩匹配时间序列数据,只要...
  • 在视差选择阶段,设计了一种改进的动态规划算法,消除了扫描线效应,提高了匹配速度和正确率;经过视差后处理得到最终视差图。实验结果表明,该算法在Middlebury测试平台上的平均误匹配率为5.31%,在低纹理区域和...
  • 适用于相对开阔地带寻找路径,而添加动态速度权重的粒子群算法(PPSO)粒子步长小,擅长于在障碍物形状复杂多变地带寻找路径;然后评估2种粒子群算法得到的路径是否符合避障条件,若均符合避障条件则选取最短路径...
  • 采用动态可变长编码的方法,以栅格表示环境。针对遗传算法大型障碍物难的问题,采用follow wall行为...该算法适应任何形状的障碍物,适用于静态和动态环境中。计算机仿真表明,该算法是一种正确和高效的路径规划方法。
  • 最终我们就能看见一个「有形状的云朵」,如果将其比喻为「算法」,其思想就将最终展现,无疑它将是一个巨大的工程,实际生活中,如果你心中有那么「一朵云」,你就能大大方方说...
  • 提出了一种基于粒子群优化算法的移动机器人全局路径规划方法. 该方法首先进行环境地图建模, 通过坐 标变换在路径的起点与终点之间建立新地图, 然后利用粒子群优化算法获得一条全局最优路径. 该方法模型简单, 算...
  • 这个算法是做图像处理的抽骨架处理,目的是求出图像的骨架,可以想象一片与物体形状相同的草,沿其外围各点同时点火。当火势向内蔓延,向前推进的火线相遇处各点的轨迹就是中轴。 一个与细化有关的运算是抽骨架,也...

     Grassfire算法:

    一、概念

    这个算法是做图像处理抽骨架处理,目的是求出图像的骨架,可以想象一片与物体形状相同的草,沿其外围各点同时点火。当火势向内蔓延,向前推进的火线相遇处各点的轨迹就是中轴。

    一个与细化有关的运算是抽骨架,也称为中轴变换(Medialaxis transform)或焚烧草地技术(grass-fire technigue)。中轴是所有与物体在两个或更多非邻接边界点处相切的圆心的轨迹。但抽骨架很少通过在物体内拟合圆来实现。
        概念上,中轴可设想成按如下方式形成。想象一片与物体形状相同的草,沿其外围各点同时点火。当火势向内蔓延,向前推进的火线相遇处各点的轨迹就是中轴。
          抽骨架的实现与细化相,可采用一个两步有条件腐蚀实现,但是删除像素的规则略有不同
        下图将细化与抽骨架进行比较。二者的主要的差别在于抽骨架在拐角处延伸到了边界,而由细化得到的骨架却没有。
    上面图a是细化的效果,下面的图b是抽骨架的效果。
    (左边是处理一个均匀的矩形区域,右边是处理一个均匀的圆形区域)

    二、简单运算实现:(加强理解)

    我们的目标是:找到start-end之间的最短路径。

    如图所示。刷过leetcode的朋友看见这张应该会会心一笑,BFS,DFS这类词争先恐后往外跳。但是呢,太高级了,我的朋友们。让我们先用一种最文艺(傻气)的办法,来解决这个问题。

    Grassfire 算法。小时候,大家都背过一首诗:离离原上草,一岁一枯荣。 野火烧不尽,春风吹又生。说的就是这种算法。这首诗告诉我们,草,都是从旁边的草开始燃烧蔓延的!grassfire-烧草,就这么简单又有力。

    1.首先把终点的距离设定为0,然后设定距离终点最近的格子距离为1。如图所示

    2.然后,距离1最近的格子是2,距离2最近的格子是3,以此类推

    3.好了,这时候每一个数字都代表这该单元格到终点的距离。我们把数字连起来,就形成了最短路径,注意了,这个路径很可能不是唯一解。

    整个过程用伪代码表示就是:

    For each node n in the graph
    2  ·n.distance=Infinity
    Create an empty list.
    goal.distance=0,add goal to list.
    While list not empty
    6  ·Let current=first node in list,remove current from list
    7  ·For each node,n that is adjacent to current
    8    ·If n.distance=Infinity
    9      ·n.distance=current.distance+1
    10      ·add n to the back of the list

     

    但是有些时候,我们会遇见走不通的情况,我们写代码的时候就要考虑好这个问题

    三、实现过程:
    1.如果start-end之间有路,找到最短路径
    2.如果start-dend之间没有路,跳出循环,报错

    下面谈谈这个算法计算复杂度的问题,这个算法是一种遍历搜索,火会席卷每一个角落。

    计算复杂度为:O(|V|)

    其中,V是图中格子的数量。我们假设我们有100个格子,要访问的格子数
    2维棋盘:100 X100 = 1000
    3维棋盘: 100X100X100 = 1000000
    6维棋盘: 100X100X100X100X100X100 =1000000000000

    1000000000000啊朋友们,什么概念,就差不多和天上的星星一样多了哇!grassfire的计算量随着格子的变多或者维度的上升而变得很大。

    好啦,总的来说这是一个很简单的算法,一定能找到全局最优解。

     

    参考:1)运动规划(Motion planning)- Grassfire 算法——https://www.jianshu.com/p/e22acfc75731?from=timeline

    2)grassfire算法——https://www.iteye.com/blog/benworld-1920217

    展开全文
  • 动态规划的四步:定义状态,设计状态转移方程,初始化边界,确定答案。 109. 数字三角形 这个形状和树的结构相似,可以利用分治的思路求解。但是会超时,时间复杂度O(n^2)。 class Solution { public: int ...

    动态规划的四步:定义状态,设计状态转移方程,初始化边界,确定答案。

    109. 数字三角形
    这个形状和树的结构相似,可以利用分治的思路求解。但是会超时,时间复杂度O(n^2)。

    class Solution {
    public:
        int minimumTotal(vector<vector<int>> &triangle) {
            int n=triangle.size();
            return dfshelper(triangle,0,0,n);
        }
        int dfshelper(vector<vector<int>> &triangle,int x,int y,int n) {
            if (x==n) return 0;
            
            return triangle[x][y]+min(dfshelper(triangle,x+1,y,n),dfshelper(triangle,x+1,y+1,n));
        }
    };

     分析结构发现,这个三角形和树不同的地方在于每个节点左上和右上均有联通节点,因此计算过程中可能存在重复访问。如果能记录下已经算过的节点的最小路径和,就不用再重复计算了。时间复杂度降低为O(n*n)。

    class Solution {
    public:
        int minimumTotal(vector<vector<int>> &triangle) {
            int n=triangle.size();
            int m=triangle[n-1].size();
            vector<vector<int>> f(n,vector<int>(m,INT_MAX));
            return dfshelper(triangle,0,0,n,f);
        }
        int dfshelper(vector<vector<int>> &triangle,int x,int y,int n,vector<vector<int>> &f) {
            if (x==n) return 0;
            if (f[x][y]!=INT_MAX) return f[x][y];
            else f[x][y]=triangle[x][y]+min(dfshelper(triangle,x+1,y,n,f),dfshelper(triangle,x+1,y+1,n,f));
            
            return f[x][y];
        }
    };

    写成for循环的形式,就是动态规划。 

    class Solution {
    public:
        int minimumTotal(vector<vector<int>> &triangle) {
            int n=triangle.size();
            
            for (int i=n-2;i>=0;i--) {
                for (int j=0;j<triangle[i].size();j++)
                    triangle[i][j]+=min(triangle[i+1][j],triangle[i+1][j+1]);
            }
            
            return triangle[0][0];
        }
    };

    124. 最长连续序列
    这个题是典型的不能使用动态规划的代表。使用的数据是一个集合而不是有序的序列(即没有方向性)。这个题为了使时间复杂度降到O(n),必须使用哈希表。且当每次访问了这个元素之后,在哈希表内删除(精髓中的精髓,保证每个元素最多访问一次)。

    class Solution {
    public:
        int longestConsecutive(vector<int> &num) {
            if (num.size()<=1) return num.size();
            
            unordered_set<int> hash(num.begin(),num.end());
            int res=1;
            for (int i=0;i<num.size();i++) {
                if (hash.find(num[i])!=hash.end()) {
                    hash.erase(num[i]);
                    int pre=num[i]-1;
                    int next=num[i]+1;
                    while (hash.find(pre)!=hash.end()) {
                        hash.erase(pre);
                        pre--;
                    }
                    while (hash.find(next)!=hash.end()) {
                        hash.erase(next);
                        next++;
                    }
                    res=max(res,next-pre-1);
                }
            }
            return res;
        }
    };

    110. 最小路径和

    状态转移方程:f[i][j]=grid[i][j]+min(f[i+1][j],f[i][j+1])

    class Solution {
    public:
        int minPathSum(vector<vector<int>> &grid) {
            int n=grid.size();
            int m=grid[0].size();
            
            vector<vector<int>> f(n,vector<int>(m,0));
            f[n-1][m-1]=grid[n-1][m-1];
            for (int i=n-2;i>=0;i--) f[i][m-1]=grid[i][m-1]+f[i+1][m-1];
            for (int i=m-2;i>=0;i--) f[n-1][i]=grid[n-1][i]+f[n-1][i+1];
            
            for (int i=n-2;i>=0;i--)
                for (int j=m-2;j>=0;j--) {
                    f[i][j]=grid[i][j]+min(f[i+1][j],f[i][j+1]);
                }
            return f[0][0];
        }
    };

    114. 不同的路径
    其实是一个数学问题。m*n的格子需要朝右走n-1步、朝下走n-1步,一共是m+n-2步,需要从中选m-1个步数下走,所以C(m+n-2,m-1)就是答案。
    使用动态规划,状态转移方程为f[i][j]=f[i+1][j]+f[i][j+1]

    class Solution {
    public:
        int uniquePaths(int m, int n) {
            
            vector<vector<int>> f(n,vector<int>(m,0));
            f[n-1][m-1]=1;
            for (int i=n-2;i>=0;i--) f[i][m-1]=1;
            for (int i=m-2;i>=0;i--) f[n-1][i]=1;
            
            for (int i=n-2;i>=0;i--)
                for (int j=m-2;j>=0;j--) {
                    f[i][j]=f[i+1][j]+f[i][j+1];
                }
            return f[0][0];
        }
    
    };

    111. 爬楼梯

    class Solution {
    public:
        int climbStairs(int n) {
            vector<int> f(n+1,0);
            f[0]=0; f[1]=1; f[2]=2;
            for (int i=3;i<=n;i++)
                f[i]=f[i-1]+f[i-2];
            return f[n];
        }
    };

    116. 跳跃游戏
    动归方法时间复杂度0(n^2)。寻找每一个格子能否由前边的格子到达。

    class Solution {
    public:
        bool canJump(vector<int> &A) {
            vector<bool> f(A.size(),false);
            f[0]=true;
            for (int i=1;i<A.size();i++)
                for (int j=0;j<=i-1;j++) {
                    if (f[j] && i-j<=A[j]) {
                        f[i]=true;
                        break;
                    }
                }
            return f[A.size()-1];
        }
    };

    更好的方法是使用一个farthest记录最远能到达的格子,只要在这个长度之内的格子都能到达,不断更新farthest,检查最后能否到达终点。

    class Solution {
    public:
        bool canJump(vector<int> &A) {
            int farthest=A[0];
            for (int i=1;i<A.size();i++) {
                if (i<=farthest && A[i]+i>farthest)
                    farthest=A[i]+i;
            }
            return farthest>=A.size()-1;
        }
    };

    117. 跳跃游戏 II
    动态规划算法时间复杂度为O(n^2)会超时。

    class Solution {
    public:
        int jump(vector<int> &A) {
            vector<int> f(A.size(),-1);
            f[0]=0;
            for (int i=1;i<A.size();i++)
                for (int j=0;j<=i-1;j++) {
                    if (f[j]>=0 && i-j<=A[j]) {
                        f[i]= f[i]==-1?f[j]+1:min(f[i],f[j]+1);//排除第一次更新f[i]中是-1的情况
                    }
                }
            return f[A.size()-1];
        }
    };

    贪心算法记录了每一跳所能达到的区间首尾,每个区间算一个jump,直到跳出去为止,记录jump的次数。有点类似BFS,感觉也可以用队列来做。

    class Solution {
    public:
        int jump(vector<int> &A) {
           int start=0; int end=0;
           int jump=0;
           int farthest=A[0];
           while (end<A.size()-1) {
               jump++;
               for (int i=start;i<=end;i++) {
                   if (i<=farthest && A[i]+i>farthest)
                       farthest=A[i]+i;
               }
               start=end+1;
               end=farthest;
           }
           return jump;
        }
    };

    76. 最长上升子序列
    简单算法,时间复杂度O(n^2)。

    class Solution {
    public:
        int longestIncreasingSubsequence(vector<int> &nums) {
            if (nums.size()<=1) return nums.size();
            
            vector<int> f(nums.size(),1);
            int res=1;
            for (int i=1;i<nums.size();i++){
                for(int j=0;j<=i-1;j++) {
                    if (nums[j]<nums[i]) f[i]=max(f[i],f[j]+1);
                }
                res=max(res,f[i]);
            }
            return res;
        }
    };

    如果要求时间复杂度为O(nlogn),需要使用二分法求解,指路https://blog.csdn.net/musechipin/article/details/84620023

    513. 完美平方

    class Solution {
    public:
        int numSquares(int n) {
            vector<int> f(n+1,INT_MAX);
            f[0]=0; f[1]=1;
            for (int i=2;i<=n;i++) {
                if (int(sqrt(i))*int(sqrt(i))==i) f[i]=1;
                else {
                    for (int j=1;j<=sqrt(i);j++)
                        f[i]=min(f[i],f[j*j]+f[i-j*j]);
                }
            }
            return f[n];
        }
    };

    603. 最大整除子集
    使用prev数组记录前序数字的位置。

    class Solution {
    public:
        vector<int> largestDivisibleSubset(vector<int> &nums) {
            sort(nums.begin(),nums.end());
            vector<int> f(nums.size(),0);
            vector<int> prev(nums.size(),-1);
            
            f[0]=1;
            int count=1;
            int index=0;
            
            for (int i=1;i<nums.size();i++) {
                for (int j=0;j<=i-1;j++) {
                    if (nums[i]%nums[j]==0 && f[j]+1>f[i]) {
                        f[i]=f[j]+1;
                        prev[i]=j;
                    }
                }
                if (count<f[i]) {
                    count=f[i];
                    index=i;
                }
            }
            
            vector<int> res;
            res.push_back(nums[index]);
            while (prev[index]!=-1) {
                res.push_back(nums[prev[index]]);
                index=prev[index];
            }
            return res;
        }
    };

    ​​​​​​​602. 俄罗斯套娃信封
    还是只用之前双重for循环的思路,代码如下。但是会超时。

    class Solution {
    public:
        int maxEnvelopes(vector<pair<int, int>>& envelopes) {
            sort(envelopes.begin(),envelopes.end());
            vector<int> f(envelopes.size(),1);
            
            int res=0;
            for (int i=1;i<envelopes.size();i++) {
                for (int j=0;j<=i-1;j++) {
                    if (envelopes[i].second>envelopes[j].second 
                    && envelopes[i].first>envelopes[j].first )
                        f[i]=max(f[i],f[j]+1);
                    else break;
                }
                res=max(res,f[i]);
            }
            
            return res;
        }
    };

    不超时的方法是对w进行升排序,对h进行降排序,然后用nlogn的方法求h的最长不下降子序列。之所以h要降序是因为w相同的信封是不能嵌套的,如果h也按升序排说明允许w相等的信封嵌套。

    class Solution {
    public:
        static bool cmp(const pair<int,int> &x,const pair<int,int> &y) {
            return x.first!=y.first?x.first<y.first:x.second>y.second;
        }
        int maxEnvelopes(vector<pair<int, int>>& envelopes) {
            sort(envelopes.begin(),envelopes.end(),cmp);
            
            vector<int> dp; 
            dp.push_back(envelopes[0].second);
            
            for (int i=1;i<envelopes.size();i++) {
                if (envelopes[i].second>dp[dp.size()-1]) dp.push_back(envelopes[i].second);
                else {
                    vector<int>::iterator it=lower_bound(dp.begin(),dp.end(),envelopes[i].second);
                    *it=envelopes[i].second;
                }    
            }
            
            return dp.size();
        }
    };

    622. 青蛙跳
    使用一个map记录能跳到当前石头的步数都有哪些。但不是从当前石头搜索前面的石头都有哪些能跳过来,而是从第一块石头开始,尝试它所有能跳的步数,跳到的石头就在跳到的石头上记录下是跳了多少步。只要查看最后一块石头上有没有步数即可。
    设置steps[0]=0,因为-1和0都不能跳,所以只能跳1步,符合题目要求。

    class Solution {
    public:
        bool canCross(vector<int> &stones) {
           unordered_map<int,unordered_set<int>> step;
            
           for (int i=0;i<stones.size();i++) {
               step[stones[i]]=unordered_set<int>();
           }
           
           step[0].insert(0);
           
           for (auto position:stones)
               for (auto k:step[position]) {
                   if (k-1>0 && step.find(position+k-1)!=step.end()) {
                       step[position+k-1].insert(k-1);
                   }
                   if (k>0 && step.find(position+k)!=step.end()) {
                       step[position+k].insert(k);
                   }
                    if (step.find(position+k+1)!=step.end()) {
                       step[position+k+1].insert(k+1);
                   }
               }
            return !step[stones[stones.size()-1]].empty();
        }
    };


     

    展开全文
  • 说起来到处都有他,但是真正用的时候,如果变个形状,又很难想到动态方程。所以,目前的动态规划,还是要多总结,找到思路。 抛砖迎玉,先来说个简单的:上台阶问题。现在有一层楼梯,共n层台阶。每次上台阶,可以...

    前言

    动态规划是是我目前觉得遇见的最难的题目了。说起来到处都有他,但是真正用的时候,如果变个形状,又很难想到动态方程。所以,目前的动态规划,还是要多总结,找到思路。
    抛砖迎玉,先来说个简单的:上台阶问题。现在有一层楼梯,共n层台阶。每次上台阶,可以上1层或者2层,问一共有多少层走完楼梯的方案。

    入门

    这个题应该都很熟悉,很明显这个是斐波那契数列。怎么推算出来,这是个斐波那契数列的呢?

    假设我站在第k层台阶上面,要到达第k层台阶,可以由第k-1层台阶,走一步,和第k-2层台阶,走两步,到达第k层。

    在思考的过程中,可能最搞不明白的就是,为什么不考虑第k-3层,或者其他k-n层的台阶,走3层,或者走4层来到达第k层。(最长回文子串也会这样想,考虑k的时候,是否考虑string[i-1]和string[j-1])。
    原因是因为第k-1层台阶,和第k-2层台阶,已经考虑到了k-3层,和k-4层。(其中,k-1层考虑到了k-2和k-3层,原因就在于只能走1步和2步,但是k-1考虑到的k-2只是从 k-2 走 一步 到k-1这一情况)
    这种状态的转移很饶人,就跟刚学一个知识点一样,很不清楚。习惯就好了,想真正熟悉,估计只有多用,不断重复了。

    进阶

    下面说一下这个题目的进阶,现在我能走的不是只有1层或2层,而是能走a[1],a[2]…a[n]层,怎么说?难道a[k] = a[1]+a[2]+…+a[k-1];没错,就是这样!

    for (int i = 0; i < n; i++) {
    	int ans = 0;
    	for (int j = a[i]; j <= k; j++) {
    		ans = ans + dp[j - a[i]];
        }
    	dp[i] = ans;
    }
    return dp[k];
    

    从这里面应该就能明白,为什么说菲波拉契数列是最简单的动态规划了。

    进阶之背包

    ok,先放下这个题,来看看另一个题,就是动态规划的经典——背包问题(关于背包可以看之前的博客总结笔试中背包问题的应用)——先来看看多重背包的场景:被告容量为V,一共有n中物品,每个价值为a[i],体积为b[i],问背包最大的能装多少(物品可以装多次)。动态转移方程如下:

    
    for(int i=0;i<n;i++){
       for(int j=a[i];j<=V;j++){
            dp[j] = Math.max(dp[j],d[j-b[i]+a[i]]);
       }
    }
    return dp[V];
    

    注意,这里每个物品可以装多次。为什么不采用贪心?贪心采用性价比最高的,从大到小排,从大往小装(当然不对了,这样装,无法保证全局最优)。
    如果是递归呢?每个背包选或者不选,有2^n情况,显然复杂度太大了。

    重点来了

    上面的背包得到的的是能装的最大价值,而无法拿到达到最大价值有多少种方案。
    那么,要求出多少方案怎么做呢?
    是不是可以这样想,把走台阶看成背包容量,这样,状态方程就和上面走台阶一样了。
    来个例子:
    有两种硬币,第一种可以无限使用,第二种每个只能使用一次.。第一种硬币为n个,价值分别任a[0],a[1],a[2]…a[n-1],第二种硬币为m个,价值分别为b[0],b[1]…b[n-1],求组合成价值为k的硬币数量

    for(int i=0;i<n;i++){
    	for(int j=a[i];j<=k;j++{
    		dp1[j] = dp1[j]+dp1[j-a[i]];
    	}
    }
    for(int i=0;i<m;i++){
    	for(int j=k;j>=a[i];j--){
    		dp2[j] = dp2[j-a[i]]+dp2[j];
    	}
    }
    for(int i=1;i<=k;i++){
    	ans[k] += dp1[i]*dp[k-i];
    }
    return ans[k];
    

    如果要求打印所有路径呢?这就必须要用到dfs了。

    高级进阶

    还是来给个例题。leetcode上面
    单词接龙 II
    这个题,是单词接龙 I的变形。
    这个题目其实与动态规划可以说有关,也可以说是无关(为什么,等会说明),也可以当做一个搜索题,放在这里是为了把相似的题目放在一起,便于总结。
    首先,需要基于单词接龙 I得到最短的路径的单词个数(两种方法,1.bfs。2.动态规划),其次,需要基于搜索(宽搜或者广搜都可以,网上面的教程大多是基于广搜,所以楼主等会给一下宽搜的代码)。
    这里暂时先来讨论如何得到最短路径的单词数。宽搜就不说了。那么动态规划怎么做呢?
    不知道大家时候还记得最短路算法。最短路算法中,有一个很有代表性的算法,bellman_ford算法(dijkstra基于贪心),其中,基于bellman_ford算法的优化版本,也可以说是bellman_ford和dijkstra的结合版本,SPFA(中国人发明的,挺牛的),就可以运用到这个题目中。
    怎么看成是最短路呢?题目要求,只能相差一个字母,所以,可以把,相差一个字母的两个字符串之间的距离,看做1,相差不是1的,可以把其看成是无限远,然后利用这个1去更新其他的距离。
    具体代码就不给了,这个不难,只要理解了SPFA的概念,这个题目的答案就很好写出来。
    下面再来看 单词接龙 II 的解法。
    这个网上大部分都是基于dfs,楼主这里给出bfs。需要说明的是,这个题目有点卡数据。采用bfs时,如果从beginWord开始搜索,会超时,但是,从endWord开始搜索,就可以通过,注意这一点就行了。
    一下是AC代码:

    
        class T {
            int x;//记录这个单词的位置
            String ts;//记录单词
            List<String> list;//记录走到当前单词,路径上的所有单词
    
            public T(int x, String ts, List<String> list) {
                this.x = x;
                this.ts = ts;
                this.list = list;
            }
        }
    
        /**
         * 记录下单词是否只差一个,注意,必须差一个,两个单词完全相同也不可以
         * 如果两个单词完全相同,相当于重复了,当做一个单词算
         */
        boolean isT(String ts, String ts2) {
            int s = 0;
            for (int i = 0; i < ts.length(); i++) {
                if (ts.charAt(i) != ts2.charAt(i)) {
                    s++;
                }
                if (s > 1) {
                    return false;
                }
            }
            return s == 1;
        }
    
        class Arr {//记录邻接矩阵(即Arr[i].slist代表与第i个单词相差一个字母的所有单词)
            List<String> slist;//
            List<Integer> ilist;//对应单词的相对位置
    
            public Arr(List<String> slist, List<Integer> ilist) {
                this.slist = slist;
                this.ilist = ilist;
            }
        }
    
        public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
            if (beginWord == null || beginWord.length() == 0 || endWord == null || endWord.length() == 0)
                return null;
            int len = wordList.size();
            Arr[] listArrArr = new Arr[len + 1];
            //构建动态表Arr[len+1]
            for (int i = 0; i < len; i++) {
                listArrArr[i] = new Arr(new ArrayList<String>(), new ArrayList<Integer>());
                for (int j = 0; j < len; j++) {
                    if (j != i && isT(wordList.get(i), wordList.get(j))) {
                        listArrArr[i].slist.add(wordList.get(j));
                        listArrArr[i].ilist.add(j);
                    }
                }
            }
            //把beginWord,当做第len个单词
            listArrArr[len] = new Arr(new ArrayList<String>(), new ArrayList<Integer>());
            for (int i = 0; i < len; i++) {
                if (isT(beginWord, wordList.get(i))) {
                    listArrArr[len].slist.add(wordList.get(i));
                    listArrArr[len].ilist.add(i);
                }
            }
            //这个单词二维list,记录没有过前的所有答案()
            List<List<String>> preList = new ArrayList<>();
            int min = Integer.MAX_VALUE;//记录最短路径的单词数
            //可能这里有疑问,既然已经记录了最短路径单词数,为什么还要过滤preList呢
            //原因在于,可能到达最短路径的时候,"下一层"的单词已经放如队列中了
            Queue<T> queue = new LinkedList<>();
            ArrayList<String> tsi = new ArrayList<>();
            tsi.add(endWord);
    
            int anspos = wordList.indexOf(endWord);//记录endWord出现的位置
            if (anspos != -1)//说明能达到beginWord
                queue.add(new T(anspos, endWord, tsi));
            Set<String> set = new HashSet<>();//这里Set,用来记录所有已经走过的路
            /*
            举个例子,比如答案是
            [tad ted red rex],[tad,red,
             */
            boolean isGetAns = false;
            while (!queue.isEmpty()) {
                T t = queue.poll();
                set.add(t.ts);//注意,只有到了这里,才能加入set。以后如果还能通过这个t.ts变换,也不是最优解,
                // 所以通过这里,让以后不能再使用这个单词。
                if (listArrArr[len].ilist.contains(t.x)) {//最终答案邻接表是否包含,包含说明是一个解。
                    t.list.add(beginWord);
                    min = Math.min(t.list.size(), min);
                    if (!preList.contains(t.list))//可能有重复的,重复的去掉
                        preList.add(new ArrayList<>(t.list));
                    isGetAns = true;//得到最优解之后,不再往queue里面添加任何东西。
                    continue;
                }
                if (!isGetAns) {
                    for (int i = 0; i < listArrArr[t.x].slist.size(); i++) {
                        //这是其中很大的一个优化,就是把能到达的单词预存到这个数组中。而不是采用遍历所有的数组的方式
                        if (!set.contains(listArrArr[t.x].slist.get(i))) {
                            List<String> sArr = new ArrayList<>(t.list);
                            sArr.add(listArrArr[t.x].slist.get(i));
                            //set.add(listArrArr[t.x].slist.get(i));不能在这里添加入set
                            queue.add(new T(listArrArr[t.x].ilist.get(i), listArrArr[t.x].slist.get(i), sArr));
                        }
                    }
                }
            }
            //由于是从后往前搜索,所以最后顺序需要倒过来
            List<List<String>> ansLL = new ArrayList<>();
            for (int i = 0; i < preList.size(); i++) {
                if (preList.get(i).size() == min) {
                    ArrayList<String> tsList = new ArrayList<>();
                    int jlen = preList.get(i).size();
                    for (int j = 0; j < preList.get(i).size(); j++) {
                        tsList.add(preList.get(i).get(jlen - j - 1));
                    }
                    ansLL.add(tsList);
                }
            }
            return ansLL;
    
        }
    
    展开全文
  • 基于先验形状和Mumford-Shah模型的活动轮廓分割是一种抗噪声干扰、稳定的图像分割方法。该模型采用水平集方法,并结合活动轮廓模型、先验形状和Mumford-Shah模型来控制曲线演化。特定目标的先验知识可以有效地指导...
  • 前面说过动态规划最典型的就是解决最优化问题的(具有最优子结构的最优化问题),最优二叉查找树就是一个典型的最优化问题。问题描述:给定一个n元素的中序序列,它可以有卡特兰数个不同形状的二叉排序树。(卡特兰...

    前面说过动态规划最典型的就是解决最优化问题的(具有最优子结构的最优化问题),最优二叉查找树就是一个典型的最优化问题。


    问题描述:

    给定一个n元素的中序序列,它可以有卡特兰数个不同形状的二叉排序树。(卡特兰数的定义及证明参见组合数学):



    ,如果我们知道每个键的查找概率,怎么来构造一个平均查找代价最小(查找成功)的最优二叉查找树呢?


    -------------------------------------------------------------------------------------------------------------


    用动态规划来求解,首先要找到它的最优子结构性质,然后根据这个最优子结构来描述和刻画问题,得到状态转移的方程:


    1)最优子结构性质:


    看看一颗最优二叉查找树是怎么得到的?逆向思维,如果现在有一棵最优二叉查找树,root是ak,很容易得出:ak的左右子

    树也是最优二叉查找树(如果它的子树不是最优的,那就说明这个子树还可以继续调整,那么ak那颗树就也不是最优的

    了)。




    2)根据最优子结构性质来描述和刻画问题


    用C[i , j]表示从 i 到 j 的最优二叉查找树的代价,那么问题就被划分为了n^2个子问题了(顶点号从0计数),假设有n个顶

    点,那么我们的目标是要求C[0 , n-1]。(编号从0还是1开始无所谓,在编程的时候注意下标范围就行了)。


    现在根据它的最优子结构来找状态转移方程:

    从 i 到 j的一个最优二叉查找树是怎么得到的?(即一个C[i , j]是怎么来的),它是从 i 到 j 之间的顶点中选出一个顶点来做

    root,假设选出的这个做root的顶点是 k (i <= k <= j ),那么显然有:



    这个式子其实可以直接想到,不用那么复杂的推导,它就是要找一个能使得C[i , j]代价最小的 k (这个k的范围在 i 到 j之

    间),而后面为什么要加一个从i到j的概率呢?因为挑出了k后,它作root,每个点的查找长度都增加了1。当然,也有更严格

    的推导,可以参考下:



    3)有了状态转移方程,就可以画个矩阵看看初始条件,以及每个C[i , j]依赖那些值(填表顺序)。

    初始条件有:C[i , i] = Pi,C[i , i-1] = 0

    试探一下一个C[i , j]是怎么来的,就可以看出,应该沿对角线来填。

    注意状态转移方程里当 k = i 或者 k = j 时,C[i , i - 1] 或者 C[j+1 , j]是没有定义的,在编程中只需要特殊处理下就

    行:
    对于这种没有定义的取0,其他的取矩阵中的值。


    最后一点,至于具体的实现,tmd书上总喜欢画一个不是从0开始的表,有时候甚至还横坐标从0开始,纵坐标从1开始,虽说

    是为了填矩阵的方便,但看起来很狗。我一般n规模的问题,就开n * n的矩阵,下表从0到n-1,对超出边界的做一些特殊处

    理就行了,就像上面的C[i , i-1]。看看书上的表(理解意思,具体实现我开的矩阵不一样,下标控制不一样):




    它这样来画表其实就是为了解决C[i , i-1]不在定义范围内,为了能直接从矩阵中取值才这么做的。


    -------------------------------------------------------------------------------------------------------------


    上面就构造出了最优二叉查找树的最优代价的动态规划过程,利用上述状态转移方程可以填出所有的C[i , j]。

    还有一个问题,跟上一篇文章中提到的一样,怎么去不仅仅得到C[i , j]这个代价,更要知道对应于这个代价的二叉树的形

    状?

    仍然是构造一个矩阵 A[0...n-1,0...n-1] 来记录动态规划的过程,每次选出一个 k 作root时,就把 k 记录下来,即用

    A[i , j] = k 表示从 i 到 j 的最优二叉查找树的root是 k。(它还蕴含从 i 到 k - 1是左子树,k+1到 j 是右子树,注意我们

    给定的从0到n-1顶点是一个中序序列!)

    初始值 A[i , i] = i,表示只有自己的最优二叉查找树的root就是它自己。最后将得到一个矩阵A。它表达了二叉查找树的形

    状,当然,还得根据A的含义,从A中获取从 i 到 j的最优二叉查找树的形状。




    可以有下列算法,从A中输出从 i 到 j 的最优二叉查找树的形状(输出它的前序序列,因为中序序列是已知的):

    已知前序序列和中序序列,一个二叉树的形状就确定了:




    也是用递归(最优子结构),其实方法跟上一篇Floyd的也差不多。


    -------------------------------------------------------------------------------------------------------------

    实现:

    复制代码
    package Section8;


    /*第八章 动态规划 最优二叉查找树(难!!!)*/

    public class OptBST {

    /**
    * @param args
    */
    public static void main(String[] args) {
    // TODO Auto-generated method stub
    float[] P = {(float) 0.1,(float) 0.2,(float) 0.4,(float) 0.3};

    //若返回值是最小代价,测试最小代价是否正确
    //System.out.println("输出最优二叉排序树的最小代价:\n");
    //float result = OptBST(P);
    //System.out.println(result);

    //若返回值是表达最优二叉排序树形状的矩阵,测试矩阵是否正确
    System.out.println("输出表达最优二叉排序树形状的矩阵:\n");
    int[][] R = OptBST(P);
    for(int i = 0;i < R.length;i++)
    {
    for(int j = 0;j < R.length;j++)
    System.out.print(R[i][j] + "  ");
    System.out.println();
    }

    }


    public static int[][] OptBST(float[] P){
    //接受一个中序序列的点的查找概率数组,返回最优的二叉查找树的代价(注意P中的概率按顺序对应于点的中序序列)
    int n = P.length; //结点个数
    float[][] result = new float[n][n];

    int[][] R = new int[n][n]; //表达二叉查找树形状的矩阵

    for(int i = 0;i < n;i++)
    {
    result[i][i] = P[i]; //填充主对角线C[i,i] = P[i]
    R[i][i] = i; //R[i][j]表示若只构造从i到j的树,那么root是R[i][j]
    }

    for(int d = 1;d <= n - 1;d++) //共n-1条对角线需要填充
    {
    for(int i = 0;i <= n - d - 1;i++) //横坐标的范围与对角线编号d的关系
    {
    int j = i + d; //一旦横坐标确定后,纵坐标可以用横坐标与对角线编号表示出来
    float min = 1000000;

    int root = 0;

    for(int k = i;k <= j;k++)
    {
    float C1 = 0,C2 = 0; //用C1,C2表示result[i,k-1]和result[k+1,j]
    if(k > i)
    C1 = result[i][k - 1];
    if(k < j)
    C2 = result[k + 1][j];

    if(C1 + C2 < min)
    {
    min = C1 + C2;
    root = k;
    }
    }

    R[i][j] = root; //R[i][j]的值代表从i到j的最优二叉查找树的根

    float sum = 0;
    for(int s = i;s <= j;s++)
    //sum = sum + P[i]; //你妈啊,一个小错误找了半天
    sum = sum + P[s];

    result[i][j] = sum + min;
    }
    }

    //return result[0][n-1]; //返回C[1,n],最小代价
    return R; //返回表达最优二叉排序树形状的矩阵
    }

    }
    复制代码



    最优代价的矩阵和表达形状的矩阵在一起求的,需要哪个就返回哪个值,见代码。

    很容易看出时间复杂度是 n^3(k的选择需要一个循环) 的,空间复杂度是 n^2的。

    运行结果(返回表达二叉查找树形状的矩阵R):


    输出表达最优二叉排序树形状的矩阵:

    0  1  2  2  
    0  1  2  2  
    0  0  2  2  
    0  0  0  3  


    -------------------------------------------------------------------------------------------------------------


    思考:

    1,分析最优二叉查找树的时间复杂度,空间复杂度。(见分析讲解)


    2,写一个线性时间的伪代码,从根表(矩阵A)生成最优二叉查找树(见分析讲解)


    3,判断正误:一颗最优二叉查找树的根总是包含概率最高的键(错,很容易举反例,如果这句话是对的,那还要动态规划干

    吗,那就可以用更简单的办法来构造最优二叉排序树了--直接根据概率选最大值就行了)


    4,把最优二叉查找树(查找成功的代价最小)推广到成功和不成功总代价最小的情况(easy,改变C[i ,j]的含义,加上不成

    功的概率即可)


    5,证明:




    重要:事实上卡特兰数就是这样定义的,(如果后面有机会,我会深入学习下卡特兰数),该递推关系的解是由卡特兰数给

    出的(现在都忘了,可以参见《组合数学》  卢开澄   北京大学出版社)。


    如果不要求严格的证明,根据b(n)的含义,其实很容易写出上述递推式。(想一想)


    -------------------------------------------------------------------------------------------------------------


    最后一题:矩阵连乘问题,见下一篇文章,作为一个解题报告。

    ------------------------------------------------------------------------------------------------------------


    总结:

    1)怎么去分析一个问题,试图用动态规划去解决?(先找最优子结构,从最有子结构去刻画和描述问题

    2)逆向思维去找状态转移方程

    3)相对前面的几个动态规划算法,最优二叉查找树的填矩阵顺序稍显复杂(沿对角线),直接看递推式的数学形式可能看不

    出来,画出矩阵,去尝试一个C[i ,j]是怎么求出来的,就能清晰的看出来。

    4)卡特兰数非常重要,许多问题都是卡特兰数问题(参见什么叫做professional?),习题5给出了一个很好的引导证明。

    转载于:https://www.cnblogs.com/huenchao/p/5950848.html

    展开全文
  • 设有形状一样的多米诺牌,每张牌恰好覆盖棋盘上相邻的两个方格,即一张多米诺牌是一张 1 行 2 列或者 2 行 1 列的牌。那么,是否能够把 32 张多米诺牌摆放到棋盘上,使得任何两张多米诺牌均不重叠,每张多米诺牌覆盖...
  • 01 目录环境需求怎样使用本地化扩展卡尔曼滤波本地化无损卡尔曼滤波本地化粒子滤波本地化直方图滤波本地化映射高斯网格映射光线投射网格映射k均值物体聚类圆形拟合物体形状识别SLAM迭代最近点匹配EKF SLAMFastSLAM ...
  • 算法面向复杂的监视区场景,监视区中存在形状各异的障碍物,各区域的重要程度不同。以二维离散网格模型描述监视区场景,用编码描述矩阵表示监视区域,用七元组描述有向无线视频传感器。通过严格的数学推导得出了问题...
  • 人工鱼群算法应用

    2021-01-18 08:43:06
    基于人工鱼群算法的机器人路径规划 环境描述 ​ 路径规划的第一步是建立适当的环境模型,建模的方法有多种,例如:栅格法、实际坐标系建模及链接图法建模等。栅格法当规划范围较大时计算量相当大,用实际坐标系建模...
  • 路径平滑算法汇总

    千次阅读 2020-02-10 16:44:44
    全局路径规划(例如A*算法算法计算路径完毕后,其结果是一串用来表示所经过的路径点的坐标, 但这样的路径通常是有“锯齿” 形状的,并不符合实际需求,因此需要进行平滑处理。 路径平滑处理的相关算法有:...
  • 3.3 阶梯式模型算法控制 矩阵(H T H +I )1 H T 的计算十分麻烦特别是在线调整控制量变化量的权重和在 线校正系统...由图3.8 可见经过规划的控制量有类似于台阶的形状阶梯式控制的名称即源于此 一 合理性 在优化控制算
  • 弯月00 目录环境需求怎样使用本地化扩展卡尔曼滤波本地化无损卡尔曼滤波本地化粒子滤波本地化直方图滤波本地化映射高斯网格映射光线投射网格映射k均值物体聚类圆形拟合物体形状识别SLAM迭代最近点匹配EKF ...
  • 识别阶段采用动态时间规划算法分别计算两种特征对于每种类别的后验概率,在决策级采用加权平均法对两种特征的后验概率进行融合,将最大概率对应的类别记为最终分类结果。针对动态时间规划算法提出一种基于椭圆边界...
  • 使用后缀数组提取链码中的重复字符串作为特征编码,应用动态规划算法在近似匹配条件下计算特征编码在链码中的重复频率。最后,以特征编码及其重复频率作为目标的形状特征和尺度特征,使用贝叶斯分类方法对纹理进行...
  • 结合无人机室内导航谈谈3DVFH+算法

    千次阅读 2019-03-18 17:30:41
    算法在之前做本科毕设时候有所接触,不过当时是进行了2D的Matlab仿真,down个代码改改障碍物形状,飞机当做质点考虑,没有深入了解这个算法在实际中该怎么应用。今天找到了论文的原文《3DVFH+:Real-Time Three_...

空空如也

空空如也

1 2 3 4
收藏数 74
精华内容 29
关键字:

形状规划算法