程序的基石——线性结构

简介: 2. 数据结构基础2.1 什么是数组2.1.1 概念数组对应的英文是array, 是有限个相同类型的变量所组成的有序集合, 数组中的每一个变量被称为元素。 数组是最为简单、 最为常用的数据结构。数组在内存中顺序存储(连续内存)2.1.2 数组的基本操作读取由于数组在内存中顺序存储, 所以只要给出一个数组下标, 就可以读取到对应的数组元素。 输入的下标必须在数组的长度范围之内, 否则会出现数组越界更新利用数组下标, 就可以把新值赋给该元素插入尾部插入:直接把插入的元素放

2. 数据结构基础

2.1 什么是数组

2.1.1 概念

数组对应的英文是array, 是有限个相同类型的变量所组成的有序集合, 数组中的每一个变量被称为元素。 数组是最为简单、 最为常用的数据结构。

数组在内存中顺序存储(连续内存)

2.1.2 数组的基本操作

  1. 读取

    由于数组在内存中顺序存储, 所以只要给出一个数组下标, 就可以读取到对应的数组元素。 输入的下标必须在数组的长度范
    围之内, 否则会出现数组越界

  2. 更新

    利用数组下标, 就可以把新值赋给该元素

  3. 插入

    • 尾部插入:直接把插入的元素放在数组尾部的空闲位置即可。
    • 中间插入:会涉及到元素的移动。
    • 超范围插入:在插入时数组越界进行扩容。
  4. 删除

    删除元素也会涉及元素的移动。

2.1.3 数组的优势和劣势

  1. 优势:数组拥有非常高效的随机访问能力,只要给出下标就能迅速获取数据。
  2. 劣势:数组的元素都存放在连续的内存中,插入和删除涉及大量的元素移动,影响效率。
  3. 总结:数组适合读操作多,写操作少的场景。

2.2 什么是链表

2.2.1 概念

  1. 链表(linked list) 是一种在物理上非连续、 非顺序的数据结构, 由若干节点(node)所组成。
  2. 单向链表的每一个节点又包含两部分, 一部分是存放数据的变量data, 另一部分是指向下一个节点的指针next。
  3. 双向链表比单向链表稍微复杂一些, 它的每一个节点除了拥有data和next指针, 还拥有指向前置节点的prev指针。

2.2.2 链表的基本操作

  1. 查找节点:从头节点开始向后一个一个节点逐一查找
  2. 更新节点:更新=查找+替换。替换直接把旧数据替换成新数据即可。
  3. 插入节点

    • 尾部插入:最后一个节点的next指针指向新插入的节点
    • 头部插入:把新节点的next指针指向原先的头节点 ,把新节点变为链表的头节点 。
    • 中间插入:新节点的next指针, 指向插入位置的节点 。插入位置前置节点的next指针, 指向新节点。
  4. 删除元素

    • 尾部删除:把倒数第2个节点的next指针指向空即可。
    • 头部删除:把链表的头节点设为原先头节点的next指针即可。
    • 中间删除:把要删除节点的前置节点的next指针, 指向要删除元素的下一个节点即可。

2.2.3 数组VS链表

数据结构没有绝对的好坏,数组和链表各有千秋。

在这里插入图片描述

  • 从表格可以看出,数组的优势在于能够快速定位元素,对于读操作多,写操作少的业务场景,数组更合适。
  • 链表的优势在于能够灵活的进行插入和删除,适合需要频繁插入和删除元素的业务场景。

2.3 栈和队列

2.3.1 物理结构和逻辑结构

逻辑结构是抽象的概念, 它依赖于物理结构而存在。

在这里插入图片描述

栈和队列就属于逻辑结构,他们的物理实现可以使链表也可以是数组。

2.3.2 什么是栈

栈(stack) 是一种线性数据结构。栈中的元素只能先入后出(First In Last Out, 简称FILO)。最早进入的元素存放的位置
叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶(top) 。

栈这种数据结构既可以用数组来实现, 也可以用链表来实现。

栈的数组实现如下。

在这里插入图片描述

栈的链表实现如下。

在这里插入图片描述

2.3.3 栈的基本操作

  1. 入栈

    入栈操作(push) 就是把新元素放入栈中, 只允许从栈顶一侧放入元素, 新元素的位置将会成为新的栈顶。

  2. 出栈

    出栈操作(pop) 就是把元素从栈中弹出, 只有栈顶元素才允许出栈, 出栈元素的前一个元素将会成为新的栈顶。

2.3.4 什么是队列

队列(queue) 是一种线性数据结构,不同于栈的先入后出, 队列中的元素只能先入先出(First In First Out, 简称FIFO) 。 队列的出口端叫作队头(front) , 队列的入口端叫作队尾(rear) 。

与栈类似, 队列这种数据结构既可以用数组来实现, 也可以用链表来实现。

队列的数组实现如下。

在这里插入图片描述

队列的链表实现如下。

在这里插入图片描述

2.3.5 队列的基本操作

对于链表实现方式, 队列的入队、 出队操作和栈是大同小异的。 但对于数组实现方式来说, 队列的入队和出队操作有了一些有趣的变化。

  1. 入队

    入队(enqueue) 就是把新元素放入队列中, 只允许在队尾的位置放入元素, 新元素的下一个位置将会成为新的队尾。

  2. 出队

    出队操作(dequeue) 就是把元素移出队列, 只允许在队头一侧移出元素, 出队元素的后一个元素将会成为新的队头。

  3. 数组队列

    在数组不做扩容的前提下, 如何让新元素入队并确定新的队尾位置呢? 我们可以利用已出队元素留下的空间, 让队尾指针重新指回数组的首位。

在这里插入图片描述

一直到( 队尾下标+1) %数组长度 = 队头下标时, 代表此队列真的已经满了。 需要注意的是, 队尾指针指向的位置永远空出1位, 所以队列最大容量比数组长度小1。

2.3.6 栈和队列的应用

  1. 栈的应用

    栈的输出顺序和输入顺序相反, 所以栈通常用于对“历史”的回溯, 也就是逆流而上追溯“历史”。

    例如实现递归的逻辑, 就可以用栈来代替, 因为栈可以回溯方法的调用链。

  2. 队列的应用

    队列的输出顺序和输入顺序相同, 所以队列通常用于对“历史”的回放, 也就是按照“历史”顺序, 把“历史”重演一遍。

    例如在多线程中, 争夺公平锁的等待队列, 就是按照访问顺序来决定线程在队列中的次序的。

    再如网络爬虫实现网站抓取时, 也是把待抓取的网站URL存入队列中, 再按照存入队列的顺序来依次抓取和解析的。

  3. 双端队列

在这里插入图片描述

双端队列这种数据结构, 可以说综合了栈和队列的优点, 对双端队列来说, 从队头一端可以入队或出队, 从队尾一端也可以入队或出队。

  1. 优先队列

    还有一种队列, 它遵循的不是先入先出, 而是谁的优先级最高, 谁先出队。

    这种队列叫做优先队列

在这里插入图片描述

优先队列已经不属于线性数据结构的范畴了, 它是基于二叉堆来实现的。

2.4 散列表

2.4.1 散列表

散列表也叫作哈希表(hash table) , 这种数据结构提供了键( Key) 和值( Value)的映射关系。 只要给出一个Key, 就可以高效查找到它所匹配的Value, 时间复杂度接近于O(1)。

2.4.2 哈希函数

散列表本质上是一个数组,通过hash函数将key值映射到数组下标上。

在java以及大多数语言中,每个对象都有属于自己的hashcode,这个hashcode是区分不同对象的重要标识。无论对象自身的类型是什么,他们的hashcode都是一个整型变量。

通过这个整型变量转化成数组下标就不难实现了。最简单的转化方式如下:按照数组长度进行取模运算。

index = HashCode (Key) % Array.length

实际上, JDK(Java Development Kit, Java语言的软件开发工具包) 中的哈希函数并没有直接采用取模运算, 而是利用了位运算的方式来优化性能。 不过在这里可以姑且简单理解成取模操作

key=001121时,
index = HashCode ("001121") % Array.length = 1420036703 % 8 = 7
而当key=this时,
index = HashCode ("this") % Array.length = 3559070 % 8 = 6

2.4.3 散列表的读写操作

  1. 写操作(put):

    • 通过hash函数计算出数组下标
    • 如果数组下标位置没有数据,则存入数据。
    • 如果下标位置已经存在数据,即产生hash冲突

      • 开放地址法:线性探测、再平方、伪随机
      • 链地址法:
  2. 读操作(get):

    • 通过hash函数计算key的index地址
    • 找到数组对应的元素位置,然后根据key的equals判断获取到对应的value。
  3. 扩容(resize):

    对于JDK中的散列表实现类HashMap来说, 影响其扩容的因素有两个。

    • Capacity, 即HashMap的当前长度
    • LoadFactor, 即HashMap的负载因子, 默认值为0.75f

    当HashMap的长度大于等于Capacity*LoadFactor触发扩容

    1. 扩容, 创建一个新的Entry空数组, 长度是原数组的2倍
    2. 重新Hash, 遍历原Entry数组, 把所有的Entry重新Hash到新数组中。

2.5 小结

  1. 什么是数组

    数组是由有限个相同类型的变量所组成的有序集合, 它的物理存储方式是顺序存储,访问方式是随机访问。 利用下标查找数组元素的时间复杂度是O(1), 中间插入、 删除数组元素的时间复杂度是O(n)。

  2. 什么是链表

    链表是一种链式数据结构, 由若干节点组成, 每个节点包含指向下一节点的指针。 链表的物理存储方式是随机存储, 访问方式是顺序访问。 查找链表节点的时间复杂度是O(n), 中间插入、 删除节点的时间复杂度是O(1)。

  3. 什么是栈

    栈是一种线性逻辑结构, 可以用数组实现, 也可以用链表实现。 栈包含入栈和出栈操作, 遵循先入后出的原则(FILO) 。

  4. 什么是队列

    队列也是一种线性逻辑结构, 可以用数组实现, 也可以用链表实现。 队列包含入队和出队操作, 遵循先入先出的原则(FIFO) 。

  5. 什么是散列表

    散列表也叫哈希表, 是存储Key-Value映射的集合。 对于某一个Key, 散列表可以在接近O(1)的时间内进行读写操作。 散列表通过哈希函数实现Key和数组下标的转换, 通过开放寻址法和链表法来解决哈希冲突。

目录
相关文章
|
存储
【数据结构】连通图、连通分量与强连通图、强连通分量—区别在于强,强强在哪里?
【数据结构】连通图、连通分量与强连通图、强连通分量—区别在于强,强强在哪里?
3980 1
【数据结构】连通图、连通分量与强连通图、强连通分量—区别在于强,强强在哪里?
|
2月前
|
存储 算法 分布式数据库
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
【初阶数据结构】理解堆的特性与应用:深入探索完全二叉树的独特魅力
|
2月前
|
消息中间件 存储 Java
数据结构之 - 深入探析队列数据结构: 助你理解其原理与应用
数据结构之 - 深入探析队列数据结构: 助你理解其原理与应用
40 4
|
2月前
|
搜索推荐 索引
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(二)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2月前
|
搜索推荐 C++
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(一)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
2月前
|
人工智能 搜索推荐 算法
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理(三)
【初阶数据结构】深度解析七大常见排序|掌握底层逻辑与原理
|
7月前
顺序表的实现(迈入数据结构的大门)(1)
顺序表的实现(迈入数据结构的大门)(1)
34 0
|
7月前
|
C语言
顺序表的实现(迈入数据结构的大门)(2)
顺序表的实现(迈入数据结构的大门)(2)
43 0
|
7月前
|
机器学习/深度学习 存储 Java
揭秘数组:数据结构的基石与代码实践解析
揭秘数组:数据结构的基石与代码实践解析
31 0
|
7月前
|
算法 程序员 C语言
速学数据结构 | (超级干货)业界程序员公认的实现栈最简单的方法!太简单了
速学数据结构 | (超级干货)业界程序员公认的实现栈最简单的方法!太简单了
42 0