數據結構與算法:常見排序算法

本章內容銜接上一章 數據結構與算法:二分查找javascript

內容提要

  • 兩種基本數據結構:java

    • 數組node

      • 常見操做: 數組降維數組去重
    • 鏈表
  • 遞歸:遞歸是不少算法都使用的一種編程方法

  - 如何將問題分紅基線條件遞歸條件
  - 分而治之策略解決棘手問題git

 - 調用棧(call stack)
 - 遞歸調用棧github

  • 常見排序算法:不少算法僅在數據通過排序後才管用。如二分查找,它只能用於有序元素列表

 - 冒泡排序
 - 選擇排序
 - 插入排序
 - 希爾排序
 - 歸併排序
 - 快速排序
 - 堆排序
 - 計數排序
 - 桶排序
 - 基數排序算法

數組

須要將數據存儲到內存時,你請求計算機提供存儲空間,計算機給你一個存儲地址。須要存
儲多項數據時,有兩種基本方式——數組和鏈表。但它們並不是都適用於全部的情形,所以知道它
們的差異很重要

圖片描述

給出:let arr = [[1, 2, 3], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10, 0];
需求:降維、去重、排序
作法:Array.from(new Set(arr.flat(Infinity).sort((a, b) => a - b)))


解析以下:
0. arr.flat(Infinity)  //直接降維值一維數組。
1. sort     //排序就不說了。
2. new Set()  //達到去重效果
3. Array.from(上一步輸出的結果)  //將上一步結果轉換爲數組

鏈表

遞歸

遞歸(recursion):程序調用自身的編程技巧。shell

遞歸知足2個條件:編程

  • 有反覆執行的過程(調用自身)
  • 有跳出反覆執行過程的條件(遞歸出口)
遞歸就是指在定義一個概念和過程時,又用到了自己。
哲學的將, 遞歸的妙用就在,若是一個過程當中又包含自身,那麼這個過程就能夠無窮地展開,不會在有窮的步驟後中止。可是描述這個過程只須要有窮的指令。以有窮表現無窮。

在數學與計算機科學中,是指在函數的定義中使用函數自身的方法。遞歸一詞還較經常使用於描述以自類似方法重複事物的過程。例如,當兩面鏡子相互之間近似平行時,鏡中嵌套的圖像是以無限遞歸的形式出現的。也能夠理解爲自我複製的過程。
另外多提一句,遞歸lambda 演算是兩個與圖靈機等價的計算機理論模型,感興趣的讀者能夠去進一步研究,這裏不贅述。segmentfault

基線條件和遞歸條件

因爲遞歸函數調用本身,編寫這樣的函數時很容易出錯,致使無限循環。數組

例:編寫這樣倒計時的函數。

5...4...3...2...1

爲此,你能夠用遞歸的方式編寫:

const countdown = (i) => {
    console.log(i)
    // base case 基準條件
    if (i <= 0){
      return null
    } 
    // 隱藏的else是 遞歸條件
    countdown(i-1)
    return null
  }

  countdown(5)

圖片描述

編寫遞歸函數時,必須告訴它什麼時候中止遞歸。正由於如此,每一個遞歸函數都有兩部分:基線
條件(base case)和遞歸條件(recursive case)。遞歸條件指的是函數調用本身,而基線條件則
指的是函數再也不調用本身,從而避免造成無限循環。

能夠去掉基準條件執行下代碼:

const countdown = (i) => {
    console.log(i)
    // base case 基準條件
    // if (i <= 0){
    //   return null
    // } 
    // 隱藏的else是 遞歸條件
    countdown(i-1)
    return null
  }

  countdown(5)

圖片描述
由於無限循環致使Maximum call stack size exceeded error

常見例子和應用場景

階乘

n! = n (n-1) (n-2) ... 1(n>0)

// 階乘
const fact = (x) => {
  if(x === 1) {
    return 1
  }
  return x * fact(x - 1) 
}

console.log(fact(5))

圖片描述

斐波那契數組

斐波那契數列能夠定義爲如下序列:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, …
能夠看到,該序列是由前兩項數值相加而成的。這個數列的歷史很是悠久,至少能夠追溯 到公元700年。它以意大利數學家列奧納多·斐波那契(Leornardo Fibonacci)的名字命 名,斐波那契在1202年使用這個數列描述理想狀態下兔子的增加。

這是一個簡單的遞歸函數,你可使用它來生成數列中指定序號的數值

clipboard.png

clipboard.png

這個函數的問題在於它的執行效率很是低

有太多值在遞歸調用中被從新計算。若是編譯器能夠將已經計算的值記錄下來,函

數的執行效率就不會如此差。咱們可使用動態規劃的技巧來設計一個效率更高的算法。

動態規劃的本質其實就是兩點:

  • 自底向上分解子問題
  • 經過變量存儲已經計算過的解

根據上面兩點,咱們的斐波那契數列的動態規劃思路:

  • 斐波那契數列從 0 和 1 開始,那麼這就是這個子問題的最底層
  • 經過數組來存儲每一位所對應的斐波那契數列的值

clipboard.png

二叉樹的最大深度

樹的最大深度:該題目來自 Leetcode,題目須要求出一顆二叉樹的最大深度

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
   if(!root) return 0
   return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1 
};

對於該遞歸函數能夠這樣理解:一旦沒有找到節點就會返回 0,每彈出一次遞歸函數就會加一,樹有三層就會獲得 3。
可是遞歸真的很慢

clipboard.png

咱們能夠寫一個函數 用以遞歸的快速計算

// memoize 全局函數:用以階乘,斐波那契數組等遞歸調用的快速計算
// useage: const memoizeFibonacci = memoize(fibonacci); memoizeFibonacci(45)

export function memoize(fn) {
  const cache = {};
  return function () {
    const key = JSON.stringify(arguments);
    var value = cache[key];
    if (!value) {
      value = [fn.apply(this, arguments)]; // 放在一個數組中,方便應對undefined,null等異常狀況
      cache[key] = value;
    }
    return value[0];
  }
}
Stack Overflow上說的一句話:「若是使用循環,程序的性能可能更高;若是使用遞歸,程序可能
更容易理解。如何選擇要看什麼對你來講更重要。」 Recursion or Iteration?

棧是一個線性結構,在計算機中是一個至關常見的數據結構。

棧的特色是隻能在某一端添加或刪除數據,遵循先進後出的原則

圖片描述

js實現

每種數據結構均可以用不少種方式來實現,其實能夠把棧當作是數組的一個子集,因此這裏使用數組來實現

clipboard.png

快速排序

冒泡排序

Python實現

arr = [2,3,6,5,33,7,23]

def bubbleSort(arr):
    for i in range(1, len(arr)):
        for j in range(0, len(arr)-i):
            if arr[j] > arr[j+i]:
                arr[j],arr[j + i] = arr[j + i], arr[j]
    return arr

print(bubbleSort(arr))

選擇排序

Python實現

arr = [2,3,6,5,33,7,23]

def selectionSort(arr):
    for i in range(len(arr) - 1):
        # 記錄最小的索引
        minIndex = i
        for j in range(i + 1, len(arr)):
            if arr[j] < arr[minIndex]:
                minIndex = j
        # i 不是最小數時, 將i 和最小數進行交換
        if i != minIndex:
            arr[i], arr[minIndex] = arr[minIndex], arr[i]
    return arr

print(selectionSort(arr))

插入排序

Python實現

arr = [2,3,6,5,33,7,23]

def insertionSort(arr):
    for i in range(len(arr)):
        preIndex = i-1
        current = arr[i]
        while preIndex >= 0 and arr[preIndex] > current:
            arr[preIndex+1] = arr[preIndex]
            preIndex-=1
        arr[preIndex+1] = current
    return arr

print(insertionSort(arr))

希爾排序

Python實現

arr = [2,3,6,5,33,7,23]

def shellSort(arr):
    import math
    gap = 1
    while(gap < len(arr)/3):
        gap = gap*3 + 1
    while gap > 0:
        for i in range(gap, len(arr)):
            temp = arr[i]
            j = i - gap
            while j >= 0 and arr[j] >temp:
                arr[j+gap] = arr[j]
                j-=gap
            arr[j+gap] = temp
        gap = math.floor(gap/3)
    return arr

print(shellSort(arr))

下一篇文章 數據結構與算法:二叉樹算法

參考

JS-Sorting-Algorithm
javascript描述數據結構與算法(改自imooc)
算法圖解
常見算法js實現
javascript-algorithms
排序算法總結

相關文章
相關標籤/搜索