JavaScript中常见的数据结构

作为一名开发者,无论是前端还是后端,哪能不知道常见的数据结构???数据结构是通用的,无论你是学js、java、python,基本上数据结构都是相通的,了解数据结构也让我们更容易学会其他编程语言!

好吧,我是菜鸟一枚,职业Ctrl+C、Ctrl+V者,精通js、vue、react等单词的拼写,工作范围了解到的数据结构有限,这里记录下几种与我们息息相关的数据结构吧,后面再循循渐进!

数据结构与算法

介绍

  • 栈属于受限的线性结构,其特点为先进后出

常见操作

  • push(element):添加一个新元素到栈顶位置;
  • pop:移除栈顶的元素,同时返回被移除的元素;
  • peek:返回栈顶的元素,不对栈做任何修改(该方法不会移除栈顶的元素,仅仅返回它);
  • isEmpty:如果栈里没有任何元素就返回true,否则返回false;
  • size:返回栈里的元素个数。这个方法和数组的length属性类似;
  • toString:将栈结构的内容以字符串的形式返回。

栈操作封装

js 复制代码
// 栈
class Stack {
    constructor(items = []) {
        this.items = items
    }

    // 向栈内添加元素
    push(element) {
        this.items.push(element)
    }
    // 栈内弹出元素
    pop() {
        return this.items.pop()
    }
    // 返回栈顶元素
    peek() {
        const length = this.items.length
        return this.items[length - 1]
    }
    // 判断栈是否为空
    isEmpty() {
        return this.items.length === 0
    }
    // 判断栈内元素个数
    size() {
        return this.items.length
    }
    // 转字符串
    toString() {
        let str = '';
        for (let i of this.items) {
            str = i + ' ' + str
        }
        return str
    }
}

// 测试
// const stack = new Stack();
// stack.push('abc');
// stack.push('def');
// stack.push('ghi');
// console.log(stack.items);

// console.log(stack.pop())
// console.log(stack.items);

// console.log(stack.peek());

// console.log(stack.isEmpty());
// console.log(stack.size());
// console.log(stack.toString())

实战:十进制转二进制

js 复制代码
// 实战:二进制转十进制
function dec2bin(dec) {
    let stack = new Stack();
    while (dec > 0) {
        // 十进制除2取余,依次放入栈中
        stack.push(dec % 2);
        dec = Math.floor(dec / 2) 
    }
    return stack.toString()
}

console.log(dec2bin(50))

队列

介绍

  • 队列属于受限的线性结构,其特点为先进先出

常见操作

  • enqueue(element):添加,向队列尾部添加一个(或多个)新的项;
  • dequeue:删除,移除队列的第一(即排在队列最前面的)项,并返回被移除的元素;
  • front:返回队列中的第一个元素
  • isEmpty 判断是否为空
  • size 返回队列长度
  • toString 队列转字符串,(将队列已字符串的形式返回)

队列操作的封装

js 复制代码
// 队列
class Queue {
    constructor(items = []) {
        this.items = items
    }
    // 添加
    enqueue(element) {
        // 从队尾添加
        this.items.push(element)
    }
    //删除   
    dequeue() {
        // 从队首删除
        return this.items.shift()
    }
    // 返回第一个元素
    front() {
        return this.items[0]
    }
    //是否为空   
    isEmpty() {
        return this.items.length === 0
    }
    //大小   
    size() {
        return this.items.length
    }
    //转字符串   
    toString() {
        let str = '';
        for (let i of this.items) {
            str += i + ' '
        }
        return str;
    }
}

const queue = new Queue()

// 测试
queue.enqueue('a');
queue.enqueue('b');
queue.enqueue('c');
queue.enqueue('d');

console.log(queue.dequeue())
console.log(queue.front())
console.log(queue.isEmpty())
console.log(queue.size())
console.log(queue.toString())
 

队列实践

击鼓传花游戏:指定一个队列,和一个数字,列如数字3,每次将队列从前往后数3,当数到队列某个元素为3时,移除队列,找出一个元素!

js 复制代码
// 击鼓传花  指定一个队列,和一个数字,列如数字3,每次将队列从前往后数3,当数到队列某个元素为3时,移除队列,找出一个元素
function passGame(nameList, number) {
    let queue = new Queue(nameList)

    while (queue.size() > 1) {
        for (let i = 0; i < number - 1; i++) {
            // 依次放入到队尾
            queue.enqueue(queue.dequeue())
        }
        //符合number的,直接删除    
        queue.dequeue()
    }
    return queue.toString()
}
 
const winer = passGame(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3);
console.log(winer)

优先级队列封装

可以指定插入优先级,数字越小,优先级越高,放在队列越靠前

js 复制代码
//  优先级队列
class PriorityQueue {
    // 封装属性
    items = []

    // 内部类
    enqueue(element) {
        const { priority } = element;
        // 判断队列是否为空
        if (this.items.length === 0) {
            this.items.push(element)
        } else {
            // 定义插入状态
            let added = false;
            for (let i = 0; i < this.items.length; i++) {
                // 数字越小,优先级越高
                if (priority < this.items[i].priority) {
                    this.items.splice(i, 0, element);
                    added = true;
                    // 找到后就停止循环
                    break;
                }
            }

            // priority比所有都大 优先级最低  直接放到最后
            if (!added) {
                this.items.push(element);
            }
        }
    }

    //删除   
    dequeue() {
        // 从队首删除
        return this.items.shift()
    }
    // 返回第一个元素
    front() {
        return this.items[0]
    }
    //是否为空   
    isEmpty() {
        return this.items.length === 0
    }
    //大小   
    size() {
        return this.items.length
    }
    //转字符串   
    toString() {
        let str = '';
        for (let i of this.items) {
            str += i.name + ' '
        }
        return str;
    }
}

const priorityQueue = new PriorityQueue();
priorityQueue.enqueue({ name: 'Tom', priority: 1 });
priorityQueue.enqueue({ name: 'Rose', priority: 3 });
priorityQueue.enqueue({ name: 'Jack', priority: 2 });
priorityQueue.enqueue({ name: 'Duck', priority: 0 });


console.log(priorityQueue.toString()); 

单向链表

介绍

优点

  • 链表的元素在内存中不必是连续的空间,可以充分利用计算机内存,实现灵活的内存动态管理
  • 链表不必在创建时确定大小,并且可以无限延申下去
  • 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多

缺点

  • 链表访问任一元素,都需要从头还是查找(无法跳过第一个元素访问任意一个元素)
  • 无法通过下标值直接访问元素,需要从头开始一个个访问,知道查找到对应元素
  • 虽然轻松到达下一节点,但是返回到前一个节点困难

常见操作

  • append(element):添加,向链表尾部添加原素
  • insert(position,element):向链表的特定位置插入一个新的项
  • get(position):获取对应位置的元素;
  • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素就返回-1;
  • update(position,element):修改某个位置的元素;
  • removeAt(position):从链表的特定位置移除一项;
  • remove(element):从链表中移除一项;
  • isEmpty():如果链表中不包含任何元素,返回trun,如果链表长度大于0则返回false;
  • size():返回链表包含的元素个数,与数组的length属性类似;
  • toString():由于链表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值;

单向链表封装

js 复制代码
// 封装单向链表
class LinkedList {
    // 内部类
    Node(data) {
        return {
            data: data,
            next: null
        }
    }
    //头部 
    head = null;
    length = 0;

    // 插入(链表最后位置)
    append(data) {
        //1.创建新节点
        let newNode = this.Node(data);
        //2.添加节点
        //情况1:链接为空时
        if (this.length === 0) {
            this.head = newNode;
        } else {
            //情况2:链表不为空
            let current = this.head;
            // 找到最后一个节点
            while (current.next) {
                current = current.next
            }
            // 将最后节点的next 指向新节点
            current.next = newNode;
            // 长度价1
        }
        this.length += 1
    }

    insert(data, position) {
        //边界判定
        if (position < 0 || position > this.length) {
            return false
        }
        //1.创建新节点
        let newNode = this.Node(data);
        //2.插入新节点
        if (position === 0) {
            //2.1插入到首部
            //让新节点的next 指向第一个节点
            newNode.next = this.head;
            //头部指向新节点    
            this.head = newNode;
        } else {
            let index = 0;
            let prev = null;
            let current = this.head;
            while (index++ < position) {
                // 往后移(prev变为当前节点  当前节点后移一位)(先把当前节点保存,再向后移,就可以知道链接的上一个节点了)  
                prev = current
                current = current.next
            }
            // 切记先改  再保存(赋值原则)  否则  先赋值,后值又改了,赋值就不对了
            newNode.next = current
            prev.next = newNode;
        }

        this.length += 1;
        return true
    }

    // 查找方法
    get(position) {
        //边界判断
        if (position < 0 || position >= this.length) {
            return null
        }
        let current = this.head;
        let index = 0;
        while (index++ < position) {
            current = current.next;
        }
        return current.data;
    }

    // indexOf方法判断是否含有某个节点
    indexOf(data) {
        let current = this.head;
        let index = 0;
        // current不为null就一直向下查找 找不到就返回-1
        while (current) {
            if (current.data === data) {
                return index
            }
            current = current.next;
            index += 1
        }
        return -1
    }

    // 更新指定位置节点数据
    update(position, newData) {
        //越界判断
        if (position < 0 || position >= this.length) {
            return false
        }
        // 查找到当前节点
        let current = this.head;
        let index = 0;
        while (index++ < position) {
            current = current.next;
        }
        // 将节点数据修改
        current.data = newData;
        return true
    }

    // 删除指定位置节点数据
    removeAt(position) {
        //越界判断
        if (position < 0 || position >= this.length) {
            return false
        }
        //删除元素
        if (position == 0) {
            // 第一个节点  直接更改head的指向就行
            this.head = this.head.next
        } else {
            // 找到指定位置节点
            let current = this.head;
            let index = 0;
            let prev = null;
            while (index++ < position) {
                prev = current;
                current = current.next;
            }
            previous.next = current.next

        }
        this.length -= 1;
        return current.data;
    }

    // 删除数据
    remove(data) {
        let index = this.indexOf(data);
        return this.removeAt(index);
    }

    // 查询是否未空
    isEmpty(){
        return this.length == 0
    }

    // 查询大小
    size(){
        return this.length
    }

}


// 插入测试
// const linkedList = new LinkedList();
// linkedList.append('aaa');
// linkedList.append('bbb');
// linkedList.append('ccc');
// linkedList.append('ddd');
// linkedList.insert('eee',2);

// console.log(linkedList)

双向链表

介绍

  • 双向链表:既可以从头遍历到尾,又可以从尾遍历到头。链接是双向的,实现原理是一个节点既有向前链接的引用,又有向后链接的引用
  • 双向链表不仅有head指针指向第一个节点,又有tail指针指向最后一个节点
  • 每一个节点都有prev、data、next3个部分组成
  • 双向链表第一个节点的prev指向null,最后一个节点的next指向null

双向链表封装

关于删除节点:

关于插入节点:

js 复制代码
// 封装双向链表
class DoubleLinklist {
    Node(data) {
        return {
            data,
            prev: null,
            next: null
        }
    }
    // 属性
    head = null;
    tail = null;
    length = 0;

    // append添加
    append(data) {
        let newNode = this.Node(data);
        //判断链表是否为空
        if (this.length == 0) {
            // 为空直接头尾指向新节点
            this.head = newNode;
            this.tail = newNode;
        } else {
            // 当不是空时
            this.tail.next = newNode;
            newNode.prev = this.tail;
            this.tail = newNode;
        }

        this.length += 1;
    }

    // 向后遍历字符串
    backwardString() {
        let current = this.head;
        let resultStr = '';
        while (current) {
            resultStr = resultStr + ' ' + current.data;
            current = current.next;
        }
        return resultStr;
    }
    // 向前遍历成字符串
    forwardString() {
        let current = this.tail;
        let resultStr = '';
        while (current) {
            resultStr = resultStr + ' ' + current.data;
            current = current.prev;
        }
        return resultStr;
    }

    // 向指定位置插入数据
    insert(position, data) {
        // 边界判断
        if (position < 0 || position > this.length) {
            return false
        }
        // 创建节点
        let newNode = this.Node(data);

        // 1.原链表为空
        if (this.length == 0) {
            this.head = newNode;
            this.tail = newNode;
        } else {
            // 2原链表不为空
            if (position == 0) {
                //2.1插入头部
                newNode.next = this.head;
                this.head.prev = newNode;
                this.head = newNode;
            } else if (position == this.length) {
                //2.2插入尾部
                this.tail.next = newNode;
                newNode.prev = this.tail;
                this.tail = newNode;
            } else {
                //插入中间
                // 找到position对应节点
                let index = 0;
                let current = this.head;
                while (index++ < position) {
                    current = current.next;
                }
                current.prev.next = newNode;
                newNode.prev = current.prev;
                newNode.next = current;
                current.prev = newNode;
            }
        }
        this.length += 1;
    }

    // 获取数据
    get(position) {
        // 边界判断
        if (position < 0 || position >= this.length) {
            return null;
        }
        let current = null;
        if (position < (this.length / 2)) {
            // 当查找的值在前半段 
            current = this.head;
            let index = 0;
            if (index++ < position) {
                current = current.next
            }

        } else {
            // 当查找的值在后半段
            current = this.tail;
            let index = this.length - 1
            while (index-- > position) {
                current = current.prev;
            }
        }
        return current.data;
    }

    // 找到数据的位置,没有返回-1
    indexOf(data) {
        //1.定义变量
        let current = this.head
        let index = 0

        //2.遍历链表,查找与data相同的节点
        while (current) {
            if (current.data == data) {
                return index
            }
            current = current.next
            index += 1
        }
        return -1
    }

    // 更新
    update(position, newData) {
        // 边界判断
        if (position < 0 || position >= this.length) {
            return null;
        }
        let current = null;
        if (position < (this.length / 2)) {
            // 当查找的值在前半段 
            current = this.head;
            let index = 0;
            if (index++ < position) {
                current = current.next
            }

        } else {
            // 当查找的值在后半段
            current = this.tail;
            let index = this.length - 1
            while (index-- > position) {
                current = current.prev;
            }
        }
        current.data = newData;
        return true;
    }
    // 删除指定位置数据
    removeAt(position) {
        // 边界判断
        if (position < 0 || position >= this.length) {
            return false;
        }

        // 删除节点
        if (this.length == 1) {
            // 1.链表只有一个节点
            this.head = null;
            this.tail = null;
        } else {
            //2.链表不止一个节点
            if (position == 0) {
                //3.1删除第一个节点
                this.head.next.prev = null;
                this.head = this.head.next;
            } else if (position == this.length - 1) {
                //3.2删除最后一个节点
                this.tail.prev.next = null;
                this.tail = this.tail.prev;
            } else {
                //3.3删除中间的节点
                let current = this.head;
                let index = 0;
                while (index++ < position) {
                    current = current.next;
                }
                current.next.prev = current.prev;
                current.prev.next = current.next;
            }
        }
        this.length -= 1
    }

}


// // 测试
let list = new DoubleLinklist()

// //2.测试append方法
list.append('aaa')
list.append('bbb')
list.append('ccc')
// console.log(list);

// 3.测试输出字符串
// console.log(list.backwardString(),list.forwardString())


// 4.测试insert方法
list.insert(0, '000');
list.insert(4, '111');
list.insert(3, 'ddd')
// console.log(list)

// 5.测试get
// console.log(list.get(3))
// console.log(list.get(1))

// 6.测试removeAt
// console.log(list)
// list.removeAt(0);
// list.removeAt(4);
// list.removeAt(2)
// console.log(list)

以上的栈、队列、链表在我们日常学习源码中非常常见,列如vue、react的源码中。

待进阶更新:哈希表、树、图结构

相关推荐
ZZZ_O^O22 分钟前
二分查找算法——寻找旋转排序数组中的最小值&点名
数据结构·c++·学习·算法·二叉树
CV-King1 小时前
opencv实战项目(三十):使用傅里叶变换进行图像边缘检测
人工智能·opencv·算法·计算机视觉
代码雕刻家1 小时前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain1 小时前
算法 | 位运算(哈希思想)
算法
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
Kalika0-03 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d