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

  1. 如果数组中部分元素已按自然数顺序排放,例如,数组
    《算法7.自然合并排序与最长公共子序列。》

,则初期自然排好序的子数组段显然有4段,分别为
《算法7.自然合并排序与最长公共子序列。》

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

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

《算法7.自然合并排序与最长公共子序列。》
。请充分利用上述特点设计并实现一个自然合并排序算法。
(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) 算法运行结果及计算时间复杂度分析
《算法7.自然合并排序与最长公共子序列。》

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

时间复杂度为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) 算法运行结果及计算时间复杂度分析
《算法7.自然合并排序与最长公共子序列。》

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

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

点赞