学习JavaScript数据结构与算法【持续更新】

283 阅读6分钟

学习JavaScript数据结构与算法【第三版】 阅读笔记

1.栈

1.1 特点

  • 后进先出
  • 拥有push/pop/isEmpty/size/peek/toString/clear方法

1.2 实现


class Stack {
    constructor() {
        this.items = {};
        this.count = 0;
    }

    // 入栈
    push(el) {
        this.items[this.count] = el;
        this.count++;
    }

    // 大小
    size() {
        return this.count;
    }

    // 是否为空
    isEmpty() {
        return this.count === 0;
    }

    // 出栈
    pop() {
        if (this.isEmpty()) return undefined;
        this.count--;
        const result = this.items[this.count];
        delete this.items[this.count];
        return result;
    }

    // 查看栈顶
    peek() {
        if (this.isEmpty()) return undefined;
        return this.items[this.count - 1];
    }

    // 清空栈
    clear() {
        this.items = {};
        this.count = 0;
    }

    // 打印栈
    toString() {
        if (this.isEmpty()) return '';
        let objString = this.items[0];
        for (let i = 1; i < this.count; i++) {
            objString += `,${this.items[i]}`;
        }
        return objString
    }
}

1.3 其他

  1. 也可以数组写法,相对简单但是开销大
  2. 变量私有化可以用Symbol和WeakMap解决

1.4 应用示例

十进制转换为二进制

function decimalToBinary(decimal){
    const stack = new Stack();
    let rem, binary = '';
    while(decimal > 0 ){
        rem = decimal % 2;
        decimal = Math.floor(decimal/2);
        stack.push(rem);
    }

    while(!stack.isEmpty()){
        binary += stack.pop()
    }

    return binary;
}

消消乐

function pair(str) {
    const stack = new Stack();
    const pairArr = {
        '{': "}",
        '[': ']',
        '(': ')'
    };
    for(let i of str){
        const peek = stack.peek();
        if(pairArr[peek] === i){
            stack.pop()
        }else{
            stack.push(i)
        }
    }
    return stack.size() === 0;
}

2. 队列

2.1 普通队列

2.1.1 特点

  • 先进先出
  • 拥有push/pop/isEmpty/size/peek/toString/clear方法

2.1.2 实现

class Queue {
    constructor() {
        this.items = {};
        this.count = 0; //记录队列的最后面
        this.lowestCount = 0; // 记录队列的最前面
    }

    // 入队
    enQueue(el) {
        this.items[this.count] = el;
        this.count++;
    }

    // 出队
    delQueue() {
        if (this.isEmpty()) return undefined;
        const result = this.items[this.lowestCount];
        delete this.items[this.lowestCount];
        this.lowestCount++;
        return result;
    }

    // 是否为空
    isEmpty() {
        return this.size() === 0;
    }

    // 尺寸
    size() {
        return this.count - this.lowestCount;
    }

    // 查看队头
    peek() {
        if (this.isEmpty()) return undefined;
        return this.items[this.lowestCount];
    }

    // 清空队列
    clear() {
        this.items = {};
        this.count = 0;
        this.lowestCount = 0;
    }

    // 打印
    toString() {
        if (!this.isEmpty()) return '';
        let string = this.items[this.lowestCount];
        for (let i = this.lowestCount + 1; i < this.count; i++) {
            string += `,${this.items[i]}`
        }
        return string;
    }
}

2.1.3 应用示例

击鼓传花游戏,每次鼓声停止手里拿花的那个被淘汰退出,然后从下一个继续开始,知道剩余最后一个获胜

function hotPhoto(nameList){
    const queue = new Queue();
    const deleteName = [];
    nameList.forEach(name => {
        queue.enQueue(name);
    })
    while(queue.size() > 1){
        const loopNum = Math.floor(Math.random() * 10);
        for(let i=0; i<loopNum; i++){
            queue.enQueue(queue.delQueue());
        }
        deleteName.push(queue.delQueue());
        console.log(`${deleteName[deleteName.length - 1]}被淘汰`)
        
    }
    const warnName =  queue.delQueue();
    console.log(`最后胜利者:${warnName}`)
    return {
        deleteName,
        warnName,
    }
}

const result = hotPhoto(
    ['小明', '小红', '小王', '小李', '小花', '小龙']
)

小明被淘汰
小龙被淘汰
小李被淘汰
小红被淘汰
小花被淘汰
最后胜利者:小王

2.2 双端队列

2.2.1 特点

  • 允许从前端和后端添加移除的特殊队列
  • 可以获取第一个和最后一个元素

2.2.2 实现

class Dequeue {
    constructor() {
        this.items = {};
        this.count = 0;
        this.lowestCount = 0;
    }

    // 前端增加
    addFront(el) {
        if (this.isEmpty()) {
            return this.addBack(el);
        }

        // 前面有空位
        if (this.lowestCount > 0) {
            this.lowestCount--;
            this.items[this.lowestCount] = el;
        } else {
            for (let i = this.count; i > 0; i--) {
                this.items[i] = this.items[i - 1]
            }
            this.count++;
            this.lowestCount = 0;
            this.items[0] = el;
        }
    }

    // 后端增加
    addBack(el) {
        this.items[this.count] = el;
        this.count++;
    }

    // 前端删除
    removeFront() {
        if (this.isEmpty()) return undefined;
        const result = this.items[this.lowestCount];
        delete this.items[this.lowestCount];
        this.lowestCount++;
        return result;
    }

    // 后端删除
    removeBack() {
        if (this.isEmpty()) return undefined;
        const result = this.items[this.count - 1];
        delete this.items[this.count - 1];
        this.count--;
        return result;
    }

    //前端第一个元素
    peekFront() {
        if (this.isEmpty()) return undefined;
        return this.items[this.lowestCount];
    }

    // 后端第一个元素
    peekBack() {
        if (this.isEmpty()) return undefined;
        return this.items[this.count - 1];
    }

    // 是否为空
    isEmpty() {
        return this.size() === 0;
    }

    // 尺寸
    size() {
        return this.count - this.lowestCount;
    }

    // 清空
    clear() {
        this.items = {};
        this.count = 0;
        this.lowestCount = 0;
    }

    // 打印
    toString() {
        if (this.isEmpty()) return '';
        let objString = this.items[this.lowestCount];
        for (let i = this.lowestCount + 1; i < this.count; i++) {
            objString += `,${this.items[i]}`
        }
        return objString;
    }
}

2.2.3 应用示例

回文判断

function palindromeChecker(string){
    if(typeof string !== 'string' || string.length === 0)  return false;
    const dequeue = new Dequeue();
    const dealStr = string.toLowerCase().replace(/\s+/g, '');
    [...dealStr].forEach(e => dequeue.addFront(e));
    let isEqual = true, pre, next;
    while(isEqual && dequeue.size() > 1){
        pre = dequeue.removeFront();
        next = dequeue.removeBack();
        if(pre !== next) isEqual = false;
    }
    return isEqual;
}

3. 链表

3.1 单向链表

3.1.1 特点

  • 用一组任意的内存空间去存储数据元素(这里的内存空间可以是连续的,也可以是不连续的)
  • 每个节点(node)都由数据本身和一个指向后续节点的指针组成
  • 最后一个节点的指针指向空(NULL)

3.1.2 实现

class Node {
    constructor(ele) {
        this.ele = ele;
        this.next = null;
    }
}

class LinkList {
    constructor() {
        this.head = null;
        this.count = 0;
    }

    // 在链表尾部插入节点
    push(ele) {
        const node = new Node(ele);
        if (this.head === null) {
            this.head = node;
        } else {
            let current = this.head;
            while (current.next !== null) {
                current = current.next;
            }
            current.next = node;
        }
        this.count++;
    }

    // 移除其中一个节点
    removeAt(index) {
        if (index >= 0 && index < this.count) {
            let current = this.head;
            if (index === 0) {
                this.head = current.next;
            } else {
                const previous = this.getElementAt(index - 1);
                current = previous.next;
                previous.next = current.next;
            }
            this.count--;
            return current.ele;
        }
        return undefined;
    }

    // 得到节点
    getElementAt(index) {
        if (index >= 0 && index <= this.count) {
            let node = this.head;
            for (let i = 0; i < index && node !== null; i++) {
                node = node.next;
            }
            return node && node.next;
        }
        return undefined;
    }

    // 插入元素
    insert(ele, index) {
        if (index >= 0 && index <= this.count) {
            const node = new Node(ele);
            if (index === 0) {
                const current = this.head;
                node.next = current;
                this.head = node;
            } else {
                const previous = this.getElementAt(index - 1);
                const current = previous.next;
                previous.next = node;
                node.next = current;
            }
            this.count++;
            return true;
        }
        retun false;
    }

    // 返回一个元素的位置
    indexOf(ele) {
        let current = this.head;
        for (let i = 0; i < this.count && current !== null; i++) {
            if (this.equalFn(ele, current.next)) {
                return i;
            }
            current = current.next;
        }
        return -1;
    }

    // 从链表中移除元素
    remove(ele) {
        const index = this.indexOf(ele);
        return this.removeAt(index);
    }

    // 长度
    size() {
        return this.count;
    }

    // 是否为空
    isEmpty() {
        return this.size() === 0;
    }

    //获取第一个
    getHead() {
        return this.head;
    }

    // 打印
    toString() {
        if (this.head === null) return '';
        let objString = this.head.ele;
        let current = this.head.next;
        for (let i = 1; i < this.size() && current !== null; i++) {
            objString += `,${current.ele}`;
            current = current.next;
        }
        return objString;
    }
}

3.2 双向链表

3.2.1 特点

  • 链接双向的,一个节点一端链向上一个节点,另一个端链向下一个节点

3.2.2 实现

3.2.2 实现

class DoubleNode extends Node{
    constructor(ele){
        super(ele);
        this.pre = null;
    }
}

class DoubleLinkList extends LinkList{
    constructor(){
        super();
        this.tail = null;
    }

    // 在任意位置插入
    insert(ele, index){
        if(index >= 0 && index<=this.count){
            const node = new DoubleNode(ele);
            let current = this.head;
            if(index === 0){
                if(this.head === null){
                    this.head = node;
                    this.tail = node;
                }else{
                    node.next = this.head;
                    current.pre = node;
                    this.head = node;
                }
            }else if(index === this.count){
                current = this.tail;
                current.next = node;
                node.pre = current;
                this.tail = node;
            }else{
                const previous = this.getElementAt(index - 1);
                current = previous.next;
                node.next = current;
                previous.next = node;
                current.pre = node;
                node.pre = previous;
            }
            this.count++;
            return true;
        }
        return false;
    }

    // 移除某个位置
    removeAt(index){
        if(index>=0 && index<this.count){
            let current = this.head;
            if(index === 0){
                this.head = current.next;
                if(this.count === 1){
                    this.tail = null;
                }else{
                    this.head.pre = null;
                }
            }else if(index === this.count-1){
                current = this.tail;
                this.tail = current.pre;
                this.tail.next = null;
            }else{
                current = this.getElementAt(index-1);
                const previous = current.pre;
                previous.next = current.next;
                current.next.pre = previous;
            }
            this.count--;
            return current.ele;
        }
        return undefined;
    }
}

3.3 循环链表

3.3.1 实现

class CircleLinkList extends LinkList{
    constructor(){
        super();
    }

    insert(ele, index){
        if(index >= 0 && index <= this.count){
            const node = new Node(ele);
            let current = this.head;
            if(index === 0){
                if(this.head === null){
                    this.head = node;
                    node.next = this.head;
                }else{
                    node.next = this.head;
                    current = this.getElementAt(this.size());
                    this.head = node;
                    current.next = this.head;
                }
            }else{
                const previous = this.getElementAt(index - 1);
                node.next = previous.next;
                previous.next = node;
            }
            this.count++;
            return true;
        }
        return false;
    }

    removeAt(index){
        if(index >= 0 && index<this.count){
            let current = this.head;
            if(index === 0){
                if(this.size() === 1){
                    this.head = null;
                }else{
                    const remove = this.head;
                    current = this.getElementAt(this.size());
                    current.next = remove.next;
                    this.head = this.head.next;
                    current = remove;
                }
            }else{
                const previous = this.getElementAt(index - 1);
                current = previous.next;
                previous.next = current.next;
            }
            this.count--;
            return current.ele;
        }
        return undefined;
    }
}

3.5 有序链表

3.5.1 实现

function compare(a, b){
    if(a === b) return 0;
    return a > b ? -1 : 1;
}

class SortLinkList extends LinkList{
    constructor(){
        super();
    }

    insert(ele){
        if(this.isEmpty()){
            return this.insert(ele, 0);
        }
        const pos = this.getIndexNextSortEle(ele);
        return this.insert(ele, pos);
    }

    getIndexNextSortEle(ele){
        let current = this.head;
        let i = 0;
        for(; i<this.size() && current;i++){
            const comp = this.compare(ele, current.el);
            if(comp === -1){
                return i;
            }
            current = current.next;
        }
        return i;
    }

}

4. 集合

集合是一种不允许有重复值的类数组结构,JavaScript中的Set类

// 求并集
Set.prototype.union = function(otherSet){
    const unionSet = new Set();
    this.values.forEach(e => {
        unionSet.add(e);
    })

    otherSet.values().forEach(e => {
        unionSet.add(e)
    })

    return unionSet;
}

// 求交集
Set.prototype.intersection = function(otherSet){
    const intersectionSet = new Set();
    this.values.forEach(e => {
        if(otherSet.has(e)){
            intersectionSet.add(e)
        }
    })
    return intersectionSet;
}

// 求差集
Set.prototype.difference = function(otherSet){
    const differenceSet = new Set();
    this.values.forEach(e => {
        if(!otherSet.has(e)){
            differenceSet.add(e)
        }
    })
    return differenceSet;
}

// 求子集
Set.prototype.isSubsetOf = function(otherSet){
    if(this.size > otherSet.size) return false;
    let isSubset = true;
    this.values().every(e => {
        if(!otherSet.has(e)){
            isSubset = false;
            return false;
        }
        return true;
    })
    return isSubset;
}

5. 树

二叉查找树

二叉查找树(BST)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值,在右侧节点存储(比父节点)大的值

实现

class Node{
    constructor(key){
        this.key = key;
        this.left = null; // 左侧子节点
        this.right = null; // 右侧子节点
    }


}


class SearchTree{
    constructor(){
        this.root = null;
    }

    compareFn(a, b){
        if(a > b) return 1;
        if(a < b) return -1;
        return 0;
    }

    // 插入新的键
    insert(key){
        if(this.root === null){
            this.root = new Node(key);
        }else{
            this.insertNode(this.root, key);
        }
    }

    insertNode(node, key){
        if(this.compareFn(key, node.key) === -1){ //左小右大
            if(node.left === null){  // 左侧空则插入
                node.left = new Node(key);
            }else{ //左侧有值 找下一个左侧
                this.insertNode(node.left, key);
            }
        }else{
            if(node.right === null){
                node.right = new Node(key);
            }else{
                this.insertNode(node.right, key);
            }
        }
    }

    // 中序遍历 一个键遍历左边同时遍历右边
    middle(callback){
        this.middleNode(this.root, callback);
    }

    middleNode(node, callback){
        if(node !== null){
            this.middleNode(node.left, callback);
            callback(node.key);
            this.middleNode(node.right, callback);
        }
    }

    // 先序遍历
    pre(callback){
        this.preNode(this.root, callback);
    }

    preNode(node, callback){
        if(node !== null){
            callback(node.key);
            this.preNode(node.left, callback);
            this.preNode(node.right, callback);
        }
    }

    // 后序遍历
    back(callback){
        this.backNode(this.root, callback);
    }

    backNode(node, callback){
        if(node !== null){
            this.backNode(node.left, callback);
            this.backNode(node.right, callback);
            callback(node.key);
        }
    }

    // 获取最小值
    min(){
        return this.minNode(this.root);
    }

    minNode(node){
        let current = node;
        while(current !== null && current.left !== null){
            current = current.left;
        }
        return current;
    }

    // 获取最大值
    max(){
        return this.maxNode(this.root);
    }

    maxNode(node){
        let current = node;
        while(current !== null && current.right !== null){
            current = current.right;
        }
        return current;
    }

    // 搜索一个键是否存在
    search(key){
        return this.searchNode(this.root, key);
    }

    searchNode(node, key){
        if(node === null){
            return false;
        }
        if(this.compareFn(key, node.key) === -1){
            return this.searchNode(node.left, key);
        }else if(this.compareFn(key, node.key) === 1){
            return this.searchNode(node.right, key);
        }else{
            return true;
        }
    }

    // 移除一个节点
    remove(key){
        this.root = this.removeNode(this.root, key);
    }

    removeNode(node, key){
        if(node === null) return null;
        if(this.compareFn(key, node.key) === -1){
            node.left = this.removeNode(node.left, key);
            return node;
        }else if(this.compareFn(key, node.key) === 1){
            node.right = this.removeNode(node.right, key);
            return node;
        }else{
            if(node.left === null && node.right === null){
                node = null;
                return node;
            }
            if(node.left === null){
                node = node.right;
                return node;
            }else if(node.right === null){
                node = node.left;
                return node;
            }

            const aux = this.minNode(node.right);
            node.key = aux.key;
            node.right = this.removeNode(node.right, aux.key);
            return node;
        }
    }

}

二叉堆

二叉堆属于特殊的二叉树,它是一棵完全二叉树(尽可能保持所有叶节点都有左右两棵子节点,并且最后一层子节点尽可能是左侧子节点),存在最大二叉堆(所有子节点均小于等于父节点)和最小二叉堆(所有子节点均大于等于父节点)

二叉树有两种表示形式,搜索二叉树的动态指针表示形式和数组表示形式

  • 左侧子节点获取:2*index + 1 比如通过2获取4的位置
  • 右侧子节点获取:2*index + 2 比如通过2获取5的位置

最大二叉堆

实现

class MinHeap{
    constructor(){
        this.heap = [];
    }

    compareFn(a, b){
        if(a > b) return 1;
        if(a < b) return -1;
        return 0;
    }

    // 获取左侧子节点下标
    getLeftIndex(index){
        return 2*index + 1;
    }

    // 获取右侧子节点的下表
    getRightIndex(index){
        return 2*index + 2;
    }

    // 获取父节点的下表
    getParentIndex(index){
        if(index === 0) return undefined; //最小值父节点为空
        return Math.floor((index-1)/2);
    }

    // 堆插入值
    insert(value){
        if(value !== null){
            this.heap.push(value);//先插入最后
            //根据大小上移
            this.shiftUp(this.heap.length - 1);
            return true;
        }
        return false;
    }

    shiftUp(index){
        let parentIndex = this.getParentIndex(index);
        while(index > 0 && this.compareFn(this.heap[parentIndex], this.heap[index]) === 1){
            [this.heap[parentIndex],  this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
            index = parentIndex;
            parentIndex = this.getParentIndex(index);
        }
    }

    size(){
        return this.heap.length;
    }

    isEmpty(){
        return this.size() === 0;
    }

    findMin(){
        this.isEmpty() ? undefined : this.heap[0];
    }

    // 导出堆中的最小值
    extract(){
        if(this.isEmpty()) return undefined;
        if(this.size() === 1) return this.heap.shift();
        const removeValue = this.heap.shift();
        this.shiftDown(0); //下移 堆化
        return removeValue; 
    }

    shiftDown(index){
        let ele = index;
        const left = this.getLeftIndex(index);
        const right = this.getRightIndex(index);
        const size = this.size();
        if(left < size && this.compareFn(this.heap[ele], this.heap[left]) === 1){
            ele = left;
        }
        if(right < size && this.compareFn(this.heap[ele], this.heap[right]) === 1){
            ele = right;
        }
        if(index !== ele){
            [this.heap[index], this.heap[ele]] = [this.heap[ele], this.heap[index]];
            this.shiftDown(ele);
        }
    }
}

应用示例

堆排序


const arr = [2,6,3,5,4,1,7]

var len;
function buildMaxHeap(arr) {   //建堆
    len = arr.length;
    // [n/2-1]表示的是最后一个有子节点 (本来是n/2(堆从1数起),但是这里arr索引是从0开始,所以-1)
    for (var i = Math.floor(len/2)-1; i>=0; i--) {
        maxHeapify(arr, i);
    }
    //对每一个节点(非叶节点),做堆调整
}
function maxHeapify(arr, i) {     //堆调整
    var left = 2*i+1,
        right = 2*i+2,
        largest = i;   //i为该子树的根节点

    if (left < len && arr[left] > arr[largest]) {
        largest = left;
    }

    if (right < len && arr[right] > arr[largest]) {
        largest = right;
    }

    if (largest != i) {  //即上面的if中有一个生效了
        swap(arr, i, largest);  //交换最大的为父节点
        maxHeapify(arr, largest);  //交换后,原值arr[i](往下降了)(索引保存为largest),
        //作为根时,子节点可能比它大,因此要继续调整
    }
}
function swap(arr, i, j) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
function heapSort(arr) {
    buildMaxHeap(arr);
    for (var i = arr.length-1; i > 0; i--) {
        swap(arr, 0, i);
        len--;
        maxHeapify(arr, 0);
    }
    return arr;

}
console.log(heapSort(arr))

邻接矩阵

两个点有连接则为1,无连接则为0,二维数组表示

邻接表

只把与自己有连接的写入对象的数组里面

关联矩阵

矩阵的行表示顶点,列表示边,若顶点A是边v1的入射点则arr[A][v1] = 1否则为0