Javascript 實現基礎數據結構

Tip

計算機基礎不管是什么語言、什么開發崗位都是必須要了解的知識,本文主要是通過 JS 來實現一些常用的數據結構,很簡單。

本文插圖引用:啊哈!算法 ,由于是總結性文章,所以會不定時地修改并更新,歡迎關注!

Stack 棧

堆棧(stack),也可以直接稱為,它是一種后進先出的數據結構,并且限定了只能在一段進行插入和刪除操作。

如上圖所示,假設我們有一個球桶,并且桶的直徑只能允許一個球通過。如果按照 2 1 3 的順序把球放入桶中,然后要將球從桶中取出,取出的順序就是 3 1 2,先放入桶中的球被后取出,這就是 插入和刪除的原理。

我們接觸過的算法書基本也都是用c語言實現這個數據結構,它的特殊之處在于只能允許鏈接串列或者陣列的一端(top)進行 插入數據(push)和 輸出數據(pop)的運算。那么如果要用 JS 來實現它又該怎么操作呢?

// 棧可以用一維數組或連結串列的形式來完成

class Stack {
    constructor() {
        this.data = []
        this.top = 0
    }
    // 入棧
    push() {
        const args = [...arguments]
        args.forEach(arg => this.data[this.top++] = arg)
        return this.top
    }
    // 出棧
    pop() {
        if(this.top === 0) {
            throw new Error('The stack is already empty!')
        }
        const popData = this.data[--this.top]
        this.data = this.data.slice(0, -1)
        return popData
    }
    // 獲取棧內元素的個數
    length() {
        return this.top
    }
    //  判斷棧是否為空
    isEmpty() {
        return this.top === 0
    }
    // 獲取棧頂元素
    goTop() {
        return this.data[this.top-1]
    }
    // 清空棧
    clear() {
        this.top = 0
        return this.data = []
    }
}

通過 面向對象 的思想就可以抽象出一個 的對象,實例化:

const stack = new Stack()

stack.push(1, 2)
stack.push(3, 4, 5)
console.log(stack.data)
// [ 1, 2, 3, 4, 5 ]
console.log(stack.length())
// 5
console.log(stack.goTop())
// 5
console.log(stack.pop())
// 5
console.log(stack.pop())
// 4
console.log(stack.goTop())
// 3
console.log(stack.data)
// [ 1, 2, 3 ]

stack.clear()
console.log(stack.data)
// []

啊哈!算法 描述 的應用時,舉了一個求回文數的例子,我們也用 JS 來把它實現一波:

const isPalindrome = function(words) {
    const stack = new Stack()
    let copy = ''
    words = words.toString()

    words.split('').forEach(word => stack.push(word))

    while(stack.length() > 0) {
        copy += stack.pop()
    }
    return copy === words
}

console.log(isPalindrome('123ada321'))
// true
console.log(isPalindrome(1234321))
// true
console.log(isPalindrome('addaw'))
// false

Linked List 鏈表

在存儲一大波數的時候,我們通常使用的是數組,但有時候數組顯得不夠靈活,比如:有一串已經從小到大排好序的數 2 3 5 8 9 10 18 26 32。現需要往這串數中插入 6 使其得到的新序列仍符合從小到大排列。

如上圖所示,如果使用數組來實現的話,則需要將 8 和 8 后面的 數都依次往后挪一位,這樣操作顯然很耽誤時間,如果使用 鏈表 則會快很多:

鏈表(Linked List)是一種常見的數據結構。它是一種線性表,但是并不會按線性的順序存儲數據,而是在每一個節點里存到下一個節點的指針。和圖中一樣,如果需要在 8 前面插入一個 6,直接插入就行了,而無需再將 8 及后面的數都依次往后挪一位。

那么問題來了,如何使用 JS 實現 鏈表呢?還是運用 面向對象 的思想:

// 抽象節點類
function Node(el) {
    this.el = el
    this.next = null
}

// 抽象鏈表類
class LinkedList {
    constructor() {
        this.head = new Node('head')
    }
    // 查找數據
    find(item) {
        let curr = this.head
        while(curr.el !== item) {
            curr = curr.next
        }
        return curr
    }
    // 查找前一個節點
    findPre(item) {
        if(item === 'head') {
            throw new Error('Now is head!')
        }
        let curr = this.head
        while(curr.next && curr.next.el !== item) {
            curr = curr.next
        }
        return curr
    }
    // 在 item 后插入節點
    insert(newEl, item) {
        let newNode = new Node(newEl)
        let curr = this.find(item)
        newNode.next = curr.next
        curr.next = newNode
    }
    // 刪除節點
    remove(item) {
        let pre = this.findPre(item)
        if(pre.next !== null) {
            pre.next = pre.next.next
        }
    }
    // 顯示鏈表中所有元素
    display() {
        let curr = this.head
        while(curr.next !== null) {
            console.log(curr.next.el)
            curr = curr.next
        }
    }
}

實例化:

const list = new LinkedList()
list.insert('0', 'head')
list.insert('1', '0')
list.insert('2', '1')
list.insert('3', '2')
console.log(list.findPre('1'))
// Node { el: '0', next: Node { el: '1', next: Node { el: '2', next: [Object] } } }
list.remove('1')
console.log(list)
// LinkedList { head: Node { el: 'head', next: Node { el: '0', next: [Object] } } }
console.log(list.display())
// 0 2 3

Binary tree 二叉樹

二叉樹(binary tree)是一種特殊的樹。二叉樹 的特點是每個結點最多有兩個兒子,左邊的叫做左兒子,右邊的叫做右兒子,或者說每個結點最多有兩棵子樹。更加嚴格的遞歸定義是:二叉樹 要么為空,要么由根結點、左子樹和右子樹組成,而左子樹和右子樹分別是一棵 二叉樹

和前兩個例子一樣,通過類的抽象來實現 二叉樹

// 抽象節點類
function Node(key) {
    this.key = key
    this.left = null
    this.right = null
}
// 插入節點的方法
const insertNode = function(node, newNode) {
    if (newNode.key < node.key) {
        if (node.left === null) {
            node.left = newNode
        } else {
            insertNode(node.left, newNode)
        }
    } else {
        if (node.right === null) {
            node.right = newNode
        } else {
            insertNode(node.right, newNode)
        }
    }
} 
// 抽象二叉樹
class BinaryTree {
    constructor() {
        this.root = null
    }
    insert(key) {
        let newNode = new Node(key)
        if(this.root === null) {
            this.root = newNode
        } else {
            insertNode(this.root, newNode)
        }
    }
}

實例化測試一波:

const data = [8, 3, 10, 1, 6, 14, 4, 7, 13]

// 實例化二叉樹的數據結構
const binaryTree = new BinaryTree()
// 遍歷數據并插入
data.forEach(item => binaryTree.insert(item))
// 打印結果
console.log(binaryTree.root)
/*
Node {
  key: 8,
  left:
   Node {
     key: 3,
     left: Node { key: 1, left: null, right: null },
     right: Node { key: 6, left: [Object], right: [Object] } },
  right:
   Node {
     key: 10,
     left: null,
     right: Node { key: 14, left: [Object], right: null } } }
*/

總結

算法和數據結構是非常重要的一環,之后也會慢慢總結更新,歡迎關注!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容