1. 程式人生 > >Android中的演算法

Android中的演算法

演算法是思想的體現形式,常見的演算法做一些總結

演算法簡介

演算法—Algorithm

解題方案的準確而完整的描述,是一系列解決問題的清晰指令

特徵

有窮性,確切性,輸入項,輸出項,可行性

演算法運算要素

算術運算:加減乘除等運算
邏輯運算:或、且、非等運算
關係運算:大於、小於、等於、不等於等運算
資料傳輸:輸入、輸出、賦值等運算

演算法優劣評定

時間複雜度,空間複雜度,正確性,可讀性,健壯性

LogN

二分法查詢最壞的情況:對於N個元素的陣列,第一次查詢未找到則捨棄 N/2 個元素,剩下 N/2,同理第二次剩 N/4 ···,一直到最後剩 N/2^k >= 1,所以二分法查詢的次數 k 滿足 N/2^k = 1,於是
2 ^ k = N
k = log2

N
所以二分法查詢的最壞時間複雜度是O(logN)

NLogN

首先決策樹是一顆二叉樹,每個節點表示元素之間一組可能的排序
先來說明一些二叉樹的性質,令T是深度為d的二叉樹,則T最多有2^片樹葉
具有L片樹葉的二叉樹的深度至少是logL
所以,對n個元素排序的決策樹必然有n!片樹葉(因為n個數有n!種不同的大小關係),所以決策樹的深度至少是log(n!),即至少需要log(n!)次比較,而

log(n!) = logn + log(n-1) + log(n-2) +...+ log2 + log1
>= logn + log(n-1) + log(n-2) +...+ log(n/2)
>= (n/2)log(n/2)
>= (n/2)logn - n/2
= O(nlogn)

演算法分析方法

遞迴法:漢諾塔
窮舉法:暴力密碼破解法
貪心演算法:加勒比海盜偷寶藏
分治法:樂毅連下齊72城 二分搜尋
動態規劃法:導彈攔截
迭代法:超能生的兔子
回溯法:八皇后

排序

由於前面已經對排序進行過總結了,這裡不再贅述其思想,直接貼出示例程式碼
前面使用的是C語言,這裡使用Java,原理在前面已經說明,推薦看前面的,包括程式碼
由於下面的排序涉及到交換的比較多,這裡直接先定義一個交換的方法

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

交換排序

氣泡排序

public void bubbleSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        for (int j = 0; j < array.length - 1 - i; j++) {
            if(array[j] > array[j + 1]) {
                swap(array, j, j + 1);
            }
        }
    }
}

快速排序

private void quickSort(int[] array, int left, int right) {
    if(left < right) {
        int mid = getMid(array, left, right);
        quickSort(array, 0, mid -1);
        quickSort(array, mid + 1, right);
    }
}

private int getMid(int[] array, int left, int right) {
    int temp = array[left];
    while(left < right) {
        while(left < right && array[right] >= temp) {
            right--;
        }
        array[left] = array[right];
        while(left < right && array[left] <= temp) {
            left++;
        }
        array[right] = array[left];
    }
    array[left] = temp;
    return left;
}

選擇排序

簡單選擇排序

public void selectSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int key = i;
        for (int j = i; j < array.length; j++) {
            if(array[key] > array[j]) {
                key = j;
            }
        }
        if(key != i) {
            swap(array, key, i);
        }
    }
}

堆排序

public void heapSort(int[] array) {
    if(array == null || array.length <= 1) {
        return;
    }
    //構建大堆
    buildMaxHeap(array);
    for (int i = array.length - 1; i > 0; i--) {
        swap(array, 0, i);
        adjustHeap(array, i, 0);
    }
}

private void buildMaxHeap(int[] array) {
    int half = (array.length - 1) / 2;
    for (int i = half; i >= 0 ; i--) {
        adjustHeap(array,array.length,i);
    }
}

private void adjustHeap(int[] array, int length, int i) {
    int left = 2 * i + 1;
    int right = 2 * i + 2;
    int largest = i;
    if(left < length && array[left] > array[largest]) {
        largest = left;
    }
    if(right < length && array[right] > array[largest]) {
        largest = right;
    }
    if(i != largest) {
        swap(array,i,largest);
        adjustHeap(array, length, largest);
    }
}

插入排序

直接插入排序

public void insertSort(int[] array) {
    for (int i = 1; i < array.length; i++) {
        int temp = array[i];
        int j;
        for (j = i - 1; j >= 0; j--) {
            if(array[j] > temp) {
                array[j + 1] = array[j];
            }else {
                break;
            }
        }
        array[j + 1] = temp;
    }
}

二分插入排序

public void binaryInsertSort(int[] array) {
    for (int i = 0; i < array.length; i++) {
        int temp = array[i];
        int left = 0;
        int right = i - 1;
        int mid = 0;
        while(left <= right) {
            mid = (left + right) / 2;
            if(temp < array[mid]) {
                right = mid - 1;
            }else {
                left = mid + 1;
            }
        }
        for (int j = i - 1; j >= left; j--) {
            array[j + 1] = array[j];
        }
        if(left != i) {
            array[left] = temp;
        }
    }
}

希爾排序

public void heerSort(int[] array) {
    int k = -1;
    int temp = -1;
    int gap = array.length;
    while(gap > 1) {
        gap = gap / 3 + 1; //通常取3 O(n 1.3)
        for (int i = gap; i < array.length; i += gap)
        {
            k = i;
            temp = array[k];
            for (int j = i - gap; (j >= 0) && (array[j] > temp); j -= gap)
            {
                array[j + gap] = array[j];
                k = j;
            }
            array[k] = temp;
        }
    }
}

歸併排序

public void mergeSort(int[] array) {
    mergeSort(array,0,array.length - 1);
}

private void mergeSort(int[] array, int left, int right) {
    if(left < right) {
        int mid = (left + right) / 2;
        mergeSort(array, left, mid);
        mergeSort(array, mid + 1, right);
        mergeSort(array, left, mid, right);
    }
}

private void mergeSort(int[] array, int left, int mid, int right) {
    int[] temp = new int[array.length];
    int rightStart = mid + 1;
    int leftStart = left;
    int tempIndex = left;
    while(leftStart <= mid && rightStart <= right) {
        if(array[leftStart] <= array[rightStart]) {
            temp[tempIndex++] = array[leftStart++];
        }else {
            temp[tempIndex++] = array[rightStart++];
        }
    }
    while(leftStart <= mid) {
        temp[tempIndex++] = array[leftStart++];
    }
    while(rightStart <= right) {
        temp[tempIndex++] = array[rightStart++];
    }
    tempIndex = left;
    while(tempIndex <= right) {
        array[tempIndex] = temp[tempIndex];
        tempIndex++;
    }
}

基數排序

基數排序原理:由於數字都是由0~9組成,所以在排序的時候可以按照0~9排序

這裡只考慮了正數,這是體現其思想實現,負數的話處理一下就可以了

public void basicSort(int[] array) {
    int maxNum = 0; //記錄最大值
    int count = 0; //記錄最大值位數
    for (int i = 0; i < array.length; i++) {
        if(maxNum < array[i]) {
            maxNum = array[i];
        }
    }
    while(maxNum > 0) {
        maxNum /= 10;
        count++;
    }
    List<ArrayList> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        ArrayList arrayList = new ArrayList<>();
        list.add(arrayList);
    }
    for (int i = 0; i < count; i++) {
        for (int j = 0; j < array.length; j++) {
            int x = array[j] % (int)Math.pow(10, i + 1) / (int)Math.pow(10, i);
            ArrayList aList = list.get(x);
            aList.add(array[j]);
            list.set(x, aList);
        }
        int index = 0;
        for (int j = 0; j < 10; j++) {
            while(list.get(j).size() > 0) {
                ArrayList<Integer> aList = list.get(j);
                array[index] = aList.get(0);
                aList.remove(0);
                index++;
            }
        }
    }
}

排序的java原始碼:連結:https://pan.baidu.com/s/1mmiJV6k4vIAS53u2eNf63g 密碼:q6rq

遞迴

遞迴在程式中很常見,那麼接下來就用一些經典的遞迴來探索遞迴的運用吧

二分法查詢

public int binarySearch(int[] array, int elem, int low, int high) {
    if(low > high) {
        return -1;
    }
    int mid = (low + high) / 2;
    if(array[mid] == elem) {
        return mid;
    }
    if(array[mid] < elem) {
        return binarySearch(array, elem, mid + 1, high);
    }
    if(array[mid] > elem) {
        return binarySearch(array, elem, low, mid - 1);
    }
    return -1;
}

漢諾塔

有三根柱子,在一根柱子上從下往上按照大小順序摞著圓盤,把圓盤按大小順序重新擺放在另一根柱子上,並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤
實現思路:

  1. 將1針上n-1個盤藉助3針移動到2針上
  2. 將1針上剩下的一個盤移動到3針上
  3. 將n-1個盤從2針藉助1針移動到3針上
    那麼這裡用C語言來描述應該是這個樣子
void hanoi(int n, int one, int two, int three)
{
    if (n == 1)
        printf("%d->%d\n", one, three);
    else
    {
        hanoi(n - 1, one, three, two);
        printf("%d->%d\n", one, three);
        hanoi(n - 1, two, one, three);
    }
}

用Java也是一樣

public void hanoi(int n, int one, int two, int three) {
    if (n == 1) {
        System.out.println(one + "->" + three);
    } else {
        hanoi(n - 1, one, three, two);
        System.out.println(one + "->" + three);
        hanoi(n - 1, two, one, three);
    }
}

這裡可以得出一個結論,n個盤子需要移動zn-1次

歐幾里得擴充套件演算法

定理:兩個整數的最大公約數等於其中較小的那個數和兩數相除餘數的最大公約數。最大公約數(Greatest Common Divisor)縮寫為GCD。
gcd(a,b) = gcd(b,a mod b)(不妨設a>b 且r=a mod b ,r不為0)
證明:
第一步:令c = gcd(a,b),則設a = mc,b = nc
第二步:可知r = a - kb = mc - knc = (m - kn)c
第三步:根據第二步結果可知c也是r的因數
第四步:可以斷定m - knn互素【否則,可設m - kn = xd, n = yd, (d > 1),則m = kn + xd = kyd + xd = (ky + x)d,則a = mc = (ky + x)dcb = nc = ycd,故a與b最大公約數≥cd,而非c,與前面結論矛盾】
從而可知gcd(b,r) = c,繼而gcd(a,b) = gcd(b,r),得證
下面來實現

public int gcd(int m, int n) {
    if(n == 0) {
        return m;
    }else {
        return gcd(n, m % n);
    }
}

階乘求解演算法

public int factorial(int num) {
    if(num == 1) {
        return 1;
    }else {
        return num * factorial(num - 1);
    }
}

窮舉

泊松分酒
在多個不同容積的杯子裡,通過反覆倒酒,得到規定的酒量
例如:
有3個容器,容量分別為12升,8升,5升。其中12升中裝滿,另外兩個空著。要求你只用3個容器操作,最後使得某個容器中正好有6升
這裡制定一個規則,倒酒順序:12 -> 8 -> 5 -> 12,防止錯亂

private int b1 = 12;
private int b2 = 8;
private int b3 = 5;
private int target = 6;

public void backBottle(int cup1, int cup2, int cup3) {
    System.out.println("cup1:" + cup1 + ",cup2:" + cup2 + ",cup3:" + cup3);
    if(cup1 == target || cup2 == target || cup3 == target) {
        System.out.println("success");
        return;
    }
    if(cup2 != 0 && cup3 != b3) {
        if(cup2 + cup3 <= b3) {
            backBottle(cup1, 0, cup2 + cup3);
        }else {
            backBottle(cup1, cup2 - (b3 - cup3), b3);
        }
    }else if(cup3 == b3){
        if(cup1 + cup3 <= b1) {
            backBottle(cup1 + cup3, cup2, 0);
        }else {
            backBottle(b1, cup2, cup3 - (b1 - cup1));
        }
    }else if(cup2 == 0){
        if(cup1 >= b2) {
            backBottle(cup1 - b2, b2, cup3);
        }else {
            backBottle(0, cup1, cup3);
        }
    }
}

貪心

揹包演算法

public void packageGreedy(int capacity,int weights[],int[] values){
    int num = weights.length;
    double[] priceRatio = new double[num];
    int[] index = new int[num];
    for(int i = 0;i < num; i++){
        priceRatio[i] = (double)values[i] / weights[i];
        index[i] = i;
    }
    
    double temp = 0;//對價效比進行排序
    for(int i = 0; i < num - 1; i++){
        for(int j = i + 1; j < num; j++){
            if(priceRatio[i] < priceRatio[j]){
                temp = priceRatio[i];
                priceRatio[i] = priceRatio[j];
                priceRatio[j] = temp;
                int x = index[i];
                index[i] = index[j];
                index[j] = x;
            }
        }
    }
    //排序好的重量和價值分別存到陣列
    int[] w1 = new int[num];
    int[] v1 = new int[num];
    for(int i = 0;i < num; i++){
        w1[i] = weights[index[i]];
        v1[i] = values[index[i]];
    }
    int[] x = new int[num];
    int maxValue = 0;
    for(int i = 0;i<num;i++){
        if(w1[i] < capacity){
            //還可以裝得下
            x[i] = 1;//表示該物品被裝了
            maxValue += v1[i];
            System.out.println("裝入物品:" + w1[i]);
            capacity = capacity - w1[i];
        }
    }
    System.out.println("放下物品數量:" + Arrays.toString(x));
    System.out.println("最大價值:" + maxValue);
}

測試

public static void main(String[] args) {
    int maxWeight = 150;
    int[] weights = {10, 20, 30, 40, 50};
    int[] values = {30, 50, 60, 60, 60};
    GreedyBackPack backPack = new GreedyBackPack();
    backPack.packageGreedy(maxWeight, weights, values);
}

分治

分治法的設計思想是:

  1. 分–將問題分解為規模更小的子問題
  2. 治–將這些規模更小的子問題逐個擊破
  3. 合–將已解決的子問題合併,最終得出問題的解

迴圈賽日程表

n個隊伍,n-1天完成比賽
一個先自頂向下,再自底向上的過程

public void scheduleTable(int[][] table, int n) {
    if(n == 1) {
        table[0][0] = 1;
    }else {
        //填充左上區域矩陣
        int m = n / 2;
        scheduleTable(table, m);
        //填充左下區域矩陣
        for (int i = 0; i < m; i++) {
            for (int j = m; j < n; j++) {
                table[i][j] = table[i][j - m] + m;
            }
        }
        //填充右上區域矩陣
        for (int i = m; i < n; i++) {
            for (int j = 0; j < m; j++) {
                table[i][j] = table[i - m][j] + m;
            }
        }
        //填充右下區域矩陣
        for (int i = m; i < n; i++) {
            for (int j = m; j < n; j++) {
                table[i][j] = table[i - m][j - m];
            }
        }
    }
}

輸出

[1, 2, 3, 4, 5, 6, 7, 8]
[2, 1, 4, 3, 6, 5, 8, 7]
[3, 4, 1, 2, 7, 8, 5, 6]
[4, 3, 2, 1, 8, 7, 6, 5]
[5, 6, 7, 8, 1, 2, 3, 4]
[6, 5, 8, 7, 2, 1, 4, 3]
[7, 8, 5, 6, 3, 4, 1, 2]
[8, 7, 6, 5, 4, 3, 2, 1]

棋盤問題

在一個2^k×2^k(k≥0)個方格組成的棋盤中,恰有一個方格與其他方格不同,稱該方格為特殊方格

private int[][] board; // 棋盤
private int sRow; // 特殊點的行下標
private int sCol; // 特殊點的列下標
private int size;
private int type = 0;

public ChessBoard(int[][] borad, int sRow, int sCol) {
    this.board = borad;
    this.sRow = sRow;
    this.sCol = sCol;
    size = borad.length;
    System.out.println(size);
}

public void chessBoard() {
    chessBoard(sRow, sCol, 0, 0, size);
}

private void chessBoard(int sRow, int sCol, int leftRow, int leftCol, int size) {
    if (size == 1) {
        return;
    }
    int subSize = size / 2;
    type = type % 4 + 1;
    int n = type;
    // 特殊點在左上角
    if (sRow < leftRow + subSize && sCol < leftCol + subSize) {
        chessBoard(sRow, sCol, leftRow, leftCol, subSize);
    } else {
        board[leftRow + subSize - 1][leftCol + subSize - 1] = n;
        chessBoard(leftRow + subSize - 1, leftCol + subSize - 1, leftRow, leftCol, subSize);
    }
    // 特殊點在右上角
    if (sRow < leftRow + subSize && sCol >= leftCol + subSize) {
        chessBoard(sRow, sCol, leftRow, leftCol + subSize, subSize);
    } else {
        board[leftRow + subSize - 1][leftCol + subSize] = n;
        chessBoard(leftRow + subSize - 1, leftCol + subSize, leftRow, leftCol + subSize, subSize);
    }
    // 特殊點在左下角
    if (sRow >= leftRow + subSize && sCol < leftCol + subSize) {
        chessBoard(sRow, sCol, leftRow + subSize, leftCol, subSize);
    } else {
        board[leftRow + subSize][leftCol + subSize - 1] = n;
        chessBoard(leftRow + subSize, leftCol + subSize - 1, leftRow + subSize, leftCol, subSize);
    }
    // 特殊點在右下角
    if (sRow >= leftRow + subSize && sCol >= leftCol + subSize) {
        chessBoard(sRow, sCol, leftRow + subSize, leftCol + subSize, subSize);
    } else {
        board[leftRow + subSize][leftCol + subSize] = n;
        chessBoard(leftRow + subSize, leftCol + subSize, leftRow + subSize, leftCol + subSize, subSize);
    }
}

動態規劃

最長公共子序列

public int findLCS(String A, String B) {
    int n = A.length();
    int m = B.length();
    char[] a = A.toCharArray();
    char[] b = B.toCharArray();
    int[][] dp = new int[n][m];
    //第一列判斷
    for (int i = 0; i < n; i++) {
        if(a[i] == b[0]) {
            dp[i][0] = 1;
            for (int j = i + 1; j < n; j++) {
                dp[j][0] = 1;
            }
            break;
        }
    }
    //第一行判斷
    for (int i = 0; i < m; i++) {
        if(a[0] == b[i]) {
            dp[0][i] = 1;
            for (int j = i + 1; j < m; j++) {
                dp[0][j] = 1;
            }
            break;
        }
    }
    //遍歷內部
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < m; j++) {
            if(a[i] == b[j]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[n -1][m - 1];
}

回溯

八皇后問題

public class Queen {
    public static int num = 0; // 累計方案
    public static final int MAX_QUEEN = 8;
    public static int[] cols = new int[MAX_QUEEN]; // 表示8列棋子擺放的位置

    public void getCount(int n) {
        boolean[] rows = new boolean[MAX_QUEEN]; // 記錄列方格是否可以放
        for (int m = 0; m < n; m++) {
            rows[cols[m]] = true;
            int d = n - m;
            if (cols[m] - d >= 0) {
                rows[cols[m] - d] = true;
            }
            if (cols[m] + d <= MAX_QUEEN - 1) {
                rows[cols[m] + d] = true;
            }
        }

        for (int i = 0; i < MAX_QUEEN; i++) {
            if (rows[i]) {
                continue;
            }
            cols[n] = i;
            if (n < MAX_QUEEN - 1) {
                getCount(n + 1);
            } else {
                num++;
                printQueen();
            }
        }
    }

    private void printQueen() {
        System.out.println("第" + num + "種方案");
        for (int i = 0; i < MAX_QUEEN; i++) {
            for (int j = 0; j < MAX_QUEEN; j++) {
                if (i == cols[j]) {
                    System.out.print("0 ");
                } else {
                    System.out.print("+ ");
                }
            }
            System.out.println();
        }
    }
}

約瑟夫問題

public class Joseph {
    private static int COUNT = 20;
    private static int NUM = 5;
    
    public void killNode() {
        Node header = new Node(1);
        Node xNode = header;
        for (int i = 2; i <= COUNT; i++) {
            xNode = (xNode.next = new Node(i));
        }
        xNode.next = header;
        while(xNode != xNode.next) {
            for (int i = 1; i < NUM; i++) {
                xNode = xNode.next;
            }
            System.out.println("剔除節點:" + xNode.next.val);
            xNode.next = xNode.next.next;
        }
        System.out.println("剩下的節點為:" + xNode.val);
    }
    
    class Node{
        int val;
        Node next;
        public Node(int val) {
            this.val = val;
        }
    }
}