1. 程式人生 > 實用技巧 >資料結構與演算法-緒論

資料結構與演算法-緒論

緒論

演算法:即是在特定計算模型下,旨在解決特定問題的指令序列
要保證正確性、確定性、可行性、有窮性
有窮性:

例子1:HailStone序列

 @Test
    public void test1() {

        int n = 7;
        int length = 1;
        while (n > 1) {
            n = ((n % 2) > 0) ? 3 * n + 1 : n / 2;
            length++;
        }
        System.out.println(length);
    }

層級級別:

例子2:計算任意N個整數之和

減而治之

@Test
    public void test4() {
        int[] A = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};

        long begintime = System.nanoTime();
        /*int n = A.length;//簡單遞迴
        System.out.println(sum1(A,n));*/
        int lo = 0;//二分遞迴
        int hi = A.length - 1;
        System.out.println(sum2(A, lo, hi));
        long endtime = System.nanoTime();
        long costTime = (endtime - begintime);
        System.out.println(costTime);

    }

    public int sum1(int A[], int n) {
        return (n < 1) ? 0 : sum1(A, n - 1) + A[n - 1];
    }

分而治之

public int sum2(int A[], int lo, int hi) {
        if (lo == hi) {
            return A[lo];
        }
        int mi = (lo + hi) >> 1;
        return sum2(A, lo, mi) + sum2(A, mi + 1, hi);
    }

陣列反向

@Test
    public void test5() {
        int[] A = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
        int lo = 0;
        int hi = A.length - 1;
        reverse(A, lo, hi);
    }

    public void reverse(int[] A, int lo, int hi) {
        if (lo < hi) {
            int a = A[lo];
            A[lo] = A[hi];
            A[hi] = a;
            System.out.println(Arrays.toString(A));
            reverse(A, lo + 1, hi - 1);
        }
    }

例子3:從陣列區間中找出最大的兩個整數元素

@Test
    public void test6() {
        int[] A = {100, 836, 3236, 5, 16, 26, -3, 89, 69, 43};
        int lo = 0;
        int hi = A.length - 1;
        /*int[] int1 = max2(A, lo, hi);*/
        /*int[] int2 = max02(A, lo, hi);*/
        int[] int3 = max002(A, lo, hi);

        System.out.println(Arrays.toString(int3));
    }

    public int[] max2(int[] A, int lo, int hi) {
        int x1 = lo;
        int x2 = lo;
        //掃描lo-hi,找到x1
        for (int i = lo + 1; i < hi; i++) {
            if (A[x1] < A[i]) {
                x1 = i;
            }
        }
        //掃描lo-x1,找到較大值
        for (int i = lo + 1; i < x1; i++) {
            if (A[x2] < A[i]) {
                x2 = i;
            }
        }
        //掃描x1-hi,找到x2
        for (int i = x1 + 1; i < hi; i++) {
            if (A[x2] < A[i]) {
                x2 = i;
            }
        }
        return new int[]{x1, x2};
    }

改進1:

public int[] max02(int[] A, int lo, int hi) {
        int x1 = lo;
        int x2 = lo + 1;
        if (A[x1] < A[x2]){
            int a = A[x1];
            A[x1] = A[x2];
            A[x2] = a;
        }
        for (int i = lo + 2;i < hi;i++){
            if (A[x2] < A[i]){
                if (A[x1] < A[x2 = i]){
                    int a = A[x1];
                    A[x1] = A[x2];
                    A[x2] = a;
                }
            }
        }
        return new int[]{x1, x2};
    }

改進2:

public int[] max002(int[] A, int lo, int hi) {
        int x1 = 0;
        int x2 = 0;
        if (lo + 1 == hi){
            if (A[x1 = lo] < A[x2 = hi]){
                int a = x1;
                x1 = x2;
                x2 = a;
            }
            return new int[]{x1,x2};
        }
        if (lo + 2 == hi){
            int mid = (lo + hi)/2;
            if ((A[lo] > A[mid]) && (A[lo] > A[hi])){
                x1 = lo;
                x2 = (A[mid] > A[hi]) ? mid:hi;
            }else if((A[lo] < A[mid]) && (A[mid] > A[hi])){
                x1 = mid;
                x2 = (A[lo] > A[hi]) ? lo:hi;
            }else{
                x1 = hi;
                x2 = (A[lo] > A[mid]) ? lo:mid;
            }
            return new int[]{x1,x2};
        }

        int mi = (lo + hi) / 2;
        int[] L= max002(A, lo, mi);
        int[] R = max002(A, mi+1, hi);

        if (A[L[0]] > A[R[0]]){
            x1 = L[0];
            x2 = (A[L[1]] > A[R[0]]) ? L[1]:R[0];
        }else{
            x1 = R[0];
            x2 = (A[L[0]] > A[R[1]]) ? L[0]:R[1];
        }
        return new int[]{x1,x2};
    }

相關連結

例子3.5:最大綜合區間(還沒寫)

蠻力

遞增

分治

減治

例子4:斐波那契數列

動態規劃

@Test
    public void test7(){
        for (int i = 0;i < 25;i++){

            System.out.println(fib(i));
        }
    }
    public int fib(int n){
        int f = 0;
        int g = 1;
        while(0 < n--){
            g = g + f;
            f = g - f;
        }
        return g;
    }

例子5:最長公共子序列

情況分析


@Test
    public void test8(){
        char[] x = {'A','B','C','B','D','A','B'};
        char[] y = {'B','D','C','A','B','A'};
        int[][] b = new int[x.length+1][y.length+1];
        int[][] c = lcsLength(x,y,b);
        System.out.println(c[x.length][y.length]);
        lcs(x.length,y.length,x,b);
    }
    //從[0][0]向[x.length+1][y.length+1]不斷的得到1、共同序列個數 2、各種情況並作出標記
    public int[][] lcsLength(char[] x,char[] y,int[][] b) {
        //給第一行,第一列設定空序列
        int[][] c = new int[x.length+1][y.length+1]; //0存空序列
        for(int i=0;i<c.length;i++){
            for(int j=0;j<c[0].length;j++){
                c[i][j] = 0;
            }
        }
        //進行規劃
        for (int i = 1;i <= x.length;i++){
            for (int j = 1;j <= y.length;j++){
                //情況一、末位相等,去除最後一個值並比較前面的(減而治之)
                if (x[i-1] == y[j-1]){
                    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;
                    }
                }
            }
        }
        //返回規劃好的情況c
        return c;
    }
    public void lcs(int i,int j,char[]x,int[][]b){
        //結束條件
        if (i==0 || j==0){
            return;
        }
        //判斷b[i][j]進入不同分支
        if (b[i][j]==1){
            //減而治之
            lcs(i-1,j-1,x,b);
            System.out.print(x[i-1]);
        }else {
            //分而治之
            if (b[i][j] == 2){
                lcs(i-1,j,x,b);
            }else{
                lcs(i,j-1,x,b);
            }
        }
    }

空間優化

/**
     * 方法二、進行空間上的優化
     * 通過狀態方程可知,計算c[i][j]時只需知道c[i-1][j-1]、c[i-1][j]、c[i][j-1]就行了
     * 那麼就和斐波那契數列比較相似,可以利用滾動陣列
     */
    //從[0][0]向[x.length+1][y.length+1]不斷的得到1、共同序列個數 2、各種情況並作出標記
    public Set<Character> lcsLength2(char[] x, char[] y) {
        Set<Character> set = new HashSet<>();
        //給第一行,第一列設定空序列
        int[][] c = new int[x.length+1][y.length+1]; //0存空序列
        for(int i=0;i<c.length;i++){
            for(int j=0;j<c[0].length;j++){
                c[i][j] = 0;
            }
        }
        //進行規劃
        for (int i = 1;i <= x.length;i++){
            for (int j = 1;j <= y.length;j++){
                //情況一、末位相等,去除最後一個值並比較前面的(減而治之)
                if (x[i-1] == y[j-1]){
                    c[i%2][j] = c[(i-1)%2][j-1]+1;
                    char a = y[j-1];
                    set.add(a);
                }else{
                    //情況二、不相等,分別去除其中一個數列的末位並進行比較(分而治之)
                    c[i%2][j] = Math.max(c[i%2][j-1],c[(i-1)%2][j]);
                }
            }
        }
        //返回規劃好的情況c
        return set;
    }

遞迴版本(沒寫JAVA版的)

function LCS(str1, str2, a, b) {
      if(a === void 0){
          a = str1.length - 1
      }
      if(b === void 0){
          b = str2.length - 1
      }
      if(a == -1 || b == -1){
          return 0
      } 
      if(str1[a] == str2[b]) {
         return LCS(str1, str2,  a-1, b-1)+1;
      }
      if(str1[a] != str2[b]) {
         var x =  LCS(str1, str2, a, b-1)
         var y =  LCS(str1, str2, a-1, b)
         return x >= y ? x : y
      }
  }

相關連結

例子6:就地迴圈位移(還沒寫)

例子7:就地隨機置亂(還沒寫)