js视角的数据结构和算法(二)

js视角的数据结构和算法(二)

数据结构划分

数据结构分为物理结构和逻辑结构,二者相辅相成,且逻辑结构依托物理结构存在。
换句话说,任何逻辑结构都需要依赖物理结构去实现,不可独立存在。

什么是物理结构?

  • 物理结构就是数据的逻辑结构在计算机中存储的方式。
  • 若将逻辑结构比作房屋,物理结构就是房屋内部的框架结构。

物理结构包括哪些?

  • 顺序存储结构:具有代表性的是数组
  • 链式存储结构:具有代表性的是链表

什么是逻辑结构?

  • 逻辑结构是指数据对象中各个数据元素之间的相互关系。

逻辑结构包括哪些?

  • 集合,线性表,树,图…

数组

数组是一种内存中连续存储数据的结构,常用的操作是增删改查。数组的容量问题,不同语言实现有差异。


  • 与java的数组实现不同,js是动态扩容,而且常规存储很难有撑爆数组的可能。

  • js中,数组最大容量是Math.pow(2,32)-1,也就是2的32次方再减去1。指数爆炸,这是个很大的数(4294967295)

  • new Array()方法可接受一个无符号整型数字,含4个字节,一个字节占8位,即总共占32位,2进制存储就是2的32次方。

  • 原本第一位是符号位,无符号整型不需要考虑,从第一位开始计,故最大长度是Math.pow(2,32)-1。


模拟实现数组的查找方法–find

  • 数组在内存中开辟的是连续空间,但有意思的是它可以根据数组下标随机访问某个元素,时间复杂度为O(1)
function find(index) {
    const arr = [1, 2, 3, 4, 5]
    return arr[index]
}

模拟实现数组的更新方法–update

  • 直接更改某个下标对应的元素值,就是更新操作,时间复杂度为O(1)
function update(index, value) {
    const arr = [1, 2, 3, 4, 5]
    arr[index] = value
    return arr
}

模拟实现数组的插入方法–insert

  • 数组的插入分为两种情况,尾插和中间插(含头插),中间插会导致待插入位置及其后续元素位置索引改变,是比较耗性能的,时间复杂度为O(n),但如果恰好是尾插,那就是O(1),这也是js数组方法中push比unshift性能高的原因。

function insert(index, value) {
    const arr = [1, 2, 3, 4, 5]
    let len = arr.length - 1;
    //尾插
    if (index > len) {
        arr[index] = value;
        return arr;
    } else {
        //中间插

        //从右往左遍历,元素右移一位

        for (let i = len; i >= index; i--) {
            arr[i + 1] = arr[i]
        }
        //插入目标元素
        arr[index] = value
        return arr
    }

}

模拟实现数组的删除方法–remove

  • 数组的删除也分为两种情况,尾删和中间删(含头删),中间删会导致待删元素位置及其后续元素位置索引改变,是比较耗性能的,时间复杂度为O(n),但如果恰好是尾删,那就是O(1),这也是js数组方法中pop比shift性能高的原因。
    在这里插入图片描述

function remove(index) {
    const arr = [1, 2, 3, 4, 5]
    let len = arr.length - 1;
    //尾删
    if (index === len) {
        arr.length = len;
        return arr;
    } else {
        //中间删(含头删),从左往右遍历,元素左移一位
        for (let i = 0; i < len; i++) {
            arr[i] = arr[i + 1]
        }

        //长度减一,对应的元素也会删除掉
        arr.length--;
        return arr
    }

}


数组的特点

  • 访问和更新速度快,时间复杂度为O(1)
  • 删除和插入速度慢,时间复杂度为O(n)
  • 占用的内存空间是连续的,对分散的碎小空间无法利用
  • 适用于频繁读(查找,更新)不频繁写的场景(删除和插入)

链表

根据数组特点来看,擅长读不擅长写。那如果我们不需要频繁读取,只需要频繁写呢?
数组开辟的内存空间都是连续的,对碎片化内存空间无法充分利用,那如果就想充分利用呢?
存在即合理,基于以上两点,链表诞生了。它是一种非线性,不连续的物理结构,本质是诸多节点组成。

单向链表

  • 上图就是单向链表,也是链表中最简单的一种,每个节点由数据和指向下一个节点的指针两部分组成。
  • 第一个节点称为头节点,最后一个节点称为尾节点,next指针指向为NULL.
  • 顾名思义,单向链表就是单向的,上级可以知道下级是谁,但是下级不清楚上级是谁

双向链表

  • 双向链表弥补了单向链表在访问上级的不足,它的每个节点拥有数据,前指针,后指针三项。

链表的查找

  • 不论是哪种类型链表,由于结构限制,无法像数组那样支持根据下标随机访问
  • 链表访问目标节点需要逐个寻找,直到找到目标节点的上一个节点,其next指针指向的就是目标节点
  • 最好情况自然是第一个就是,时间复杂度为O(1),最坏的就是都找一次,时间复杂度为O(n)

链表的更新

  • 若不考虑找到目标节点耗费的时间,链表也可以像数组那样直接更新(替换数据项data即可),时间复杂度为O(1)
更新前 2|next->  更新后3|next->

链表的插入

链表的插入要考虑头插,尾插,中间插三种情况。

  • 头插:将新节点next指针指向原头节点并将新节点视为插入完成后的新头节点;时间复杂度为O(1)
    在这里插入图片描述

  • 中间插:新节点next指针指向插入位置节点,插入位置的上一个节点next指针指向新节点 。时间复杂度为O(1)

  • 尾插:最后一个节点next指针的指向由NULL改为指向新节点,新节点的next指针指向为NULL。时间复杂度为O(1)

链表的删除

同插入一样,链表的删除也分三种情况。头删,尾删,中间删

  • 头删:直接干掉现有头节点,其后首个节点为新的头节点。时间复杂度为O(1)

在这里插入图片描述

  • 中间删:将要删除节点上一级节点的next指针跨过要删除的节点,指向其下一个节点。时间复杂度为O(1)

在这里插入图片描述

  • 尾删:直接将尾节点的上一级节点next指针置为NULL

在这里插入图片描述

链表的特点

  • 若不考虑查找目标节点耗费的时间,不管是删除还是插入,时间复杂度都是O(1)
  • 不是连续存储的,可充分利用内存中的碎小空间
  • 适合频繁写(插入,更新和删除),不适合频繁读(查找)

时间复杂度比较

在这里插入图片描述

发布了464 篇原创文章 · 获赞 809 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_42813491/article/details/104073939