JavaScript中的数据结构及代码实现

173 阅读11分钟

一、栈

堆栈具有后进先出的特点

image.png

栈具有如下方法:

push:从最后插入元素
pop:弹出栈顶元素并返回
peek:返回栈顶元素

实现代码:

function Stack() {
    this.storage = [];
    this.count = -1;
    this.length = this.count + 1;
    
    this.push = function (value) {
        this.count++;
        this.length = this.count + 1;
        this.storage[this.count] = value;
    }

    this.pop = function () {
        if (this.count === -1) {
            return undefined
        }
        const result = this.storage[this.count];
        this.storage.splice(this.count,1)
        this.count--;
        this.length = this.count + 1;
        return result
    }

    this.peek = function () {
        if (this.count === -1) {
            return undefined
        }
        return this.storage[this.count];
    }

}

个人实现思路:

  • push方法:将栈中的内容存入一个数组中,创建一个count变量充当类似指针的作用,一直指向数组最后一个值的索引,在调用push方法时先+1再赋值。
    
  • pop方法:由于count变量是指向数组最后一个值的索引,先对count变量进行判断,确定栈是否为空,再使用splice方法删除count对应的数据,count--。
    
  • peek方法:就是pop方法但是不删除对应数据,count不--
    

二、集合

集合与数组类似但是没有索引也不能重复

image.png 集合具有以下方法:

  1. values:返回集合所有元素
  2. size:返回集合长度
  3. has:判断集合是否有某个值
  4. add:添加值
  5. remove:删除值
  6. interaction:合并两个集合
  7. union:取交集
  8. difference:取差集
  9. subset:判断是否子集

实现代码:

function MySet() {
    const collection = []
    const length = 0;

    this.values = function () {
        return collection
    }

    this.size = function () {
        return length;
    }

    this.has = function (value) {
        return collection.includes(value)
    }

    this.add = function (value) {
        if (this.has(value)) {
            return
        } else {
            collection.push(value)
        }

    }

    this.remove = function (value) {
        if (this.has(value)) {
            const index = collection.indexOf(value)
            collection.splice(index,1)
        }
        else {
            return undefined
        }
    }

    /* 合集 */
    this.interaction = function (otherSet) {
        var interactionSet = new MySet();
        var firstSet = this.values();
        var secondSet = otherSet.values();
        firstSet.forEach(function (e) {
            interactionSet.add(e);
        });
        secondSet.forEach(function (e) {
            interactionSet.add(e);
        });
        return interactionSet;
    }

    /* 交集 */
    this.union = function (otherSet) {
        var unionSet = new MySet()
        var firstSet = this.values();

        firstSet.forEach(function (e) {
            if (otherSet.has(e)) {
                unionSet.add(e)
            }
        })
        return unionSet
    }

    this.difference = function (otherSet) {

        const unionSet = this.union(otherSet).values()
        const interactionSet = this.interaction(otherSet)
        unionSet.forEach(function (e) {
            interactionSet.remove(e)
        })
        return interactionSet.values()
    }

    this.subset = function (otherSet) {
        const firstSet = this.values()
        const secondSet = otherSet.values()
        if (firstSet.length > secondSet.length) {
            return false
        }
        else {
            return firstSet.every(item => secondSet.includes(item))

        }
    }

}

个人实现思路: 首先创建一个collection数组用来存数据,再用length记录集合的长度

  1. values:直接返回collection数组
  2. size:直接返回length
  3. has:使用数组的includes方法实现,返回布尔值
  4. add:首先使用has判断集合是否有这个值,没有就使用push方法传入,length别忘+1
  5. remove:也是使用has先判断,再用数组的indexof取得这个值的索引,splice删除,再length-1
  6. interaction:先创建一个合集的新的Set实例,再将要合并的两个集合分别使用foreach将他们的值一个一个传入,由于之前定义的集合的性质,他们重合的值并不会重复导入
  7. union:创建一个交集的Set实例,将两个判断交集的集合,其中传入的集合使用foreach一个一个取得值,另一个集合使用has判断,如果有就添加进交集的集合中
  8. difference:差集,先取得他们的合集再取得他们的交集,交集使用foreach取得值,在合集中使用之前定义的remove方法删除,可得差集
  9. subset:首先判断如果传入的集合的length还要小于其判断是其父集的length,那么直接false,然后再使用数组的every方法,如果每个值在其父集中使用has方法都为true,则返回true。

三、队列

队列具有先进先出的特性

image.png

队列具有如下方法:

  1. enqueue:添加值
  2. dequeue:删除值
  3. front:取得队首值
  4. isEmpty:判断是否为空

实现代码如下:

unction Queue() {
    this.storage = [];
    this.frontPointer = 0;
    this.backPointer = 0;
    this.size = 0;

    this.enqueue = function (value) {
        this.storage[this.backPointer] = value;
        this.backPointer++;
        this.size = this.backPointer - this.frontPointer

    }

    this.dequeue = function () {
        if (this.frontPointer === this.backPointer) {
            return undefined
        }
        this.storage.splice(this.frontPointer,1)
        this.backPointer--;
        this.size = this.backPointer - this.frontPointer
    }

    this.front = function () {
        return this.storage[this.frontPointer];
    }

    this.isEmpty = function () {
        if (this.frontPointer === this.backPointer) {
            return true
        }
        else {
            return false
        }
    }


}

个人实现思路: 首先创建storage数组存放数据,创建两个类似指针,一个指向队首,一个指向队尾,初始化队列的长度为size

  1. enqueue:队尾指针指向队尾索引,直接进行赋值,队尾指针+1,重新赋值size的数据
  2. dequeue:先判断size的值是否为0,及首尾指针是否相等,不相等的话直接splice,并且队尾指针--,队首指针不变一直指向队首,重新赋值size
  3. front:与dequeue差不多,只是只需要取得队首值就行
  4. isEmpty:判断size是否为0

四、单链表

单链表的信息有两个,一个是当前的值element,另一个是指向下一个节点的next,链表节点的存在是由其前后节点来确定的

image.png

单链表有如下方法:

  1. head:返回其头结点
  2. size:返回链表节点数
  3. add:添加节点
  4. remove:删除节点
  5. indexof:返回节点的索引
  6. elementAt:返回索引的节点值
  7. addAt:在指定索引添加节点
  8. removeAt:在指定索引删除节点

实现代码:

function Node(element) {
    this.element = element;
    this.next = null;
}

function LinkedList() {
    var head = null;
    var length = 0;

    this.head = function () {
        return head;
    }

    this.size = function () {
        return length;
    }

    this.add = function (element) {
        var node = new Node(element)
        if (head == null) {
            head = node;
        }
        else {
            var currentNode = head
            while (currentNode.next) {
                currentNode = currentNode.next
            }
            currentNode.next = node
        }
        length++;
    }

    this.remove = function (element) {
        var currentNode = head;
        var previousNode;
        if (currentNode.element === element) {
            head = currentNode.next
        } else {
            while (currentNode.element !== element) {
                previousNode = currentNode
                currentNode = currentNode.next

            }

            previousNode.next = currentNode.next
        }
        length--;

    }

    this.indexOf = function (element) {
        var index = 0;
        var currentNode = head

        while (currentNode) {
            if (currentNode.element == element) {
                return index
            }
            currentNode = currentNode.next
            index++;
        }
        return -1
    }

    this.elementAt = function (index) {
        var count = 0
        var currentNode = head
        if (index >= length || index < 0) {
            throw new RangeError('index out of bounds')
        }
        else {
            while (count != index) {
                count++;
                currentNode = currentNode.next
            }
            return currentNode.element
        }

    }

    this.addAt = function (index, element) {
        const node = new Node(element)
        var currentIndex = 0;
        var currentNode = head;
        var previousNode;
        if (index >= length || index < 0) {
            throw new RangeError('index out of bounds')
        }
        if (index == 0) {
            node.next = currentNode
            head = node

        } else {
            while (currentIndex < index) {
                currentIndex++;
                previousNode = currentNode
                currentNode = currentNode.next
            }
            previousNode.next = node
            node.next = currentNode
        }
        length++;
    }

    this.removeAt = function (index) {
        var currentIndex = 0
        var currentNode = head
        var previousNode

        if (index > length || index < 0) {
            throw new RangeError('index out of bounds')
        }
        if (index == 0) {
            head = currentNode.next
        }
        else {
            while (currentIndex < index) {
                currentIndex++;
                previousNode = currentNode
                currentNode = currentNode.next
            }
            previousNode.next = currentNode.next
        }
        length--;
    }
}

个人实现思路: 首先创建一个node函数,其用于创建节点,在LinkedList中先初始化head节点为null,length为0

  1. head:返回head
  2. size:返回length
  3. add:首先判断如果head为null,那么赋值给head,否则从头结点开始往下遍历,如果遍历的当前节点的下一个节点为null,跳出遍历,将创建的node节点赋值给它
  4. remove:首先判断其传入的element是否与头节点符合,如果符合那么将头结点向其next节点传递,如果不符合,就一直向下遍历,直到找到与其符合的节点,那么这个节点的上一个节点的next属性指向当前节点的下一个节点。
  5. indexof:创建一个count变量,使用循环判断当前节点的值是否与传入的值,count++,符合便跳出循环返回count,不符合继续向下遍历
  6. elementAt:同样创建一个count变量,记录当前已经循环到了哪个索引,先进行判断传入的值是否合理,再利用循环,如果不符合的话count++并且当前节点指向下一个节点,符合便跳出循环返回count
  7. addAt:使用indexof一样的思路,遍历找到索引对应的节点,然后将其上一个节点的next指向新创建的节点,新创建的节点next指向当前节点
  8. removeAt:与addAt差不多,不同的是找到后其上一个节点的next指向下一个节点

五、哈希表

哈希表是键值数据结构,其通过hash函数将key的值转换成一个数字,然后将值传入到数字对应索引中。如果哈希表值与传入值冲突,1. 开放寻址法:当发生冲突时,通过一定的方法(如线性探测、二次探测、双重散列等)寻找另一个空槽位来存储冲突的键值对。2. 链地址法:将哈希表的每个槽位指向一个链表(或其他数据结构),当发生冲突时,将冲突的键值对添加到对应槽位的链表中。3. 再哈希法:通过另一个哈希函数再次对发生冲突的键进行哈希运算,得到另一个槽位来存储键值对。

image.png

哈希表有如下方法:

  1. add:添加值
  2. remove:删除值
  3. lookup:返回key对应的值

实现代码:

function hash(string, max) {
    var hash = 0
    for (let i = 0; i < string.length; i++) {
        hash += string.charCodeAt(i);
    }
    return hash % max
}

function HashTable() {
    let storage = [];
    const storageLimit = 15;

    this.add = function (key, value) {
        var index = hash(key, storageLimit)
        if (storage[index] === undefined) {
            storage[index] = [[key, value]];
        }
        else {
            var inserted = false
            for (var i = 0; i < storage[index].length; i++) {
                if (storage[index][i][0] === key) {
                    storage[index][i][1] = value;
                    console.log('true');
                    inserted = true;
                }
            }
            if (inserted === false) {
                storage[index].push([key, value])
            }

        }
    }

    this.remove = function (key) {
        var index = hash(key, storageLimit)

        if (
            storage[index] === undefined
        ) {
            return false
        }
        else {
            for (let i = 0; i < storage[index].length; i++) {
                if (storage[index][i][0] === key) {
                    storage[index].splice(i, 1)
                }
            }
        }
    }

    this.lookup = function (key) {
        var index = hash(key, storageLimit)
        if (storage[index] === undefined) {
            return undefined
        }
        else {
            for (let i = 0; i < storage[index].length; i++) {
                if (storage[index][i][0] === key) {
                    return storage[index][i][1]
                }
            }
        }
    }
}

个人实现思路: 首先创建一个hash函数,传入参数为哈希表最大存储长度,以及hashTable存入数据的key,通过charCodeAt方法其他方法也行,将其变成一个对应的数字,并且返回这个数字。创建一个数组存数据。

  1. add:先利用hash函数得到索引,再判断对应索引是否为空,为空则直接push,不为空使用for循环对于数组中的数据进行判断,若数组对应的key与add传入的key吻合,则重新赋值,不吻合则直接push。
  2. remove:将传入的key通过hash函数转换为index,如果index对应的没值则返回false,有值就使用for循环找到对应index索引下的数组是否有值与传入的key相等,符合则splice
  3. lookup:与remove逻辑差不多,只是找到后返回其value不用删除

六、树

树的结构有很多如二叉树、红黑树、二叉平衡树,这里是二叉搜索树

image.png

树有如下方法:

  1. add:添加一个树节点
  2. findMin:找到节点值最小的节点
  3. findMax:找到节点值最大的节点
  4. find:找到对应节点值的节点
  5. isPresent:对应节点是否存在
  6. remove:删除节点

实现代码:

function Node(element) {
    this.element = element;
    this.left = null;
    this.right = null;
    this.parent = null;
}

function Tree() {
    this.root = null;

    this.add = function (element) {
        var newNode = new Node(element);
        if (this.root === null) {
            this.root = newNode;
        } else {
            var currentNode = this.root;
            while (true) {
                if (element < currentNode.element) {
                    if (currentNode.left === null) {
                        currentNode.left = newNode;
                        newNode.parent = currentNode;
                        break;
                    } else {
                        currentNode = currentNode.left;
                    }
                } else {

                    if (currentNode.right === null) {
                        currentNode.right = newNode;
                        newNode.parent = currentNode;
                        break;
                    } else {
                        currentNode = currentNode.right;
                    }
                }
            }
        }
    };
    this.findMin = function () {
        var currentNode = this.root;
        if (currentNode === null) {
            return undefined;
        } else {
            while (currentNode.left !== null) {
                currentNode = currentNode.left;
            }
            return currentNode.element;
        }
    };

    this.findMax = function () {
        var currentNode = this.root;
        if (currentNode === null) {
            return undefined;
        } else {
            while (currentNode.right !== null) {
                currentNode = currentNode.right;
            }
            return currentNode.element;
        }
    };

    this.find = function (element) {
        var currentNode = this.root;
        if (currentNode === null) {
            return undefined;
        }
        while (currentNode) {
            if (currentNode.element === element) {
                return currentNode
            }
            if (element < currentNode.element) {
                currentNode = currentNode.left
            } else {
                currentNode = currentNode.right
            }

        }
    }

    this.isPresent = function (element) {
        if (this.find(element)) {
            return true
        }
        else {
            return false
        }
    }

    this.remove = function (element) {
        var deleNode = this.find(element);
        if (!deleNode) {
            return undefined; // 如果找不到要删除的节点,则返回 undefined
        }

        // 定义一个辅助函数,用于获取节点的子节点中的最小节点
        function findMinNode(node) {
            while (node && node.left !== null) {
                node = node.left;
            }
            return node;
        }

        // 情况1:要删除的节点没有子节点
        if (deleNode.left === null && deleNode.right === null) {
            if (deleNode === this.root) {
                this.root = null; // 如果要删除的节点是根节点,则直接将根节点置空
            } else {
                if (deleNode.parent.left === deleNode) {
                    deleNode.parent.left = null; // 更新父节点的左子节点
                } else {
                    deleNode.parent.right = null; // 更新父节点的右子节点
                }
            }
        }
        // 情况2:要删除的节点只有一个子节点
        else if (deleNode.left === null) {
            if (deleNode === this.root) {
                deleNode.right.parent = null;
                this.root = deleNode.right; // 如果要删除的节点是根节点,则将根节点指向其右子节点
            } else {
                if (deleNode.parent.left === deleNode) {
                    deleNode.parent.left = deleNode.right; // 更新父节点的左子节点
                } else {
                    deleNode.parent.right = deleNode.right; // 更新父节点的右子节点
                }
            }
        } else if (deleNode.right === null) {
            if (deleNode === this.root) {
                deleNode.left.parent = null;
                this.root = deleNode.left; // 如果要删除的节点是根节点,则将根节点指向其左子节点
            } else {
                if (deleNode.parent.left === deleNode) {
                    deleNode.parent.left = deleNode.left; // 更新父节点的左子节点
                } else {
                    deleNode.parent.right = deleNode.left; // 更新父节点的右子节点
                }
            }
        }
        // 情况3:要删除的节点有两个子节点
        else {
            var minRightNode = findMinNode(deleNode.right); // 找到右子树中的最小节点
            deleNode.element = minRightNode.element; // 将右子树中的最小节点的值赋值给要删除的节点
            this.remove(minRightNode.element); // 递归地删除右子树中的最小节点gogogo~ ~ ~
        }
    };

}

个人实现思路: 首先创建树节点函数,其中定义四个值,节点值element,左节点left,右节点right,父节点parent。树中先初始化一个根节点

  1. add:需要判断的情况很多,首先判断是否根节点为null,再判断如果不为null其值与当前节点的值进行比较,如果小于当前节点值就往其左子树遍历,如果大于当前值就往右子树遍历,直到遍历到其左节点或右节点为null,赋值后便break跳出循环
  2. findMin:由于左边节点是最小的,所以只需要往左子树循环遍历,找到最左边的节点,取得其值。
  3. findMax:则是取右子树,找到最右边的节点的值
  4. find:循环遍历树,如果传入的值与当前根节点值相等则返回根节点,如果不相等再根据其值与根节点值比较,小于根节点值就往左子树遍历,大于就往右指数遍历,每循环一次当前节点就往其左节点或右节点赋值。
  5. isPresent:就返回find的值就行
  6. remove:这个函数判断的情况很多,首先使用find函数找到element对应的节点,如果没用则返回undefined,如果有在判断三种情况,情况一,删除的节点没有子节点,那么再判断该删除的节点是否为根节点,如果不是就再判断其是其父节点的左节点还是右节点,根据不同情况将其父节点的left或right置null,情况二,要删除的节点有一个子节点,首先判断其子节点位于left还是right,再判断其为根节点还是其他节点,情况三,要删除的节点有两个节点,那么只要找到当前要删除的节点的右子树中的最小节点,找到后将其值赋值给要删除的节点,并使用remove方法递归将右子树中的最小节点删除就行,其实就是用右子树的最小节点替换当前要删除的节点。