数据结构的JavaScript实现

241 阅读9分钟

一、数组

二、栈

栈是一种遵从后进先出原则的有序集合。新添加的或待删除的元素都保存在栈的末尾,称作栈顶,另一端叫做栈底。在栈里,新元素都靠近栈顶,旧元素都接近栈底。

class Stack{
    constructor(){
        this.item = []
    }
    push(element){
        this.item.push(element)
    }
    pop(){
        return this.item.pop()
    }
    peek(){
        return this.item[this.item.length-1]
    }
    isEmpty(){
        return this.item.length === 0
    }
    size(){
        return this.item.length
    }
    clear(){
        this.item = []
    }
    print(){
        console.log(item.toString())
    }
}

三、队列

队列是一种遵循先进先出原则的一组有序的项。队列在尾部添加元素,并从顶部移除元素。最新添加的元素必须排在队列的末尾。

class Queue{
    constructor(){
        this.item = []
    }
    enqueue(element){
        this.item.push(element)
    }
    dequeue(){
        return this.item.shift()
    }
    front(){
        return this.item[0]
    }
    isEmpty(){
        return this.item.length === 0
    }
    clear(){
        this.items = []
    }
    size(){
        return this.item.length
    }
}

四、链表

链表储存有序的元素集合,但不同于数组,链表的每个元素由一个储存元素本身的节点和一个指向下一个元素的引用组成。

1. 单向链表

节点只有链向下一个节点的链接。

Markdown

class Node{
    constructor(element){
        this.element = element
        this.next = null
    }
}
class LinkedList{
    constructor(){
        this.length = 0
        this.head = null
    }
    
/********末尾插入元素********/
    append(element){
        let node = new Node(element),current
        if(this.head === null){
            this.head = node
        }else{
            current = this.head
            while(current.next){
                current = current.next
            }
            current.next = node
        }
        this.length++
    }

/********指定位置插入元素********/
    insert(position, element){
        if(position>=0 && position<this.length){
            let node = new Node(element)
            let current = this.head
            let previous
            let index = 0
            if(position ===0 ){
                current.next = current
                current = node
            }else{
                while(index++ < position){
                    previous = current
                    current = current.next
                }
                node.next = current
                previous.next = node
            }
            length++
            return true
        }else{
            return false
        }
    }

/********指定位置删除元素********/
    removeAt(position){
        if(position>=0 && position<this.length){
            let current = this.head
            let previous
            let index = 0
            if(position ===0 ){
               this.head = current.next
            }else{
                while(index++ < position){
                    previous = current
                    current = current.next
                }
                previous.next = current.next
            }
            length--
            return current.element
        }else{
            return null
        }
    }

/********指定值删除元素********/
    remove(element){
        let index = this.indexOf(element)
        return this.removeAt(index)
    }

/********将链表转换成一个字符串********/
    toString(){
        let current = this.head
        let string = ""
        while(current){
            string += current.element + (current.next ? ">" : "")
            current = current.next
        }
        return string
    }
/********在链表中查找元素的位置********/
    indexOf(element){
        let current = this.head
        index = 0
        while(current){
            if(element === current.element){
                return index
            }
            index++
            current = current.next
        }
        return -1
    }
    
    isEmpty(){
        return this.length === 0    
    }
    
    size(){
        return this.length
    }
    
    getHead(){
        return this.head
    }
}

2. 双向链表

节点的链接是双向的,一个链向下一个元素,另一个链向前一个元素。

Markdown

class Node{
    constructor(element){
        this.element = element
        this.next = null
        this.prev = null
    }
}
class DoublyLinkedList{
    constructor(){
        this.length = 0
        this.head = null
        this.tail = null
    }
    insert(position, element){
        if(position>=0 && position<this.length){
            let node = new Node(element)
            let current = this.head
            let previous
            let index = 0
            if(position === 0){
                if(!this.head){
                    this.head = node
                    this.tail = node
                }else{
                    node.next = current
                    current.prev = node
                    this.head = node
                }
            }else if(position === this.length){
                current = this.tail
                current.next = node
                node.prev = current
                tail = node
            }else{
                while(index++ < position){
                    previous = current
                    current = current.next
                }
                node.next = current
                previous.next = node
                
                current.prev = node
                node.prev = previous
            }
            length++
            return true
        }else{
            return false
        }
    }
    removeAt(position){
        if(position>=0 && position<this.length){
            let current = this.head
            let previous
            let index = 0
            if(position ===0 ){
               this.head = current.next
               if(this.length === 1){
                   tail = null
               }else{
                   head.prev = null
               }
            }else if(position === this.length-1){
               current = tail
               tail = current.prev
               tail.next = null
            }else{
                while(index++ < position){
                    previous = current
                    current = current.next
                }
                previous.next = current.next
                current.next.prev = previous
            }
            length--
            return current.element
        }else{
            return null
        }
    }
}

3. 循环链表

最后一个元素指向下一个元素的指针不是引用null,而是指向第一个元素head。

Markdown

4. 双向循环链表

有指向head元素的tail.next,和指向tail元素的head.prev。

五、集合

无序且唯一的项,以值-值形式储存元素。

class Set{
    constructor(){
        this.items = {}
    }
    has(value){
        return value in this.items
    }
    add(value){
    
        if(!this.has(value)){
            this.items[value]=value
            return true
        }
        else{
            return false
        }
    }
    remove(){
        if(this.has(value)){
            delete this.items[value]
            return true
        }
        else{
            return false
        }
    }
    clear(){
        this.items = {}
    }
    size(){
        return Object.keys(this.items).length
    }
    values(){
        let values = []
        for(let i=0,keys=Object.keys(this.items); i<keys.length; i++){
            values.push(this.items[keys[i]])
        }
        return values
    }
}

六、字典和散列表

(一)字典

以键-值形式储存元素,以遍历整个数据结构的方式获取值。

class Dictionary{
    constructor(){
        this.items = {}
    }
    has(key){
        return key in this.items
    }
    set(key, value){
        item[key] = value
    }
    delete(key){
        if(this.has(key)){
            delete this.items[key]
            return true
        }
        else{
            return false
        }
    }
    get(key){
        return this.has[key] ? items[key] : undefined    
    }
    values(){
        let values = []
        for(let k in items){
            if(this.has(k)){
                values.push(items[key])
            }
        }
        return values
    }
    keys(){
        return Object.keys(items)
    }
    getItems(){
        return items
    }
    clear(){
        this.items = {}
    }
    size(){
        return Object.keys(this.items).length
    }
}

(二)散列表(哈希表)

以键-值形式储存元素,能够知道值的具体位置,因此能够快速检索到该值。

冲突版:将 key 的每个字符的 Unicode 码值的和加到 hash 变量中,hash会有冲突。

class HashTable{
    constructor(){
        this.table = []
    }
    loseloseHashCode(key){
        let hash = 0
        for(let i = 0; i<key.length; i++){
            hash += key.charCodeAt(i)
        }
        return hash % 37
    }
    put(key, value){
        let position = this.loseloseHashCode(key)
        console.log(position + ":" + key)
        table[position] = value
    }
    get(key){
        return table[loseloseHashCode(key)]
    }
    remove(key){
        table[loseloseHashCode(key)] = undefined
    }
}

解决冲突版:分离链接,为散列表的每一个位置创建一个链表并将元素存储在里面。

class valuePair{
    constructor(key, value){
        this.key = key
        this.value = value
    }
    toString(){
        return `[${this.key} - ${this.value}]`
    }
}
class HashTable{
    constructor(){
        this.table = []
    }
    loseloseHashCode(key){
        let hash = 0
        for(let i = 0; i<key.length; i++){
            hash += key.charCodeAt(i)
        }
        return hash % 37
    }
    put(key, value){
        let position = this.loseloseHashCode(key)
        if(table[position] === undefined){
            table[position] = new LinkedList()
        }
        table[position].append(new ValuePair(key, value))
    }
    get(key){
        let position = loseloseHashCode(key)
        if (table[position] !== undefined){
            let current = table[position].getHead()
            while(current.next){
                if (current.element.key === key){
                    return current.element.value
                }
                current = current.next
            }
            if (current.element.key === key){
                return current.element.value
            }
        }
        return undefined
    }
    remove(key){
        let position = loseloseHashCode(key)
        if (table[position] !== undefined){
            var current = table[position].getHead()
            while(current.next){
                if (current.element.key === key){
                    table[position].remove(current.element)
                    if (table[position].isEmpty()){
                        table[position] = undefined
                    }
                    return true
                }
                current = current.next;
            }
        if (current.element.key === key){
            table[position].remove(current.element)
            if (table[position].isEmpty()){
                table[position] = undefined
            }
            return true
            }
        }
        return false
    }
}

解决冲突版:线性探查,当想向表中某个位置加入一个新元素的时候,如果索引为index的位置已经被占据了,就尝试index+1的位置。如果index+1的位置也被占据了,就尝试index+2的位置,以此类推。

class valuePair{
    constructor(key, value){
        this.key = key
        this.value = value
    }
    toString(){
        return `[${this.key} - ${this.value}]`
    }
}
class HashTable{
    constructor(){
        this.table = []
    }
    loseloseHashCode(key){
        let hash = 0
        for(let i = 0; i<key.length; i++){
            hash += key.charCodeAt(i)
        }
        return hash % 37
    }
    put(key, value){
        let position = this.loseloseHashCode(key)
        if (table[position] == undefined) {
            table[position] = new ValuePair(key, value)
        } else {
            let index = ++position
            while (table[index] != undefined){
                index++
            }
            table[index] = new ValuePair(key, value)
        }
    }
    get(key){
        let position = loseloseHashCode(key)
        if (table[position] !== undefined){
            if (table[position].key === key) {
                return table[position].value
            } else {
                let index = ++position
                while (table[index] === undefined || table[index].key !== key){
                    index++
                }
                if (table[index].key === key) {
                    return table[index].value
                }
            }
        }
        return undefined
    }
    remove(key){
        let position = loseloseHashCode(key)
        if (table[position] !== undefined){
            if (table[position].key === key) {
                table[index] = undefined
            } else {
                let index = ++position
                while (table[index] === undefined || table[index].key !== key){
                    index++
                }
                if (table[index].key === key) {
                    table[index] = undefined
                }
            }
        }
        return undefined
    }
}

djb2散列表:没有冲突

let djb2HashCode = function (key) {
    let hash = 5381
    for (var i = 0; i < key.length; i++) {
        hash = hash * 33 + key.charCodeAt(i)
    }
    return hash % 1013
}

七、树

一个树结构包含一系列存在父子关系的节点,每个节点都有一个父节点(除了顶部的第一个节点)以及零个或多个子节点。

位于树顶部的节点叫作根节点(11)。至少有一个子节点的节点称为内部节点(7、5、9、15、13和20是内部节点)。没有子元素的节点称为外部节点或叶节点(3、6、8、10、12、14、18和25是叶节点)。

子树由节点和它的后代构成。

节点的一个属性是深度,节点的深度取决于它的祖先节点的数量。

(一)二叉树和二叉搜索树

二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。

二叉搜索树(BST)是二叉树的一种,但是它只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大(或者等于)的值。

中序遍历:左跟右(3-5-6-7-8-9-10-11-12-13-14-15-18-20-25)

def dfs(self,root):
    if not root:return
    self.dfs(root.left)
    self.res.append(root)
    self.dfs(root.right)

先序遍历:根左右(11-7-5-3-6-9-8-10-15-13-12-14-20-18-25)

def dfs(self,root):
    if not root:return
    self.res.append(root)
    self.dfs(root.left)
    self.dfs(root.right)

后序遍历:左右根(3-6-5-8-10-9-7-12-14-13-18-25-20-15-11)

def dfs(self,root):
    if not root:return
    self.dfs(root.left)
    self.dfs(root.right)
    self.res.append(root)
class Node{
    constructor(key){
        this.key = key
        this.left = null
        this.right = null
    }
}
class BinarySearchTree{
    constructor(){
        this.root = null
    }
    
// 向树中插入一个键
    insert(key){
        let newNode = new Node(key)
        function insertNode(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)
                }
            }
        }
        if (this.root === null){
            this.root = newNode
        } else {
            insertNode(this.root,newNode)
        }
    }

// 树的中序遍历:以上行顺序访问BST所有节点的遍历方式,也就是以从最小到最大的顺序访问所有节点。

    inOrderTraverse(callback){
        function inOrderTraverseNode(node, callback) {
            if (node !== null) {
                inOrderTraverseNode(node.left, callback)
                callback(node.key)
                inOrderTraverseNode(node.right, callback)
            }
        }
        inOrderTraverseNode(this.root, callback)
    }
    
// 树的先序遍历:以优先于后代节点的顺序访问每个节点。
    preOrderTraverse(callback){
        function preOrderTraverseNode(node, callback) {
            if (node !== null) {
                callback(node.key)
                preOrderTraverseNode(node.left, callback)
                preOrderTraverseNode(node.right, callback)
            }
        }
        preOrderTraverseNode(this.root, callback)
    }

// 树的后序遍历:以优先于后代节点的顺序访问每个节点。
    postOrderTraverse(callback){
        function postOrderTraverseNode(node, callback) {
            if (node !== null) {
                postOrderTraverseNode(node.left, callback)
                postOrderTraverseNode(node.right, callback)
                callback(node.key)
            }
        }
        postOrderTraverseNode(this.root, callback)
    }
    
    min(){
        function minNode(node) {
            if (node){
                while (node && node.left !== null) {
                    node = node.lef
                }
                return node.key
            }
            return null
        }
        return minNode(this.root)
    }
    max(){
        function maxNode(node) {
            if (node){
                while (node && node.right !== null) {
                node = node.right
                }
                return node.key
            }
            return null
        }
        return maxNode(root)
    }
    search(key){
        function searchNode(node, key){
            if (node === null){
                return false
            }
            if (key < node.key){
                return searchNode(node.left, key)
            } else if (key > node.key){
                return searchNode(node.right, key)
            } else {
                return true
            }
        }
        return searchNode(this.root, key)
    }
    
    remove(key){
        function removeNode(node, key){
            function findMinNode(node){
                while (node && node.left !== null) {
                    node = node.left
                }
                return node
            }
            if (node === null){
                return null
            }
            if (key < node.key){
                node.left = removeNode(node.left, key)
                return node
            } else if (key > node.key){
                node.right = removeNode(node.right, key)
                return node
            } else {
                //第一种情况——一个叶节点
                if (node.left === null && node.right === null){
                    node = null{10}
                    return node
                }
                //第二种情况——一个只有一个子节点的节点
                if (node.left === null){
                    node = node.right
                    return node
                } else if (node.right === null){
                    node = node.left
                    return node
                }
                //第三种情况——一个有两个子节点的节点
                var aux = findMinNode(node.right)
                node.key = aux.key
                node.right = removeNode(node.right, aux.key)
                return node
            }
        }
        root = removeNode(root, key)
    }
}

(二)自平衡树

平衡二叉树是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

  1. AVL树
  2. 红黑树

八、图

一个图G = (V, E)由以下元素组成:

  • V:一组顶点
  • E:一组边,连接V中的顶点

相邻顶点:由一条边连接在一起的顶点称为相邻顶点。比如,A和B是相邻的,A和D是相邻的,A和C是相邻的,A和E不是相邻的。

度:一个顶点的度是其相邻顶点的数量。比如,A和其他三个顶点相连接,因此,A的度为3;E和其他两个顶点相连,因此,E的度为2。

路径:路径是顶点v1, v2,…,vk的一个连续序列,其中vi和vi+1是相邻的。以上一示意图中的图为例,其中包含路径A B E I和A C D G。

简单路径:要求不包含重复的顶点。举个例子,A D G是一条简单路径。除去最后一个顶点(因为它和第一个顶点是同一个顶点),环也是一个简单路径,比如A D C A(最后一个顶点重新回到A)。

环:如果图中不存在环,则称该图是无环的。如果图中每两个顶点间都存在路径,则该图是连通的。

方向:图可以是无向的或是有向的。

权重:图还可以是未加权的或是加权的。

(一)图的表示

  1. 邻接矩阵
  2. 邻接表
  3. 关联矩阵
class Graph{
    constructor(){
        this.vertices = []
        this.adjList = new Dictionary()
    }
    addVertex(v){
        vertices.push(v)
        adjList.set(v, [])
    }
    this.addEdge = function(v, w){
        adjList.get(v).push(w)
        adjList.get(w).push(v)
    }
}

(二)图的遍历

图遍历可以用来寻找特定的顶点或寻找两个顶点之间的路径,检查图是否连通,检查图是否含有环等。

  1. 广度优先搜索(Breadth-First Search,BFS)
  2. 深度优先搜索(Depth-First Search,DFS)

(三)最短路径算法

  1. 贪心算法
  2. 动态规划算法

(四)最小生成树

  1. Prim
  2. Kruskal