参考视频: 【黑马程序员】2020最新数据结构与算法教程(求职面试必备)
参考leetcode学习资料: 图解算法数据结构
文章目录
第三章 线性表
3-1 线性表
-
线性表是最基本,最简单,也是最常用的一种数据结构;一个线性表是n个具有相同特性的数据元素的有限序列
-
- 前驱元素 : 若A元素在B元素的前面,则称为A为B的前驱元素
- 后继元素 : 若B元素在A元素的后面,则称称为B为A的后继元素
-
线性表的特征 : 数据元素之间具有“一对一”的逻辑关系
- 第一个数据元素没有前缀,这个数据被称为头节点
- 最后一个数据元素没有后继,这个数据元素称为尾节点
-
线性表分类 :
- 线性表中数据存储的方式可以是顺序存储,也可以是链式存储
- 按照数据的存储方式不同,则可以把线性表分为顺序表和链表
3-2 顺序表
3-2-1 顺序表的实现
-
- 顺序表是在计算机内存中以数组的形式保存的线性表,
- 线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素,使得线性表中在逻辑 结构上响铃的 数据元素 存储在 相邻的物理存储单元中,
- 即通过数据元素物理存储的相邻关系 来反映数据元素之间逻辑上的相邻关系
-
顺序表API设计 :
类名 SequenceList 构造方法 SequenceList(int capacity) : 创建容量为capacity的SequenceList对象 成员方法 1. public void clear() : 空置线性表
2. public boolean isEmpty() : 判断线性表是否为空, 是返回true,否则返回false
3. public int length() : 获取线性表中元素的个数
4. public T get(int i) : 获取并返回线性表中第i个元素的值
5. public void insert(int i, T t) : 在线性表中的第i个元素之前插入一个值为t的数据元素
6. public void insert(T t) : 向线性表中添加一个元素t
7. public T remove(int i):删除并返回线性表中第i个数据元素
8. public int indexOf(T t) : 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1成员变量 1. private T[] eles : 存储元素的数组
2. private int N : 当前线性表的长度 -
顺序表的遍历
- 一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式
- 在java中, 遍历结合的方式一般都是用 foreach 循环,如果让我们的SequenceList 支持 foreach 循环,需要做以下操作 :
- 让SequenceList 实现 iterable 接口, 重写iterator(方法
- 在SequenceList 内部提供了 一个内部类SIterator, 实现Iterator 接口,重写hasNext方法和next方法
-
顺序表的容量可变
我们使用SequenceList时, 先new SequenceList(5)创建一个对对象,创建对象就需要指定容器的大小,初始化指定大小的数组来存储元素。
这种设计不符合容器的设计理念,我们在设计顺序表时,应该考虑容器的伸缩性
- 添加元素,应该检查当前数组的大小是否能容忍新的元素,如果不能容忍,则需要创建新的容量更大的数组,需要创建一个是原数组两倍容量的新数组存储元素
- 移除元素,应该检查当前数组的大小是否太大,应该创建一个容量更小的数组存储元素。数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素
3-2-2 顺序表的时间复杂度
-
- get(i) : 只需要依次eles[i]就可以获取对应的元素,时间复杂度为
O(1)
- insert(int i,T t) :每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为
O(n)
- remove(int i) :同insert()
- get(i) : 只需要依次eles[i]就可以获取对应的元素,时间复杂度为
-
由于顺序表的底层有数组实现, 数组的长度是固定的,所以操作时涉及了容器扩容操作(申请内存重建数组),会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的节点处,耗时会突增
3-2-3 java中ArrayList实现(顺序表)
- java中ArrayList集合的底层也是一种顺序表,使用数组实现,同样提供了增删改查以及扩容等功能
- 是否使用数组实现
- 有没有扩容操作
- 有没有提供遍历方式
3-3 链表
顺序表的查询很快,时间复杂度为O(1),但是增删的效率很低,每次增删操作都伴随着大量数据元素移动
3-3-1 链式存储结构
-
链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只表示数据元素的逻辑结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
- 链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成
-
如何使用链表 :
- 可以设计一个类,来描述结点这个事物,用一个属性描述这个结点存储的元素,用来另外一个属性描述这个节点的下一个结点
-
结点API设计
类名 Node 构造方法 Node( T t, Node next) : 创建Node对象 成员变量 T item : 存储数据
Node next : 指向下一个节点 -
-
结点类实现 :
public class Node<T>{ //存储元素 public T item; // 指向下一个结点 public Node next; public Node(T item, Node next){ this.item = item; this.next = next; } }
-
生成链表 :
public static void main(String[] args) throw Exception{ //构造结点 Node<Integer> first = new Node<Integer>(11,null); Node<Integer> second = new Node<Integer>(11,null); Node<Integer> third = new Node<Integer>(11,null); Node<Integer> fourth = new Node<Integer>(11,null); }
-
3-3-2 单向列表
- 单向链表由多个结点组成,每个结点都有一个数据域和一个指针域组成
- 数据域用来存储数据
- 指针域用来指向其他后继结点
- 链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点
-
单向链表API设计
类名 LinkList 构造方法 LinkLsit() : 创建LinkList对象 成员方法 1. public void clear() : 空置线性表
2. public boolean isEmpty(): 判断线性表是否为空,true或false
3. public int length() : 获取线性表中元素的个数
4. public T get(int i) : 读取并返回线性表中的第i个元素的值
5. public void insert(T t) : 往线性表中添加一个元素
6. public void insert(int i.T t): 王线性表的第i个元素之前插入一个值为t的数据元素
7. public T remove(int i) : 删除并返回线性表中第i个数据元素
8. public int indexOf(T t) : 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
9. public T getFirst(): 获取第一个元素
10 public T getLast(): 获取最后一个元素成员内部类 private class Node: 结点类 成员变量
1. private Node first : 记录首结点
2. private Node last : 记录尾结点
2. private int N: 记录链表的长度
3-3-3 双向链表【程序第二遍写的时候有bug,第三遍搞定】
-
双向链表也叫双向表,是链表的一种,由多个结点组成,每个结点都由一个数据域和两个指针域组成,
- 数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。
- 链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点
-
按照面向对象的思想,需要设计一个类,来描述结点这个事物
- 由于结点是属于链表的,所以我们把结点类作为链表类的一个内部类来实现
-
结合API设计
类名 Node 构造方法 Node(T t,Node pre,Node next()) : 创建Node对象 成员变量 T item:存储数据
Node next: 指向下一个结点
Node pre:指向上一个结点 -
双向链表API设计 :
类名 TowWayLinkList 构造方法 TowWayLink(): 创建TowWayLinkLink对象 成员方法 1. public void clear() : 空置线性表
2. public boolean isEmpty(): 判断线性表是否为空,true或false
3. public int length() : 获取线性表中元素的个数
4. public T get(int i) : 读取并返回线性表中的第i个元素的值
5. public void insert(T t) : 往线性表中添加一个元素
6. public void insert(int i.T t): 王线性表的第i个元素之前插入一个值为t的数据元素
7. public T remove(int i) : 删除并返回线性表中第i个数据元素
8. public int indexOf(T t) : 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1成员内部类 private Node first : 记录首结点 成员变量 1.private Node first: 记录首结点
2.private Node last: 记录尾结点
3.private int N: 记录
3-3-4 java中的LinkList实现
-
java中的LinkList集合也是使用双向链表实现,并提供了增删改查等
-
底层是否用双向链表实现
-
节点类是否有三个域 : 两个指针域和一个数据域
-
3-3-5 链表的复杂度分析
get int(i)
: 每一次查询, 都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)insert(int i, T t):
每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)- remove(int i): 每一次移除,需要先找到i位置的前一个元素。然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
- 相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然又很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换
- 相比较顺序表,链表的查询操作性能会比较低
- so,查询操作多,使用顺序表
- 增删操作多,使用链表
3-3-6 链表反转【我就是看不懂代码;again好点了;】
-
单链表的反转,是面试中的一个高频题目
-
需求:
- 原链表中数据为: 1->2->3->4
- 反转后链表中的数据为: 4
- 使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个节点反转完毕,整个链表就反转完毕
-
- 调用reverse(Node curr)方法反转每一个结点,从元素1结点开始
- 如果发现curr还有下一个结点,则递归调用reverse(curr.next)对下一个结点反转
- 最终递归的出口是元素4结点,因为他没有下一个元素了,当到了出口处,让head指向元素4结点;共递归调用4
- 递归开始返回 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gOCgs7M6-1637986404635)(https://i.loli.net/2021/09/14/KoiYCs4uefvjM95.png)]
3-4 快慢指针
3-4-1 中间值问题【没有跟着桥案例;第三遍敲了但是有bug,againok了,不是很明白;】
- 查找中间值:
- 最开始,slow和fast指针都指向链表第一个指针,然后slow每次移动一个指针,fast每次移动两个指针
- 单向链表是否有环
- 有环指针中快慢指针会相遇,指向同一结点
- 当快慢指针相遇时,我i们可以判断到链表有环,这是重新设定一个新指针指向链表的起点,且步长与慢指针一样,则满指针与新指针相遇的地方就是环的入口,
3-4-2 单链表是否有环问题【MyThird有问题,而且不是很懂;】
快慢指针指向有环无环问题
3-4-3 有环链表入口问题
3-5 循环链表
3-5-1 循环链表
-
最后一个指针的指针域指向第一个结点
-
循环链表的构建:
public class FastSlowTest { public static void main(String[] args) throws Exception{ // 创建结点 Node<String> first = new Node<String>("aa", null); Node<String> second = new Node<String>("bb", null); Node<String> third = new Node<String>("cc", null); Node<String> fourth = new Node<String>("dd", null); Node<String> fifth = new Node<String>("ee", null); Node<String> six = new Node<String>("ff", null); Node<String> seven = new Node<String>("gg", null); // 完成结点之间的指向 first.next = second; second.next = third; third.next = fourth; fourth.next = fifth; fifth.next = six; six.next = seven; // 构建循环链表,让最后一个结点指向第一个结点 } }
3-5-2 约瑟夫问题—循环链表【MyThird】
- 问题转换:
- 41个人做一圈,第一个人编号为1,第二个人编号为2
- 编号为3的人推出圈
- 自推出的那个人开始的下一个人再次从1开始报数,以此类推
- 求出最后推出的那个人的编号
- 构建思路:
- 构建含有41个结点的单向循环列表,分别存储1-41 的值,分别代表41个人
- 使用计数器count,记录当前报数的值
- 遍历链表,每循环依次,count++
- 判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0
- 约瑟夫将朋友和自己安排在第16个和第31个位置
3-4 栈
3-4-1 概述
-
生活中的栈 引入到计算机中, 就是提供数据休息的地方; 数据可以进入栈中,也可以从栈中出去
-
栈是一种基于先进后出(FILO)的数据结构,是一种只能在一端进行插入和删除操作的特殊线性表。
-
按照先进后出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出)
-
我们称数据进入到栈的动作是压栈,数据从栈中出去的动作为弹栈
-
3-4-2 栈的代码实现【数组和链表都可以实现栈】
-
栈API设计
类名 Stack 构造方法 Stack: 创建Stack对象 成员方法 1. public boolean isEmpty(): 判断栈是否为空,是返回true,否返回false
2. public int size(): 获取栈中元素的个数
3. public T pop(): 弹出栈顶元素
4. public void push(T t): 向栈中压入元素t成员变量 1. private Node head: 记录首结点
2. private int N: 当前栈的元素个数成员内部类 private class Node: 节点类
3-4-3 括号匹配问题1【Mythird有问题,而且我没有看懂;】
-
给定一个字符串,里边包含"()"小括号和其他字符,请编写检查该字符串的小括号是否成对出现
例如: "(上海)(长安)": 是否匹配 "上海((长安))": 正确匹配 “上海(长安(北京)(深圳)南京)”: 正确匹配 “上海(长安))”: 错误匹配 “((上海)长安”: 错误匹配
-
示例代码:
3-8 逆波兰表达式
2-8-1 逆波兰表达式求值问题
- 中缀表达式:
- 中缀表达式的特点: 二元运算符总是置于两个操作数中间
- 中缀表达式对于计算机来说: 运算顺序不由规律性,不同的运算符有不同的优先级。
- 计算机执行中缀表达式,需要解析表达式语义,做大量的优先级相关操作
- 逆波兰表达式(后缀表达式):
- 运算符总是放在跟他相关的操作数之后
2-8-2 逆波兰表达式的实现
2-8-3 【MyThird的实现有bug,看不懂;】
3-5 队列
队列是一种基于先进先出(FIFO)的数据结构,是一种只能在一端进行插入,在另一端进行删除操作的特殊线性表,它代表先进先出的原则存储数据,先进入的数据,在读取数据时先读被读出来
1.
3-5-2 队列的API设计和实现
-
类名 Queue 构造方法 Queue():创建Queue对象 成员方法 1. public boolean isEmpty(): 判断队列是否为空,是返回true,否返回 false
2. public int size(): 获取队列中元素的个数
3. public T dequeue(): 从队列中拿出一个元素
4. publilc void enqueue(T t): 往队列中 插入 一个元素成员变量 1. private Node head: 记录首结点
2. private int N: 当前栈的元素个数
3. private Node last: 记录最后一个结点成员内部类 private class Node: 节点类
3-6 符号表【第二遍没有实现;MyThird看不太懂;】
3-6-1 符号表
-
符号表最主要的目的就是将一个键和一个值联系起来,符号表能够存储的数据元素是一个键和一个值共同组成的键值对数据,可以根据键来查找对应的值
- 键具有唯一性
-
符号表在实际生活中的使用场景很广泛
应用 查找目的 键 值 字典 找到单词的释义 单词 释义 图书索引 找到某个术语相关的页码 术语 一串页码 网络搜索 找到某个关键字对应的网页 关键字 网页名称 -
符号表API设计
-
类名 Node<key.Value> 构造方法 Node(Key key,Value value,Node next):创建Node对象 成员变量 1. public Key key: 存储键
2. public Value value:存储值
3. public Node next:存储下一个结点 -
符号表:
类名 SymbolTable<Key.Value> 构造方法 SYmbolTable():创建SymbolTable对象 成员方法 1. public Value get(Key key):根据键key,找对应的值
2. public void put(Key key , Value val):想符号表中插入一个键值对
3. public void delete(Key key):删除键为key的键值对
4. public int size(): 获取符号表的大小成员变量 1. private Node head: 记录首结点
2. private int N: 记录符号表中键值对的个数 -
orderSymbolTable.java
3-6-2 有序符号表【MyThird没有做】
3-7 总结:
线性结构:底层的的结构可以是数组(查询快),也可以是线性表(增增删慢)
每日小知识:为啥子我直接粘贴笔记,不会显示“图片外链无法获取”,因为我的typora搭有图床:
- 参考博客:Gitee搭建免费博客
- 老方便了,随后搭个博客平台或者把文件发给别人,都不用发愁自己的图片发不出去了
笔记在博主本人的博客上也有奥!全套!!!
- 翼遥bingo【持续完善中,biu~~】