前端的幾種基本算法(二分查找,選擇排序,插入排序,希爾排序,歸並排序,快速排序,堆排序)


現在前端對於算法的要求是越來越高了,以下簡單歸納下前端的幾種基本的排序算法與二分查找相關的內容

二分查找

二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結構,而且表中元素按關鍵字有序排列。

在有序的數組中查詢一個元素用二分查找法是非常高效的,在應用中可以簡單的分為三種情況,即:查找目標值,查找比目標值大的第一個元素,查找比目標值小的第一個元素。

 

 

 

 

 

查找目標值

function binarySearch(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] > target) {
      r = mid - 1
    } else if (arr[mid] < target) {
      l = mid + 1
    } else {
      return mid
    }
  }

  return -1
}

查找比目標值大的第一個元素

function binarySearchFirstGreate(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] > target) {
      r = mid - 1
    } else {
      l = mid + 1
    }
  }

  return l
}

查找比目標值小的第一個元素

function binarySearchFirstLess(arr, target) {
  let l = 0
  let r = arr.length - 1
  let mid = 0

  while(l <= r) {
    mid = (l + r) >> 1
    if (arr[mid] < target) {
      l = mid + 1
    } else {
      r = mid - 1
    }
  }

  return r
}

 

選擇排序

選擇排序的工作原理是:第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,然后再從剩余的未排序元素中尋找到最小(大)元素,然后放到已排序的序列的末尾。以此類推,直到全部待排序的數據元素的個數為零。

選擇排序是不穩定的排序方法。

 

function selectionSort(arr) {
  let l = arr.length
  for(let i = 0; i < l; i++) {
    for(let j = i + 1; j < l; j++) {
      if (arr[i] > arr[j]) {
        [arr[i], arr[j]] = [arr[j], arr[i]]
      }
    }
  }
}

 

插入排序

插入排序,一般也被稱為直接插入排序。對於少量元素的排序,它是一個有效的算法。

插入排序是一種最簡單的排序方法,它的基本思想是將一個記錄插入到已經排好序的有序表中,從而一個新的、記錄數增1的有序表。在其實現過程使用雙層循環,外層循環對除了第一個元素之外的所有元素,內層循環對當前元素前面有序表進行待插入位置查找,並進行移動

它與選擇排序的區別是:

  1. 選擇排序是在未排列的數據中選取最大(小)的值。
  2. 插入排序是在已排列的數據中尋找正確的位置,所以插入排序比選擇排序性能會好很多。

function insertSort(arr) {
  let l = arr.length
  for(let i = 1; i < l; i++) {
    for(let j = i; j > 0; j--) {
      if (arr[j - 1] > arr[j]) {
        [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]]
      }
    }
  }
}

 

希爾排序(增強版的插入排序)

希爾排序(Shell's Sort)是插入排序的一種又稱“縮小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。

希爾排序是把記錄按下標的一定增量分組,對每組使用直接插入排序算法排序;隨着增量逐漸減少,每組包含的關鍵詞越來越多,當增量減至 1 時,整個文件恰被分成一組,算法便終止。

 

function shellSort(arr) {
  let t = new Date()
  let len = arr.length
  let h = 1
  while(h < len / 3) h = 3 * h + 1 // 1, 4, 13, 40, 121, 364, 1093
  while(h >= 1) {
    // 將數組變為h有序
    for(let i = h; i < len; i++) {
      for(let j = i; j >= h; j -= h) {
        if (arr[j] < arr[j - h]) {
          [arr[j - h], arr[j]] = [arr[j], arr[j - h]]
        }
      }
    }
    h = Math.floor(h / 3)
  }
}

 

歸並排序

歸並排序(Merge Sort)是建立在歸並操作上的一種有效,穩定的排序算法,該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合並,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合並成一個有序表,稱為二路歸並。

 

首先歸並排序需要一個將兩個有序數組合並的方法:

function merge(a, l, m, r) {
  let i = l, j = m + 1, aux = []

  for (let k = l;k <= r; k++) {
    aux[k] = a[k]
  }

  for (let k = l; k <= r; k++) {
    if (i > m) {
      a[k] = aux[j++]
    } else if (j > r) {
      a[k] = aux[i++]
    } else if (aux[j] < aux[i]) {
      a[k] = aux[j++]
    } else {
      a[k] = aux[i++]
    }
  }

  return a
}

歸並排序的算法可以分為兩種方式:

  1. 自頂向下:采用遞歸的方式,不斷的將分割的子數組,直到將子數組的個數分割成1,然后再用merge合並成一個有序的大數組
  2. 自底向上:采用雙層循環的方式,先將數組內的元素與相鄰元素歸並,然后遞增到最后的一個大數組

自頂向下

function sort_down(a, l, r) {
  if (l >= r) return
  let m = (l + r) >> 1
  sort_down(a, l, m) // 左邊排序
  sort_down(a, m + 1, r) // 右邊排序
  if (a[m] > a[m + 1]) {
    merge(a, l, m, r) // 合並
  }
}

自底向上

function sort_up(a) {
  let n = a.length
  for (let i = 1; i < n; i += i) {
    for (let j = 0; j < n - i; j += i + i) {
      merge(a, j, i + j - 1, Math.min(j + i + i - 1, n - 1))
    }
  }
}

 

快速排序

快速排序(Quicksort)是對冒泡排序算法的一種改進。

快速排序是通過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的所有數據都比另外一部分的所有數據都要小,然后再按此方法對這兩部分數據分別進行快速的原地排序,整個排序過程可以遞歸進行,以此達到整個數據變成有序序列。

快速排序分為兩種方式:

  1. 二向切分快速排序:先進行左指針的值與base的比較,如果比base大,則從右指針遞減與base的比較,如果遇到比base小的則進行左指針與右指針互換,以此規則循環,直到比base小的都在左,大的都在右。然后進行遞歸直到整個數組有序。
  2. 三向切分快速排序:設立三個指針:左指針,中指針,右指針。三向切分只比較中指針指向的值與base的大小,如果中指針小於base,則左指針與中指針互換且都遞增1,如果比base大,則中指針與右指針互換,繼續與base比較,如果相等,則中指針加1,直到整個數組的左部分都比base小,右部分都比base大,然后遞歸直到整個數組有序。(左指針的索引是與base最近的,直到右指針的索引靠近base,則該循環結束。)

三向切分比二向切分的優化點在於:如果數組能有重復值的話,三向切分不需要重復比較,而二向切分是要重復比較的,對於大批量的用戶數據排序,該特性非常有用。

 

 

 三向切分圖

二向切分快速排序

function quickSort(arr, l, r) {
  if (l >= r) return

  let base = arr[l]
  let i = l
  let j = r

  while(l <= r) {
    while(l < r && arr[++l] < base) {}
    while(l < r && arr[--r] > base) {}
    
    if (l < r) {
      [arr[l], arr[r]] = [arr[r], arr[l]]
    } else {
      [arr[l - 1], arr[i]] = [base, arr[l - 1]]
      break
    }
  }

  quicksort(arr, i, l - 2)
  quicksort(arr, l, j)
}

三向切分快速排序

function sQuickSort(arr, l, r) {
  if (l >= r) return
  let lf = l
  let ri = r
  let v = arr[lf]
  let i = l + 1

  while(i <= ri) {
    if (v > arr[i]) {
      [arr[lf++], arr[i++]] = [arr[i], arr[lf]]
    } else if (v < arr[i]) {
      [arr[i], arr[ri--]] = [arr[ri], arr[i]]
    } else {
      i++
    }
  }
  squicksort(arr, l, lf - 1)
  squicksort(arr, ri + 1, r)
}

堆排序

堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。

 

 堆有兩個重要的基本操作,即在堆有序時對單個元素的下沉和上浮操作。

以大頂堆為例(大頂堆即堆頂元素為最大,小頂堆的堆頂元素為最小):

大頂堆的下沉:

function sink(arr, k, len) {
  while(2 * k + 1 < len) {
    let j = 2 * k + 1
    if (j < len - 1 && arr[j] < arr[j + 1]) j++
    
    if (arr[k] >= arr[j]) break
    
    [arr[k], arr[j]] = [arr[j], arr[k]]
    k = j
  }
}

大頂堆的上浮:

function swim(arr, len) {
  let p = 0 // 父級節點
  while(len > 0) {
    p = (len - 1) >> 1

    // (len & 1) 為0的情況下是有兄弟節點的,選出最大的與父級節點比較
    if ((len & 1) === 0 && arr[len] < arr[len - 1]) len--

    if (arr[len] <= arr[p]) break

    [arr[len], arr[p]] = [arr[p], arr[len]]
    len = p
  }
}

在有序堆中每次添加和刪除元素后執行的下沉和上浮操作,都會得到目前有序堆中的最大(小)元素,以此特性就可以進行對元素排序。

function heapSort(arr) {
  let len = arr.length
  let copy = []
  // 建立一個有序的堆
  for (let i = (len - 1) >> 1; i >= 0; i--) {
    sink(arr, i, len)
  }
  // 每次將堆頂元素與堆尾元素進行替換,再進行堆頂元素的下沉且堆長度減一,以此可以得到一個有序的數據
  while(len--) {
    [arr[0], arr[len]] = [arr[len], arr[0]]
    sink(arr, 0, len)
  }
}

大頂堆的的排序得到的是一個降序排序,小頂堆的則得到的是升序數據。

 

簡單描述各排序算法的性能特點:

算法 是否穩定 是否原地排序 時間復雜度 空間復雜度 備注
選擇排序 N2 1 取決於輸入元素的排列情況
插入排序 介於N和N2之間 1
希爾排序 NlogN?
N6/5?
1
快速排序 NlogN lgN 運行效率由概率提供保證
三向快速排序 介於N和NlogN之間 lgN 運行效率由概率保證,同時也
取決於輸入元素的分布情況
歸並排序

NlogN

N  
堆排序 NlogN 1  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM