You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

735 lines
14 KiB

3 years ago
---
title: 数据结构和算法-未完成
author: TianZD
top: true
cover: true
toc: true
mathjax: false
summary: 基于Java的数据结构和算法,未完成,粗略学了一下,没有参考价值
tags:
- 数据结构
- 算法
categories:
- 数据结构和算法
reprintPolicy: cc_by
abbrlink: b1f89a38
date: 2022-04-29 10:42:56
coverImg:
img:
password:
---
[TOC]
> 概述:学习《漫画算法:小灰的算法之旅》
# 1、数据结构基础
## 1.1、数据结构分类
>数据结构是以某种特定的布局方式可存储数据的容器,布局方式决定了数据结构对于某些操作是高效的,对于其他操作是低效的。
数据结构 = 逻辑结构+物理结构(顺序、链式、索引、散列)
* 逻辑结构
* 线性结构:顺序表、栈、队列
* 非线性结构:树、图
* 物理结构:(存储结构),在计算机存储器中的存储形式
* 顺序存储结构:数组
* 链式存储结构:链表
逻辑分类:
1. 线性结构:结构中的元素存在一对一的相互关系(元素之间有序,一个挨一个),常见的有:线性表、栈、队列、串(一维数组);
2. 树形结构:元素存在一对多的相互关系;常见的有:二叉树、红黑树、B树、哈夫曼树等;
3. 图形结构:元素存在多对多的关系;常见:有向图、无向图、简单图;
## 1.2、数组
### 概述
* 有限个相同类型的变量组成的有序集合
* 物理结构:**顺序存储结构**,在内存中顺序存储
* 逻辑结构:**线性结构**
### 基本操作
> 读取元素
可以直接通过下标进行随机读取,时间复杂度O(1)
> 更新元素
通过下标直接进行赋值替换,时间复杂度O(1)
> 插入元素
涉及到元素的移动,时间复杂度O(n)
* 尾部插入
* 中间插入
* 超范围插入(扩容)
> 删除元素
和插入相反,时间复杂度O(n)
### 优劣势
优点:
* 数据访问:可以通过下标进行,效率高
缺点:
* 插入删除:在内存中采用顺序存储,插入和删除导致大量元素移动,效率低
总结:适用于**读操作多、写操作少**的场景
## 1.3、链表
### 概述
* 分为单向链表和双向链表
* 物理上非连续、非顺序,在内存中**随机存储**
* 由若干节点**node**组成
* 物理结构:**链式存储结构**
* 逻辑结构:**线性结构**
> 单向链表
每一个node包含两个属性:
* data:存放数据的变量
* node节点指针next:指向下一个节点
```java
private static class Node{
int data;
Node next;
}
```
单向链表只能通过next指针找到关联的下一个节点,逐一寻找,只需要知道第一个节点,便可以根据找到所有的节点,因此用第一个节点标识链表,单向链表的第一个节点又称为**头节点**
头尾节点:
* 头节点Head:链表的第一个节点
* 尾节点:链表的最后一个节点,next指针指向空,null
> 双向链表
双向链表每一个node包含三个属性:
* data
* node节点指针next
* **node节点指针prev:指向前一个节点**
### 基本操作
> 查找节点
从头节点开始,向后逐一查找,时间复杂度O(n)
> 更新节点
找到后直接将node节点数据进行替换,查找时间复杂度O(n),替换复杂度O(1)
> 插入节点
如果不考虑查找元素的过程,时间复杂度O(1)
* 尾部插入
将尾节点的next指针指向新节点
* 头部插入
1.把新节点的next指向原来的头节点
2.把新节点变为头节点
* 中间插入
1.将新节点的next指向插入位置的节点
2.将插入位置前的节点的next指向新节点
> 删除元素
如果不考虑查找元素的过程,时间复杂度O(1)
* 尾部删除
把倒数第二个node的next指向空
* 头部删除
把链表的头节点设为原来的第二个节点
* 中间删除
把要删除节点的前一个节点的next指向后一个节点
### 优劣势
优势:
* 在于能够灵活地进行插入和删除操作
劣势:
* 查找需要从头部开始逐一遍历
## 1.4、栈
### 概述
* 逻辑结构:**线性存储结构**
* 物理结构:可以用数组实现也可以用链表
* 先入后出FILO
* 栈顶:最后进入元素的位置
* 栈底:最先进入元素的位置
### 基本操作
> 入栈push
把新元素放入栈中,只能从栈顶放入,作为新的栈顶
时间复杂度O(1)
> 出栈pop
把元素弹出,只能弹出栈顶元素
时间复杂度O(1)
### 应用
输出顺序和输入顺序相反,通常用于对“历史”的回溯
* 代替递归
* 面包屑导航(浏览页面)
## 1.5、队列
### 概述
* 逻辑结构:**线性存储结构**
* 物理结构:可以用数组实现也可以用链表
* 先入先出FIFO
* 对头front
* 对尾rear
### 基本操作
> 入队enqueue
把新元素放入队尾
时间复杂度O(1)
> 出队dequeue
把对头元素移出队列
时间复杂度O(1)
> 循环队列
数组实现的队列为了防止出队时,对头左边的空间失去作用,可以采用循环队列的方式维持队列容量的恒定
* 入队:
* 队列满:`(rear+1)%array.length==front`
* `rear=(rear+1)%array.length;`
* 出队:
* 队列空:`rear==front`
* `front=(front+1)%array.length;`
### 应用
输出顺序和输入顺序相同,用于对历史的回放
* 多线程中争夺锁
* 网络爬虫抓取
## 1.6、散列表(哈希表)
### 概述
* 散列表提供了**键(key)** 和**值(value)**的映射关系,可以根据key快速找到对应的value,时间复杂度接近O(1)
* 本质上是**数组**
* **通过哈希函数将key转换为数组中的下标**
### 哈希函数
> hashcode
在java等面向对象的语言中,每一个对象都有自己的**hashcode**,hashcode是区分不同对象的重要标识,不论对象的类型是什么,hashcode都是一个**整型变量**
> 转化
通常采用hashcode对数组长度的**取模运算**
`index = HashCode(key) % Array.length`
java中的哈希函数采用的是**位运算**的方式,将数组长度取为2的次方,提高性能。
### 基本操作
> 写操作
1. 通过哈希函数,将key转化为数组下标
2. 放入元素
1. 如果对应的下标没有元素,直接放入
2. 如果已有元素,产生**哈希冲突**
哈希冲突解决:
* **开放寻址法**:已有元素时,`index++`,放入下一个,直至对应的下标没有元素占用,ThreadLocal采用
* **链表法**:**HashMap采用**,其数组中的每一个元素对应链表的一个节点,最先放入的为头节点,后续放入的通过将上一个节点的next指向该元素,插入到链表中
> 读操作
1. 通过哈希函数,将key转化为数组下标
2. 找到对应下标对应的元素
1. 如果只有一个,则找到
2. 如果对应的链表,遍历链表,**直到key值对应**
> 扩容
当多次插入后,key映射位置发生冲突的概率提高,大量元素拥挤在相同的位置,形成链表很长,导致查询和写入效率低下
条件:
`HashMap.Size >= Capacity * LoadFactor`
**步骤**:
* **扩容**:新建一个空数组,长度为两倍
* **重新Hash**:数组长度变化,需要重新根据哈希函数计算哈希值
## 1.7、树和二叉树
### 概述
>
* 逻辑结构:非线性结构
> 二叉树
### 二叉树应用
> 查找
> 维持相对顺序
### 二叉树遍历
从节点位置:
* **前序遍历**
* **中序遍历**
* **后序遍历**
* **层序遍历**
从宏观角度:
* **深度优先遍历:前序、中序、后序**
* **广度优先遍历:层序**
> 深度优先遍历
> 广度优先遍历
## 1.8、二叉堆
### 概述
* 最大堆
* 最小堆
### 自我调整
> 插入
> 删除
> 构建
> 代码
## 1.9、优先队列
### 概述
### 实现
# 2、排序算法
## 2.1、分类
## 2.2、冒泡排序
### 基本冒泡排序
交换排序,两两比较,两层循环,第一层为遍历的轮数:`array.length-1`
时间复杂度O(n^2)
### 优化冒泡排序
> 第一轮
防止数列已经有序后仍然进行下一轮排序
如{5,8,6,3,9,2,1,7}
> 第二轮
现有逻辑:从`for(int j=0;j<array.length-1;i++)`可以看出有序区的长度为以进行排序的轮数`i`
当后面大部分已经有序后,仍然会进行比较,对此进行优化
如{3,4,2,1,5,6,7,8}
### 鸡尾酒排序
双向交换,奇数轮从左向右,偶数轮从右向左
解决大部分已经有序的情况,如{2,3,4,5,6,7,8,1}
## 2.3、快速排序
### 概述
核心:**分治思想**
选定基准元素,大的放右边,小的放左边,一分为2
时间复杂度O(nlogn)
### 基准元素选择
* 默认数列的第一个元素
* 可以随机选择一个元素作为基准,然后和首元素交换位置
### 元素交换
> 双边循环法
* 基准元素pivot
* 左指针left
* 右指针right
> 单边循环法
* 基准元素pivot
* 元素区域边界mark
### 递归、非递归实现
## 2.4、堆排序
## 2.5、计数排序和桶排序
## 2.6、排序算法复杂度分析
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 |
| ---------- | -------------- | -------------- | ---------- | -------- |
| 冒泡排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
| 鸡尾酒排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
| 快速排序 | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
| 计数排序 | O(n+m) | O(n+m) | O(m) | 稳定 |
| 桶排序 | O(n) | O(nlogn) | O(n) | 稳定 |
# 3、面试算法
## 3.1、判断链表是否有环
### 基础版
> 问题
如何判断一个链表是否有环
> 思路
链表若有环,两个指针分别不同速度,速度快的会追上速度慢的
* 两个指针指向头节点:`Node p1 = head; Node p2 = head;`
* p1每次移动一个位置:`p1=p1.next;`
* p2每次移动两个位置`p2=p2.next.next;`
* p1和p2相遇则有环:`p1==p2`
> 代码
### 扩展
> 问题
若有环,求环的长度和入环节点
> 思路
**环的长度**:p1和p2的速度相差1,因此二次相遇之间走过的步数就是环的长度
**入环节点**:通过数学计算可以得到,入环点到头节点的距离==首次相遇点到入环点之间的距离,因此首次相遇后,把一个指针重新指向头节点,两个指针同时一次走一步,再次相遇的点就是入环节点
## 3.2、最小栈
> 问题
实现一个栈,有出栈、入栈,另外要有取最小值方法,同时三个方法的时间复杂度都为O(1)
> 思路
> 代码
## 3.3、求最大公约数
> 问题
求两个整数的最大公约数
> 思路
**辗转相除法(欧几里得算法)**:两个正整数a和b(a>b)的最大公约数等于a除以b的余数c和b之间的最大公约数
**更相减损法**:两个正整数a和b(a>b)的最大公约数等于a-b的差值c和b之间的最大公约数
> 代码
> 优化
位运算
## 3.4、判断一个数是否是2的整数次幂
> 问题
判断一个数是否是2的整数次幂
> 思路
* 2的整数次幂装换位二进制时,首位为1,其余位为0
* 2的整数次幂减去1,转换为二进制时,全部为1,首位为0
* 与运算&
> 代码
## 3.5、无序数组排序后的最大相邻差
> 问题
求出数组排序后的任意两个相邻元素的最大差值
> 思路
采用桶排序的思想,
* 分桶
* 入桶,记录每一个桶的最大值和最小值
* 统计相邻两个桶的最大值和最小值的差
> 代码
## 3.6、用栈实现队列
> 问题
用栈模拟队列,实现:入队、出队
> 思路
* 两个栈A和B
* 入队在A中压栈
* 出队
* B是否为空,空的话首先将A中的元素弹出到B中
* B弹栈
> 代码
## 3.7、寻找全排列的下一个数
> 问题
给一个正整数,找出全排列的下一个数
用这个数包含的全部数字重新排列,找出一个大于且仅大于原数的新整数
> 思路
固定的几个数字进行排列,逆序排列时数字最大,顺序排列时数字最小,同时为了保持重新排列的数仅比原数大,要尽可能保持高位不变,仅仅改变低位的数字
* 从后找,找到当前整数的逆序区域(逆序区域已经是最大的了,要找到不是逆序区域的数字的位置),如12354,只变换5和4不能满足比原数大,要从3开始变
* 更换找到的位置的数字和最末尾数字,变为12435
* 把逆序区域变为顺序,即交换5和4,保持最小
> 代码
## 3.9、删除k个数字后的最小值
> 问题
给一个整数,删除k个数字,要求剩下的数字尽可能小
> 思路
删除掉一个数字后,整数的位数减少1
需要从高位起,若该位置数字比后一位大,则需要去掉
> 代码
## 3.9、实现大整数相加
> 问题
实现两个很大的整数相加
> 思路
根据手动相加思路,低位相加,大于10后,高位加1
* 数字逆序存到两个数组中
* 遍历,若两个数组对应的位置值相加大于10,则保留个位,且高位加1
* 下一位相加,同时要加上低位进的1
> 代码
## 3.10、金矿问题
> 问题
10名工人,5堆金矿,价值{500,400,350,300,200},需要人力{5,5,3,4,3}
求解如何最大收益
> 思路
动态规划
> 代码
## 3.11、寻找缺失的整数
### 基本问题
> 问题
一个无序数组,有99个不重复的正整数,范围1-100,求出100中缺少的那个
> 思路
先计算累加和,然后减去有的99个数,得到的即为缺少的
### 扩展1
> 问题
数量若干,同时99个整数出现了偶数次,1个出现了奇数次,求出这一个数
> 思路
异或运算
### 扩展2
> 问题
2个数字出现了奇数次,求这两个数
> 思路
分治法
# 4、算法的应用
## 4.1、Bitmap
## 4.2、LRU
## 4.3、A星寻路算法
## 4.4、红包算法