算法设计与分析: 2-13 标准二维表问题

2-13 标准二维表问题


问题描述

设n是一个正整数。2*n的标准二维表是由正整数1,2,…,2n组成的2*n数组,该数组的每行从左到右递增,每列从上到下递增。2*n的标准二维表全体记为Tab(n)。例如,当n=3时,tab(3)二维表如下图所示。

1 2 3
4 5 6
1 2 4
3 5 6
1 2 5
3 4 6
1 3 4
2 5 6
1 3 5
2 4 6

给定正整数n,试计算Tab(n)中2*n的标准二维表的个数。


分析

一维数组递归遍历法

将上面标准二维表从左到右从上往下看成一维数组,递归遍历,记录符合条件的情况。

进栈出栈法

1 2 3
4 5 6
1 2 3 4 5 6
0 0 0 1 1 1

如上图,先把2*n个数字排成一行来看(1~2*n 有序)。之前上表中放到第一行的数字1,2,3在下表中标记为0,放到第二行的数字4,5,6在下表中标记为1,这样就可以有一个0,1的序列,如下图(按照上面方式,将前面n=3时的所有5种标准二维表转变为一维表):

1 2 3
4 5 6
1 2 3 4 5 6
0 0 0 1 1 1
1 2 4
3 5 6
1 2 3 4 5 6
0 0 1 0 1 1
1 2 5
3 4 6
1 2 3 4 5 6
0 0 1 1 0 1
1 3 4
2 5 6
1 2 3 4 5 6
0 1 0 0 1 1
1 3 5
2 4 6
1 2 3 4 5 6
0 1 0 1 0 1

当n=3时,5种标准二维表转化成的总表如下

1 2 3 4 5 6
0 0 0 1 1 1
0 0 1 0 1 1
0 0 1 1 0 1
0 1 0 0 1 1
0 1 0 1 0 1

可见,题目要求前面的数字比后面、上面的数字比下面的数字小的问题,可以转化为,序列中每个数字前面0的个数要大于等于1的个数问题。

该问题又可以转换成进栈出栈问题。2*n个数字,进栈是0,出栈是1,且进栈出栈次数均为n,这样就可以保证输出的进栈出栈序列中每个数字前面0的个数大于等于1的个数,符合题目要求。

Catalan数法

上面方法中涉及到的 进栈出栈问题 本质上属于 Catalan数问题。因此,又可以转化为 Catalan数问题。

令h(0)=1,h(1)=1,Catalan数满足递推式[1] :
h ( n ) = h ( 0 ) h ( n 1 ) + h ( 1 ) h ( n 2 ) + . . . + h ( n 1 ) h ( 0 ) ( n >= 2 )

例如:h(2)=h(0)*h(1)+h(1)*h(0)=1*1+1*1=2
h(3)=h(0)*h(2)+h(1)*h(1)+h(2)*h(0)=1*2+1*1+2*1=5

另类递推式[2] :
h ( n ) = h ( n 1 ) ( 4 n 2 ) / ( n + 1 ) ;

递推关系的解为:
h ( n ) = C ( 2 n , n ) / ( n + 1 ) ( n = 0 , 1 , 2 , . . . )

递推关系的另类解为:
h ( n ) = c ( 2 n , n ) c ( 2 n , n 1 ) ( n = 0 , 1 , 2 , . . . )

Catalan数法(大数乘法与除法)

当输入的数n较大时,可以用 大数乘法与除法(二维数组实现) 来解决,计算Catalan数。


一维数组递归遍历法

Java

import java.util.Scanner;

public class Main {

    private static long count = 0L;

    private static int[] numbers;
    private static int n;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        while (true) {
            //还原
            count = 0;

            System.out.println("Input n: ");
            n = input.nextInt();
            System.out.println("-----------------");

            numbers = new int[2*n];
            for(int i=0;i<2*n;i++)
                numbers[i]=i+1;

            if(n==1)
                print();
            else
                perm(1,2*n-2);

            System.out.println("Tables: "+count);
            System.out.println("------------------------");
        }
    }

    private static void perm(int start, int end){
        if(start==end && isOk())
            print();
        else{
            for(int i=start; i<=end; i++)
                if(start==i || (start!=i && numbers[start]!=numbers[i])){//剔除重复项
                    swap(start,i);
                    perm(start+1,end);
                    swap(start,i);
                }
        }
    }

    private static void swap(int i, int j){
        int temp=numbers[i];
        numbers[i]=numbers[j];
        numbers[j]=temp;
    }

    private static boolean isOk(){
        int i,j;
        for(i=1;i<n;i++)//第一排比较
//            if(arr[i]<arr[i-1]||arr[i]>arr[i+n])
            if(numbers[i]<numbers[i-1])
                return false;
        for(++i;i<2*n;i++)//第二排比较
            if(numbers[i]<numbers[i-1])
                return false;

        for(j=0; j<n; j++)//列比较
            if(numbers[j]>numbers[j+n])
                return false;

        return true;
    }

    private static void print(){
        int m;
        count++;
        for(m=0; m<n; m++)
            System.out.print(String.format("%3d", numbers[m]));
        System.out.println();
        for(; m<2*n; m++)
            System.out.print(String.format("%3d", numbers[m]));
        System.out.println();
        System.out.println("-----------------");
    }
}

Input & Output

Input n: 
1
-----------------
  1
  2
-----------------
Tables: 1
------------------------
Input n: 
0
-----------------
Tables: 0
------------------------
Input n: 
2
-----------------
  1  2
  3  4
-----------------
  1  3
  2  4
-----------------
Tables: 2
------------------------
Input n: 
3
-----------------
  1  2  3
  4  5  6
-----------------
  1  2  4
  3  5  6
-----------------
  1  2  5
  3  4  6
-----------------
  1  3  4
  2  5  6
-----------------
  1  3  5
  2  4  6
-----------------
Tables: 5
------------------------
Input n: 
4
-----------------
  1  2  3  4
  5  6  7  8
-----------------
  1  2  3  5
  4  6  7  8
-----------------
  1  2  3  6
  4  5  7  8
-----------------
  1  2  3  7
  4  5  6  8
-----------------
  1  2  4  5
  3  6  7  8
-----------------
  1  2  4  6
  3  5  7  8
-----------------
  1  2  4  7
  3  5  6  8
-----------------
  1  2  5  6
  3  4  7  8
-----------------
  1  2  5  7
  3  4  6  8
-----------------
  1  3  4  5
  2  6  7  8
-----------------
  1  3  4  6
  2  5  7  8
-----------------
  1  3  4  7
  2  5  6  8
-----------------
  1  3  5  6
  2  4  7  8
-----------------
  1  3  5  7
  2  4  6  8
-----------------
Tables: 14
------------------------
Input n: 

进栈出栈法

Java

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int i, j;
        int count0, count1;
        long count;
        int num;
        int n;

        while (true) {

            //还原初始值
            count = 0;
            n = input.nextInt();

            //转化为 进栈出栈的问题:2*n个数字,进栈是0,出栈是1,且进栈与出栈次数相等,均为n
            //B:二进制 D:十进制
            //当n=3时,有6个数字,最大01排列为:111111B < 1000000B = 2^(2*3)D = 2^6D
            for (i = 0; i < Math.pow(2, 2 * n); i++) {
                num = i;
                count0 = count1 = 0;
                for (j = 0; j < 2 * n; j++) {
                    if (num % 2 == 0) {
                        count0++;
                    } else {
                        count1++;
                    }
                    num = num / 2;
                    if (count0 > count1) //二进制最后为0,表示进栈,当进栈与出栈次数相等时,前面出栈必然比进栈多一次,无进栈如何出栈,不可能。
//                    if(count1 > count0)
                        break;
                }
                if (count1 == count0 && count0 == n)
                    count++;
            }

            System.out.println("Tables: " + count);
            System.out.println("-----------------");
        }
    }
}

Input & Output

0
Tables: 1
-----------------
1
Tables: 1
-----------------
2
Tables: 2
-----------------
3
Tables: 5
-----------------
4
Tables: 14
-----------------
5
Tables: 42
-----------------
6
Tables: 132
-----------------
7
Tables: 429
-----------------
8
Tables: 1430
-----------------
9
Tables: 4862
-----------------
10
Tables: 16796
-----------------

Catalan数法

Java

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n;
        long catalan;

        while (true){
            n = input.nextInt();

            catalan = Catalan(n);

            System.out.println(catalan);
            System.out.println("--------------");
        }
    }

    //第一种Catalan数计算方法
    //h(n)= h(0)*h(n-1)+h(1)*h(n-2) + ... + h(n-1)h(0) (n>=2)
    private static long Catalan(int n){ //0<=n<=35
        if(n <= 1)
            return 1;
        long[] h = new long[n+1];
        h[0] = h[1] = 1;
        for(int i = 2 ; i <= n ; i++){
            h[i] = 0;
            for(int j = 0 ; j < i ; j++)
                h[i] += h[j]*h[i-j-1];
        }

        return h[n];

    }

    //第二种Catalan数计算方法
    //h(n)=h(n-1)*(4*n-2)/(n+1)
    private static long Catalan2(int n){ //0<=n<=33
        if(n <= 1)
            return 1;
        long[] h = new long[n+1];
        h[0] = h[1] = 1;
        for(int i=2; i<=n; i++)
            h[i] = h[i-1]*(4*i-2)/(i+1);

        return h[n];
    }
}

Input & Output

0
1
--------------
1
1
--------------
2
2
--------------
3
5
--------------
4
14
--------------
5
42
--------------
7
429
--------------
27
69533550916004
--------------

Catalan数法(大数乘法与除法)

Java

import java.util.Scanner;

public class Main {

    private static int MAX = 100;
    private static int BASE = 10000;

    public static void main(String[] args) {

        Scanner input = new Scanner(System.in);

        int i, j, n; //1<=n<=100
        int[][] a = new int[105][MAX];
        for (i=0; i<MAX; i++) {
            a[1][i] = 0;
        }

        for (i=2, a[1][MAX-1]=1; i<=100; i++) {
            for (j=0; j<MAX; j++)
                a[i][j] = a[i-1][j];
            multiply(a[i], MAX, 4*i-2);
            divide(a[i], MAX, i+1);
        }
        while (true) {
            n = input.nextInt();

            for (i = 0; i<MAX && a[n][i]==0; i++);
            System.out.print(a[n][i++]);
            for (; i<MAX; i++)
                System.out.print(String.format("%04d", a[n][i]));

            System.out.println();
            System.out.println("------------------");
        }

    }

    //大数乘法
    private static void multiply(int[] a, int Max, int b) {
        int i, array = 0;
        for (i=Max-1; i>=0; i--) {
            array += b*a[i];
            a[i] = array%BASE;
            array /= BASE;
        }

    }

    //大数除法
    private static void divide(int[] a, int Max, int b) {
        int i, div=0;
        for (i=0; i<Max; i++) {
            div = div*BASE+a[i];
            a[i] = div/b;
            div %= b;
        }

    }
}

Input & Output

1
1
------------------
2
2
------------------
3
5
------------------
4
14
------------------
35
3116285494907301262
------------------
100
896519947090131496687170070074100632420837521538745909320
------------------
57
26700952856774851904245220912664
------------------
24
1289904147324
------------------
68
86218923998960285726185640663701108500
------------------
79
289450081175264899454283846029490767264392230
------------------
46
8740328711533173390046320
------------------

Reference

王晓东《计算机算法设计与分析》(第3版)P47
https://blog.csdn.net/llwwlql/article/details/52920198

猜你喜欢

转载自blog.csdn.net/IOIO_/article/details/80984338