Javascript 實(shí)現(xiàn)基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)

Tip

計(jì)算機(jī)基礎(chǔ)不管是什么語言娘扩、什么開發(fā)崗位都是必須要了解的知識同诫,本文主要是通過 JS 來實(shí)現(xiàn)一些常用的數(shù)據(jù)結(jié)構(gòu)粤策,很簡單。

本文插圖引用:啊哈误窖!算法 叮盘,由于是總結(jié)性文章,所以會(huì)不定時(shí)地修改并更新霹俺,歡迎關(guān)注汞舱!

Stack 棧

堆棧(stack)剂陡,也可以直接稱為,它是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),并且限定了只能在一段進(jìn)行插入和刪除操作母市。

如上圖所示,假設(shè)我們有一個(gè)球桶划栓,并且桶的直徑只能允許一個(gè)球通過怀估。如果按照 2 1 3 的順序把球放入桶中,然后要將球從桶中取出胡本,取出的順序就是 3 1 2牌柄,先放入桶中的球被后取出,這就是 插入和刪除的原理侧甫。

我們接觸過的算法書基本也都是用c語言實(shí)現(xiàn)這個(gè)數(shù)據(jù)結(jié)構(gòu)珊佣,它的特殊之處在于只能允許鏈接串列或者陣列的一端(top)進(jìn)行 插入數(shù)據(jù)(push)和 輸出數(shù)據(jù)(pop)的運(yùn)算蹋宦。那么如果要用 JS 來實(shí)現(xiàn)它又該怎么操作呢?

// 椫涠停可以用一維數(shù)組或連結(jié)串列的形式來完成

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
    }
    // 獲取棧內(nèi)元素的個(gè)數(shù)
    length() {
        return this.top
    }
    //  判斷棧是否為空
    isEmpty() {
        return this.top === 0
    }
    // 獲取棧頂元素
    goTop() {
        return this.data[this.top-1]
    }
    // 清空棧
    clear() {
        this.top = 0
        return this.data = []
    }
}

通過 面向?qū)ο?/strong> 的思想就可以抽象出一個(gè) 的對象冷冗,實(shí)例化:

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)
// []

啊哈!算法 描述 的應(yīng)用時(shí)虫碉,舉了一個(gè)求回文數(shù)的例子贾惦,我們也用 JS 來把它實(shí)現(xiàn)一波:

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 鏈表

在存儲(chǔ)一大波數(shù)的時(shí)候,我們通常使用的是數(shù)組敦捧,但有時(shí)候數(shù)組顯得不夠靈活须板,比如:有一串已經(jīng)從小到大排好序的數(shù) 2 3 5 8 9 10 18 26 32。現(xiàn)需要往這串?dāng)?shù)中插入 6 使其得到的新序列仍符合從小到大排列兢卵。

如上圖所示习瑰,如果使用數(shù)組來實(shí)現(xiàn)的話,則需要將 8 和 8 后面的 數(shù)都依次往后挪一位秽荤,這樣操作顯然很耽誤時(shí)間甜奄,如果使用 鏈表 則會(huì)快很多:

鏈表(Linked List)是一種常見的數(shù)據(jù)結(jié)構(gòu)。它是一種線性表窃款,但是并不會(huì)按線性的順序存儲(chǔ)數(shù)據(jù)课兄,而是在每一個(gè)節(jié)點(diǎn)里存到下一個(gè)節(jié)點(diǎn)的指針。和圖中一樣晨继,如果需要在 8 前面插入一個(gè) 6烟阐,直接插入就行了,而無需再將 8 及后面的數(shù)都依次往后挪一位紊扬。

那么問題來了蜒茄,如何使用 JS 實(shí)現(xiàn) 鏈表呢?還是運(yùn)用 面向?qū)ο?/strong> 的思想:

// 抽象節(jié)點(diǎn)類
function Node(el) {
    this.el = el
    this.next = null
}

// 抽象鏈表類
class LinkedList {
    constructor() {
        this.head = new Node('head')
    }
    // 查找數(shù)據(jù)
    find(item) {
        let curr = this.head
        while(curr.el !== item) {
            curr = curr.next
        }
        return curr
    }
    // 查找前一個(gè)節(jié)點(diǎn)
    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 后插入節(jié)點(diǎn)
    insert(newEl, item) {
        let newNode = new Node(newEl)
        let curr = this.find(item)
        newNode.next = curr.next
        curr.next = newNode
    }
    // 刪除節(jié)點(diǎn)
    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
        }
    }
}

實(shí)例化:

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)是一種特殊的樹餐屎。二叉樹 的特點(diǎn)是每個(gè)結(jié)點(diǎn)最多有兩個(gè)兒子檀葛,左邊的叫做左兒子,右邊的叫做右兒子腹缩,或者說每個(gè)結(jié)點(diǎn)最多有兩棵子樹屿聋。更加嚴(yán)格的遞歸定義是:二叉樹 要么為空,要么由根結(jié)點(diǎn)藏鹊、左子樹和右子樹組成胜臊,而左子樹和右子樹分別是一棵 二叉樹

和前兩個(gè)例子一樣伙判,通過類的抽象來實(shí)現(xiàn) 二叉樹

// 抽象節(jié)點(diǎn)類
function Node(key) {
    this.key = key
    this.left = null
    this.right = null
}
// 插入節(jié)點(diǎn)的方法
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)
        }
    }
}

實(shí)例化測試一波:

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

// 實(shí)例化二叉樹的數(shù)據(jù)結(jié)構(gòu)
const binaryTree = new BinaryTree()
// 遍歷數(shù)據(jù)并插入
data.forEach(item => binaryTree.insert(item))
// 打印結(jié)果
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 } } }
*/

總結(jié)

算法和數(shù)據(jù)結(jié)構(gòu)是非常重要的一環(huán),之后也會(huì)慢慢總結(jié)更新黑忱,歡迎關(guān)注宴抚!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勒魔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菇曲,更是在濱河造成了極大的恐慌冠绢,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件常潮,死亡現(xiàn)場離奇詭異弟胀,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)喊式,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門孵户,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岔留,你說我怎么就攤上這事夏哭。” “怎么了献联?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵竖配,是天一觀的道長。 經(jīng)常有香客問我里逆,道長进胯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任原押,我火速辦了婚禮胁镐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘班眯。我一直安慰自己希停,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布署隘。 她就那樣靜靜地躺著宠能,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磁餐。 梳的紋絲不亂的頭發(fā)上违崇,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機(jī)與錄音诊霹,去河邊找鬼羞延。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脾还,可吹牛的內(nèi)容都是我干的伴箩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鄙漏,長吁一口氣:“原來是場噩夢啊……” “哼嗤谚!你這毒婦竟也來了棺蛛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤巩步,失蹤者是張志新(化名)和其女友劉穎旁赊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椅野,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡终畅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了竟闪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片离福。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瘫怜,靈堂內(nèi)的尸體忽然破棺而出术徊,到底是詐尸還是另有隱情,我是刑警寧澤鲸湃,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布赠涮,位于F島的核電站,受9級特大地震影響暗挑,放射性物質(zhì)發(fā)生泄漏笋除。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一炸裆、第九天 我趴在偏房一處隱蔽的房頂上張望垃它。 院中可真熱鬧,春花似錦烹看、人聲如沸国拇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽酱吝。三九已至,卻和暖如春土思,著一層夾襖步出監(jiān)牢的瞬間务热,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工己儒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崎岂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓闪湾,卻偏偏與公主長得像冲甘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容