算法7.自然合并排序与最长公共子序列。

  1. 如果数组中部分元素已按自然数顺序排放,例如,数组
    这里写图片描述

,则初期自然排好序的子数组段显然有4段,分别为
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述
。请充分利用上述特点设计并实现一个自然合并排序算法。
(1) 算法设计思路
先对数组进行一次线性扫描,记录下部分有序数组的断开位置和个数,个数用于判断最后一个断开的位置是否为数组末尾,位置用于合并数组。循环-依次将相邻的两两数组进行合并,直到最后剩下一个数组,即为合并排序好的数组。

(2) 算法实现的伪代码
功能描述:对数组进行线性扫描,记录下该断点的位置和断点个数
输入:数组大小n
输出:数组划分后的断点个数+1

    int fenduan(int n)
    int j=0;
    rem[j++]=0
    for i=0 to n-2
    if (arr[i]>arr[i+1]) rem[j]=i;j++;//记录断点的位置
    end for 
    rem[j++]=n-1;
    return j;
功能描述:通过记录的断点位置将两两相邻的伪数组进行合并排序
输入:起始位置a,断点位置m,终点位置r
输出:
    void merge(int a,int m,int r)
    int i=a,j=m+1,k=a;
    while (i<=m&&j<=r)
    if (arr[i]<=arr[j]) newarr[k++]=arr[i++]
    else newarr[k++]=arr[j++];
    end while
    if (i>m)
    for (int q=j to r) newarr[k++]=arr[q] end for 
    else for (int q=I to m) newarr[k++]=arr[q] end for 
功能描述:循环活动最终的自然合并排序
输入:数组的大小n
输出:
    sort(int num)
    int a=huafen(num)
    while (a!=2)
    for (int i=0 to a-1)
    if (i==0)
    merge(rem[i] ,rem[i+1], rem[i+2]);
    else if (i+2>a-1)
    merge(rem[i+1],rem[i+1],rem[i+1])
    else merge(rem[i]+1,rem[i+1],rem[i+2])
    end for
    /将合并好的数组复制到原数组
    for (int j=0;j<n;j++)
    arr[j]=newarr[j];end for 
    a=huafen(n);//继续获得断点个数+1

(3) 实现代码

    import java.util.Scanner;
    public class main {
        static int[] arr;//创建录入数据用的数组
        static int[] newarr;//用于存储合并后的数组
        static int[] rem;//记录数组断点的位置
        static int n;//数组的大小

        //自然合并主方法
        static void sort(int num){
            int a=huafen(num);//断点的个数+1
            while (a!=2){//如果只有一个断点,则证明已经排好顺序
                int i=0;            
                for (i=0;i<a-1;i+=2){
                    if (i==0)//对第一队有序数组进行排序
                        merge(rem[i] ,rem[i+1], rem[i+2]);
                    else if (i+2>a-1)//剩余的部分有序数组无法两两匹配
                        merge(rem[i+1],rem[i+1] , rem[i+1]);
                    else 
                        merge(rem[i]+1, rem[i+1], rem[i+2]);

    //              else if (i+2>a-1&&i+1==a-1)//最后两个有序数组两两合并
    //                  merge(rem[i], rem[i], rem[i+1]);
    //              else//有序数组两两合并
    //                  merge(rem[i] ,rem[i+1], rem[i+2]);
                }
                //将合并好的数组复制到原数组
                for (int j=0;j<n;j++)
                    arr[j]=newarr[j];
                a=huafen(n);//继续获得断点个数+1
            }
        }

        //划分数组的有序部分,并且记录下该断点的位置和断点个数
        private static int huafen(int n) {
            int num=0;
            rem[num++]=0;
            for (int i=0;i<n-1;i++){
                if (arr[i]>arr[i+1])
                    rem[num++]=i;
            }
            rem[num++]=n-1;//将最后一个断点位置赋值为数组最后一个位置,便于排序
            return num;
        }   

        //进行合并排序
        private static void merge(int a, int m, int r) {
            int i=a;
            int j=m+1;
            int k=a;
            while (i<=m&&j<=r){
                if (arr[i]<=arr[j])
                    newarr[k++]=arr[i++];
                else {
                    newarr[k++]=arr[j++];
                }
            }
                if (i>m)
                    for (int q=j;q<=r;q++)
                        newarr[k++]=arr[q];
                else {
                    for (int q=i;q<=m;q++)
                        newarr[k++]=arr[q];
                }
        }

        public static void main(String []args){
            Scanner in=new Scanner(System.in);
            System.out.println("输入数组的大小");
            n=in.nextInt();
            //初始化数据
            newarr = new int[n];
            arr=new int[n];
            rem=new int[n+1];

            System.out.println("录入数据,数据用空格格开");
            String line=in.nextLine();
            line=in.nextLine();
            String arrg[]=line.split(" ");
            for (int j=0;j<arrg.length;j++)
                arr[j]=Integer.parseInt(arrg[j]);   
            sort(n);
            System.out.println("自然合并排序后");
            for (int i=0;i<n;i++)
                System.out.print(newarr[i]+" ");
        }
    }

(4) 算法运行结果及计算时间复杂度分析
这里写图片描述

这里写图片描述
这里写图片描述

时间复杂度为O(n)

(5) 体会
自然合并排序算法比一般合并排序算法多了一个扫描的方法。通过扫描后记录断点的位置和断点的个数,可以很清晰的使用合并算法来合并两个相邻的有序数组断。在每次的合并后,继续扫描一次,然后再合并,直到扫描到只有一个断点个数。在编写代码的过程中,老是在循环递归合并算法卡住了,不能很好的判断要合并的相邻的两个数组断的起点,断开的位置,和终点位置,导致耗费了比较多的时间

  1. 最长公共子序列LCS问题:给定2个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。给定X = {x1, x2, …, xm}和Y = {y1, y2, …, yn},请找出X和Y的最长公共子序列。例如:
    输入:X = ABCHFGASJKXBD Y = DBDHASXB
    输出:LCS = BHASXB
    (1) 算法设计思路
    创建二维数组c存储X和Y的最长公共子序列的长度,二维数组b记录c的值是由哪一个子问题解得到的,然后构造最长公共子序列。

(2) 算法实现的伪代码
功能描述:创建备忘录,记录最长子序列的长度和对应所在位置
输入:字符串x,y和其长度n,m。备忘录c,b
输出:备忘录c,b

    void max(int n,int m ,char[] x,char[] y,int[][] c,int[][] b)
    for(int I to n) c[i][0]=0
    for (int j to m) c[0][j]=0;
    for (int i to n)
    for (int j to m)
    if (x[i]=y[j])  c[i][j]=c[i-1][j-1]+1;b[i][j]=1;
    else if (c[i-1][j]>=c[i][j-1]) c[i][j]=c[i-1][j];b[i][j]=2; 
    else c[i][j]=c[i][j-1];b[i][j]=3;
    end for 
    end for
功能描述:使用备忘录输出共同的子序列
输入:字符串x和x的长度,y的长度。备忘录b
输出:最长公共子序列
        if (i==0||j==0)return;
        if (b[i][j]==1) input(i-1, j-1,x,b); 输出(x[i]);
        else if (b[i][j]==2)input(i-1, j,x,b);
        else input(i, j-1,x,b);

(3) 实现代码

    package 算法分析;

    import java.util.Scanner;

    public class main {
        //创建备忘录,记录所有的数据
        static void max(int n,int m,char[] x,char[] y,int[][] c,int[][] b){
            //初始化数组
            for (int i=1;i<=n;i++)
                c[i][0]=0;
            for (int j=1;j<=m;j++)
                c[0][j]=0;
        /*
         * c[i][j]=① 0 ,i=0,j=0
         *          ②c[i]-1[j-1]+1 , Xi=Yj
         *          ③max{ (c[i][j-1],c[i-1][j]) } , Xi!=Yi
         */
            for (int i=1;i<=n;i++)
                for (int j=1;j<=m;j++){
                    if (x[i]==y[j]){
                        c[i][j]=c[i-1][j-1]+1;
                        b[i][j]=1;
                    }
                    else if (c[i-1][j]>=c[i][j-1]){
                        c[i][j]=c[i-1][j];
                        b[i][j]=2;
                    }
                    else {
                        c[i][j]=c[i][j-1];
                        b[i][j]=3;
                    }
                }
        }

        //使用备忘录
        static void input(int i,int j,char[] x,int[][] b){
            if (i==0||j==0)
                return;
            if (b[i][j]==1){//如果Xi=Yj,递归,输出该值
                input(i-1, j-1,x,b);
                System.out.print(x[i]);
            }
            else if (b[i][j]==2){
                input(i-1, j,x,b);
            }
            else 
                input(i, j-1,x,b);
        }

        public static void main(String []args){
            Scanner in=new Scanner(System.in);
            System.out.println("输入字符串x[]");
            String line=in.nextLine();
            char[] x;
            x=line.toCharArray();
            int n=x.length;//获取字符串x的长度
            //为x头加上空格
            char c1[]=null;
            c1=x;
            x=new char[n+1];
            x[0]=' ';
            for (int i=1;i<=n;i++)
                x[i]=c1[i-1];
            System.out.println("输入字符串Y[]");
            String line2=in.nextLine();
            char[] y;
            y=line2.toCharArray();
            int m=y.length;//获取字符串y的长度
            //为字符串y头加上空格
            char c2[]=null;
            c2=y;
            y=new char[m+1];
            y[0]=' ';
            for (int i=1;i<=m;i++)
                y[i]=c2[i-1];

            int[][] c=new int[n+1][m+1];
            int[][] b=new int[n+1][m+1];

            max(n, m, x, y, c, b);
            input(n, m, x, b);
        }
    }

(4) 算法运行结果及计算时间复杂度分析
这里写图片描述

这里写图片描述

这里写图片描述
O(mn)+O(n+m)
(5) 体会
重在理解。备忘录的使用非常的巧妙,经常会忘记使用备忘录而导致结果错误。使用动态规划的时候,先找出最优解的性质,并刻画其结构特征,然后递归定义最优解,再自底而上的方式计算最优值,最后根据计算最优值时得到的信息,构造最优解。分析最优解的性质很重要,也比较有难度。通过做这道题,发现我现在对动态规划的理解还差得远呢,但是只要肯努力,就没有什么不可能。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 数字三角形问题可能出现的问题: - 如果数字三角形很大,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果使用递归方式求解,可能会发生栈溢出的问题,需要使用递推的方式。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以使用递推的方式进行计算,而不是使用递归,避免发生栈溢出的问题。 2. 矩阵链乘问题可能出现的问题: - 如果矩阵的个数很多,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果使用暴力枚举方式求解,时间复杂度将会是指数级别的,无法处理大规模的数据。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以使用矩阵连乘的性质,采用分治法的思想,将大问题分解成小问题,逐步合并,降低计算量。 3. 最长公共子序列问题可能出现的问题: - 如果序列的长度很长,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果使用暴力枚举方式求解,时间复杂度将会是指数级别的,无法处理大规模的数据。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以使用优化的动态规划算法,如滚动数组、矩阵压缩等方式,来降低空间复杂度。 4. 最大字段和问题可能出现的问题: - 如果序列的长度很长,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果使用暴力枚举方式求解,时间复杂度将会是指数级别的,无法处理大规模的数据。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以使用优化的动态规划算法,如滚动数组、一维数组等方式,来降低空间复杂度。 5. 0-1背包问题可能出现的问题: - 如果物品的数量很多,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果使用暴力枚举方式求解,时间复杂度将会是指数级别的,无法处理大规模的数据。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以使用优化的动态规划算法,如滚动数组、一维数组等方式,来降低空间复杂度。 - 可以使用贪心算法、分支定界等方式,来解决背包问题。 6. 矩形嵌套问题可能出现的问题: - 如果矩形的个数很多,会导致计算量很大,时间复杂度高,程序运行速度慢。 - 如果矩形之间没有嵌套关系,或者存在多个矩形之间相互嵌套的情况,会导致计算结果不准确。 解决方法: - 可以使用动态规划的方式来解决该问题,将计算过程中重复计算的部分存储起来,避免重复计算,从而降低时间复杂度。 - 可以先按照长或宽进行排序,然后使用动态规划来解决该问题,设计一个一维数组来存储每个矩形的嵌套深度,递推公式为dp[i] = max(dp[j])+1(当第i个矩形可以嵌套在第j个矩形内部时),dp[i] = 1(当第i个矩形无法嵌套在其他矩形内部时),最终判断是否存在一个矩形的嵌套深度等于矩形的个数即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值