算法概念 經常使用排序算法

算法

算法(Algorithm): 是對特定問題求解步驟的一種描述,它是指令的有序序列,其中每一條指令表示一個或者多個操做算法

5個重要特徵

有窮性

執行有窮步驟後結束,有窮時間內完成數組

肯定性

每一條指令有確切含義,任何條件下只有惟一的執行路徑,相同的輸入相同的輸出markdown

可行性

一個算法是能行的, 描述的步驟基於已實現的基本運算 執行有限次 實現ide

輸入

有0個或者多個輸入,刻畫運算的對象的初始狀況函數

輸出

有一個個或者多個輸出, 這些輸出和輸入存在特定關係測試

算法設計的要求:

  • 正確性(語法正確、對於一切合法不管簡單或苛刻的輸入數據均可以獲得知足規格說明要求的結果)
  • 可讀性(容易理解、交流、調試和修改,沒有隱藏錯誤易)
  • 健壯性(對非法輸入不會產生莫名其妙的輸出結果)
  • 效率與低存儲量需求,效率指的是算法執行時間存儲量指的是算法執行過程當中須要的最大存儲空間.

算法效率的度量

過後統計法

經過設計好的測試程序和數據,利用計算機計時器對不一樣算法編制的程序的運行時間進行比較,從而肯定算法效率的高低.優化

缺陷:ui

  • 必須依據算法實現編制好測試程序
  • 所得時間的統計依賴計算軟硬件環境因素。

事前分析估算

  • 算法採用的策略和方案
  • 問題的規模, n = 1000, n = 10000;
  • 書寫的語言,語言級別越高,執行效率越低
  • 編譯程序所產生的代碼的質量
  • 機器執行指令的速度。

因而可知,拋開這些與計算機硬件,軟件有關的因素,一個程序的運行時間依賴於算法的好壞和問題的輸入規模.(所謂的問題輸入規模是指輸入量的多少(一般用整數量n 表示))spa

一個算法是有控制結構(順序,分支,循壞)和原操做(固定數據類型的操做),算法時間取決於二者的綜合效果。設計

算法時間度量:

對所研究問題(算法類型)基本操做的原操做以及對基本操做重複執行的次數度量。算法的基本操做的重複次數是問題規模的某個函數f(n). 算法時間量度記作

T(n) = O(f(n));

時間複雜度:

隨着問題規模的n 的增大,算法執行時間的增加率和f(n)的增加率相同。

T(n) = O(f(n));

頻度

某些控制語句的重複的執行的次數,(for, while, do while)。

問題的基本的原操做重複執行次數和算法的執行時間成正比。

常見的時間複雜度:

image.png 經常使用的時間複雜度所耗費的時間從小到大依次是:

O(1) < O(logn) < (n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!);

算法平均複雜度

對全部可能的輸入數據集合的指望值

最壞時間複雜度。

分析最壞狀況估算執行時間的上線

...
 for(i =2; i <=n; i++){
   for(j =2; j <=i-1; j++){
     ++x;
     a[i][j] = x;
   }
 }
 語句頻度:(n - 1)(n-2)/2
 時間平均複雜度T arg(n) = O(n^2);
 最壞時間複雜度T(n) = O(n^2);
複製代碼

算法空間複雜度

S(n) = O(f(n));

n 問題的規模 f(n)爲語句關於n所佔存儲空間的函數。 一個上機程序除了需喲啊存儲空間寄存自己所用的指令、常數、變量和輸入數據外,對數據進行操做的工做單元和存儲一些爲實現計算所須要的信息和輔助空間.

排序算法

經過特定的算法因式一組或多組數據 按照既定模式進行從新排序

評價標準

  • 穩定性:

兩個相同的元素同時出現於某個序列之中,則通過必定的排序算法以後,二者在排序先後的相對位置不發生變化.

注:穩定性是一個特別重要的評估標準。穩定的算法在排序的過程當中不會改變元素彼此的位置的相對次序,反之不穩定的排序算法常常會改變這個次序,這是咱們不肯意看到的

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

排序算法的分類

非線性時間比較類排序:比較,由時間複雜度不能突破(nlogn) 交換排序(冒泡、快速)、插入排序(直接插入、希爾排序)、選擇排序(簡單選擇排序,堆排序), 歸併排序(二路歸併,多路歸併)

線性時間非比較類排序:不比較,突破基於比較排序的時間下界,以線性時間運行 基數排序、桶排序、計數。

內部排序:指的是待排序記錄存放在計算機隨機存儲器進行排序

外部排序:待排序記錄數量的數量很大,一次不能夠存放所有記錄,需對外存訪問

image.png

快速排序(Quicksort)

經過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另外一部分的關鍵字小,則可分別對這兩部分記錄繼續進行遞歸排序,以達到整個序列有序。 這個關鍵字是從數列中挑出一個元素,也稱爲 「基準」(pivot).

實現步驟:

  • 選擇一個基準元素target(通常選擇第一個數)
  • 將比target小的元素移動到數組左邊,比target大的元素移動到數組右邊
  • 分別對target左側和右側的元素進行快速排序

複雜度

時間複雜度:平均O(nlogn),最壞O(n2),實際上大多數狀況下小於O(nlogn)

空間複雜度:O(logn)(遞歸調用消耗)

穩定性

不穩定

寫法1 左開右閉

記錄一個索引l從數組最左側開始,記錄一個索引r從數組右側開始

l<r的條件下,找到右側小於target的值array[r],並將其賦值到array[l]

l<r的條件下,找到左側大於target的值array[l],並將其賦值到array[r]

這樣讓l=r時,左側的值所有小於target,右側的值所有小於target

const quickSort = function(array, start , end) {
   if(end - start < 1) return;
   let l = start;
   let r = end;
   const target = array[l];
   while(l < r) {
     while(l < r && array[r] >=  target) {
       r--
     };
     array[l] = array[r];
     while(l < r && array[l] < target) {
      l++
     };
     array[r] = array[l];
   }
   array[l] = target
   quickSort(array, start, l - 1);
   quickSort(array, l+1, end);
 }
 const arr = [4, 1, 5, 7, 3, 9];
 quickSort(arr, 0, 5);
 
複製代碼

image.png

寫法2

單獨開闢兩個存儲空間leftright來存儲每次遞歸比target小和大的序列

每次遞歸直接返回left、target、right拼接後的數組

浪費大量存儲空間,寫法簡單

function quickSort(array) {
  if(array.length < 2) {
     return array;
  }
  const target = array[0];
  const left = [];
  const right = [];
  for(let i = 1; i < array.length; i++) {
      if(array[i] < target) {
        left.push(array[i])
      } else {
         right.push(array[i]);
      }
  }
  return quickSort(left).concat([target], quickSort(right));
}
const arr = [4, 1, 5, 7, 3, 9];
quickSort(arr, 0, 5);
複製代碼

image.png

插入排序

將左側序列當作一個有序序列,每次將一個數字插入該有序序列。

插入時,從有序序列最右側開始比較,若比較的數較大,後移一位。

複雜度

時間複雜度:O(n2)

空間複雜度:O(1)

穩定性

穩定

function insertSort(array) {
  for(let i = 1, len = array.length; i < len; i++) {
     let target = i;
     for(let j = i - 1; j >=0; j--) {
       if(array[target] < array[j]) {
         [array[target], array[j]] = [array[j], array[target]];
         target = j;
       }else break;
     }
  }
}
const arr = [4, 1, 5, 7, 3, 9];
insertSort(arr, 0, 5);

複製代碼

image.png

堆排序

建立一個大頂堆,大頂堆的堆頂必定是最大的元素。

交換第一個元素和最後一個元素,讓剩餘的元素繼續調整爲大頂堆。

從後往前以此和第一個元素交換並從新構建,排序完成。

複雜度

時間複雜度:O(nlogn)

空間複雜度:O(1)

穩定性

不穩定

function heapSort(array) {
   creatHeap(array);
   for(let i = array.length - 1; i > 0; i--) {
      [array[i], array[0]] = [array[0], array[i]];
      adjust(array, 0, i);
   }
   return array;
}
function creatHeap(array) {
  const len = array.length;
  const start = parseInt(len/2) - 1;
  for(let i = start; i >=0; i--) {
    adjust(array, i, len);
  }
}
// 將第target個元素進行下沉,孩子節點有比他大的就下沉
function adjust(array, target, len) {
   for(let i = 2 * target + 1; i< len; i = 2 * i + 1) {
   // 找到孩子節點中最大的
     if(i + 1 < len && array[i + 1] > array[i]) {
       i = i + 1;
     }
     // 下沉
     if(array[i] > array[target]){
        [array[i], array[target]] = [array[target], array[i]]
     }else {
     break;}
   }
}
const arr = [4, 1, 5, 7, 3, 9];
heapSort(arr);
複製代碼

image.png

歸併排序

利用歸併的思想實現的排序方法。

該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。(分治法將問題分紅一些小的問題而後遞歸求解,而治的階段則將分的階段獲得的各答案"修補"在一塊兒,即分而治之)。

  • 若將兩個有序表合併成一個有序表,稱爲二路歸併。

分割:

  • 將數組從中點進行分割,分爲左、右兩個數組
  • 遞歸分割左、右數組,直到數組長度小於2

歸併

須要合併,那麼左右兩數組已經有序了。

建立一個臨時存儲數組temp,比較兩數組第一個元素,將較小的元素加入臨時數組

若左右數組有一個爲空,那麼此時另外一個數組必定大於temp中的全部元素,直接將其全部元素加入temp

  • 時間複雜度:O(nlogn)

  • 空間複雜度:O(n)

  • 穩定

解法一

分割數組時直接將數組分割爲兩個數組,合併時直接合並數組。 缺點:空間複雜度略高,須要複製多個數組

function mergeSort(array) {
  if(array.length < 2) {
    return array;
  }
  const mid = array.length >> 1;
  const front = array.slice(0, mid);
  const end = array.slice(mid);
  return merge(mergeSort(front), mergeSort(end));
 }
 function merge(front, end) {
   const temp = [];
   while(front.length && end.length) {
     if(front[0] < end[0]) {
        temp.push(front.shift())
     } else {
       temp.push(end.shift())
     }
   }
   while(front.length) {
   temp.push(front.shift());
   }
   while(end.length) {
    temp.push(end.shift())
   }
   return temp;
 }

const arr = [4, 1, 5, 7, 3, 9];
mergeSort(arr);
複製代碼

image.png

解法2 記錄數組的索引,使用left、right兩個索引來限定當前分割的數組。

優勢:空間複雜度低,只需一個temp存儲空間,不須要拷貝數組

function mergeSort(array, left, right, temp) {
      if (left < right) {
        const mid = Math.floor((left + right) / 2);
        mergeSort(array, left, mid, temp)
        mergeSort(array, mid + 1, right, temp)
        merge(array, left, right, temp);
      }
      return array;
    }

    function merge(array, left, right, temp) {
      const mid = Math.floor((left + right) / 2);
      let leftIndex = left;
      let rightIndex = mid + 1;
      let tempIndex = 0;
      while (leftIndex <= mid && rightIndex <= right) {
        if (array[leftIndex] < array[rightIndex]) {
          temp[tempIndex++] = array[leftIndex++]
        } else {
          temp[tempIndex++] = array[rightIndex++]
        }
      }
      while (leftIndex <= mid) {
        temp[tempIndex++] = array[leftIndex++]
      }
      while (rightIndex <= right) {
        temp[tempIndex++] = array[rightIndex++]
      }
      tempIndex = 0;
      for (let i = left; i <= right; i++) {
        array[i] = temp[tempIndex++];
      }
    }
const arr = [4, 1, 5, 7, 3, 9];
mergeSort(arr, 0, 5, []);
複製代碼

image.png

冒泡

循環數組,比較當前元素和下一個元素,若是當前元素比下一個元素大,向上冒泡。

這樣一次循環以後最後一個數就是本數組最大的數。

下一次循環繼續上面的操做,不循環已經排序好的數。

優化:當一次循環沒有發生冒泡,說明已經排序完成,中止循環。

  • 時間複雜度:O(n2)

  • 空間複雜度:O(1)

  • 穩定

function bubbleSort(array) {
   for(let j = 0, len = array.length; j < len; j++) {
     let complete = true;
     for(let i = 0; i < len - 1 - j; i++) {
       if(array[i] > array[i+1]) {
          [array[i], array[i+1]] = [array[i+1], array[i]];
          complete = false;
       }
     }
     if(complete) {
     break;
     }
   }
   return array;
 }
const arr = [4, 1, 5, 7, 3, 9];
bubbleSort(arr);
複製代碼

image.png

選擇排序

每次循環選取一個最小的數字放到前面的有序序列中。

  • 時間複雜度:O(n2)

  • 空間複雜度:O(1)

  • 不穩定

function selectionSort(array) {
  for(let i = 0, len = array.length; i < len -1; i++) {
     let minIndex = i;
     for(let j = i+1; j < len; j++) {
       if(array[j] < array[minIndex]) {
       minIndex = j
       }
     }
     [array[minIndex], array[i]] = [array[i], array[minIndex]];
  }
}
const arr = [4, 1, 5, 7, 3, 9];
selectionSort(arr);
複製代碼

image.png

相關文章
相關標籤/搜索