js資料結構與演算法 陣列、棧部分

Cuke發表於2021-05-11

演算法的定義:

  • 一個有限指令集,每條指令的描述不依賴與語言。

  • 接受一些輸入

  • 產生輸出

  • 一定在有限步驟後終止

演算法的通俗理解

  • Algorithm 這個單詞本意就是解決問題的辦法/步驟邏輯

  • 資料結構的實現,離不開演算法

JS的陣列API掌握的已經足夠熟練了,所以不講了。

這裡只講一下js與其他語言有關的地方

  • 首先常見語言的陣列不能存放不同的資料型別,因此封裝是通常存放在陣列中的是object型別

  • 常見的語言的陣列的容量不會自動改變,因此需要擴容。擴容在c是極為效率低的一個操作

  • 同樣低效率的操作還有在陣列中間進行插入和刪除的操作。因為要移動後面的或者前面的所有節點。

    優點:

    • 在有關於下標的時候,陣列方便的很。

    陣列是可以在任何位置進行增加或者刪除的一種線性結構。

    有時候必須對資料進行限制,對其任意性加以限制。

    棧和佇列就是這種的受限的線性結構。

    特性:後進先出(last in first out)

    • 只允許在一端進行插入和刪除操作,這端叫棧頂。相對的其中另一端叫棧低

    • 插入叫入棧,進棧或者壓棧

    • 刪除叫出棧或者退棧

    程式中什麼是使用棧實現的呢?

    • 學了這麼久的程式設計,是否聽說過,函式呼叫棧呢?

    • 我們知道函式之間和相互呼叫:A呼叫B,B中又呼叫C,C中又呼叫D.

    • 那樣在執行的過程中,會先將A壓入棧,A沒有執行完,所有不會彈出 棧

    • 在A執行的過程中呼叫了B,會將B壓入到棧,這個時候B在棧頂,A在 棧底

    • 如果這個時候B可以執行完,那麼B會彈出棧,但是B有執行完嗎?沒有 它呼叫了C.

    • 所以C會壓棧,並且在棧頂.而C呼叫了D,D會壓入到棧頂.

    • 所以當前的棧順序是:棧頂A->B->C->D棧頂

    • D執行完,彈出棧.C/B/A依次彈出棧.

    • 所以我們有函式呼叫棧的稱呼,就來自於它們內部的實現機制.(通過 棧來實現的)

    例題

    有六個元素6,5,4,3,2,1的順序進棧,問下列哪一個不是合法的出棧序列?(c)
    A.543612 B.453216 C.346521 D.234156

    // 棧的封裝
    function Stack() {
    // 棧中的屬性
    this.items = []

    }
    // 原型要放在外面,這樣不會重複執行。
    // 實測放在外面新建五個物件平均0.05ms。放在外面要0.15ms

    // 1.入棧操作
    Stack.prototype.push = function (params) {
    this.items.push(params)
    }
    // 2.取出棧頂 元素
    Stack.prototype.pop = function () {
    return this.items.pop()
    }
    // 3.檢視棧頂 元素
    Stack.prototype.peek = function () {
    return this.items[this.items.length - 1]
    }

    // 4.判斷棧是否為空
    Stack.prototype.isEmpty = function () {
    return this.items.length === 0
    }

    // 5.獲取棧中元素個數
    Stack.prototype.size = function () {
    return this.items.length
    }
    // 6.toString方法
    Stack.prototype.toString = function () {
    let str = this.items.reduce((pre, cur) => {
    return pre + ‘ ‘ + cur
    })
    return str
    }

    console.time(‘global’)

    // 棧的使用
    let s = new Stack()
    let s1 = new Stack()
    let s2 = new Stack()
    let s3 = new Stack()


    console.timeEnd(‘global’)

    // 棧的操作
    // s.push(10)
    // s.push(20)
    // s.push(30)
    // console.log(s); // Stack{ items:[10,20,30]}
    // s.pop()
    // console.log(s);
    // console.log(s.peek());
    // console.log(s);
    // console.log(s.size());
    // s.push(40)
    // console.log(s.toString());

    export default Stack

    import Stack from ‘./1_棧的封裝.js’

    // 100
    // 計算:100/2餘數:0
    // 計算:50/2餘數:0
    // 計算:25/2餘數:1
    // 計算:12/2餘數:0
    // 計算:6/2餘數:0
    // 計算:3/2餘數:1
    // 計算:1/2餘數:1
    // 從底下到上面:1100100

    // 主要是運用隊棧的進去再拿出來
    // 思路:
    // 1.計算主要留下兩個有用的數,就是餘數和除法的結果。
    // 2.結果儲存下來接著用
    // 3.餘數直接放到棧裡
    // 4.結束條件是當除法結果為0的時候,結束。
    // 5.然後把陣列搗騰出來組合成字串就行了

    function dec2bin(decNumber) {
    let stack = new Stack()
    while (decNumber > 0) {
    stack.push(decNumber % 2)
    decNumber = Math.floor(decNumber / 2)
    }
    let res = ‘’

    console.log(stack.isEmpty());
    while (!stack.isEmpty()) {
    res += stack.pop()
    }
    console.log(res);
    }

    dec2bin(1)

    // 佇列本質還是陣列
    // 這波用的class 效能和直接構造原型的方法基本一致。

    class Queue {
    constructor() {
    this.items = []
    }
    // enqueue
    emqueue(element) {
    this.items.push(element)
    }
    // dequeue
    dequeue() {
    return this.items.shift()
    }
    // front
    front() {
    return this.items[this.items.length - 1]
    }
    // isEmpty
    isEmpty() {
    return this.items.length === 0
    }
    // size
    size() {
    return this.items.length
    }
    // toString
    toString() {
    let str = this.items.reduce((pre, cur) => {
    return pre + ‘ ‘ + cur
    })
    return str
    }
    }


    console.time(‘global’)
    let queue = new Queue()
    console.timeEnd(‘global’)
    queue.emqueue(10)
    queue.emqueue(20)
    queue.emqueue(30)
    queue.dequeue()
    console.log(queue);

    // 匯出,供2用
    export default Queue

    // 擊鼓傳花是一個常見的面試演算法題.使用佇列可以非常方便的實現最終的結果.

    // 原遊戲規則:
    // 口 班級中玩一個遊戲,所有學生圍成一圈,從某位同學手裡開始向旁邊的同學傳一束花.
    // 口 這個時候某個人(比如班長),在擊鼓,鼓聲停下的一顆,花落在誰手裡,誰就出來表演節目.

    // 修改遊戲規則:
    // 口 我們來修改一下這個遊戲規則.
    // 口幾個朋友一起玩一個遊戲,圍成一圈,開始數數,數到某個數字的人自動淘汰
    // 口最後剩下的這個人會獲得勝利,請問最後剩下的是原來在哪一個位置上的人?

    // 封裝一個基於佇列的函式
    // 口 引數:所有參與人的姓名,基於的數字
    // 口結果:最終剩下的一人的姓名


    // 解決思路
    // 1.佇列裡不斷迴圈,佇列外加一個計數器。
    // 2.出隊一個計數器+1,看計數器等不等於5,不過不等於,則證明不是第五個,所以不淘汰。
    // 3.不淘汰的元素出隊,然後再加入到隊尾,計數器加1
    // 4.淘汰的直接出隊,不加入到隊尾,計數器歸為1
    // 5.這樣人越來越少,判斷結束的條件是佇列的元素是否等於一個

    import Queue from ‘./1_封裝佇列.js’

    let q1 = new Queue()

    let arr = [‘小李’, ‘bill’]

    for (let item of arr) {
    q1.emqueue(item)
    }
    let i = 1
    // console.log(typeof q1.size());
    while (q1.size() > 1) {
    console.log(111);
    let tmp = q1.dequeue()
    if (i != 5) {
    q1.emqueue(tmp)
    i++
    } else if (i == 5) {
    i = 1
    }
    }

    console.log(q1);

    // 優先順序佇列的特點:
    // 口 我們知道,普通的佇列插入一個元素,資料會被放在後端.並且需要前面所有的元素都處理完成後才會處理前面的資料.
    // 口 但是優先順序佇列,在插入一個元素的時候會考慮該資料的優先順序.
    // 口 和其他資料優先順序 進行比較.
    // 口 比較完成後,可以得出這個元素在佇列中正確的位置
    // 口 其他處理方式,和基本佇列的處理方式一樣.

    // 二優先順序佇列主要考慮的問題:
    // 口每個元素不再只是一個資料,而且包含資料的優先順序
    // 口在新增方式中,根據優先順序放入正確的位置.

    // 現實中的優先順序佇列
    // 1. 機場登機 老人和小孩,或者商務艙的多
    // 2. 醫院的急診室和普通病房
    import Queue from ‘./1_封裝佇列.js’

    // 優先順序佇列的封裝
    // 優先順序佇列和普通佇列的區別僅在於 優先順序佇列的插入與普通的不同
    // 所以可以僅僅通過定義一個插入佇列,然後剩下的直接通過extends繼承下來就可以了。
    class PriorityQueue extends Queue {

    constructor() {
    // 要繼承的話必須執行super()方法,因為super是呼叫原來的父類,將方法和值賦值到this上。
    // 和原先老的es5寫法是不一樣的,原來的es5是通過原型鏈來實現的.
    super()
    this.items = []
    }

    emqueue(element, priority) {
    let queueElement = new QueueElement(element, priority)
    if (this.items.length === 0) {
    this.items.push(queueElement)
    } else {
    let added = false
    this.items.every((item, index) => {
    if (queueElement.priority < item.priority) {
    this.items.splice(index, 0, queueElement)
    added = true;
    // 這裡用every 或者 some 都可以break。
    // break的方法就是 every中用return false
    // some中用return true
    return false
    }
    })
    if (!added) {
    this.items.push(queueElement)
    }
    }
    }
    }


    // es6 class 沒有內部類的概念,所以不推薦用function寫在裡面這種寫法
    // 直接在外面寫就行,效能高。
    // 內部類的寫法就是重複的去新建QueueElement,寫在裡面沒有任何意義。
    class QueueElement {
    constructor(element, priority) {
    this.element = element
    this.priority = priority
    }
    }

    let pq = new PriorityQueue()

    pq.emqueue(111, 1)
    pq.emqueue(222, 2)
    pq.emqueue(333, 0)
    pq.dequeue()

    優先順序佇列

    例題

    實現

    • 只能在前端進行刪除

    • 只能在後盾進行插入

    先進先出(fifo first in first out)

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章