线性数据结构:数组、受限数组(栈、队列)、线性表

1. 数组

数组定义

  数组(Array)是有序的元素序列。属于线性结构(有且仅有一个前驱、有且仅有一个后继)。

数组特点

  数组的关键在于在内存中的物理地址对应的是一段连续的内存。这意味着如果想要在任意位置删除/新增一个元素,那么该位置往后的所有元素,都需要往前挪/往后挪一个位置。假设数组的长度是 n,那么因增加/删除操作导致需要移动的元素数量,就会随着数组长度 n 的增大而增大,呈一个线性关系。所以说数组增加/删除操作对应的时间复杂度就是 O(n)

在js中的数组比较特殊,如果我们在一个数组中只定义了一种类型的元素,比如:

const arr = [1,2,3,4]

它是一个纯数字数组,那么对应的确实是连续内存。
但如果我们定义了不同类型的元素:

const arr = ['haha', 1, {a:1}]

它对应的就是一段非连续的内存。此时,JS 数组不再具有数组的特征,其底层使用哈希映射分配内存空间,是由对象链表来实现的。
谨记“JS 数组未必是真正的数组

2. 栈和队列(操作受限的数组)

  栈是一种后进先出(LIFO,Last In First Out)的数据结构。——只用 pop 和 push 完成增删的“数组”

  • 只允许从尾部添加元素
  • 只允许从尾部取出元素

队列

  队列(Queue)是一种先进先出(FIFO,First In First Out)的数据结构。——只用 push 和 shift 完成增删的“数组”。在栈元素出栈时,我们关心的是栈顶元素(数组的最后一个元素);队列元素出队时,我们关心的则是队头元素(数组的第一个元素)。

3. 链表

  链表和数组相似,线性结构(有且仅有一个前驱、有且仅有一个后继),不同点在于,链表中,数据单位的名称叫做“结点”,而结点和结点的分布,在内存中可以是离散的
一个内容为1->2->3->4->5的链表,在内存中的形态可以是散乱如下的:

在链表中,每一个结点的结构都包括了两部分的内容:数据域和指针域。JS 中的链表,是以嵌套的对象的形式来实现的:

{
    // 数据域
    val: 1,
    // 指针域,指向下一个结点
    next: {
        val:2,
        next: ...
    }
}

数据域存储的是当前结点所存储的数据值,而指针域则代表下一个结点(后继结点)的引用。

创建链表:

function ListNode(val) {
    this.val = val;
    this.next = null;
}
const node = new ListNode(1)  
node.next = new ListNode(2)

链表元素的添加和删除操作,本质上都是在围绕 next 指针做文章,例如在节点1和节点2之间插入节点3:

// 如果目标结点本来不存在,那么记得手动创建
const node3 = new ListNode(3)     
// 把node3的 next 指针指向 node2(即 node1.next)
node3.next = node1.next
// 把node1的 next 指针指向 node3
node1.next = node3

删除节点3:

node1.next = node3.next 

在涉及链表删除操作的题目中,重点不是定位目标结点,而是定位目标结点的前驱结点。做题时,完全可以只使用一个指针(引用),这个指针用来定位目标结点的前驱结点。

总结:链表的插入/删除效率较高,而访问效率较低;数组的访问效率较高,而插入效率较低。

热门相关:第一强者   盛华   铁血大明   北宋大表哥   士子风流