大廠面試必知源碼實現

call函數實現

func 是要調用的函數,是由 context 調用,func 的指向 context,因此關注點在 context 指向javascript

Function.prototype.call = function (context) {
  context = context || window
  const func = Symbol()
  context[func] = this
  const args = [...arguments].slice(1)
  const result = context[func](...args)
  delete context[func]
  return result
}
複製代碼

apply函數實現

該方法的語法和做用與 call() 方法相似,只有一個區別,就是 apply() 方法接受的是一個包含多個參數的數組,而 call() 方法接受的是一個參數列表java

Function.prototype.apply = function (context) {
  context = context || window
  const func = Symbol()
  context[func] = this
  // 不一樣點
  const args = [...arguments[1]]
  const result = context[func](...args)
  delete context[func]
  return result
}
複製代碼

bind函數實現

注意:返回的是函數node

Function.prototype.bind = function (context) {
  const self = this
  const args = [...arguments].slice(1)
  return function () {
    return self.apply(context, args.concat(...arguments))
  }
}
複製代碼

Object.assign實現

Object.assign() 方法用於將全部可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。算法

Object.assign = function (target) {
  for (let index = 1; index < arguments.length; index++) {
    let nextObj = arguments[index]
    if (nextObj != null) {
      for (let nextKey in nextObj) {
        // 忽略掉那些從原型鏈上繼承到的屬性
        if (nextObj.hasOwnProperty(nextKey)) {
          target[nextKey] = nextObj[nextKey]
        }
      }
    }
  }
  return target
}
複製代碼

Object.create實現

Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__。shell

Object.create = function (proto) {
  function F () {}
  F.prototype = proto
  return new F()
}
複製代碼

new操做符實現

new 運算符建立一個用戶定義的對象類型的實例或具備構造函數的內置對象的實例。new 關鍵字會進行以下的操做:編程

  1. 建立一個空的簡單JavaScript對象(即**{}**);
  2. 連接該對象(即設置該對象的構造函數)到另外一個對象 ;
  3. 將步驟1新建立的對象做爲**this**的上下文 ;
  4. 若是該函數沒有返回對象,則返回**this**。
function newFunc (func) {
  return function () {
    // 建立了一個新對象,而且新對象原型指向func構造函數的原型對象
    // 不太懂能夠看上面的Object.create基本實現
    const obj = Object.create(func.prototype)
    // 將構造函數的this指向obj,這樣obj就能夠訪問到構造函數中的屬性和方法
    func.call(obj, ...arguments)
    // 返回
    return obj
  }
}

// 示例
function Person (name, age) {
  this.name = name
  this.age = age
}
const obj = newFunc(Person)('小白', 18)
// >>> person {name: "小白", age: 18}
複製代碼

instanceof操做符實現

instanceof運算符用於測試構造函數的prototype屬性是否出如今對象的原型鏈中的任何位置數組

function instanceOf (lift, right) {
  let lp = lift.__proto__
  let rp = right.prototype
  while (true) {
    if (lp === null) return false
    if (lp === rp) return true
    lp = lp.__proto__
  }
}

// 示例
function Person (name, age) {
  this.name = name
  this.age = age
}
function Student (name, age, school){
  Person.call(this, name, age)
  this.school = school
}
Student.prototype = Object.create(Person.prototype)
Student.prototype.constructor = Student
let student = new Student('小白', 18, '掘金')
instanceOf(student, Person)
// >>> true
複製代碼

繼承方式

  1. 原型鏈繼承緩存

    缺點:不能給父類構造函數傳遞參數,全部新實例都會共享父類實例的屬性數據結構

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    // 子類構造函數
    function Student (school){
      this.school = school
    }
    Student.prototype = new Person()
    複製代碼
  2. 借用構造函數繼承閉包

    優勢:能夠解決給父類構造函數傳遞參數的問題,不存在共享問題。

    缺點:父類原型的屬性沒法共享,父類的方法沒有被共享。

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    // 子類構造函數
    function Student (name, age, school){
      Person.call(this, name, age)
      this.school = school
    }
    複製代碼
  3. 組合繼承

    優勢:能夠繼承父類原型上的屬性,能夠傳參,可複用。

    缺點:調用了兩次父類構造函數。

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    // 子類構造函數
    function Student (name, age, school){
      Person.call(this, name, age)
      this.school = school
    }
    Student.prototype = new Person()
    Student.prototype.constructor = Student
    複製代碼
  4. 原型式繼承

    缺點:原型引用屬性會被全部實例共享

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    let person = new Person('小白'18)
    // 原型式繼承的子類
    let student = Object.create(person)
    複製代碼
  5. 寄生式繼承

    給原型式繼承穿了個馬甲而已

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    function createStudent (person) {
    	let student = Object.create(person)
    	return student
    }
    let person = new Person('小白'18)
    let student = createStudent(person)
    複製代碼
  6. 寄生組合式繼承

    最理想的繼承方式

    // 父類構造函數
    function Person (name, age) {
      this.name = name
      this.age = age
    }
    // 子類構造函數
    function Student (name, age, school){
      Person.call(this, name, age)
      this.school = school
    }
    Student.prototype = Object.create(Person.prototype)
    Student.prototype.constructor = Student
    複製代碼
  7. ES6繼承

    // 父類構造函數
    class Person {
      constructor (name, age) {
        this.name = name
        this.age = age
      }
    }
    // 子類構造函數
    class Student extends Person {
      constructor (name, age, school) {
        super(name, age)
        this.school = school
      }
    }
    複製代碼

數據類型判斷

使用原生js中的 Object.prototype.toString.call()檢測

class Check {
  constructor () {
    const toString = Object.prototype.toString
    ;['String', 'Number', 'Boolean', 'Array',
      'Object', 'Function', 'Date', 'Symbol', 
      'RegExp', 'Undefined', 'Null', 'Error',
      'Map', 'Set'
    ].forEach(name => {
      this[`is${name}`] = function (val) {
        return toString.call(val) === `[object ${name}]`
      }
    })
  }
}

let instance = null

// 單文件實現的代理單例
// 不懂的可使用閉包替代
class ProxyCheck {
  constructor () {
    return instance || (instance = new Check())
  }
}

export default ProxyCheck

// 示例
// 下面輸出的結果爲 >>> true
const check = new ProxyCheck()
check.isArray([1])
check.isBoolean(true)
check.isDate(new Date())
check.isError(new Error())
check.isFunction(() => {})
check.isMap(new Map())
check.isNull(null)
check.isNumber(1)
check.isObject({})
check.isRegExp(/a/)
check.isSet(new Set())
check.isString('a')
check.isSymbol(Symbol('a'))
check.isUndefined(void 0)
複製代碼

發佈訂閱

let events = {}
let instance = null

function returnActives (eventName) {
  let actives = events[eventName]
  if (Array.isArray(actives)) {
    return actives
  }
  return events[eventName] = []
}

class Event {
  // 訂閱
  on (eventName, callback) {
    returnActives(eventName).push(callback)
  }
  // 發佈
  emit (eventName, param) {
    returnActives(eventName).forEach(callback => {
      callback(param)
    })
  }
  // 取消訂閱
  off (eventName, callback) {
    let actives = returnActives(eventName)
    if (typeof (callback) === 'function') {
      for (let i = 0; i < actives.length; i++) {
        if (actives[i] === callback) {
          actives.splice(i, 1)
          break
        }
      }
    } else if (callback === true) {
      events[eventName] = []
    }
  }
  // 只訂閱一次
  once (eventName, callback) {
    function callbackWrap (params) {
      callback(params)
      app.off(eventName, callbackWrap)
    }
    app.on(eventName, callbackWrap)
  }
} 

class ProxyEvents {
  constructor () {
    return instance || (instance = new Event())
  }
}

export default ProxyEvents
複製代碼

緩存

Map版本

class CacheCell {
  constructor (data, timeout) {
    // 緩存的數據
    this.data = data
    // 設置超時時間,單位秒
    this.timeout = timeout
    // 對象建立時候的時間
    this.createTime = Date.now()
  }
}

let cacheMap =  new Map()
let instance = null
let timeoutDefault = 1200

function isTimeout (name) {
  const data = cacheMap.get(name)
  if (!data) return true
  if (data.timeout === 0) return false
  const currentTime = Date.now()
  const overTime = (currentTime - data.createTime) / 1000
  if (overTime > data.timeout) {
    cacheMap.delete(name)
    return true
  }
  return false
}

class Cache {
  // 將數據存儲在緩存中指定的 name 中
  // timeout設置爲0表示永久緩存
  set (name, data, timeout = timeoutDefault) {
    const cachecell = new CacheCell(data, timeout)
    return cacheMap.set(name, cachecell)
  }
  // 從緩存中獲取指定 name 對應的內容
  get (name) {
    return isTimeout(name) ? null : cacheMap.get(name).data
  }
  // 從緩存中移除指定 name
  delete (name) {
    return cacheMap.delete(name)
  }
  // 返回一個布爾值,表示 name 是否在緩存之中
  has (name) {
    return !isTimeout(name)
  }
  // 清空數據緩存
  clear () {
    return cacheMap.clear()
  }
  // 設置緩存默認時間
  setTimeoutDefault (num) {
    if (timeoutDefault === 1200) {
      return timeoutDefault = num
    }
    throw Error('緩存器只能設置一次默認過時時間')
  }
}

class ProxyCache {
  constructor () {
    return instance || (instance = new Cache())
  }
}

export default ProxyCache
複製代碼

LocalStorage版本

class LocalStorageCacheCell {
  constructor (data, timeout) {
    // 緩存的數據
    this.data = data
    // 設置超時時間,單位秒
    this.timeout = timeout
    // 對象建立時候的時間
    this.createTime = Date.now()
  }
}

let instance = null
let timeoutDefault = 1200

function isTimeout (name) {
  const data = JSON.parse(localStorage.getItem(name))
  if (!data) return true
  if (data.timeout === 0) return false
  const currentTime = Date.now()
  const overTime = (currentTime - data.createTime) / 1000
  if (overTime > data.timeout) {
    localStorage.removeItem(name)
    return true
  }
  return false
}

class LocalStorageCache {
  // 將數據存儲在本地緩存中指定的 name 中
  // timeout設置爲0表示永久緩存
  set (name, data, timeout = timeoutDefault) {
    const cachecell = new LocalStorageCacheCell(data, timeout)
    return localStorage.setItem(name, JSON.stringify(cachecell))
  }
  // 從本地緩存中獲取指定 name 對應的內容
  get (name) {
    return isTimeout(name) ? null : JSON.parse(localStorage.getItem(name)).data
  }
  // 從本地緩存中移除指定 name
  delete (name) {
    return localStorage.removeItem(name)
  }
  // 返回一個布爾值,表示 name 是否在本地緩存之中
  has (name) {
    return !isTimeout(name)
  }
  // 清空本地數據緩存
  clear () {
    return localStorage.clear()
  }
  // 設置緩存默認時間
  setTimeoutDefault (num) {
    if (timeoutDefault === 1200) {
      return timeoutDefault = num
    }
    throw Error('緩存器只能設置一次默認過時時間')
  }
}

class ProxyLocalStorageCache {
  constructor () {
    return instance || (instance = new LocalStorageCache())
  }
}

export default ProxyLocalStorageCache
複製代碼

AOP

面向切面編程:動態地將代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程。

Function.prototype.before = function (beforeFn) {
  const _self = this
  return function () {
    beforeFn.apply(this, arguments)
    return _self.apply(this, arguments)
  }
}

Function.prototype.after = function (afterFn) {
  const _self = this
  return function () {
    const ret = _self.apply(this, arguments)
    afterFn.apply(this, arguments)
    return ret
  }
}

// 示例
function test () {
  console.log(test)
}
test = test.before(function(){
  console.log('before')
}).after(function(){
  console.log('after')
})
test()
// >>> before
// >>> test
// >>> after
複製代碼

防抖

指觸發事件後在 n 秒內函數只能執行一次,若是在 n 秒內又觸發了事件,則會從新計算函數執行時間。

// immediate = true >>> 事件觸發時當即執行,而後必定時間後才能再次執行
// immediate = false >>> 規定必定時間後只執行一次
function debounce (func, wait, immediate = true) {
  let timeout
  function debounced () {
    if (timeout) clearTimeout(timeout)
    let callNow = !timeout && immediate
    timeout = setTimeout(() => {
      timeout = null
      if (!immediate) {
        func.apply(this, arguments)
      }
    }, wait)
    if (callNow) func.apply(this, arguments)
  }
  debounced.cancel = function () {
    clearTimeout(timeout)
    timer = null
  }
  return debounced
}
複製代碼

節流

指連續觸發事件可是在 n 秒中只執行一次函數。

// immediate = true >>> 事件觸發時當即執行,觸發完畢還能執行一次
// immediate = false >>> 規定時間內必定執行一次,觸發完畢還能執行一次
function throttle(func, wait, immediate = true) {
  let timeout
  if (immediate) {
    let prev = 0
    return function () {
      let now = Date.now()
      clearTimeout(timeout)
      if (now - prev > wait) {
        func.apply(this, arguments)
        prev = now
      } else {
        timeout = setTimeout(() => {
          timeout = null
          func.apply(this, arguments)
        }, wait)
      }
    }
  }
  return function () {
    if (!timeout) {
      timeout = setTimeout(() => {
        timeout = null
        func.apply(this, arguments)
      }, wait)
    }
  }
}
複製代碼

數組扁平化

指將一個多維數組變爲一維數組

function flatten (array) {
  return array.reduce((preValue, curValue) => {
    return preValue.concat(Array.isArray(curValue) ? flatten(curValue) : curValue)
  }, [])
}

// 示例
const array = [1, [2, [3, [4, [5, [6, [7]]]]]]]
flatten(array)
// >>> [1, 2, 3, 4, 5, 6, 7]
複製代碼

數組去重

const array1 = [1, 1, 2, 3, 3]
const array2 = [...new Set(array1)]
// array2 >>> [1, 2, 3]
複製代碼

數組合並

const array1 = [1, 2, 3]
const array2 = [4, 5, 6]
const array3 = [...array1, ...array2]
// array3 >>> [1, 2, 3, 4, 5, 6]
複製代碼

數據結構

    1. 什麼是棧

      棧做爲一種數據結構,是一種只能在一端進行插入和刪除操做的特殊線性表。它按照先進後出的原則存儲數據,先進入的數據被壓入棧底,最後的數據在棧頂,須要讀數據的時候從棧頂開始彈出數據。

    2. 棧有什麼做用

      棧是很是底層的東西,在編程語言的編譯器和內存中保存變量、方法調用。

    3. 棧結構概念

      入棧、出棧、棧頂、棧底。

    class Stack {
      constructor () {
        // 私有屬性
        this._items = []
      }
      // 棧頂添加元素
      push (el) {
        this._items.push(el)
      }
      // 棧頂移除元素
      pop () {
        return this._items.pop()
      }
      // 檢查棧頂
      peek () {
        return this._items[this._items.length - 1]
      }
      // 檢查棧是否爲空
      isEmpty () {
        return this._items.length === 0
      }
      // 清除棧
      clear () {
        this._items = []
      }
      // 查看棧的大小
      size () {
        return this._items.length
      }
    }
    複製代碼
    方法名 說明
    push 棧頂添加元素
    pop 棧頂移除元素
    peek 檢查棧頂
    isEmpty 檢查棧是否爲空
    clear 清除棧
    size 查看棧的大小
  1. 隊列

    1. 什麼是隊列

      隊列是隻容許在一端進行插入操做,而在另外一端進行刪除操做的線性表。容許插入的端是隊尾,容許刪除的端是隊頭。 隊列是一個先進先出的線性表 。

    2. 隊列結構概念

      入隊、出隊、隊列頭、隊列尾。

    class Queue {
      constructor () {
        // 私有屬性
        this._items = []
      }
      // 入隊
      enqueue (el) {
        this._items.push(el)
      }
      // 出隊
      dequeue () {
        return this._items.shift()
      }
      // 查看隊列頭
      front () {
        return this._items[0]
      }
      // 檢查隊列是否爲空
      isEmpty () {
        return this._items.length === 0
      }
      // 查看隊列的大小
      size () {
        return this._items.length
      }
      // 清除隊列
      clear () {
        this._items = []
      }
    }
    複製代碼
    方法名 說明
    enqueue 入隊
    dequeue 出隊
    front 查看隊列頭
    isEmpty 檢查隊列是否爲空
    size 查看隊列的大小
    clear 清除隊列
  2. 鏈表

    1. 什麼是鏈表

      鏈表是一種物理存儲單元上非連續、非順序的存儲結構。
      複製代碼
    2. 爲何要學習鏈表

      鏈表和數組做爲算法中兩個基本數據結構,在程序設計過程當中常用。儘管兩種結構均可以用來存儲一系列的數據,但又各有各的特色。
      
      數組的優點,在於能夠方便的遍歷查找須要的數據。在查詢數組指定位置的操做中,只須要進行一次操做便可,時間複雜度爲O(1)。可是,這種時間上的便利性,是由於數組在內存中佔用了連續的空間,在進行相似的查找或者遍歷時,本質是指針在內存中的定向偏移。然而,當須要對數組成員進行添加和刪除的操做時,數組內完成這類的操做時間複雜度則變成了O(n)。
      
      鏈表的特性,使其在某些操做上比數組更加高效。例如當進行插入和刪除操做時,鏈表的操做的時間複雜度僅爲O(1)。另外,由於鏈表在內存不是連續存儲的,因此能夠充分利用內存中的碎片空間。除此以外,鏈表仍是不少算法的基礎,最多見的哈希表就是基於鏈表來實現的。
      
       基於以上緣由,能夠看到,鏈表在程序設計過程當中是很是重要的。
      複製代碼
    1. 單向鏈表

      class LinkedList {
        // 構造函數
        constructor () {
          // 私有屬性鏈表頭
          this._head = null
          // 私有屬性鏈表尾
          this._last = null
          // 私有屬性表明鏈表的長度
          this._length = 0
          // 內部輔助類
          LinkedList.Node = class {
            constructor (el, next) {
              // 當前節點的元素
              this.el = el
              // 下一個節點連接
              this.next = next
            }
          }
        }
        // 私有方法根據索引獲取節點
        _nodeIndex (index) {
          let node = this._head
          for (let i = 0; i < index; i++) {
            node = node.next
          }
          return node
        }
        // 鏈表尾添加元素
        append (el) {
          if (!this._head) {
            // 當鏈表頭爲空時,向鏈表頭添加節點,同時向鏈表尾添加節點
            this._head = new LinkedList.Node(el, null)
            this._last = this._head
          } else {
            let node = new LinkedList.Node(el, null)
            // 向鏈表尾添加節點
            this._last.next = node
            // 重設鏈表尾
            this._last = node
          }
          // 記錄列表長度
          this._length++
          return true
        }
        // 在鏈表中插入元素
        insert (index, el) {
          if (index < 0 || index > this._length) return false
          // 當index === 0表示向鏈表頭插入元素
          if (index === 0) {
            // 當鏈表頭爲空時,向鏈表頭添加節點,同時向鏈表尾添加節點
            if (!this._head) {
              this._head = new LinkedList.Node(el, null)
              this._last = this._head
            } else {
              this._head = new LinkedList.Node(el, this._head)
            }
          } else {
            // 獲取當前須要插入的前一個節點
            let previous = this._nodeIndex(index - 1)
            let node = new LinkedList.Node(el, previous.next)
            previous.next = node
            // 判斷是否插入鏈表尾
            if (index === this._length) {
              // 重設鏈表尾
              this._last = node
            }
          }
          // 記錄列表長度
          this._length++
          return true
        }
        // 刪除指定位置的元素
        removeAt (index) {
          if (index < 0 || index > this._length - 1) return false
          let current = null
          // 當index === 0表示刪除鏈表頭元素
          if (index === 0) {
            // 緩存鏈表頭
            current = this._head
            // 改變鏈表頭
            this._head = current.next
          } else {
            // 獲取當前須要刪除的前一個節點
            let previous = this._nodeIndex(index - 1)
            // 獲取當前須要刪除的節點
            current = previous.next
            // 刪除元素
            previous.next = current.next
            // 判斷是否刪除鏈表尾
            if (index === this._length - 1) {
              // 重設鏈表尾
              this._last = previous
            }
          }
          // 記錄列表長度
          this._length--
          return current.el
        }
        // 獲取指定元素的索引位置
        indexOf (el) {
          // 索引
          let index = 0
          // 緩存鏈表頭
          let current = this._head
          while (current) {
            // 判斷給定的元素是否等於當前元素節點的元素
            // 若是時引用類型的還須要自定義判斷引用類型是否相等
            if (el === current.el) {
              // 若是相等就返回索引
              return index
            }
            current = current.next
            index++
          }
          return -1
        }
        // 根據索引獲取元素
        get (index) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el
        }
        // 根據索引修改元素
        set (index, el) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el = el
        }
        // 獲取鏈表頭
        getHead () {
          return this._head
        }
        // 獲取鏈表尾
        getLast () {
          return this._last
        }
        // 刪除指定的元素
        remove (el) {
          return this.removeAt(this.indexOf(el))
        }
        // 檢查鏈表是否爲空
        isEmpty () {
          return this._length === 0
        }
        // 獲取鏈表的大小
        size () {
          return this._length
        }
        // 清空鏈表
        clear () {
          this._head = null
          this._length = null
          this._length = 0
        }
      }
      複製代碼
    2. 單向循環鏈表

      class LinkedList {
        constructor () {
          this._head = null
          this._last = null
          this._length = 0;
          LinkedList.Node = class {
            constructor (el, next) {
              this.el = el
              this.next = next
            }
          }
        }
        _nodeIndex (index) {
          let node = this._head
          for (let i = 0; i < index; i++) {
            node = node.next
          }
          return node
        }
        append (el) {
          if (!this._head) {
            this._head = new LinkedList.Node(el, null)
            // ========= 不一樣點 =========
            // 鏈表尾指向鏈表頭
            this._head.next = this._head
            this._last = this._head
          } else {
            // ========= 不一樣點 =========
            // 初始化給定鏈表頭
            let node = new LinkedList.Node(el, this._head)
            this._last.next = node
            this._last = node
          }
          this._length++
          return true
        }
        insert (index, el) {
          if (index < 0 || index > this._length) return false
          if (index === 0) {
            if (!this._head) {
              this._head = new LinkedList.Node(el, null)
              // ========= 不一樣點 =========
              // 鏈表尾指向鏈表頭
              this._head.next = this._head
              this._last = this._head
            } else {
              this._head = new LinkedList.Node(el, this._head)
              // ========= 不一樣點 =========
              // 鏈表尾指向新的鏈表頭
              this._last.next = this._head
            }
          } else {
            let previous = this._nodeIndex(index - 1)
            let node = new LinkedList.Node(el, previous.next)
            previous.next = node
            if (index === this._length) {
              this._last = node
            }
          }
          this._length++
          return true
        }
        removeAt (index) {
          if (index < 0 || index > this._length - 1) return false
          let current = null
          if (index === 0) {
            current = this._head
            this._head = current.next
            // ========= 不一樣點 =========
            // 鏈表尾指向新的鏈表頭
            this._last.next = this._head
          } else {
            let previous = this._nodeIndex(index - 1)
            current = previous.next
            previous.next = current.next
            if (index === this._length - 1) {
              this._last = previous
              // ========= 不一樣點 =========
              // 新的鏈表尾指向鏈表頭
              this._last.next = this._head
            }
          }
          this._length--
          return current.el
        }
        indexOf (el) {
          let index = 0
          let current = this._head
          while (current) {
            if (el === current.el) {
              return index
            }
            current = current.next
            index++
          }
          return -1
        }
        get (index) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el
        }
        set (index, el) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el = el
        }
        getHead () {
          return this._head
        }
        getLast () {
          return this._last
        }
        remove (el) {
          return this.removeAt(this.indexOf(el))
        }
        isEmpty () {
          return this._length === 0
        }
        size () {
          return this._length
        }
        clear () {
          this._head = null
          this._length = null
          this._length = 0
        }
      }
      複製代碼
    3. 雙向鏈表

      class LinkedList {
        // 構造函數
        constructor () {
          // 私有屬性鏈表頭
          this._head = null
          // 私有屬性鏈表尾
          this._last = null
          // 私有屬性表明鏈表的長度
          this._length = 0;
          // 內部輔助類
          LinkedList.Node = class {
            constructor (previous, el, next) {
              // 上一個節點連接
              this.previous = previous
              // 當前節點的元素
              this.el = el
              // 下一個節點連接
              this.next = next
            }
          }
        }
        // 私有方法根據索引獲取節點
        _nodeIndex (index) {
          let node = this._head
          for (let i = 0; i < index; i++) {
            node = node.next
          }
          return node
        }
        // 鏈表尾添加元素
        append (el) {
          if (!this._head) {
            // 當鏈表頭爲空時,向鏈表頭添加節點,同時向鏈表尾添加節點
            this._head = new LinkedList.Node(null, el, null)
            this._last = this._head
          } else {
            let node = new LinkedList.Node(this._last, el, null)
            // 向鏈表尾添加節點
            this._last.next = node
            // 重設鏈表尾
            this._last = node
          }
          // 記錄列表長度
          this._length++
          return true
        }
        // 在鏈表中插入元素
        insert (index, el) {
          if (index < 0 || index > this._length) return false
          // 當index === 0表示向鏈表頭插入元素
          if (index === 0) {
            // 當鏈表頭爲空時,向鏈表頭添加節點,同時向鏈表尾添加節點
            if (!this._head) {
              this._head = new LinkedList.Node(null, el, null)
              this._last = this._head
            } else {
              let node = this._head
              this._head = new LinkedList.Node(null, el, node)
              node.previous = this._head
            }
          } else {
            // 獲取當前須要插入的前一個節點
            let previous = this._nodeIndex(index - 1)
            let node = new LinkedList.Node(previous, el, previous.next)
            previous.next = node
            // 判斷是否插入鏈表尾
            if (previous.next) {
              // 重設鏈表尾
              this._last = node
            }
          }
          // 記錄列表長度
          this._length++
          return true
        }
        // 刪除指定位置的元素
        removeAt (index) {
          if (index < 0 || index > this._length - 1) return false
          let current = null
          // 當index === 0表示刪除鏈表頭元素
          if (index === 0) {
            // 緩存鏈表頭
            current = this._head
            // 改變鏈表頭
            this._head = current.next
            this._head.previous = null
          } else {
            // 獲取當前須要刪除的前一個節點
            let previous = this._nodeIndex(index - 1)
            // 獲取當前須要刪除的節點
            current = previous.next
            // 刪除元素
            previous.next = current.next
            // 判斷是否刪除鏈表尾
            if (index === this._length - 1) {
              // 重設鏈表尾
              this._last = previous
            } else {
              current.next.previous = previous
            }
          }
          // 記錄列表長度
          this._length--
          return current.el
        }
        // 獲取指定元素的索引位置
        indexOf (el) {
          // 索引
          let index = 0
          // 緩存鏈表頭
          let current = this._head
          while (current) {
            // 判斷給定的元素是否等於當前元素節點的元素
            // 若是時引用類型的還須要自定義判斷引用類型是否相等
            if (el === current.el) {
              // 若是相等就返回索引
              return index
            }
            current = current.next
            index++
          }
          return -1
        }
        // 根據索引獲取元素
        get (index) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el
        }
        // 根據索引修改元素
        set (index, el) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el = el
        }
        // 獲取鏈表頭
        getHead () {
          return this._head
        }
        // 獲取鏈表尾
        getLast () {
          return this._last
        }
        // 刪除指定的元素
        remove (el) {
          return this.removeAt(this.indexOf(el))
        }
        // 檢查鏈表是否爲空
        isEmpty () {
          return this._length === 0
        }
        // 獲取鏈表的大小
        size () {
          return this._length
        }
        // 清空鏈表
        clear () {
          this._head = null
          this._length = null
          this._length = 0
        }
      }
      複製代碼
    4. 雙向循環鏈表

      class LinkedList {
        constructor () {
          this._head = null
          this._last = null
          this._length = 0;
          LinkedList.Node = class {
            constructor (previous, el, next) {
              this.previous = previous
              this.el = el
              this.next = next
            }
          }
        }
        _nodeIndex (index) {
          let node = this._head
          for (let i = 0; i < index; i++) {
            node = node.next
          }
          return node
        }
        append (el) {
          if (!this._head) {
            this._head = new LinkedList.Node(null, el, null)
            // ========= 不一樣點 =========
            // 鏈表頭的下一個和上一個指向指向本身
            this._head.next = this._head
            this._head.previous = this._head
            this._last = this._head
          } else {
            // ========= 不一樣點 =========
            // 建立節點給定上一個和下一個的指向
            let node = new LinkedList.Node(this._last, el, this._head)
            this._last.next = node
            this._last = node
            // 改變鏈表頭的上一個指向
            this._head.previous = this._last
          }
          this._length++
          return true
        }
        insert (index, el) {
          if (index < 0 || index > this._length) return false
          if (index === 0) {
            if (!this._head) {
              this._head = new LinkedList.Node(null, el, null)
              // ========= 不一樣點 =========
              // 鏈表頭的下一個和上一個指向指向本身
              this._head.next = this._head
              this._head.previous = this._head
              this._last = this._head
            } else {
              let node = this._head
              // ========= 不一樣點 =========
              // 須要改變鏈表尾的下一個指向
              this._head = new LinkedList.Node(this._last, el, node)
              node.previous = this._head
              this._last.next = this._head
            }
          } else {
            let previous = this._nodeIndex(index - 1)
            let node = new LinkedList.Node(previous, el, previous.next)
            // ========= 不一樣點 =========
            // 須要改變插入後的上一個指向
            previous.next.previous = node
            previous.next = node
            if (index === this._length) {
              this._last = node
            }
          }
          this._length++
          return true
        }
        removeAt (index) {
          if (index < 0 || index > this._length - 1) return false
          let current = null
          if (index === 0) {
            current = this._head
            this._head = current.next
            // ========= 不一樣點 =========
            this._head.previous = this._last
            this._last.next = this._head
          } else {
            let previous = this._nodeIndex(index - 1)
            current = previous.next
            previous.next = current.next
            // ========= 不一樣點 =========
            current.next.previous = previous
            if (index === this._length - 1) {
              this._last = previous
            }
          }
          this._length--
          return current.el
        }
        indexOf (el) {
          let index = 0
          let current = this._head
          while (current) {
            if (el === current.el) {
              return index
            }
            current = current.next
            index++
          }
          return -1
        }
        get (index) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el
        }
        set (index, el) {
          if (index < -1 && index >= this._length) throw Error('越界異常')
          return this._nodeIndex(index).el = el
        }
        getHead () {
          return this._head
        }
        getLast () {
          return this._last
        }
        remove (el) {
          return this.removeAt(this.indexOf(el))
        }
        isEmpty () {
          return this._length === 0
        }
        size () {
          return this._length
        }
        clear () {
          this._head = null
          this._last = null
          this._length = 0
        }
      }
      複製代碼
      方法名 說明
      append 鏈表尾添加元素
      insert 在鏈表中插入元素
      removeAt 刪除指定位置的元素
      indexOf 獲取指定元素的索引位置
      get 根據索引獲取元素
      set 根據索引修改元素
      getHead 獲取鏈表頭
      getLast 獲取鏈表尾
      remove 刪除指定的元素
      isEmpty 檢查鏈表是否爲空
      size 獲取鏈表的大小
      clear 清空鏈表
  3. 集合

    1. 什麼是集合

      集合,簡稱集,是數學中一個基本概念。

    2. 集合的特性:

      1. 肯定性

        給定一個集合,任給一個元素,該元素或者屬於或者不屬於該集合,兩者必居其一,不容許有模棱兩可的狀況出現。

      2. 互異性

        一個集合中,任何兩個元素都認爲是不相同的,即每一個元素只能出現一次。有時須要對同一元素出現屢次的情形進行刻畫,可使用多重集,其中的元素容許出現屢次。

      3. 無序性

        一個集合中,每一個元素的地位都是相同的,元素之間是無序的。集合上能夠定義序關係,定義了序關係後,元素之間就能夠按照序關係排序。但就集合自己的特性而言,元素之間沒有必然的序。

    // 注意: 集合裏鍵和值同樣
    class MySet {
      constructor () {
        // 私有屬性
        this._items = {}
      }
      // 檢查元素是否存在
      has (value) {
        return this._items.hasOwnProperty(value)
      }
      // 添加元素
      add (value) {
        // 當元素不存在進行添加,不然返回false
        if (!this.has(value)) {
          this._items[value] = value
          return value
        }
        return false
      }
      // 刪除元素
      delete (value) {
        if (this.has(value)) {
          // 刪除對象屬性
          delete this._items[value]
          return true
        }
        return false
      }
      // 清空集合
      clear () {
        this._items = {}
      }
      // 獲取集合的大小
      size () {
        return Object.keys(this._items).length
      }
      // 獲取全部的值
      values () {
        return Object.values(this._items)
      }
      // ================= 集合之間的操做 ====================
      // 並集
      union (otherSet) {
        let res = new MySet()
        this.values().forEach(el => {
          res.add(el)
        })
        otherSet.values().forEach(el => {
          res.add(el)
        })
        return res
      }
      // 交集
      intersection (otherSet) {
        let res = new MySet()
        this.values.forEach(el => {
          if (otherSet.has(el)) {
            res.add(el)
          }
        })
        return res
      }
      // 補集
      subtract (otherSet) {
        let res = new MySet()
        this.values().forEach(el => {
          if (!otherSet.has(el)) {
            res.add(el)
          }
        })
      }
      // 交集的補集
      exclusiveOr (otherSet) {
        let res = new MySet()
        this.subtract(otherSet).values().forEach(el => {
          res.add(el)
        })
        otherSet.subtract(this).values().forEach(el => {
          res.add(el)
        })
        return res
      }
      // ================= 集合之間的操做 ====================
    }
    複製代碼
    方法名 說明
    has 檢查元素是否存在
    add 添加元素
    delete 刪除元素
    clear 清空集合
    size 獲取集合的大小
    values 獲取全部的值
    union 並集
    intersection 交集
    subtract 補集
    exclusiveOr 交集的補集
  4. 字典

    1. 什麼是字典

      字典是一種以 鍵-值 對形式存儲數據的數據結構

    class Dictionary {
      constructor () {
        // 私有屬性
        this._items = {}
      }
      // 檢查鍵是否存在
      has (key) {
        // return key in this._items
        return this._items.hasOwnProperty(key)
      }
      // 添加鍵值對
      set (key, value) {
        this._items[key] = value
      }
      // 經過鍵刪除
      delete (key) {
        // 判斷鍵存不存在
        if (this.has(key)) {
          delete this._items[key]
          return true
        }
        return false
      }
      // 經過鍵獲取數據
      get (key) {
        // 判斷鍵存不存在
        if (this.has(key)) {
          return this._items[key]
        }
        return undefined
      }
      // 獲取全部的鍵
      keys () {
        return Object.keys(this._items)
      }
      // 清空字典
      clear () {
        this._items = {}
      }
    }
    複製代碼
    方法名 說明
    has 檢查鍵是否存在
    set 添加鍵值對
    delete 經過鍵刪除
    get 經過鍵獲取數據
    keys 獲取全部的鍵
    clear 清空字典

    下面來道算法題(兩數之和)(題型引自LeetCode) 給定一個整數數組nums和一個目標值target,請你在該數組中找出和目標值的那兩個整數,並返回他們的數組下標。

    你能夠假設每種輸入只會對應一個答案。可是,你不能重複利用這個數組中一樣的元素。

    示例:

    給定 nums = [2, 7, 11, 15], target = 9

    由於 nums[0] + nums[1] = 2 + 7 = 9

    因此返回 [0, 1]

    暴力法:

    function twoSum (nums, target) {
      let arr = []
      for (let i = 0; i < nums.length; i++) {
        for (let j = i + 1; j < nums.length; j++) {
          if (nums[i] + nums[j] === target) {
            arr = [i, j]
          }
        } 
      }
      return arr
    }
    複製代碼

    字典實現:

    function twoSum (nums, target) {
        let dictionary = new Dictionary()
        for (let i = 0; i < nums.length; i++) {
           let val = target - nums[i]
           if (dictionary.has(val)) return [dictionary.get(val), i]
           dictionary.set(nums[i], i)
        }
    }
    複製代碼

    使用Map實現:

    function twoSum (nums, target) {
        let map = new Map()
        for (let i = 0; i < nums.length; i++) {
           let val = target - nums[i]
           if (map.has(val)) return [map.get(val), i]
           map.set(nums[i], i)
        }
    }
    複製代碼
  5. 哈希表

    1. 什麼時哈希表

      散列表(Hash table,也叫哈希表),是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它經過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫作散列函數,存放記錄的數組叫作散列表。

    2. 爲何須要哈希表

      數組的特色是:尋址容易,插入和刪除困難。

      鏈表的特色是:尋址困難,插入和刪除容易。

      哈希表就是綜合二者的特性,尋址容易,插入刪除也容易的數據結構。

    1. 基本哈希表

      缺點:散列衝突,不一樣的key通過散列函數的計算獲得了相同的散列地址。

      class HashTable {
        // 構造函數
        constructor () {
          // 私有屬性
          this._items = []
        }
        // 私有方法
        // 散列函數
        // 散列值重複性過高了
        _loseloseHashCode (key) {
          let hash = 0
          for (let i = 0; i < key.length; i++) {
            hash += key[i].charCodeAt()
          }
          return hash % 37
        }
        // 私有方法
        // 散列函數
        // 比上面的更好
        _djb2HashCode (key) {
          let hash = 5381
          for (let i = 0; i < key.length; i++) {
            hash = hash * 33 + key[i].charCodeAt
          }
          return hash % 1013
        }
        // 添加
        put (key, value) {
          // 獲取散列值
          // const position = this._djb2HashCode(key)
          const position = this._loseloseHashCode(key)
          this._items[position] = value
        }
        // 獲取
        get (key) {
          // const position = this._djb2HashCode(key)
          const position = this._loseloseHashCode(key)
          return this._items[position]
        }
        // 刪除
        remove (key) {
          // const position = this._djb2HashCode(key)
          const position = this._loseloseHashCode(key)
          this._items[position] = undefined
        }
        // 清空哈希表
        clear () {
        	this._items = []
        }
      }
      複製代碼
    2. 分離連接法

      散列表的每一個單元是一個鏈表

      將散列到同一個值的全部元素保留到一個鏈表中

      優勢:解決散列衝突

      缺點:性能打折

      class HashTable {
        // 構造函數
        constructor () {
          // 私有屬性
          this._items = []
          // 內部輔助類
          HashTable.Node = class {
            constructor (key, value) {
              this.key = key
              this.value = value
            }
          }
        }
        _loseloseHashCode (key) {
          let hash = 0
          for (let i = 0; i < key.length; i++) {
            hash += key[i].charCodeAt()
          }
          return hash % 37
        }
        _djb2HashCode (key) {
          let hash = 5381
          for (let i = 0; i < key.length; i++) {
            hash = hash * 33 + key[i].charCodeAt
          }
          return hash % 1013
        }
        put (key, value) {
          const position = this._loseloseHashCode(key)
          // 插入的時候先判斷當前位置是否爲空,若是爲空就建立鏈表再添加數據
          if (this._items[position]) {
            this._items[position].append(new HashTable.Node(key, value))
          } else {
            // 使用鏈表
            // 注意這裏不能使用循環鏈表
            // 不懂的能夠看上面的鏈表實現
            let l = new LinkedList()
            this._items[position] = l
            l.append(new HashTable.Node(key, value))
          }
        }
        get (key) {
          const position = this._loseloseHashCode(key)
          if (this._items[position]) {
            let current = this._items[position].getHead()
            // 上面提到不能使用循環鏈表,是由於這裏判斷會形成死循環
            while (current) {
              if (current.el.key === key) {
                return current.el.value
              }
              current = current.next
            }
          } else {
            return undefined
          }
        }
        remove (key) {
          const position = this._loseloseHashCode(key)
          if (this._items[position]) {
            let current = this._items[position].getHead()
            while (current) {
              if (current.el.key === key) {
                let res = this._items[position].remove(current.el)
                if (this._items[position].isEmpty()) {
                  this._items[position] = undefined
                }
                return res
              }
              current = current.next
            }
          } else {
            return false
          }
        }
        clear () {
        	this._items = []
        }
      }
      複製代碼
    3. 線性探查法

      線性探測是計算機程序解決散列表衝突時所採起的一種策略。

      優勢:解決散列衝突

      缺點:性能打折

      class HashTable {
        // 構造函數
        constructor () {
          // 私有屬性
          this._items = []
          // 內部輔助類
          HashTable.Node = class {
            constructor (key, value) {
              this.key = key
              this.value = value
            }
          }
        }
        _loseloseHashCode (key) {
          let hash = 0
          for (let i = 0; i < key.length; i++) {
            hash += key[i].charCodeAt()
          }
          return hash % 37
        }
        _djb2HashCode (key) {
          let hash = 5381
          for (let i = 0; i < key.length; i++) {
            hash = hash * 33 + key[i].charCodeAt
          }
          return hash % 1013
        }
        put (key, value) {
          let position = this._loseloseHashCode(key)
          // 插入的時候先判斷當前位置是否爲空,若是不爲空就接着日後直到找到空位置
          if (!this._items[position]) {
            this._items[position] = new HashTable.Node(key, value)
          } else {
            while (this._items[++position]) {
              this._items[position] = new HashTable.Node(key, value)
            }
          }
        }
        get (key) {
          const position = this._loseloseHashCode(key)
          // 查找時先判斷是否爲空,若是不爲空就判斷key是否相等,不相等就往下找
          for (let i = position; this._items[position]; i++) {
            if (this._items[i].key === key) {
              return this._items[i].value
            }
          }
          return undefined
        }
        remove (key) {
          const position = this._loseloseHashCode(key)
          for (let i = position; this._items[position]; i++) {
            if (this._items[position].key === key) {
              let res = this._items[i].value
              this._items[i] = undefined
              return res
            }
          }
          return undefined
        }
        clear () {
        	this._items = []
        }
      }
      複製代碼
      方法名 說明
      put 添加
      get 獲取
      remove 刪除
      clear 清空哈希表
  6. 樹形結構是一層次的嵌套結構。 一個樹形結構的外層和內層有類似的結構, 因此這種結構多能夠遞歸的表示。經典數據結構中的各類樹狀圖是一種典型的樹形結構:一顆樹能夠簡單的表示爲根, 左子樹, 右子樹。 左子樹和右子樹又有本身的子樹。

    class Tree {
      constructor () {
        // 私有變量根
        this._root = null
        // 內部輔助類
        Tree.Node = class {
          constructor (el) {
            this.el = el
            this.left = null
            this.rigth = null
          }
        }
      }
      // 私有方法
      _insertNode (node, newNode) {
        // left方向
        if (newNode.el < node.el) {
          if (!node.left) {
            node.left = newNode
          } else {
         this._insertNode(node.left, newNode)
          }
        }
        // rigth方向
        if (newNode.el > node.el) {
          if (!node.rigth) {
            node.rigth = newNode
          } else {
            this._insertNode(node.rigth, newNode)
          }
        }
      }
      // 私有方法
      _searchNode (node, el) {
        if (node) return false
        if (node.el = el) return true
        if (node.el > el) this._searchNode(node.rigth, el)
        if (node.el < el) this._searchNode(node.left, el)
      }
      // 私有方法
      _removeNode (node, el) {
        if (!node) return null
        if (node.el > el) {
          node.left = this._removeNode(node.left, el)
          return node
        }
        if (node.el < el) {
          node.rigth = this._removeNode(node.rigth, el)
          return node
        }
        if (node.el === el) {
          if (!node.left && !node.rigth) {
            return null
          }
          if (node.left && !node.rigth) {
            return node.left
          }
          if (!node.left && node.rigth) {
            return node.rigth
          }
          if (node.left && node.rigth) {
            let minNode = this._minNode(node.rigth)
            node.el = minNode.el
            node.rigth = this._removeNode(node.rigth, minNode.el)
            return node
          }
        }
      }
      // 私有方法
      // 深度優先遍歷可進一步按照根節點與其左右子節點的訪問前後順序劃分爲前序遍歷、中序遍歷和後序遍歷。
      // 根節點放在左節點的左邊,稱爲前序遍歷;
      // 根節點放在左節點和右節點的中間,稱爲中序遍歷;
      // 根節點放在右節點的右邊,稱爲後序遍歷。
      _traverseNode (node, callback) {
        // 終止條件
        if (!node) return
        // 前序遍歷
        // callback(node.el)
        this._traverseNode(node.left, callback) 
        // 中序遍歷
        // callback(node.el)
        this._traverseNode(node.rigth, callback)
        // 後序遍歷
        callback(node.el)
      }
      // 私有方法
      // 棧與深度優先遍歷
      // 只是實現了前序遍歷
      // 中序遍歷、後序遍歷,讀者能夠自行編寫,遞歸自己就和棧相關
      _traverseNodeStack (callback) {
        // 初始化棧並將根節點壓棧
        let stack = new Stack()
        stack.push(this._root)
        // 循環遍歷直到棧爲空
        while (!stack.isEmpty()) {
          // 出棧,並對其域進行訪問
          let node = stack.pop()
          callback(node.el)
          // 判斷rigth節點是否爲空,若是不爲空,入棧處理
          if (node.rigth) stack.push(node.rigth)
          // 判斷left節點是否爲空,若是不爲空,入棧處理
          if (node.left) stack.push(node.left)
        }
      }
      // 私有方法
      // 隊列與廣度優先遍歷
      // 從根節點開始,一層一層的訪問。實現的核心是經過隊列的入隊和出隊操做
      _traverseNodeQueue (callback) {
        // 初始化隊列並將根節點入隊
        let queue = new Queue()
        queue.enqueue(this._root)
        // 循環遍歷隊列直到隊列爲空
        while (!queue.isEmpty()) {
          // 出隊,並對其域進行訪問
          let node = queue.dequeue()
          callback(node.el)
          // 判斷left節點是否爲空,若是不爲空,入隊處理
          if (node.left) queue.enqueue(node.left)
          // 判斷rigth節點是否爲空,若是不爲空,入隊處理
          if (node.rigth) queue.enqueue(node.rigth)
        }
      }
      // 私有方法
      _minNode (node) {
        if (!node) return null
        while (node && node.left) {
          node = node.left
        }
        return node
      }
      // 私有方法
      _max (node) {
        if (!node) return null
        while (node && node.rigth) {
          node = node.rigth
        }
        return node.el
      }
      // 插入節點
      insert (el) {
        if (!this._root) {
          this._root = new Tree.Node(el)
        } else {
          this._insertNode(this._root, new Tree.Node(el))
        }
      }
      // 搜索節點
      search (el) {
        return this._searchNode(this._root, el)
      }
      // 最小值
      min () {
        let node = this._minNode(this._root)
        if (node) {
          return node.el
        }
        return null
      }
      // 最大值
      max () {
        return this._max(this._root)
      }
      // 刪除節點
      remove (el) {
        this._root = this._removeNode(this._root, el)
      }
      // 遍歷節點
      traverse (callback) {
        // this._traverseNode(this._root, callback)
        // this._traverseNodeStack(callback)
        this._traverseNodeQueue(callback)
      }
      // 獲取樹
      getTree () {
        return this._root
      }
    }
    
    // 示例:
    let tree = new Tree()
    tree.insert(11)
    tree.insert(8)
    tree.insert(6)
    tree.insert(9)
    tree.insert(12)
    tree.insert(10)
    tree.insert(13)
    tree.traverse(console.log)
    // 樹結構以下:
                11
             8      12
           6   9       13
                 10
    // 遞歸前序 >>> 11 8 6 9 10 12 13
    // 遞歸中序 >>> 6 8 9 10 11 12 13
    // 遞歸後續 >>> 6 10 9 8 13 12 11
    // 棧與深度優先遍歷 >>> 11 8 6 9 10 12 13
    // 隊列與廣度優先遍歷 >>> 11 8 12 6 9 13 10
    複製代碼
    方法名 說明
    insert 插入節點
    search 搜索節點
    min 最小值
    max 最大值
    remove 刪除節點
    traverse 遍歷節點
    getTree 獲取樹

排序算法

  1. 冒泡排序

    冒泡排序(Bubble Sort),是一種計算機科學領域的較簡單的排序算法。

    它重複地走訪過要排序的元素列,依次比較兩個相鄰的元素,若是他們的順序(如從大到小、首字母從A到Z)錯誤就把他們交換過來。走訪元素的工做是重複地進行直到沒有相鄰元素須要交換,也就是說該元素列已經排序完成。

    這個算法的名字由來是由於越大的元素會經由交換慢慢「浮」到數列的頂端(升序或降序排列),就如同碳酸飲料中二氧化碳的氣泡最終會上浮到頂端同樣,故名「冒泡排序」。

    function bubbleSort (arr) {
      for (let i = 0; i < arr.length - 1; i++) {
        for (let j = i + 1; j < arr.length; j++) {
          if (arr[i] > arr[j]) {
            [arr[i], arr[j]] = [arr[j], arr[i]]
          }
        }
      }
    }
    複製代碼
  2. 快速排序

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

    快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:經過一趟排序將要排序的數據分割成獨立的兩部分,其中一部分的全部數據都比另一部分的全部數據都要小,而後再按此方法對這兩部分數據分別進行快速排序,整個排序過程能夠遞歸進行,以此達到整個數據變成有序序列。

    function quickSort (arr) {
      function quick (arr, start, end) {
        // 終止條件
        if (start >= end) return
        // 分界值
        let stard = arr[start]
        // 標記從分界值開始向左找比 stard 大的數的位置
        let low = start
        // 標記右側 high 向左找比 stard 小的數的位置
        let high = end
        while (low < high) {
          // 從 high 開始向左,找到第一個比 stard 小或者等於 stard 的數,標記位置
          while (low < high && stard <= arr[high]) {
            high--
          }
          // 把找到的數放到左側的空位 low 標記了這個空位
          arr[low] = arr[high]
          // 從分界值開始向左找,找到第一個比 stard 大或者等於 stard 的數,標記位置
          while (low < high && arr[low] <= stard) {
            low++
          }
          // 把找到的數放到左側的空位 high 標記了這個空位
          arr[high] = arr[low]
        }
        arr[low] = stard
        // 對 stard 左側全部數進行上述的排序
        quick(arr, start, low - 1)
        // 對 stard 右側全部數進行上述的排序
        quick(arr, low + 1, end)
      }
      quick(arr, 0, arr.length - 1)
    }
    複製代碼
  3. 插入排序

    插入排序(Insertion sort)是一種簡單直觀且穩定的排序算法。若是有一個已經有序的數據序列,要求在這個已經排好的數據序列中插入一個數,但要求插入後此數據序列仍然有序,這個時候就要用到一種新的排序方法——插入排序法,插入排序的基本操做就是將一個數據插入到已經排好序的有序數據中,從而獲得一個新的、個數加一的有序數據,算法適用於少許數據的排序,時間複雜度爲O(n^2)。是穩定的排序方法。插入算法把要排序的數組分紅兩部分:第一部分包含了這個數組的全部元素,但將最後一個元素除外(讓數組多一個空間纔有插入的位置),而第二部分就只包含這一個元素(即待插入元素)。在第一部分排序完成後,再將這個最後元素插入到已排好序的第一部分中。

    function insertSort (arr) {
      for (let i = 1; i < arr.length; i++) {
        // 若是當前的值比前一個值小
        if (arr[i] < arr[i - 1]) {
          // 把當前的值緩存下來
          let temp = arr[i]
          let j = i -1
          // 遍歷當前位置前面全部的值
          for (; j >= 0 && arr[j] > temp; j--) {
            // 把當前的值賦值給後一位
            arr[j + 1] = a[j]
          }
          // 把臨時變量賦值給不知足條件的後一位
          arr[j + 1] = temp
        }
      }
    }
    複製代碼
  4. 希爾排序

    希爾排序(Shell's Sort)是插入排序的一種又稱「縮小增量排序」(Diminishing Increment Sort),是直接插入排序算法的一種更高效的改進版本。希爾排序是非穩定排序算法。該方法因D.L.Shell於1959年提出而得名。

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

    function shellSort (arr) {
      // 遍歷步長
      for (let d = arr.length / 2 >>> 0; d > 0; d = d / 2 >>> 0) {
        // 遍歷全部元素
        for (let i = d; i < arr.length; i++) {
          // 遍歷本組全部的元素
          for (let j = i - d; j > 0; j -= d) {
            if (arr[j] > arr[j + d]) {
              [arr[j], arr[j + d]] = [arr[j + d], arr[j]]
            }
          }
        }
      }
    }
    複製代碼
  5. 選擇排序

    選擇排序(Selection sort)是一種簡單直觀的排序算法。它的工做原理是:第一次從待排序的數據元素中選出最小(或最大)的一個元素,存放在序列的起始位置,而後再從剩餘的未排序元素中尋找到最小(大)元素,而後放到已排序的序列的末尾。以此類推,直到所有待排序的數據元素的個數爲零。選擇排序是不穩定的排序方法。

    function selecStort (arr) {
      for (let i = 0; i < arr.length; i++) {
        let minIndex = i
        for (let j = i + 1; j < arr.length; j++) {
          minIndex = arr[j] < arr[minIndex] ? j : minIndex
        }
        [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
      }
    }
    複製代碼
  6. 歸併排序

    歸併排序(MERGE-SORT)是創建在歸併操做上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個很是典型的應用。將已有序的子序列合併,獲得徹底有序的序列;即先使每一個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。

    function mergeSort (arr, low = 0, high = arr.length - 1) {
      function merge (arr, low, middle, high) {
        // 用於儲存歸併後的數組
        let temp = []
        // 記錄第一個數組中須要遍歷的下標
        let l = low
        // 記錄第二個數組中須要遍歷的下標
        let m = middle + 1
        // 遍歷兩個數組取出最小的值,放入臨時數組中
        while (l <= middle && m <= high) {
          if (arr[l] <= arr[m]) {
            temp.push(arr[l++])
          } else {
            temp.push(arr[m++])
          }
        }
        // 處理多餘的數據
        while (l <= middle) {
          temp.push(arr[l++])
        }
        while (m <= high) {
          temp.push(arr[m++])
        }
        // 把臨時數組的數據從新存入原數組中
        for (let i = 0; i < temp.length; i++) {
          arr[i + low] = temp[i]
        }
      }
    
      if(low < high) {
        let middle = (low + high) / 2 >>> 0 
        // 左子數組有序
        mergeSort(arr, low, middle)
        // 右子數組有序
        mergeSort(arr, middle + 1, high)
        merge(arr, low, middle, high)
      }
    }
    複製代碼
  7. 基數排序

    基數排序(radix sort)屬於「分配式排序」(distribution sort),又稱「桶子法」(bucket sort)或bin sort,顧名思義,它是透過鍵值的部份資訊,將要排序的元素分配至某些「桶」中,藉以達到排序的做用,基數排序法是屬於穩定性的排序,其時間複雜度爲O (nlog(r)m),其中r爲所採起的基數,而m爲堆數,在某些時候,基數排序法的效率高於其它的穩定性排序法。

    function radixSort (arr) {
      let max = Number.MIN_SAFE_INTEGER
      // 緩存數組最大的數
      for (let i = 0; i <arr.length; i++) {
        if (arr[i] > max) {
          max = arr[i]
        }
      }
      // 計算最大的數是幾位數
      let maxLength = (max + '').length
      // 建立數組臨時存儲數據
      let temp = new Array(10).fill(0)
      // 記錄數量
      let counts = new Array(10)
      // 根據最大的數是幾位數決定比較次數
      for (let i = 0, n = 1; i < maxLength; i++, n *= 10) {
        // 初始化二維數組
        temp = temp.map(() => {
          return []
        })
        counts.fill(0)
        for (let j = 0; j < arr.length; j++) {
          // 計算餘數
          let y = (arr[j] / n >>> 0) % 10
          temp[y].push(arr[j])
          counts[y]++
        }
        let index = 0
        // 取出數據
        for (let j = 0; j < counts.length; j++) {
          if (counts[j] === 0) continue
          for (let k = 0; k < counts[j]; k++) {
            arr[index++] = temp[j][k]
          }
        }
      }
    }
    複製代碼
相關文章
相關標籤/搜索