1. 程式人生 > 其它 >演算法複雜度&&陣列排序

演算法複雜度&&陣列排序

計算演算法的複雜度

目錄

  • 時間複雜度
  • 空間複雜度

時間複雜度

  • 什麼叫做時間複雜度呢??

  • 我們來看一個簡單的程式

 int n = 10 ;
System.out.println("輸出" + n);

這段虛擬碼運行了多少次呢! 1次 ,時間時間複雜度為O(1):常數複雜度/常數階。

        for (int i = 0 ;i <n; i++){
            System.out.println("第幾次"+i);
        }

這個for迴圈了多少次呢! n次,時間複雜度為O(n):線性時間複雜度。

再看下一個

        for (int i = 0 ;i <n; i++){
            for (int j = 0 ;j < n; j ++) {
                System.out.println("第幾次" + i+""+j);
            }
        }

這個迴圈了多少次呢!n*n次,時間複雜度為O(n^2):平方複雜度。

  • 百度百科對時間複雜度的定義是:在電腦科學中,演算法的時間複雜度是一個函式,它定性描述了該演算法的執行時間。

我們再把常見的複雜度列舉出來看看。

        int j = 0;
        for (int i = 0 ; i < n ; i++) {
            i = i * 2;
            j++;
            System.out.println("第幾次"+j);
        }

這個迴圈了多少次呢!假設我們把次數設為x,那2^x<n,解得次數x=log(n),時間複雜度為O(log(n)):對數。

        for (int i = 0 ; i < Math.pow(2,n); i++) {
 
            System.out.println("第幾次"+i);
        }

這個迴圈了多少次呢!2n次,時間複雜度為O(2n):指數複雜度。

  • 舉個例子:
    • 快速排序
function quickSort(arr) {
            /* 終止條件 */
            if (arr.length <= 1) {
                return arr;
            }
            /* 找到陣列的中間下標 */
            var mid_index = arr.length / 2;
            /* 找到中間值 */
            var mid = arr[mid_index];
            var left_arr = [];
            var right_arr = [];
            for (var i = 0; i < arr.length; i++) {
                // 中點沒必要和中點進行比較
                if (i === mid_index) continue;
                if (arr[i] > mid) {
                    right_arr.push(arr[i]);
                } else {
                    left_arr.push(arr[i]);
                }
            }
            return quickSort(left_arr).concat([mid], quickSort(right_arr));

        }
        var arr = [4, 2, 5, 3, 1, 7, 6];
        var res = quickSort(arr)

        console.log(res);

時間複雜度

​ 快速排序涉及到遞迴呼叫,所以該演算法的時間複雜度還需要從遞迴演算法的複雜度開始說起;

遞迴演算法的時間複雜度公式:T[n] = aT[n/b] + f(n) ;對於遞迴演算法的時間複雜度這裡就不展開來說了;

最優情況下時間複雜度

快速排序最優的情況就是每一次取到的元素都剛好平分整個陣列(很顯然我上面的不是);

此時的時間複雜度公式則為:T[n] = 2T[n/2] + f(n);T[n/2]為平分後的子陣列的時間複雜度,f[n] 為平分這個陣列時所花的時間;

​ 下面來推算下,在最優的情況下快速排序時間複雜度的計算(用迭代法):

T[n] = 2T[n/2] + n ----------------第一次遞迴

令:n = n/2 = 2 { 2 T[n/4] + (n/2) } + n ----------------第二次遞迴

= 2^2 T[ n/ (2^2) ] + 2n

令:n = n/(2^2) = 2^2 { 2 T[n/ (2^3) ] + n/(2^2)} + 2n ----------------第三次遞迴

= 2^3 T[ n/ (2^3) ] + 3n

......................................................................................

令:n = n/( 2^(m-1) ) = 2^m T[1] + mn ----------------第m次遞迴(m次後結束)

當最後平分的不能再平分時,也就是說把公式一直往下跌倒,到最後得到T[1]時,說明這個公式已經迭代完了(T[1]是常量了)。

得到:T[n/ (2^m) ] = T[1] ===>> n = 2^m ====>> m = logn;

T[n] = 2^m T[1] + mn ;其中m = logn;

T[n] = 2^(logn) T[1] + nlogn = n T[1] + nlogn = n + nlogn ;其中n為元素個數

又因為當n >= 2時:nlogn >= n (也就是logn > 1),所以取後面的 nlogn;

綜上所述:快速排序最優的情況下時間複雜度為:O( nlogn )

最差情況下時間複雜度

最差的情況就是每一次取到的元素就是陣列中最小/最大的,這種情況其實就是氣泡排序了(每一次都排好一個元素的順序)

這種情況時間複雜度就好計算了,就是氣泡排序的時間複雜度:T[n] = n * (n-1) = n^2 + n;

綜上所述:快速排序最差的情況下時間複雜度為:O( n^2 )

空間複雜度

空間複雜度(Space Complexity)是對一個演算法在執行過程中臨時佔用儲存空間大小的量度,記做S(n)=O(f(n))。

簡單的講就是包括下面幾部分。

1.儲存演算法本身所佔用的儲存空間。

2.演算法的輸入輸出資料所佔用的儲存空間。

3.演算法在運算過程中臨時佔用的儲存空間這三個方面。

int a[] = new int[n];

這個例子的空間複雜度是多少呢?這個陣列開闢的空間是多少呢? O(n)。
總結

時間複雜度和空間複雜度本就是一個相互博弈的過程,一個多另一個就少,根據適當的問題,找到適當的解,這才是好辦法。

陣列的排序

  • 排序,就是把一個亂序的陣列,通過我們的處理,讓他變成一個有序的陣列
  • 今天我們講解兩種方式來排序一個數組 氣泡排序選擇排序

氣泡排序

  • 先遍歷陣列,讓挨著的兩個進行比較,如果前一個比後一個大,那麼就把兩個換個位置

  • 陣列遍歷一遍以後,那麼最後一個數字就是最大的那個了

  • 然後進行第二遍的遍歷,還是按照之前的規則,第二大的數字就會跑到倒數第二的位置

  • 以此類推,最後就會按照順序把陣列排好了

    1. 我們先來準備一個亂序的陣列

      var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
      
      • 接下來我們就會用程式碼讓陣列排序
    2. 先不著急迴圈,先來看數組裡面內容換個位置

      // 假定我現在要讓陣列中的第 0 項和第 1 項換個位置
      // 需要藉助第三個變數
      var tmp = arr[0]
      arr[0] = arr[1]
      arr[1] = tmp
      
    3. 第一次遍歷陣列,把最大的放到最後面去

      for (var i = 0; i < arr.length; i++) {
        // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置
        if (arr[i] > arr[i + 1]) {
          var tmp = arr[i]
          arr[i] = arr[i + 1]
          arr[i + 1] = tmp
        }
      }
      
      // 遍歷完畢以後,陣列就會變成 [3, 1, 5, 6, 4, 7, 2, 8, 9]
      
      • 第一次結束以後,陣列中的最後一個,就會是最大的那個數字
      • 然後我們把上面的這段程式碼執行多次。陣列有多少項就執行多少次
    4. 按照陣列的長度來遍歷多少次

      for (var j = 0; j < arr.length; j++) {
        for (var i = 0; i < arr.length; i++) {
          // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置
          if (arr[i] > arr[i + 1]) {
            var tmp = arr[i]
            arr[i] = arr[i + 1]
            arr[i + 1] = tmp
          }
        }
      }
      
      // 結束以後,陣列就排序好了
      
    5. 給一些優化

      • 想象一個問題,假設陣列長度是 9,第八次排完以後

      • 後面八個數字已經按照順序排列好了,剩下的那個最小的一定是在最前面

      • 那麼第九次就已經沒有意義了,因為最小的已經在最前面了,不會再有任何換位置出現了

      • 那麼我們第九次遍歷就不需要了,所以我們可以減少一次

        for (var j = 0; j < arr.length - 1; j++) {
          for (var i = 0; i < arr.length; i++) {
            // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置
            if (arr[i] > arr[i + 1]) {
              var tmp = arr[i]
              arr[i] = arr[i + 1]
              arr[i + 1] = tmp
            }
          }
        }
        
      • 第二個問題,第一次的時候,已經把最大的數字放在最後面了

      • 那麼第二次的時候,其實倒數第二個和最後一個就不用比了

      • 因為我們就是要把倒數第二大的放在倒數第二的位置,即使比較了,也不會換位置

      • 第三次就要倒數第三個數字就不用再和後兩個比較了

      • 以此類推,那麼其實每次遍歷的時候,就遍歷當前次數 - 1 次

        for (var j = 0; j < arr.length - 1; j++) {
          for (var i = 0; i < arr.length - 1 - j; i++) {
            // 判斷,如果陣列中的當前一個比後一個大,那麼兩個交換一下位置
            if (arr[i] > arr[i + 1]) {
              var tmp = arr[i]
              arr[i] = arr[i + 1]
              arr[i + 1] = tmp
            }
          }
        }
        
    6. 至此,一個氣泡排序就完成了

選擇排序

  • 先假定陣列中的第 0 個就是最小的數字的索引

  • 然後遍歷陣列,只要有一個數字比我小,那麼就替換之前記錄的索引

  • 知道陣列遍歷結束後,就能找到最小的那個索引,然後讓最小的索引換到第 0 個的位置

  • 再來第二趟遍歷,假定第 1 個是最小的數字的索引

  • 在遍歷一次陣列,找到比我小的那個數字的索引

  • 遍歷結束後換個位置

  • 依次類推,也可以把陣列排序好

    1. 準備一個數組

      var arr = [3, 1, 5, 6, 4, 9, 7, 2, 8]
      
    2. 假定陣列中的第 0 個是最小數字的索引

      var minIndex = 0
      
    3. 遍歷陣列,判斷,只要數字比我小,那麼就替換掉原先記錄的索引

      var minIndex = 0
      for (var i = 0; i < arr.length; i++) {
        if (arr[i] < arr[minIndex]) {
          minIndex = i
        }
      }
      
      // 遍歷結束後找到最小的索引
      // 讓第 minIndex 個和第 0 個交換
      var tmp = arr[minIndex]
      arr[minIndex] = arr[0]
      arr[0] = tmp
      
    4. 按照陣列的長度重複執行上面的程式碼

      for (var j = 0; j < arr.length; j++) {
        // 因為第一遍的時候假定第 0 個,第二遍的時候假定第 1 個
        // 所以我們要假定第 j 個就行
        var minIndex = j
        
        // 因為之前已經把最小的放在最前面了,後面的迴圈就不需要判斷前面的了
        // 直接從 j + 1 開始
        for (var i = j + 1; i < arr.length; i++) {
          if (arr[i] < arr[minIndex]) {
            minIndex = i
          }
        }
      
        // 遍歷結束後找到最小的索引
        // 第一堂的時候是和第 0 個交換,第二趟的時候是和第 1 個交換
        // 我們直接和第 j 個交換就行
        var tmp = arr[minIndex]
        arr[minIndex] = arr[j]
        arr[j] = tmp
      }
      
    5. 一些優化

      • 和之前一樣,倒數第二次排序完畢以後,就已經排好了,最後一次沒有必要了

        for (var j = 0; j < arr.length - 1; j++) {
          var minIndex = j
          
          for (var i = j + 1; i < arr.length; i++) {
            if (arr[i] < arr[minIndex]) {
              minIndex = i
            }
          }
        
          var tmp = arr[minIndex]
          arr[minIndex] = arr[j]
          arr[j] = tmp
        }
        
      • 在交換變數之前,可以判斷一下,如果我們遍歷後得到的索引和當前的索引一直

      • 那麼就證明當前這個就是目前最小的,那麼就沒有必要交換

      • 做一我們要判斷,最小作引和當前作引不一樣的時候,才交換

        for (var j = 0; j < arr.length - 1; j++) {
          var minIndex = j
          
          for (var i = j + 1; i < arr.length; i++) {
            if (arr[i] < arr[minIndex]) {
              minIndex = i
            }
          }
        
          if (minIndex !== j) {
            var tmp = arr[minIndex]
            arr[minIndex] = arr[j]
            arr[j] = tmp   
          }
        }
        
    6. 至此,選擇排序完成

快速排序

  • 口訣:找中點,分左右
  • 找中點:找到下標的中位數,分為左右陣列。再把左右作為新的陣列進行找中點分左右
  • 遞迴實現
function quickSort(arr) {
            /* 終止條件 */
            if (arr.length <= 1) {
                return arr;
            }
            /* 找到陣列的中間下標 */
            var mid_index = arr.length / 2;
            /* 找到中間值 */
            var mid = arr[mid_index];
            var left_arr = [];
            var right_arr = [];
            for (var i = 0; i < arr.length; i++) {
                // 中點沒必要和中點進行比較
                if (i === mid_index) continue;
                if (arr[i] > mid) {
                    right_arr.push(arr[i]);
                } else {
                    left_arr.push(arr[i]);
                }
            }
            return quickSort(left_arr).concat([mid], quickSort(right_arr));

        }
        var arr = [4, 2, 5, 3, 1, 7, 6];
        var res = quickSort(arr)

        console.log(res);
越努力,越幸運