js數據結構和算法(9)-排序算法

9.排序算法算法

對計算機中存儲的數據執行的兩種最多見操做是排序和檢索,對計算機中存儲的數據執行的兩種最多見操做是排序和檢索。shell

9.1 輔助函數數組

在研究算法前,咱們先建立一個輔助函數,該函數的做用就是方便對咱們的算法進行測試,避免咱們總是作一些重複性的工做。less

class cArray {
  constructor(numElements) {
    this.dataStore = new Array(numElements)
    this.init()
  }
  // 初始化數組
  init() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = i
    }
  }
  // 把數組設置成隨機數組
  setData() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = Math.floor(Math.random() * (this.dataStore.length + 1))
    }
  }
  // 清空數組
  clear() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = 0
    }
  }
  // 在數組頂部插入元素
  insert(ele) {
    this.dataStore.push(ele)
  }
  // 將數組轉成字符串
  toString() {
    let result = ''
    for (let i = 0; i < this.dataStore.length; i++) {
      result += this.dataStore[i] + ' '
      if (i > 0 && i % 10 == 9) {
        result += '\n'
      }
    }
    return result
  }
  // 交換數組中的元素
  swap(index1, index2) {
    let temp = this.dataStore[index1]
    this.dataStore[index1] = this.dataStore[index2]
    this.dataStore[index2] = temp
  }
}
複製代碼

測試:dom

// 生成工具函數對象
let cArr = new cArray(100)
// 設置隨機數據
cArr.setData()

console.log(cArr.toString())

// 打印結果
23 53 33 19 46 13 32 91 99 32 
50 57 88 4 65 46 61 77 81 3 
55 74 62 31 56 79 17 14 40 11 
65 92 93 71 80 92 37 22 92 83 
52 7 47 3 6 65 48 69 37 23 
64 43 14 76 92 4 4 20 49 96 
3 35 53 24 45 52 50 3 94 35 
43 50 68 37 14 9 75 93 97 57 
60 19 30 23 26 24 12 53 78 88 
65 28 25 68 95 8 0 35 5 10

// 清空數組
cArr.clear()
console.log(cArr.toString())
// 打印結果
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 

//  交換數組的元素
let dArr = new cArray(3)

console.log(dArr.toString())

// 打印結果
0 1 2 

dArr.swap(0, 1)

console.log(dArr.toString())

// 打印結果
1 0 2 
複製代碼

咱們的輔助函數可以生成一個隨機數組,函數

9.2 冒泡排序工具

冒泡排序算法是最慢的排序算法之一,但也是最容易實現的一種排序算法。之因此叫冒泡排序是由於使用這種排序算法排序時,數據值會像氣泡同樣從數組的一端漂浮到另外一端。假設正在將一組數字按照升序排列,較大的值會浮動到數組的右側,而較小的值則會浮動到數組的左側。之因此會產生這種現象是由於算法會屢次在數組中移動,比較相鄰的數據,當左側值大於右側值時將它們進行互換 。性能

下圖演示瞭如何對一個大的數字數據集合進行冒泡排序。在圖中,咱們分析了插入數組中的兩個特定值: 2 和 72。這兩個數字都被圈了起來。你能夠看到 72 是如何從數組的開頭移動到中間的,還有 2 是如何從數組的後半部分移動到開頭的。測試

程序:大數據

bubbleSort() {
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let i = 0; i < len - 1; i++) {
      for (let j = len - 1; j > i; j--) {
        if (dataStore[j] < dataStore[j - 1]) {
          this.swap(j, j - 1)
        }
      }
    }
  }
複製代碼

測試:

// 生成工具函數對象
let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()

console.log(cArr.toString())
//  5 3 9 4 2 6 10 5 3 8 

cArr.bubbleSort()

console.log(cArr.toString())
// 2 3 3 4 5 5 6 8 9 10 
複製代碼

測試結果正確,咱們的程序應該沒有問題。

9.3 選擇排序

咱們接下來要看的是選擇排序算法。選擇排序從數組的開頭開始,將第一個元素和其餘元素進行比較。檢查完全部元素後,最小的元素會被放到數組的第一個位置,而後算法會從第二個位置繼續。這個過程一直進行,當進行到數組的倒數第二個位置時,全部的數據便完成了排序 。

程序實現:

// 選擇排序
  selectionSort() {
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let i = 0; i < len - 1; i++) {
      for (let j = i + 1; j < len; j++) {
        if (dataStore[i] > dataStore[j]) {
          this.swap(i, j)
        }
      }
    }
  }
複製代碼

測試

// 生成工具函數對象
let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()

console.log(cArr.toString())
// 3 2 0 10 7 4 4 7 0 6

cArr.selectionSort()

console.log(cArr.toString())
// 0 0 2 3 4 4 6 7 7 10 
複製代碼

測試結果正確

9.4 插入排序

插入排序相似於人類按數字或字母順序對數據進行排序,插入排序有兩個循環。外循環將數組元素挨個移動,而內循環則對外循環中選中的元素及它前面的全部元素進行比較。若是外循環中選中的元素比內循環中選中的元素小,那麼數組元素會向右移動,而後在適當的位置插入外循環選中的元素。

以上的話,特別繞,仍是直接看程序清晰

代碼實現:

// 插入排序
  insertionSort() {
    let temp, inser
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let outer = 1; outer < len; outer++) {
      temp = dataStore[outer]
      let inner = outer
      while (inner > 0 && dataStore[inner - 1] > temp) {
        this.swap(inner, inner - 1)
        inner--
      }
      dataStore[inner] = temp
    }
  }
複製代碼

測試

// 生成工具函數對象
let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()

console.log(cArr.toString())
// 4 9 8 8 3 8 5 8 5 0 

cArr.insertionSort()

console.log(cArr.toString())
// 0 3 4 5 5 8 8 8 8 9 
複製代碼

測試結果正確,說明咱們的程序沒有問題。

9.4 算法時間比較

測試:

// 生成工具函數對象
let cArr = new cArray(1000)
// 設置隨機數據
cArr.setData()
// 保存生成的隨機數組,爲了比較各個算法的時間複雜度,必須保證它們處理的隨機數組是同樣的
let dataStore = cArr.dataStore.concat()
// 冒泡排序
console.time('bubbleSort')
cArr.insertionSort()
console.timeEnd('bubbleSort')

// 選擇排序
cArr.dataStore = dataStore.concat()
console.time('selectionSort')
cArr.insertionSort()
console.timeEnd('selectionSort')

// 插入排序
cArr.dataStore = dataStore.concat()
console.time('insertion')
cArr.insertionSort()
console.timeEnd('insertion')
複製代碼

測試結果:

bubbleSort: 5.827880859375ms
selectionSort: 3.77392578125ms
insertion: 0.712890625ms
複製代碼

處理一樣的1000條數據,咱們能夠看到冒泡排序是最慢的,其次是選擇排序,插入排序是最快的。

不過這三種排序方法,在大數據時,性能仍是較慢。如下是更加高級的算法。這算法在效率上要更高。

9.5 希爾排序

這個算法在插入排序的基礎上作了很大的改善。希爾排序的核心理念與插入排序不一樣,它會首先比較距離較遠的元素,而非相鄰的元素。和簡單地比較相鄰元素相比,使用這種方案可使離正確位置很遠的元素更快地回到合適的位置。當開始用這個算法遍歷數據集時,全部元素之間的距離會不斷減少,直處處理到數據集的末尾,這時算法比較的就是相鄰元素了 。

希爾排序的工做原理是,經過定義一個間隔序列來表示在排序過程當中進行比較的元素之間有多遠的間隔。咱們能夠動態定義間隔序列,不過對於大部分的實際應用場景,算法要用到的間隔序列能夠提早定義好。有一些公開定義的間隔序列能夠給咱們使用。這個間隔序列是: 701, 301, 132, 57, 23, 10, 4, 1

下圖演示了希爾排序的工做原理

希爾排序代碼

// 希爾排序
  shellSort () {
    // 間隔序列
    let gaps = [5, 3, 1]
    for (let g = 0; g < gaps.length; g++) {
      let tempG = gaps[g]
      for (let i = tempG; i < this.dataStore.length; i++) {
        let temp = this.dataStore[i]
        let j = i
        while (j >= tempG && this.dataStore[j - tempG] > temp) {
          this.dataStore[j] = this.dataStore[j - tempG]
          j -= tempG
        }
        this.dataStore[j] = temp
      }
    }
  }
複製代碼

測試

// 生成工具函數對象
let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()
console.log(cArr.toString()) 
// 4 7 8 10 1 1 0 3 10 4 
cArr.shellSort()
console.log(cArr.toString())
// 0 1 1 3 4 4 7 8 10 10 
複製代碼

測試結果正確,說明咱們的程序沒有問題。

9.5.1 計算動態間隔序列

希爾排序的間隔序列是能夠動態計算的,計算公式以下。

let N = this.dataStore.length;
let h = 1;
while (h < N/3) {
h = 3 * h + 1;
}
複製代碼

間隔值肯定好後,這個函數就能夠像以前定義的 shellsort() 函數同樣運行了,惟一的區別是,回到外循環以前的最後一條語句會計算一個新的間隔值。

h = (h-1)/3
複製代碼

對咱們的shell排序進行從新優化

// 希爾排序
  shellSort () {
    // 間隔序列
    let N = this.dataStore.length
    let h = 1
    while (h < N / 3) {
      h = 3 * h + 1
    }
    while (h >= 1) {
      for (let i = h; i < this.dataStore.length; i++) {
        let temp = this.dataStore[i]
        let j = i
        while (j >= h && this.dataStore[j - h] > temp) {
          this.dataStore[j] = this.dataStore[j - h]
          this.swap(j, j - h)
          j -= h
        }
        this.dataStore[j] = temp
      }
      h = (h - 1) / 3
    }
  }
複製代碼

測試是正確的,我就不貼測試結果了。關於希爾排序的知識就這些。

9.6 歸併排序

歸併排序的命名來自它的實現原理:把一系列排好序的子序列合併成一個大的完整有序序列。從理論上講,這個算法很容易實現。咱們須要兩個排好序的子數組,而後經過比較數據大小,先從最小的數據開始插入,最後合併獲得第三個數組。

歸併排序的核心思想是分治,分治是經過遞歸地將問題分解成相同或者類型相關的兩個或者多個子問題,直到問題簡單到足以解決,而後將子問題的解決方案結合起來,解決原始方案的一種思想。

歸併排序經過將複雜的數組分解成足夠小的數組(只包含一個元素),而後經過合併兩個有序數組(單元素數組可認爲是有序數組)來達到綜合子問題解決方案的目的。因此歸併排序的核心在於如何整合兩個有序數組,拆分數組只是一個輔助過程。

做者寫的這個歸併排序算法很是複雜。我跟着捋了幾遍,才略懂,有更好的講解的話請@我。

歸併排序代碼:

mergeSort() {
    let arr = this.dataStore
    if (arr.length < 2) {
      return
    }
    let step = 1
    let left, right
    while (step < arr.length) {
      left = 0;
      right = step
      while (right + step <= arr.length) {
        this.mergeArrays(arr, left, left + step, right, right + step)
        left = right + step
        right = left + step
      }
      if (right < arr.length) {
        this.mergeArrays(arr, left, left + step, right, arr.length)
      }
      step *= 2;

    }
  }
  mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {
    let rightArr = new Array(stopRight - startRight + 1)
    let leftArr = new Array(stopLeft - startLeft + 1)
    let k = startRight
    for (let i = 0; i < (rightArr.length - 1); i++) {
      rightArr[i] = arr[k]
      k++
    }
    k = startLeft
    for (let i = 0; i < (leftArr.length - 1); i++) {
      leftArr[i] = arr[k]
      k++
    }
    rightArr[rightArr.length - 1] = Infinity
    leftArr[leftArr.length - 1] = Infinity
    let m = 0;
    let n = 0;
    for (let k = startLeft; k < stopRight; ++k) {
      if (leftArr[m] < rightArr[n]) {
        arr[k] = leftArr[m]
        m++
      } else {
        arr[k] = rightArr[n]
        n++
      }
    }
  }
複製代碼

測試:

let cArr = new cArray(10)
cArr.setData()
console.log(cArr.toString())
console.time('mergeSort')
// 5 7 5 9 8 7 8 5 2 5 
cArr.mergeSort()
console.timeEnd('mergeSort')
console.log(cArr.toString())
// 2 5 5 5 5 7 7 8 8 9 
複製代碼

9.7 快速排序

快速排序是處理大數據集最快的排序算法之一。它是一種分而治之的算法,經過遞歸的方式將數據依次分解爲包含較小元素和較大元素的不一樣子序列。該算法不斷重複這個步驟直到全部數據都是有序的。

這個算法首先要在列表中選擇一個元素做爲基準值(pivot)。數據排序圍繞基準值進行,將列表中小於基準值的元素移到數組的底部,將大於基準值的元素移到數組的頂部。

程序

// 快速排序
  qSort() {
    this.dataStore = this.qSortArr(this.dataStore)
  }
  qSortArr(list) {
    if (list.length == 0) {
      return []
    }
    let lesser = []
    let greater = []
    let pivot = list[0]
    for (let i = 1; i < list.length; i++) {
      if (list[i] < pivot) {
        lesser.push(list[i])
      } else {
        greater.push(list[i])
      }
    }
    return this.qSortArr(lesser).concat(pivot, this.qSortArr(greater))
  }
複製代碼

測試

let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()
console.log(cArr.toString())
// 8 7 2 9 10 2 3 5 9 4 
console.time('qSort')
cArr.qSort()
console.timeEnd('qSort')
console.log(cArr.toString())
// 2 2 3 4 5 7 8 9 9 10 
複製代碼

自此,關於排序的算法就講解忘了,讀這一章用了不少時間,排序確實比較難,尤爲是那個歸併排序。好難理解。本身嘗試實現,結果沒實現出來,若是有好的實現方法,請必定告訴我。

本章完整的代碼以下:

class cArray {
  constructor(numElements) {
    this.dataStore = new Array(numElements)
    this.init()
  }
  // 初始化數組
  init() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = i
    }
  }
  // 把數組設置成隨機數組
  setData() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = Math.floor(
        Math.random() * (this.dataStore.length + 1)
      )
    }
  }
  // 清空數組
  clear() {
    for (let i = 0; i < this.dataStore.length; i++) {
      this.dataStore[i] = 0
    }
  }
  // 在數組頂部插入元素
  insert(ele) {
    this.dataStore.push(ele)
  }
  // 將數組轉成字符串
  toString() {
    let result = ''
    for (let i = 0; i < this.dataStore.length; i++) {
      result += this.dataStore[i] + ' '
      if (i > 0 && i % 10 == 9) {
        result += '\n'
      }
    }
    return result
  }
  // 交換數組中的元素
  swap(index1, index2) {
    let temp = this.dataStore[index1]
    this.dataStore[index1] = this.dataStore[index2]
    this.dataStore[index2] = temp
  }
  // 冒泡排序
  bubbleSort() {
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let i = 0; i < len - 1; i++) {
      for (let j = len - 1; j > i; j--) {
        if (dataStore[j] < dataStore[j - 1]) {
          this.swap(j, j - 1)
        }
      }
    }
  }
  // 選擇排序
  selectionSort() {
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let i = 0; i < len - 1; i++) {
      for (let j = i + 1; j < len; j++) {
        if (dataStore[i] > dataStore[j]) {
          this.swap(i, j)
        }
      }
    }
  }
  // 插入排序
  insertionSort() {
    let temp, inser
    let dataStore = this.dataStore
    let len = dataStore.length
    for (let outer = 1; outer < len; outer++) {
      temp = dataStore[outer]
      let inner = outer
      while (inner > 0 && dataStore[inner - 1] > temp) {
        this.swap(inner, inner - 1)
        inner--
      }
      dataStore[inner] = temp
    }
  }

  // 希爾排序
  shellSort() {
    // 間隔序列
    let N = this.dataStore.length
    let h = 1
    while (h < N / 3) {
      h = 3 * h + 1
    }
    while (h >= 1) {
      for (let i = h; i < this.dataStore.length; i++) {
        let temp = this.dataStore[i]
        let j = i
        while (j >= h && this.dataStore[j - h] > temp) {
          this.dataStore[j] = this.dataStore[j - h]
          this.swap(j, j - h)
          j -= h
        }
        this.dataStore[j] = temp
      }
      h = (h - 1) / 3
    }
  }
  // 歸併排序
  mergeSort() {
    let arr = this.dataStore
    if (arr.length < 2) {
      return
    }
    let step = 1
    let left, right
    while (step < arr.length) {
      left = 0;
      right = step
      while (right + step <= arr.length) {
        this.mergeArrays(arr, left, left + step, right, right + step)
        left = right + step
        right = left + step
      }
      if (right < arr.length) {
        this.mergeArrays(arr, left, left + step, right, arr.length)
      }
      step *= 2;

    }
  }
  mergeArrays(arr, startLeft, stopLeft, startRight, stopRight) {
    let rightArr = new Array(stopRight - startRight + 1)
    let leftArr = new Array(stopLeft - startLeft + 1)
    let k = startRight
    for (let i = 0; i < (rightArr.length - 1); i++) {
      rightArr[i] = arr[k]
      k++
    }
    k = startLeft
    for (let i = 0; i < (leftArr.length - 1); i++) {
      leftArr[i] = arr[k]
      k++
    }
    rightArr[rightArr.length - 1] = Infinity
    leftArr[leftArr.length - 1] = Infinity
    let m = 0;
    let n = 0;
    for (let k = startLeft; k < stopRight; ++k) {
      if (leftArr[m] < rightArr[n]) {
        arr[k] = leftArr[m]
        m++
      } else {
        arr[k] = rightArr[n]
        n++
      }
    }
  }
  // 快速排序
  qSort() {
    this.dataStore = this.qSortArr(this.dataStore)
  }
  qSortArr(list) {
    if (list.length == 0) {
      return []
    }
    let lesser = []
    let greater = []
    let pivot = list[0]
    for (let i = 1; i < list.length; i++) {
      if (list[i] < pivot) {
        lesser.push(list[i])
      } else {
        greater.push(list[i])
      }
    }
    return this.qSortArr(lesser).concat(pivot, this.qSortArr(greater))
  }
}
// 生成工具函數對象
let cArr = new cArray(10)
// 設置隨機數據
cArr.setData()

// 保存生成的隨機數組,爲了比較各個算法的時間複雜度,必須保證它們處理的隨機數組是同樣的
let dataStore = cArr.dataStore.concat()
// 冒泡排序
console.time('bubbleSort')
cArr.insertionSort()
console.timeEnd('bubbleSort')

// 選擇排序
cArr.dataStore = dataStore.concat()
console.time('selectionSort')
cArr.insertionSort()
console.timeEnd('selectionSort')

// 插入排序
cArr.dataStore = dataStore.concat()
console.time('insertion')
cArr.insertionSort()
console.timeEnd('insertion')

// 希爾排序
cArr.dataStore = dataStore.concat()
console.time('shell')
cArr.shellSort()
console.timeEnd('shell')

// 歸併排序
cArr.dataStore = dataStore.concat()
console.time('mergeSort')
cArr.mergeSort()
console.timeEnd('mergeSort')


cArr.dataStore = dataStore.concat()
console.time('qSort')
cArr.qSort()
console.timeEnd('qSort')
複製代碼
相關文章
相關標籤/搜索