了解一些基本数据结构

75 阅读6分钟

栈和队列

最直接的:栈是一种后进先出(Last in First Out)的数据结构,而队列是一种先进先出(First in First Out)的数据结构。

接着详细描述:

栈是限制仅在表的一端(表尾)进行操作(插入和删除)的线性表,如浏览器后退就是栈操作

表尾又叫栈顶(Top),允许插入和删除,那么另一端就叫做栈底(Bottom),啥也不能干。

栈的插入操作,叫做入栈 ,那么离开自然而然也就叫作出栈了。

既然栈是线性表,那么就应该有顺序存储链式存储两种方式

顺序存储的栈叫做顺序栈

顺序栈使用数组实现,下标为 0 的一端作为栈底,使用 top 做为栈顶,它来指示当前栈顶元素的位置,默认 top = -1 时为空栈。

链式存储的栈叫做链栈

链栈用单链表实现,一般尾节点为栈底,使用头指针指向的节点作为栈顶,不需要头节点。top = NULL 为空栈。

队列是限制仅在一端进行插入操作,在另一端进行删除操作的线性表,如排队等

允许插入的一端叫做队头,允许删除的一端叫做队尾。队列的插入叫做入队列,队列的删除叫做出队列

同为线性表,队列也有链式存储和顺序存储

head 指向头节点,tail 指向队尾。因此head 和 tail 都指向头节点时,为空队列

树和图

图主要用来描述一个从一个位置到另一个位置的路线的模型。

树,和图一样也是一系列点的集合。有一个根节点。这个根节点有一些子节点。子节点也有它们自己的孙子节点。不断重复直到所有的数据都被用树的数据结构表示。

树是没有环的图(在图里面,环的路线是开始和结束都是一样的点)一个子节点只有一个父节点

树是一种特殊的图,它永远不会有多个路径。 从A到B总是有一种方法。 图是一种具有多种方法来从任何点A到达任何其他点B的系统

树中必须有一个根节点;图中没有这种根节点。

二叉树满足以下两个条件:

  • 本身是有序树
  • 树中包含的各个结点的不能超过 2,即只能是 0、1 或者 2

二叉树又可以分为完全二叉树和满二叉树。

满二叉树:二叉树中除了叶子结点,每个结点的度都为 2

完全二叉树:二叉树中除去最后一层节点都为满二叉树,且最后一层的结点依次从左到右分布

image.png

堆通常是一个可以被看做一棵完全二叉树的数组对象。

堆分为两种:最大堆最小堆,两者的差别在于节点的排序方式。

最大堆:父节点的值比每一个子节点的值都要大。

最小堆:父节点的值比每一个子节点的值都要小。

这就是所谓的“堆属性”,并且这个属性对堆中的每一个节点都成立。

堆与栈

  1. 栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏。
  2. 每个进程拥有的栈的大小要远远小于堆的大小。
  3. 堆的效率比栈要低得多。

集合

集合(Set),指具有某种特定性质的事物的总体,里面的每一项内容称作元素

具有三大特性:

  • 确定性:集合中的元素是确定的。即一个元素,或者属于该集合,或者不属于该集合,两者必居其一
  • 无序性:在一个集合中,不考虑元素之间的顺序,只要元素完全相同,就认为是同一个集合
  • 互异性:集合中任意两个元素都是不同的

ES6中,集合本身是一个构建函数Set,用来生成 Set 数据结构

关于集合常见的方法有:

  • add():增
  • delete():删
  • has():改
  • clear():查

并集

    let s2 = new Set([1, 3, 6, 7])
    let s3 = new Set([1, 5, 3, 7])
    let union = new Set([...s2, ...s3])    //Set(5) {1, 3, 6, 7, 5}

交集

    let a = new Set([1, 3, 6, 7])
    let b = new Set([1, 5, 3, 7])
    let intersect = new Set([...a].filter(x => b.has(x)))  //Set(3) {1, 3, 7}

差集

    let a = new Set([1, 3, 6, 7])
    let b = new Set([1, 5, 3, 7])
    let diff= new Set([...a].filter(x => !b.has(x))) //Set(1) {6}

顺序表和链表

顺序表

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

链表

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

数组和链表

  1. 数组
  • 数组需要预留空间,在使用前要先申请占内存的大小,可能会浪费内存空间。 
  • 插入数据和删除数据效率低,插入数据时,这个位置后面的数据在内存中都要向后移。
  • 随机读取效率很高。因为数组是连续的,知道每一个数据的内存地址,可以直接找到给定地址的数据。
  • 不利于扩展,数组定义的空间不够时要重新定义数组。
  1. 链表
  • 在内存中可以存在任何地方,不要求连续。 
  • 每一个数据都保存了下一个数据的内存地址,通过这个地址找到下一个数据。
  • 增加数据和删除数据很容易。再来个人可以随便坐,比如来了个人要做到第三个位置,那他只需要把自己的位置告诉第二个人,然后问第二个人拿到原来第三个人的位置就行了。其他人都不用动。
  • 查找数据时效率低,因为不具有随机访问性,所以访问某个位置的数据都要从第一个数据开始访问,然后根据第一个数据保存的下一个数据的地址找到第二个数据,以此类推。 要找到第三个人,必须从第一个人开始问起。
  • 不指定大小,扩展方便。链表大小不用定义,数据随意增删。

数组和链表功能互补。