JavaScript_數據結構與算法

以JavaScript語言爲主,學習數據結構與算法。node

算法須要依靠數據結構計算.ios

什麼是算法
  1. 一個有限指令集
  2. 接受一些輸入(有些時候不須要輸入)
  3. 產生輸出
  4. 必定在有限步驟以後終止
  5. 每一條指令必須
時間複雜度Tn

根據算法寫成的程序在執行時佔用存儲單源的長度算法

空間複雜度Sn

根據算法寫成的程序在執行時好費時間的長度編程

數據結構
  • 棧:一種聽從先進後出 (LIFO) 原則的有序集合;新添加的或待刪除的元素都保存在棧的末尾,稱做棧頂,另外一端爲棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。
  • 隊列:一種遵循先進先出 (FIFO / First In First Out) 原則的一組有序的項;隊列在尾部添加新元素,並從頭部移除元素。最新添加的元素必須排在隊列的末尾。
  • 鏈表:存儲有序的元素集合,但不一樣於數組,鏈表中的元素在內存中並非連續放置的;每一個元素由一個存儲元素自己的節點和一個指向下一個元素的引用(指針/連接)組成。
  • 集合:由一組無序且惟一(即不能重複)的項組成;這個數據結構使用了與有限集合相同的數學概念,但應用在計算機科學的數據結構中。
  • 字典:以 [鍵,值] 對爲數據形態的數據結構,其中鍵名用來查詢特定元素,相似於 Javascript 中的Object
  • 散列:根據關鍵碼值(Key value)直接進行訪問的數據結構;它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度;這個映射函數叫作散列函數,存放記錄的數組叫作散列表。
  • 樹:由 n(n>=1)個有限節點組成一個具備層次關係的集合;把它叫作「樹」是由於它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的,基本呈一對多關係,樹也能夠看作是圖的特殊形式。
  • 圖:圖是網絡結構的抽象模型;圖是一組由邊鏈接的節點(頂點);任何二元關係均可以用圖來表示,常見的好比:道路圖、關係圖,呈多對多關係。

數據結構

棧是一種聽從先進後出 (LIFO) 原則的有序集合;新添加的或待刪除的元素都保存在棧的末尾,稱做棧頂,另外一端爲棧底。在棧裏,新元素都靠近棧頂,舊元素都接近棧底。數組

棧也被用在編程語言的編譯器和內存中保存變變量方法調用瀏覽器

class Stack {

    constructor() {
        this.items = []
    }

    // 入棧
    push(element) {
         this.items.push(element)
    }

    // 出棧
    pop() {
        return this.items.pop()
    }

    // 末位
    get peek() {
        return this.items[this.items.length - 1]
    }

    // 是否爲空棧
    get isEmpty() {
        return !this.items.length
    }

    // 長度
    get size() {
        return this.items.length
    }

    // 清空棧
    clear() {
        this.items = []
    }

    // 打印棧數據
    print() {
        console.log(this.items.toString())
    }
}

隊列

隊列是一種遵循先進先出 (FIFO / First In First Out) 原則的一組有序的項;隊列在尾部添加新元素,並從頭部移除元素。最新添加的元素必須排在隊列的末尾。網絡

前面的人優先完成本身的事務,完成以後,下一我的才能繼續。數據結構

class Queue {

    constructor(items) {
        this.items = items || []
    }

    enqueue(element) {
        this.items.push(element)
    }

    dequeue() {
        return this.items.shift()
    }

    front() {
        return this.items[0]
    }

    clear() {
        this.items = []
    }

    get size() {
        return this.items.length
    }

    get isEmpty() {
        return !this.items.length
    }

    print() {
        console.log(this.items.toString())
    }
}

數組

數組: 相同數據類型的元素按必定順序排列的集合併發

數組建立方式app

// C++
int array[] = {5, 3, 2, 5, 6, 0, 10}

// JS
[]
new Array();

特性:存儲連續的內存單元

好處:

  1. 省內存空間
  2. 規律整齊,獲取快速,隨機訪問

爲何須要有鏈表:
移動和插入,刪除,須要耗費性能和時間,就是鏈表產生的緣由

類型檢測

typeof

用來檢測數據類型的運算符

typeof true; // boolean

返回都是一個字符串,其次字符串中包含了對應的數據類型。

返回的類型:

  1. string
  2. boolean
  3. number
  4. object
  5. function
  6. undefined

侷限性:

  1. typeof null // "object" // null 是空對象指針
  2. typeof 不能具體區分是數組,仍是正則,仍是對象,仍是時間對象. 引用類型的值,返回的是
console.log(typeof typeof typeof function() {});  // typeof function() {} --> 'function',  typeof 'function' --> 'string' ,  typeof 'string'  --> 'string'

if (typeof num2 == 'undefined') {
     num2 = 0;
}

num2 = num2 || 0; // 這種形式,和上面的默認值形式並不徹底相同

typeof callback === 'function' ? callback() : null;

callback && callback();
instanceof

檢測某一個實例是否屬於某個類

彌補typeof不能判斷引用類型。
特性:只要在當前實例的原型鏈上,均可以查詢獲得,檢測出來的結果都是true

var obj = [123, 123];
console.log(obj instanceof Array); // true
console.log(obj instanceof RegExp); // false

instanceof不少的缺陷:

第一個缺陷:

對於基本數據類型,字面量建立出來的結果檢測都爲false。
從嚴格意義上講,只有實例建立出來的結果纔是標準的對象數據類型值。

1 instanceof Number // false
new Number(1) instanceof Number // true
true instanceof Boolean // false
'' instanceof String // false


console.log(1 instanceof Number);
console.log(new Number(1) instanceof Number);
// instanceof 的侷限性,對於基本數據類型來講字面量方式建立出來的結果和實例方式建立出來的結果是有必定區別的,
// 從嚴格意義上來說,只有實例建立出來的結果纔是標準的對象數據類型值,也是標準的Number這個類的一個實例;對於字面量方式建立出來的結果
// 是基本數據類型值,不是嚴謹的實例,可是因爲JS的鬆散特色,致使了可使用Number.prototype上提供的方法。

第二個缺陷:

特徵:
只要在當前實例的原型鏈上,均可以查詢獲得,檢測出來的結果都是true

oDiv instanceof EventTarget  // true
oDiv instanceof Node // true

缺點:不肯定性

// 在類的原型繼承中,最後檢測出來的結果未必正確
function Fn() {}
Fn.prototype = new Array(); // 原型繼承
var f = new Fn(); 
console.log(f instanceof Function); // false
console.log(f instanceof Array); // true
// f->Fn.prototype -> Array.prototype -> Object.prototype

利用這個特性,能夠建立類數組(索引和length),可使用數組的方法(在它的原型鏈上就應該有Array.prototype了)

function arg() {
    // 存放私有屬性
    this.index = 0;
    this.legnth = 0;
}
arg.prototype = Array.prototype;
constructor

構造函數,做用和instanceof很是的類似

能夠處理基本數據類型

var num = 10;
console.log(num.constructor === Number); // true

construtor檢測Object和instanceof不同,通常狀況下是檢測不了的。

var reg = /\d+/;
console.log(reg.constructor === RegExp); // true
console.log(reg.constructor === Object); // false

侷限性:
把類的原型進行從新,在從新的過程當中,頗有可能出現,把以前的constructor覆蓋,這樣檢測出來的結果不許確。(原型繼承)

對於特殊的數據類型nullundefined,它們的所屬類是NullUndefined,可是瀏覽器把這兩個類保護起來,不容許用戶使用。

Object.prototype.toString.call();

Object 瀏覽器內置類,全部對象數據的基類。

類型檢測最爲準確.
首先獲取Object原型上的toString方法,讓方法執行,而且改變方法中的this關鍵詞的指向.
Object.prototype.toString它的做用是返回當前方法的執行主體(方法中的this)所屬類的詳細信息

toString的理解:
字面意思是轉化爲字符串,可是某些toString方法不只僅是轉換爲字符串。

console.log((1).toString()); // '1' // 使用的是:Number.prototype.toString();  // Number上的有toString()參數能夠有進制轉換
console.log((1).__proto__.__proto__.toString()); // '[object Object]'  // 使用的是:Object.prototype.toString();

對於Number,Boolean,String,Date,RegExp,Array,Function原型上的toString都是把當前的數據類型轉換爲字符串類型(它們的做用僅僅使用轉換爲字符串的)

Object.prototype.toString.call();並非用來轉換爲字符串,而是一種形式[object Object]的格式的字符串

console.log(({}).toString()); // [object Object]
console.log(Math.toString());  // [object Math] 
console.log(Object.prototype.toString.call([])); // [object Array]

返回當前主體的類的屬於信息

var obj = {};
console.log(obj.toString()); // toString中的this是誰的obj,返回的是obj所屬類的信息 --> [當前實例是那種數據類型(這個是固定死的,全部數據類型都是對象類型) 當前主體的所屬類]

算法概念

排序算法

  • 冒泡排序:比較任何兩個相鄰的項,若是第一個比第二個大,則交換它們;元素項向上移動至正確的順序,好似氣泡上升至表面通常。
  • 選擇排序:每一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,以此循環,直至排序完畢。
  • 插入排序:將一個數據插入到已經排好序的有序數據中,從而獲得一個新的、個數加一的有序數據,此算法適用於少許數據的排序,時間複雜度爲 O(n^2)。
  • 歸併排序:將原始序列切分紅較小的序列,只到每一個小序列沒法再切分,而後執行合併,即將小序列歸併成大的序列,合併過程進行比較排序,只到最後只有一個排序完畢的大序列,時間複雜度爲 O(n log n)。
  • 快速排序:經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行上述遞歸排序,以此達到整個數據變成有序序列,時間複雜度爲 O(n log n)。
冒泡排序
var arr = [1, 2, 29, 12, 12, 19, 230, 120, 22]

function bubble_sort (arr) {
    var n = arr.length
    for (var i = 0; i < n - 1; i++) {
        for (var j = 0; j < n - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {
                var tmp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = tmp
            }
        }
    }
}

console.log(arr)
bubble_sort(arr)
console.log(arr)
選擇排序
var arr = [1, 2, 29, 12, 12, 19, 230, 120, 22]

function select_sort (arr) {
    var n = arr.length
    for (var j = 0; j < n-1; j++) {
       var min_index = j
        for (var i = j+1; i < n; i++) { //  1 ~ n-1 時間複雜度:1-n, 2-n, 3-n
            if (arr[min_index] > arr[i]) {
                min_index = i
            }
        }
        var tmp = arr[min_index]
        arr[min_index] = arr[j]
        arr[j] = tmp 
    }
}

console.log(arr)
select_sort(arr)
console.log(arr)
快速排序
function query_sort (arr, first, last) {
    if (first >= last) return

    mid_value = arr[first]

    low = first
    high = last

    while (low < high) {
        while (low < high && arr[high] >= mid_value) {
            high -= 1
        }
        arr[low] = arr[high]
        while (low < high && arr[low] < mid_value) {
            low += 1
        }
        arr[high] = arr[low]
    }
    arr[low] = mid_value

    query_sort(arr, first, low-1)
    query_sort(arr, low+1, last)
}

li = [54, 26, 93, 17, 77, 34]
console.log(li)
query_sort(li, 0, li.length-1)
console.log(li)

搜索算法

  • 順序搜索:讓目標元素與列表中的每個元素逐個比較,直到找出與給定元素相同的元素爲止,缺點是效率低下。
  • 二分搜索:在一個有序列表,以中間值爲基準拆分爲兩個子列表,拿目標元素與中間值做比較從而再在目標的子列表中遞歸此方法,直至找到目標元素。

其它

  • 貪心算法:在對問題求解時,不考慮全局,老是作出局部最優解的方法。
  • 動態規劃:在對問題求解時,由以求出的局部最優解來推導全局最優解。
  • 複雜度概念:一個方法在執行的整個生命週期,所須要佔用的資源,主要包括:時間資源、空間資源。

鏈表

鏈表: 一種物理存儲單元上非連續、非順序的存儲結構,數據元素的邏輯順序是經過鏈表中的指針連接次序實現的。
鏈表存儲有序的元素集合,但不一樣於數組,鏈表中的元素在內存中並非連續放置的。每一個元素由一個存儲元素本省的節點和一個指向下一個元素的引用(也稱指針或連接)組成。

圖片描述

鏈表優勢:
添加或移動元素的時候不須要移動其它元素。
鏈表缺點:
鏈表須要使用指針,所以實現鏈表時須要額外注意。數組的另外一個細節是能夠直接訪問任何位置的任何元素,而要想範文鏈表中間的一個元素,須要從起點(表頭)開始迭代列表知道找到所需的元素。

C++ 
struct node {
    int payload;
    node* next;
}

// JS
{}
new Object();

開闢內存,裏面存放key 和value, 經過指針指向下一個元素。是無序的,經過指針來尋找下一個位置。

解決插入和刪除的問題,經過指針直接指向某個一位置,尋找到該位置,插入或刪除。

對於鏈表來講,拿第一個元素和拿第一億零一個元素使用的時間是不一樣的,隨機訪問效率低下。
鏈表會指針每次判斷,直到尋找到位置相同。

鏈表,和數組爲基礎,能夠表現成或變形:
隊列,hash表
棧:只能加在頭部
隊列: 一頭近一頭出
圖:表現爲鏈接的形式,每個節點保存一堆的指針。 也能夠經過連接矩陣的方式保存

hash表須要隨機訪問,第一層使用的是數組,第二層使用的是鏈表.

時間複雜度O(n)表示程序運行時間跟n有關,而且是線性關係。
空間複雜度O(1),表示所需空間爲常量,而且與n無關。

// JavaScript鏈表

function LinkedList() {

  var Node = function(element) { // 輔助類
    this.element = element; // 添加到列表的值
    this.next = null; // 下一個節點項的指針
  }

  var length = 0; // 存儲列表項的數量
  var head = null; // 存儲第一個節點的引用

  // 向列表尾部添加一個新的項
  // 列表最後一個節點的下一個元素始終是 null
  this.append = function (element) {
    // 思路:
    // 1. 列表爲空,添加的是第一個元素
    // 2. 列表不爲空,向其追加元素

    var node = new Node(element), current;
    
    if (head == null) { // 列表中第一個節點
      head = node;
    } else {

      current = head;
      // 循環列表,直到找到最後一項  
      while(current.next) {
        current = current.next;
      }
      // 找到最後一項,將其next賦爲node,創建連接
      current.next = node;
    }
    // 更新列表的長度
    length++;
  }

  // 向列表的特定位置插入一個新的項
  this.insert = function(position, element) {
    // 檢查越界值
    if (position >= 0 && position <= length) {
      var node = new Node(element), previous, index, current = head;
      // 在第一個位置添加
      if (position == 0) {
        node.next = current;
        head = node;
      } else {
        while(index++ < position) {
          previous = current;
          current = current.next;
        }
        node.next = current;
        previous.next = node;
      }
      length++; // 更新列表長度
      return true;
    } else {
      return false;
    }
  }
  
  // 從列表中移除一項
  this.remove = function(element) {
    var index = this.indexOf(element);
    return this.removeAt(index);
  }

  // 從列表的特定位置移除一項
  // 第一種是從特定位置移除一個元素,第二種是根據元素的值移除元素

  // 給定位置移除一個元素
  this.removeAt = function(position) {
    // 思路: 1.  移除第一個元素
    // 2. 移除第一個之外的任一元素

    // 檢查越界
    if (position > -1 && position < length) {
      var current = head, previous, index = 0; // current做用:移除元素的引用 // previous 做用:移除元素的前一個元素的引用

      // 移除第一項
      if (position == 0) { // 移除方法,堆中不引用地址,GC回收機制自動回收.
        head = current.next; // head 指向列表的第二個元素。 效果:current變量就是對列表中第一個元素的引用。若是把head賦爲current.next,就會移除第一個元素。
      } else {
        while(index++ < position) {
          previous = current;
          current = current.next;
        }
        // 將previous與current的下一項連接起來:跳過current,從而移除它
        previous.next = current.next;
      }
      length--;
      return current.element;
    } else {
      return null;
    }
  }

  // 返回元素在列表中的索引。若是列表中沒有該元素則返回 -1
  this.indexOf = function(element) {
    var current = head, index = -1;
    while(current) {
      if (element == current.element) {
        return index;
      }
      index++;
      current = current.next;
    }
    return -1;
  }

  // 若是鏈表中不包含任何元素,返回 true ,若是鏈表長度大於0則返回 false
  this.isEmpty = function() {
    return length === 0;
  }

  // 返回鏈表包含的元素個數。
  this.size = function() {
    return length;
  }

  // 因爲列表項使用了 Node 類,就須要重寫繼承自JavaScript對象默認的toString 方法,讓其只輸出元素的值。
  this.toString = function() {
    var current = head, string = '';
    while(current) {
      string = current.element;
      current = current.next;
    }
    return stirng;
  }
  
  this.getHead = function() {
    return head;
  }

}

var list = new LinkedList();
list.append(15);
list.append(10);

數組反轉

經過數組反轉,推導出大O表達式.

// 思路:
// 1. 第一個和最後一個換
// 2. 第二個和倒二個換
// 3. 不斷往中間逼進
// 4. 奇數中間一個不動,完成反轉;偶數完成反轉

var arr = [12, 34, 5, 3, 34, 22, 4];

function reverse(arr) {
  let left = 0; 
  let right = arr.length - 1;
  while(left < right) { // 循環給個終止條件, 左邊不小於右邊,跳出循環
    // 位置置換
    let tmp = arr[left];
    arr[left] = arr[right];
    arr[right] = tmp;
    left++;
    right--;
  }
}

看執行的時候,須要分析指令,每條指令執行都須要時間,並非全部指令都根據數據量有關係。有的指令只執行一遍, 例如let left = 0;,let right = arr.length - 1;,無論數組再長,再大,這兩條指令執行的時間不會變化。

clipboard.png

大O表達式:O(n) <= C1 * n + C2;

大O符號 :在計算機科學中,它在分析算法複雜性的方面很是有用。

斐波那契數列

斐波那契數列的遞歸實現

在數學上,菲波那切數列是以遞歸的方法來定義:
從第3項開始,每一項都等於前兩項之和

  1. F0 = 0;
  2. F1 = 1;
  3. Fn = F(n-1) + F(n-2); (n>=2)
// JavaScript 實現 菲波那切數列算法 , 遞歸算法
/**
  * 斐波那契數列
  * 
  * 遞歸算法和如何計算時間複雜度
  */    
function fib(n) { // 經過天然概括法 遞歸算法 O(2^n)   // <= c0*n + c1;   c0*2^n + c1
  // console.log(n);  // n = 0, n=1的時候 爲 O(1), //T(n-1) , T(n) = O(2^n-1) + O(2^n-2) + O(1) = 1.5*O(2^n-1) + O(1)  <= 2*O(2^n-1) + O(1)  <= O(2^n-1) 
  if (n < 2) {
    return n;
  } else {
    return fib(n-1) + fib(n-2);  // T(n) = T(n-1) + T(n-2) + O(1);
  }
}

for (let i=0; i<50; i++) {
  console.log(fib(i));
}

遞歸算法計算時間複雜度
經過天然概括法計算時間複雜度

遞歸算法在菲波那切數列中應用時間複雜度很高,從內存的角度看,每次開闢一個新的內存空間,一直沒有被銷燬回收,佔用巨大內存。
時間複雜度爲:O(2*n)

clipboard.png

// JavaScirpt 迭代方式實現  菲波那切數列算法
function fibonacci(n) {
    var f = [];
    f[1] = 1;
    f[2] = 1;

    for (var i=3; i<n; i++) {
        f[i] = f[i-1] + f[i-2];
    }
    return f;
}

約瑟夫環問題

差數淘汰:
已知n我的(以編號1,2,3...n分別表示)圍坐在一張圓桌周圍。從編號爲k的人開始報數,數到m的那我的出列;他的下一我的又從1開始報數,數到m的那我的又出列;依此規律重複下去,直到圓桌周圍的人所有出列。

單向鏈表: 第一個單元,第二個單元... 最後一個接地.

循環鏈表: 第一個單元,第二個單元... 最後一個鏈表接回到第一個鏈表單元.

// reset(), current(), next(), prev(), search(), end() 

Array.prototype.pointer = 0; // 模擬數組內部指針

// reset 函數,0將數組內部指針歸爲,(指向第一個元素)
var reset = function (arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('reset function arguments typeof error');
    return;
  }
  // 重置指針
  arrayObj.pointer = 0;
}

// current 函數,返回數組內部指針的當前元素
var current = function (arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('current function arguments typeof error');
    return;
  }
  return arrayObj[arrayObj.pointer];
}

// end 函數,將數組內部指針指向最後一個元素,並返回最後一個元素的當前位置
var end = function (arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('end function arguments typeof error');
    return;
  }
  arrayObj.pointer = arrayObj.length - 1;
  return arrayObj[arrayObj.pointer];
}

// next函數,將數組內部指針下移一位,若是已經指向最後一個元素則返回false
var next = function (arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('next function arguments typeof error');
    return;
  }
  // 指針後移
  arrayObj.pointer++;
  // 判斷指針是否在最後一個
  if (typeof arrayObj[arrayObj.pointer] == 'undefined') {
    arrayObj.pointer--; // 重置回最後一個
    return false;
  }
  return true;
}

// prev函數,將數組內部指針上移一位,若是已經指向第一個元素則返回false
var prev = function (arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('prev function arguments error');
    return;
  }
  // 指針前移
  arrayObj.pointer--;
  // 判斷指針是否第一個
  if (typeof arrayObj[arrayObj.pointer] == 'undefind') {
    arrayObj.pointer++; // 重置回第一個
    return false;
  }
  return arrayObj[arrayObj.pointer];
}

// unset 函數, 刪除指定的數組元素
var unset = function (idx, arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('unset function arguments error');
    return;
  }
  if (typeof arrayObj[idx] == 'undefined') {
    console.warn("unset() 函數參數 idx 錯誤!不存在此元素!");
    return false;
  }
  arrayObj.splice(idx, 1);
  return true;
}

// search 函數,經過數組鍵值返回數組鍵名
var search = function (value, arrayObj) {
  if (!(arrayObj instanceof Array)) {
    console.warn('search function arguments  error');
    return;
  }
  for (var i in arrayObj) {
    if (arrayObj[i] == value) {
      return i; // 返回鍵名
    }
  }
  return false;
}

// getKingMonkey 主函數 // n 只猴子,數到 m   // 4我的數,到3中止一次。
function getKingMonkey(n, m) {
  debugger;
  // 1. 構造元素
  // 2. 循環

  a = new Array();
  for (var i = 1; i <= n; i++) {
    a[i] = i;
  }
  a[0] = 0;  // 補第0個位置 // [undefined × 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] --> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  unset(0, a); // 刪除第0個元素  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  reset(a); // 數組內部指針歸爲0 

  while (a.length > 1) {
    for (counter = 1; counter <= m; counter++) {
      if (next(a)) { // 是否具備後續指針
        if (counter == m) { 
          unset(search(prev(a), a), a);
        }
      } else {
        reset(a); // 重置指針到第0位
        if (counter == m) {
          unset(search(end(a), a), a);
          reset(a); // 重置指針到第0位
        }
      }
    }
  }
  return current(a);
}

console.log(getKingMonkey(4, 3)); // 1

對於代碼沒法去看時間複雜度,通常從邏輯上考慮。

function getKingMonkey(n, m) {
  debugger;
  // 1. 構造元素
  // 2. 循環

  a = new Array();
  for (var i = 1; i <= n; i++) {
    a[i] = i;
  }
  a[0] = 0;  // 補第0個位置 // [undefined × 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] --> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  unset(0, a); // 刪除第0個元素  // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  reset(a); // 數組內部指針歸爲0 

  while (a.length > 1) {
    for (counter = 1; counter <= m; counter++) {
      if (next(a)) { // 是否具備後續指針
        if (counter == m) { 
          unset(search(prev(a), a), a);
        }
      } else {
        reset(a); // 重置指針到第0位
        if (counter == m) {
          unset(search(end(a), a), a);
          reset(a); // 重置指針到第0位
        }
      }
    }
  }
  return current(a);
}

假設n次
主函數的getKingMonkey中的外層while循環會執行n-1次, for循環會執行 k-1 次, 加上其它固定執行命令代碼時間。 整體爲:(n-1) * (k-1) + c

(n-1) * (k-1) + c ≈ nk - C0*n - C1*k + c ≈ nk - C0*n - C1*k 

時間複雜度:O(nk)
n可能很大,k可能很大

合併鏈表

合併兩個已經排序好的鏈表

// C++ 代碼實現
#include <iostream>

struct node{
  int payload;
  node* next;
  node(int payload) {this->payload = payload; next = nullptr;};
};

class linkedlist{
  node *head, *tail;
public:
  //constructor, initialize head and tail to nullptr
  linkedlist() : head(nullptr),tail(nullptr){};
  //push a new node at the end of the list
  void push_back(int value){
    if(empty()){ 
      head = tail = new node(value);
    }else{      
      tail->next = new node(value);
      tail = tail->next;
    }
  }
  //return the value stored in the first node
  int front(){
    if(empty()){
      throw "The list is empty.";
    }
    return head->payload;
  }
  //remove the first node
  void pop_front(){
    if(empty()){
      throw "The list is empty.";
    }
    node* first_node = head;
    head = head->next;
    delete first_node; 
  }
  bool empty(){
    return head == nullptr;
  }
  void output(){
    node* iterator = head;
    while(iterator){
      std::cout << iterator->payload << " ";
      iterator = iterator->next;
    }
    std::cout << std::endl;
  }
  
};

//merge two sorted linked list, return a new list
linkedlist merge(linkedlist a, linkedlist b){
  linkedlist result;
  while(!a.empty() || !b.empty()){ // 考慮極端狀況, a 鏈表爲空,把b鏈表中的一個個添加 結果鏈表中; b 鏈表爲空,把a鏈表中的一個個添加 結果鏈表中
    if(a.empty()){
      result.push_back(b.front());
      b.pop_front();
    }else if(b.empty()){
      result.push_back(a.front());
      a.pop_front();
    }else if(a.front() > b.front()){ // a,b 鏈表都不爲空, 看a和b 的head 誰大 (比較值的大小)。 a > b , b添加到結果鏈表中
      result.push_back(b.front());
      b.pop_front();
    }else{
      result.push_back(a.front()); // a < b , a添加到結果鏈表中
      a.pop_front();
    }
  }
  return result;
}
int main(){

  linkedlist a,b;
  
  linkedlist result = merge(a, b);
  result.output();
  return 0;
}

時間複雜度計算:

linkedlist merge(linkedlist a, linkedlist b){
  linkedlist result;
  while(!a.empty() || !b.empty()){ // 考慮極端狀況, a 鏈表爲空,把b鏈表中的一個個添加 結果鏈表中; b 鏈表爲空,把a鏈表中的一個個添加 結果鏈表中
    if(a.empty()){
      result.push_back(b.front());
      b.pop_front();
    }else if(b.empty()){
      result.push_back(a.front());
      a.pop_front();
    }else if(a.front() > b.front()){ // a,b 鏈表都不爲空, 看a和b 的head 誰大 (比較值的大小)。 a > b , b添加到結果鏈表中
      result.push_back(b.front());
      b.pop_front();
    }else{
      result.push_back(a.front()); // a < b , a添加到結果鏈表中
      a.pop_front();
    }
  }
  return result;
}

邏輯上的循環,並不知道具體會循環多少次,去看關鍵邏輯

假設:a的長度是m,b的長度是n。
總共循環了m+n次。

(m+n) * O(1)

時間複雜度爲:
O(m+n)

歸併排序

雜亂無章的序列,能夠從小到大,能夠從大到小,處理以後返回.
歸併排序是比較次數最少的一種排序
二分法 時間複雜度: O(m+n)
二分法 使用遞歸方式也稱之爲 自頂向下的算法
二分法 使用普通循環的方式排序,也稱之爲 自低向上的算法

遞歸的第一種方式:
數組的位置二分法排序:

init();
var array;
var left, right;
function init() {
  array = [3, 4, 2, 1, 7, 5, 8, 9, 0, 6];
  left = 0;
  right = array.length - 1;

  mergeSort(array, left, right);
}

function mergeSort(array, left, right) {
  if (left >= right) return;
  var middle = left + parseInt((right - left) / 2);
  mergeSort(array, left, middle);
  mergeSort(array, middle + 1, right);
  merge(array, left, middle, right);
}

function merge(array, left, middle, right) {
  var i = left, j = middle + 1;
  var aux = [];
  for (var k = left; k <= right; k++) {
    aux[k] = array[k];
  }
  for (var a = left; a <= right; a++) {
    if (i > middle) {
      array[a] = aux[j++];
    } else if (j > right) {
      array[a] = aux[i++];
    } else if (parseInt(aux[i]) < parseInt(aux[j])) {
      array[a] = aux[i++];
    } else {
      array[a] = aux[j++];
    }
  }
}

console.log(array);

遞歸的第二種方式

var arr = [3, 4, 2, 1, 7, 5, 8, 9, 0, 6];

function mergeSort(arr) {
  var len = arr.length;
  var left, right, middle = Math.floor(arr.length / 2); // (left + right) / 2
  if (len <= 1) {
    return arr;
  }
  left = arr.slice(0, middle); // 獲得下標從0~middle-1的數組
  right = arr.slice(middle); // 獲得下標從index開始到末尾的數組
  return merge(mergeSort(left), mergeSort(right)); // 遞歸
}

function merge(left, right) {
  // 該函數與快排相似,每次left或者right都是要shift掉第一個元素,表示left或者right是會變化的,最後arr.concat,
  // 由於不知道left或者right其中一個哪一個剩下元素,因此要將剩下的元素給加上
  var arr = [];
  while (left.length && right.length) {
    if (left[0] < right[0]) {
      arr.push(left.shift());
    } else {
      arr.push(right.shift());
    }
  }
  return arr.concat(left, right);
}

console.log(mergeSort(arr));

普通循環實現歸併排序
思路:
從第一項和第二項合併並排序,第三和第四合並並排序,一直循環反覆.
新出來的項的序列。
從第一項和第二項合併並排序,循環反覆。
循環反覆.

5, 2, 1, 4, 3

↓
2,5 
1,4
3

↓

1,2,4,5
3,

↓

1,2,3,4,5

實現代碼:

function isArray1(arr) {
  if (Object.prototype.toString.call(arr) == '[object Array]') {
    return true;
  } else {
    return false;
  }
}
function merge(left, right) {
  var result = [];
  if (!isArray1(left)) {
    left = [left];
  }
  if (!isArray1(right)) {
    right = [right];
  }
  while (left.length > 0 && right.length > 0) {
    if (left[0] < right[0]) {
      result.push(left.shift());
    } else {
      result.push(right.shift());
    }
  }
  return result.concat(left).concat(right);
}

function mergeSort(arr) {
  var len = arr.length;
  var lim, work = [];
  var i, j, k;
  if (len == 1) {
    return arr;
  }
  for (i = 0; i < len; i++) {
    work.push(arr[i]);
  }
  work.push([]);
  for (lim = len; lim > 1;) {// lim爲分組長度
    for (j = 0, k = 0; k < lim; j++ , k = k + 2) {
      work[j] = merge(work[k], work[k + 1]);
    }
    work[j] = [];
    lim = Math.floor((lim + 1) / 2);
  }
  return work[0];
}
var arr1 = [7, 5, 9, 8];
var arr2 = [7, 5, 9, 8, 3, 20, 6, 1, 2];

console.log(mergeSort(arr1)); // 5,7,9,8

console.log(mergeSort(arr2)); // 1,2,3,5,6,7,8,9,20

快速排序

以一個項爲原始基點,全部分別放置左右大小,再次選擇基點,再分別放置左右大小,直至排序完成。

function quick_sort(array) {
  function sort(prev, numsize) {
    var nonius = prev;
    var j = numsize - 1;
    var flag = array[prev];
    if ((numsize - prev) > 1) {
      while (nonius < j) {
        for (; nonius < j; j--) {
          if (array[j] < flag) {
            array[nonius++] = array[j]; //a[i] = a[j]; i += 1;
            break;
          };
        }
        for (; nonius < j; nonius++) {
          if (array[nonius] > flag) {
            array[j--] = array[nonius];
            break;
          }
        }
      }
      array[nonius] = flag;
      sort(0, nonius);
      sort(nonius + 1, numsize);
    }
  }
  sort(0, array.length);
  return array;
}

時間複雜度:
快速排序是一分爲二的算法n/2

T(n) = 2T(n/2) + O(n)  // 有左右兩次調用函數
// 根據主定理,推出:
T(n) = O(n*logn)

百科中的主定理解釋

擊鼓傳花

遊戲規則:
在這個遊戲中,孩子們圍成一個圓圈,把花盡快地傳遞給旁邊的人。某一時刻傳花中止,這個時候花在誰手裏,誰就退出圓圈結束遊戲。重複這個過程,直到只剩一個孩子(勝者)。

//** Queue
function Queue() {
  var items = [];
  this.enqueue = function (element) {
    items.push(element);
  };
  this.dequeue = function () {
    return items.shift();
  };
  this.front = function () {
    return items[0];
  };
  this.isEmpty = function () {
    return items.length == 0;
  };
  this.clear = function () {
    items = [];
  };
  this.size = function () {
    return items.length;
  };
  this.print = function () {
    console.log(items.toString());
  };
}

function hotPotato(nameList, num) {
  var queue = new Queue();  
  for (var i = 0; i < nameList.length; i++) {
    queue.enqueue(nameList[i]); 
  }
  var eliminated = '';
  while (queue.size() > 1) {
    for (var i = 0; i < num; i++) {
      queue.enqueue(queue.dequeue()); 
    }
    eliminated = queue.dequeue(); 
    console.log(eliminated + '在擊鼓傳花遊戲中被淘汰。');
  }
  return queue.dequeue(); 
}

var names = ['A', 'B', 'C', 'D', 'E', 'F'];
var winner = hotPotato(names, 5);
console.log('勝利者:' + winner);

棧和隊列

是數組和鏈表的一種限制

棧,只能從一頭操做,(增,改都是隻能操做一頭),邏輯上是後進先出。
隊列,能夠認爲是容器,也有訪問的限制,邏輯上是先進先出. 新來的後面出去。

棧在實際中的應用,是函數的調用。隊列在實際中的應用,併發的產生數據,都放在隊列中。

相關文章
相關標籤/搜索