数据结构与算法理论基础(持续更新中~)

65 阅读6分钟

数组

数组是存放在连续内存空间上的相同类型数据的集合。

一维数组

需要两点注意的是

  • 数组下标都是从0开始的。
  • 数组内存空间的地址是连续的

正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址

数组的元素是不能删的,只能覆盖。

image.png

二维数组

image.png

经典题目

  • 二分法:704. 二分查找

    循环不变量、区间定义

  • 双指针法:27. 移除元素

    在数组和链表中常见的操作

  • 滑动窗口:209. 长度最小的子数组

    主要要理解滑动窗口如何移动 窗口起始位置,达到动态更新窗口大小的,从而得出长度最小的符合条件的长度。

    滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)的暴力解法降为O(n)。

  • 模拟行为:59. 螺旋矩阵 II

    循环不变量原则

  • 前缀和:kamacoder.com/problempage… (没做)

链表

1. 链表的类型

1.1 单链表

链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)

链表的入口节点称为链表的头结点也就是head

image.png

1.2 双链表

双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点

双链表 既可以向前查询也可以向后查询

image.png

1.3 循环链表

链表首尾相连

image.png

2. 链表的存储方式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。

链表是通过指针域的指针链接在内存中各个节点。

所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理

image.png

2.1 链表的定义

public class ListNode {
    // 结点的值
    int val;
​
    // 下一个结点
    ListNode next;
​
    // 节点的构造函数(无参)
    public ListNode() {
    }
​
    // 节点的构造函数(有一个参数)
    public ListNode(int val) {
        this.val = val;
    }
​
    // 节点的构造函数(有两个参数)
    public ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

2.2 链表的操作

2.2.1 删除节点

只需要让C的next指针指向E节点,内存回收会自动处理D节点

image.png

2.2.2 添加节点

image.png

3. 性能分析

时间复杂度插入/删除查询适用场景
数组O(n)O(1)数据量固定,频繁查询,较少增删
链表O(1)O(n)数据量不固定,频繁增删,较少查询

4. 总结

  • 链表中没有下标的概念,要想像数组一样遍历,就得定义一个cur节点,让该节点指向head,让它逐个指向下一个实现遍历

链表总结.png (我是小小知识搬运工哈哈哈哈哈

5. 经典题目

  • 虚拟头节点:避免额外处理头节点
  • 反转链表:双指针操作(链表之间是一个指向关系,当你改变它的指向,必然就发生变化)
  • 链表相交:引用一致则代表是相同的链表
  • 环形链表:数学思维

哈希表

1. 哈希表

Hash table 哈希表/散列表

哈希表是根据关键码的值而直接进行访问的数据结构。数组就是一张哈希表,关键码就是数组的索引下标,通过下标直接访问数组中的元素

imgs.png

哈希表能解决的问题:一般哈希表都是用来快速判断一个元素是否出现集合里。

例如查一个名字是否出现在这所学校,只需要初始化把这所学校里学生的名字都存在哈希表里,然后通过索引查询就知道是否存在。映射到哈希表的过程使用到了哈希函数hash function

2. 哈希函数

哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下标快速知道这位同学是否在这所学校里了。

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了

image.png

2.1 哈希碰撞

如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞

image.png

2.2 拉链法

如果两个元素发生哈希碰撞后,两个元素都会被存储在链表中

image.png (数据规模是dataSize, 哈希表的大小为tableSize) 其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

2.3 线性探测法

使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。

例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:

image.png

3. 总结

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。 但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

4.常见的三种哈希结构

  • 数组:如果能知道大小,就使用数组
  • set (集合):无法得知大小,使用set,如果返回int[],再使用stream流转换一次
  • map(映射):如果要存放两个值,使用map存放key:value

字符串

1. 定义

字符串是若干字符组成的有限序列,也可以理解为是一个字符数组

2.经典题目

  • 双指针法
  • 反转系列:当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章
  • KMP(待办)

3. 双指针法

  • 数组篇:使用双指针实现一个for循环下完成两个for循环的工作
  • 链表篇:双指针实现链表反转,改变next指针指向即可
  • n数之和篇

栈和队列

队列
定义先进后出的线性表 LIFO结构 只能在表尾进行插入删除操作的线性表先进先出的线性表 FIFO 允许插入的叫队尾,允许删除的一端叫队头
应用递归、逆波兰、匹配(括号匹配、字符串去重等)二叉树的层序遍历、滑动窗口最大值问题(自定义队列)

image-20240924223319105.png

image-20240921094836878.png