链表与栈小记

146 阅读4分钟

链表

链表是由一个个节点组成的,每个节点只存两个东西 一个就是数据(值), 一个是指针(域)。
指针用js的简单理解就是引用,也就是说一个节点存它本身和下一个节点的引用。这样, 一个节点无法访问它的上一节点,
这就是单向链表。一般只要存一个链表的头结点(第一个节点)就能访问到所有节点。
遍历一个链表只需要不停的找下一节点
删除一个节点 只需要需要把待删除节点的上个节点的指针域 指向 待删除节点的下一个节点,
在两个节点之间插入一个节点 需要先把该节点的指针 指向后面的节点 然后把前面的节点指针域指向该节点

class ListNode{
    constructor(val, next=null){
      this.val = val 
      this.next = next 
    }
  }

栈也是一种基础数据结构 和计算机里实际的栈还是有点区别
栈先进后出 因为入口就是出口
用js的数组简单理解 就是 一个数组 只能使用push和pop 不能使用索引
稍微深入一点理解 栈适用于有始有终的问题 就像括号左边是开始 右边是结束 而且必须先开始才能结束
递归容易爆栈就是因为 执行一个函数 就会有一个上下文入栈 直到执行完毕 上下文才被弹栈(出栈)所以一直开启新的函数 而之前的函数并没有执行完毕,
就在不断的入栈。并且出栈时 最近开始的函数最先执行完毕 出栈。
一件事完成了 那么最后栈应该是空的。

高阶函数的执行就是栈的进进出出。例如

image.png

上面的高阶函数 就是f1(f2(f3)). 最先开始的函数最先进栈,最后出栈,最后后开始的函数最先出栈最后进栈。

还有我们前端的组件套组件。 父组件开始初始化,然后子组件开始初始化,子组件初始化完毕之后,父组件才会初始化完毕。 父组件开始销毁,子组件开始销毁,子组件销毁之后,父组件才会销毁。

数据存储结构

有人可能喜欢问数组和链表的区别,在我这个半路出家的前端看来,我怎么也不会把这两种数据结构联系在一起,在我看来区别很大啊,都没有什么相似性。

不过从存储数据的角度来说, 它们都是存储数据的,有人可能就会说数组是顺序存储, 链表是链式存储

更准确的说是线性表的顺序存储,(js的)数组是一种更高级的实现。

简单说一下线性表,它是一个有序的元素集合,每个元素类型相同(不知道这里的类型指什么), 每个元素都只有一个前驱和一个后继。 当然,头是没有前驱的, 尾也是没有后继的。

这里的顺序存储就是字面上的意思,物理空间也就是磁盘或者内存上的实际数据也是有序的。 概念性说明就是 用一段地址连续的存储单元依次存储线性表的数据元素 这样只要知道头的位置和要访问的元素的索引, 这个元素的地址是可以被计算出来的。

如果只使用连续的地址, 难免会有一些浪费和不便。不便就是在插入和删除元素的时候需要大量移动数据。这就是为什么数组的pop 和push方法的性能要优于shift 和unshift。删除头元素需要整个表都往前移一步。

由此有了链式存储,也就是链表这种结构,代价就是需要多存储一个指针域。显然链表也是符合线性表的定义 ,节点都只有一个前驱后继,但是只能访问后继,不能像顺序存储那样算出前驱的地址。链表的优点就是 便于插入 删除节点的操作。这当然是相对于顺序存储的。缺点就是不便于查找,毕竟链表要访问一个元素必须先从前面的节点经过,而顺序存储可以直接算出。