JavaScript中的常见数据结构

JS中的数据结构

    • 1、Queue 队列
    • 2、Stack 栈
    • 3、Linked List 链表
    • 4、集合
    • 5、树
    • 6、堆

1、Queue 队列

JavaScript中没有队列这个数据结构,但是可以用数组来实现所有的功能。
JavaScript中的常见数据结构_第1张图片

队列是一个先进先出的数据结构,一般JavaScript中采用队列解决问题时会用到

  1. 入队push ():在数组的尾部添加元素
  2. 出队shift ():移除数组中第一个元素
  3. queue (0) :取数组的第一个元素
  4. isEmpty ():确定队列是否为空
  5. size ():获取队列中元素的数量

2、Stack 栈

1️⃣栈作为一种数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。它按照先进后出⬆的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
JavaScript中的常见数据结构_第2张图片

2️⃣JavaScript中没有栈,但是可以用Array实现栈的功能。

3️⃣栈中数组长度减一即为栈尾元素,也就是最后进入的那个元素,最先出去的那个元素

JavaScript中对栈的操作一般会使用到

  1. push()方法,将元素压入栈顶
  2. pop()方法,从栈顶弹出(删除)元素,并返回该元素
  3. peek()方法,返回栈顶元素,不删除
  4. clear()方法,清空栈
  5. length拿到栈中元素数量

3、Linked List 链表

链表是由多个元素组成的列表,链表中的元素储存不连续,用next指针连接在一起。
JavaScript中的常见数据结构_第3张图片

数组:增删非数组元素需要移动元素

链表:增删非首尾元素不需要移动元素只需要更改next的指向即可

链表是一个链式数据结构,每个节点由两个信息组成:节点的数据和指向下一个节点的指针。链表和传统数组都是线性数据结构,具有序列化的存储方式。

操作 数组 链表
内存分配 编译和序列化过程中静态分配 运行过程中动态分配
获取元素 从索引中读取 读取队列中的所有节点,直到特定元素,较慢
增加/删除元素 顺序增加删除,较慢 动态分配,内存消耗小,速度快
空间结构 一维或者多维 单边/多边,循环链表

JavaScript中没有链表,但是可以用object来模拟链表

const a = { val: 'a' }
const b = { val: 'b' }
const c = { val: 'c' }
const d = { val: 'd' }
    // a的next属性指向b
a.next = b;
b.next = c;
c.next = d;
// 这个嵌套的object就相当于一个链表

遍历链表

遍历链表就是跟着链表从链表的头元素(head)一直走到尾元素(但是不包含链表的头节点,头通常用来作为链表的接入点)

‍♂️还有一个问题,链表的尾元素指向一个null节点

// 声明一个指针,指向a
let p = a
    // 当p还有值得时候
while (p) {
    console.log(p.val)
        // 不断得让p指向下一个位置
    p = p.next
}

插入链表

// 在链表中插入值
const f = { val: 'f' }
c.next = f
f.next = d

删除链表

// 删除值
c.next = d

4、集合

集合:一种无序且唯一的数据结构,集合区别队列、栈、链表最大的区别就是元素不能重复

JavaScript中ES6中新增了集合这种数据结构,可以通过实例化Set对象来创建集合const set = new Set()

集合常用来解决的问题:

  1. 去重
    set = new Set(arr),返回一个不含重复元素的集合
  2. 判断元素是否在集合中
    set.has(num),判断num是否在集合中
  3. 求两个集合的交集
    先将集合set1转换为数组,然后利用数组中的filter方法将set1在set2中的值返回,最后将返回的值转换为集合
const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
const set3 = new Set([...set1].filter(item = > set2.has(item)))
//set3{4}
  1. 求两个集合的并集
const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
let set3 = new Set([...set1, ...set2]);
// set3 {1, 2, 3, 4}
  1. 求两个集合的差集
const set1 = new Set([1,2,4])
const set2 = new Set([3,4])
let set3 = new Set([...set1].filter(x => !set2.has(x)));
// set3 {1,2}

filter() 方法创建一个新的数组,新数组返回指定数组中符合条件的所有元素

...是ES6中新增的扩展运算符,对象中的扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。[...set]的意思就是将集合转换为数组

5、树

前端树结构还是比较常见的,例如级联选择、层级目录等都是树形结构。

javascript中没有树这个数据结构,但是一般用object和arrey来模拟树。

const tree = {
	values:a,
	children:[
        {
            values:b,
            children:[
                {
                    values:d,
                    children:[]
                },
                {
                    values:e,
                    children:[]
                },{
                 	values:f,
                    children:[]
                }
            ]
        },
        {
            values:c,
            children:[
                {
                    values:g,
                    children:[]
                },
                {
                    values:h,
                    children:[]
                }
            ]
        }
    ]
}

上面的代码模拟的就是下图的树结构
JavaScript中的常见数据结构_第4张图片

树的常用遍历方式

  1. 深度优先遍历
    尽可能深的遍历树的分支。先访问根节点,然后再对子节点挨个使用深度优先遍历。

上图中的树使用深度优先遍历

const deepNood = (node)=>{
//打印根节点
    console.log(node.values);
    //遍历子节点
    node.children.forEach(child => {
    //递归
        deepNood(child)
    })
}
deepNood(tree)
//a b d e f c g h
  1. 广度优先遍历

优先访问距离根节点最近的节点。广度优先遍历需要使用到队列这个数据结构

  • 新建一个队列,把根节点入队
  • 将队头出队并访问
  • 将队头的children顺序入队
  • 重复第二步和第三步,直到队列为空

上面的树使用广度优先遍历

const breadth = (node)=>{
//将树加入队列(整个object对象,)
    const arr = [node];
//队列是否为空
    while(arr.length > 0){
    //从队列中取出根节点
        const val = arr.shift()
        console.log(val.values)
        //遍历子节点
        for(let child of val.children){
        //将子节点加入队列
            arr.push(child)
        }
 // 打开console.log(arr),就能看出不断的将childern入队,然后再将队头取出
    }
}
breadth(tree)
//a b c d e f g h 

二叉树

二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成

树的每个节点最多只能有两个子节点
JavaScript中的常见数据结构_第5张图片
js中自然也没有二叉树这个数据结构,一般还是用object对象来模拟二叉树
JavaScript中的常见数据结构_第6张图片

二叉树代码实现

const tree = {
	val:1,
    left:{
            val:2,
            left:{
                    val:4,
                    left:null,
                    right:null
                },
            right:{
                    val:5,
                    left:null,
                    right:null
            }
        },
    right:{
            val:3,
            left:{
                    val:6,
                    left:null,
                    right:null
                },
            right:
                {
                    val:7,
                    left:null,
                    right:null
                }
        }
}

二叉树遍历(递归)

(1)前序遍历

DLR:根节点——左子树——右子树

每次遍历到一个节点都重复一次前序遍历

代码实现

const perorder = (node) =>{
//如果当前节点值为空则返回
    if(!node) return;
    //打印根节点
    console.log(node.val);
    //递归左子树
    perorder(node.left);
    //递归右子树
    perorder(node.right);
}
perorder(tree)
//1 2 4 5 3 6 7

(2)中序遍历

LDR:左子树——根节点——右子树

每次遍历到一个节点都重复一次中序遍历

代码实现

const perorder = (node) =>{
    
    if(!node)return;
    perorder(node.left);
    console.log(node.val);
    perorder(node.right);
}
perorder(tree)
//4 2 5 1 6 3 7

(3)后序遍历

LRD:左子树——右子树——根节点

每次遍历到一个节点都重复一次后序遍历

代码实现

const perorder = (node) =>{
    if(!node)return;
    perorder(node.left);
    perorder(node.right);
    console.log(node.val);
}
perorder(tree)
//4 5 2 6 7 3 1

注意:
前序遍历第一个为根节点
中序遍历根节点左边为左子树,右边为右子树
后序遍历最后一个为根节点

6、堆

堆是一种特殊的完全二叉树

每层节点全部都填满,最后一层如果不是满的,只能缺少右边的节点,下图为最大堆的示例
JavaScript中的常见数据结构_第7张图片
所有的节点都大于等于(最大堆)或者小于等于(最小堆)他的子节点

js中的堆

js中通常用数组来表示堆,按照广度优先的方式存储在数组中

根据广度优先的特点可以得到子节点在数组中的存储位置(index代表当前节点在数组中的下标)

左侧子节点的位置是2 * index+1
右侧子节点的位置是2 * index+1
父节点位置是(index - 1)/ 2
JavaScript中的常见数据结构_第8张图片

第k个最大元素

主要方法 :

插入元素(最小堆)

构建一个最小堆,并将元素依次添加到堆中,

  1. 将值插入堆的底部,也就是数组的尾部
  2. 上移,将这个值与其父节点进行交换,直到父节点小于等于这个插入的值
  3. 大小为k的堆中插入元素的时间复杂度为O(logk)

删除堆顶

当堆的容量超过了k就删除堆顶

  1. 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆解构)
  2. 下移,将新堆顶和子节点进行交换,直到子节点大于这个堆顶
  3. 大小为k的堆中删除堆顶的时间复杂度为O(logk)

获取堆顶

插入结束后堆顶就是第k个最大元素

获取堆大小

数组长度就是堆的大小

代码实现最小堆类

class  minHeap{
    constructor(){
        this.heap = []; 
    }
    // 获取父节点在数组中的下标
    getParentIndex(i){
        return Math.floor((i-1)/2)
        //二进制写法
        // return (i - 1) >> 1
    }
    // 获取左侧节点在数组中的下标
    getLeftIndex(i){
        return i * 2 + 1
    }
    // 获取右侧节点在数组中的下标
    getRightIndex(i){
        return i * 2 + 2
    }

    // 定义节点交换的方法
    swap(i1,i2){
        const temp = this.heap[i1];
        this.heap[i1] = this.heap[i2];
        this.heap[i2] = temp;

    }
    // 定义上移方法
    shiftUp(index){
        if(index==0){
            return;
        }
        // 不停的和父节点交换位置,直到小于等于父节点的值
        const parentIndex = this.getParentIndex(index)
        if(this.heap[parentIndex] > this.heap[index]){
            this.swap(parentIndex,index)
            this.shiftUp(parentIndex)
        }
    }
    // 定义下移方法
    shiftDown(index){
        const liftIndex = this.getLeftIndex(index)
        const RightIndex = this.getRightIndex(index)
        if(this.heap[liftIndex] < this.heap[index]){
            this.swap(liftIndex,index)
            this.shiftDown(liftIndex)
        }
        if(this.heap[RightIndex] < this.heap[index]){
            this.swap(RightIndex,index)
            this.shiftDown(RightIndex)
        }
    }
    // 插入方法
    insert (value){
        // 将元素插入到数组的最后一位
        this.heap.push(value)
        
        // 因为插入的元素为数组的最后一位,所以传入的参数是heap.length-1
        this.shiftUp(this.heap.length-1)
    }
    // 删除方法
    delect(){
        // pop可以删除数组的最后一个元素并返回这个元素
        this.heap[0] =  this.heap.pop();
        this.shiftDown(0)
    } 
    //获取堆顶
    peek(){
    	return this.heap[0]
    }
    //获取堆大小
    size(){
		return this.heap.length
	}
}
// 实例化一个最小堆
const h = new minHeap();

//插入几个数字测试
h.insert(3);
h.insert(2);
h.insert(1);
//虽然不能保证是按照最小堆来排列,但是能保证堆顶最小,也就是父节点的元素一定大于子节点的元素

你可能感兴趣的:(数据结构,数据结构,javascript,二叉树,堆栈,链表)