数据结构与算法

复杂度

(通过复杂度可以评判出算法的性能)

1. 时间复杂度:

估算程序指令的执行次数(执行时间)

2. 空间复杂度:

估算程序执行所占用的存储空间

3. 大O表示法:

用该方法可以估算出算法的复杂度
方法:忽略常数,系数,低阶

9 -----> O(1)
2n + 3 -----> O(n)
n^2 + 2n + 6 -----> O(n^2)
4n^3 + 3n^2 + 22n + 5 -----> O(n^3)

复杂度大小比较:

O(1) < O(logn) < O(n) < O(n logn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)

例如:

public static int fib2(int n) {
		if (n <= 1) return n;
		
		int first = 0;
		int second = 1;
		for (int i = 0; i < n - 1; i++) {
			int sum = first + second;
			first = second;
			second = sum;
		}
		return second;
	}

这段算法的时间复杂度为:O(n)
空间复杂度为:4

链表(LinkedList)

1. 链表设计:

  • 概念:链表是一种链式存储的线性表,所有元素的内存地址不一定是连续的,链表的每个节点(Node)中包含两个组成部分:元素:element,下一个Node的地址:next;
    链表的尾结点指向null,头结点被“LinkedList(虚拟头节点)”的first指向:
    在这里插入图片描述
  • 优点:
  1. 节省了内存:链表不像动态数组,size不足够的时候还需要扩容,链表则为添加一个元素,随时添加一个节点,删除一个元素,便删除一个节点;
  2. 增删的速度很快:链表的增删只需要改变该位置前后节点的指向,而数组则需要整体挪动;
  • 缺点:
    链表查找元素的速度相对较慢,需要从头节点开始依次按顺序遍历;不像数组可以进行随机查找,因为jvm会根据数组的索引找打数据的内存地址;

2. 动态数组的缩容:

如果对数组的数据进行删除,当剩余的容量过大时,为了节省内存,就产生了缩容这一操作;根据实际需求,可自行调节缩容时机;
例如当size < 容量的一半时进行缩容;
但要掌握好扩容缩容的时机,不然可能会导致复杂度震荡。

3. 双向链表:

双向链表的性能比单向链表更高,它的虚拟节点中包含了头节点(first)和尾节点(last),且每个节点都包含:元素,下一节点地址,上一节点地址;其中头节点的上一节点(prev)指向null,尾节点的下一节点(next)指向null;

栈(Stack)

栈是一种特殊的线性表,只能在一端进行操作,也就是只能对栈顶的数据进行操作,栈中的数据对外界是封闭的;

在这里插入图片描述

  • 入栈(push):往栈中添加元素;
  • 出栈(pop):移除栈顶元素;
    都遵循后进先出的原则

队列(Queue)

队列是一种特殊的线性表,只能在头尾两端进行操作;
队尾(rear):只能从队尾添加元素,叫做enQueue,入队;
对头(front):只能从对头移除元素,叫做deQueue,出队;
在这里插入图片描述

1. 双端队列(Deque)

双端队列可以再头尾两端进行添加和删除;

2. 循环队列(Circle Queue)

头尾之间连接在一起,形成了循环;
循环双端队列:可以进行头尾两端的增删操作;

二叉树(Binary Tree)

基本概念:

在这里插入图片描述

  • 空树:没有任何节点的树
  • 只有一个节点的二叉树即只有根节点;
  • 节点的度:子树的个数
  • 树的度:所有节点度中的最大值;
  • 叶子节点:度为0的节点;
  • 非叶子节点:度部位零的节点;
  • 层数(level):根节点在第一层,根节点的子节点在第二层,以此类推;
  • 节点的深度(depth):从根节点到当前节点的唯一路径上的节点总数;
  • 节点的高度:从当前节点到最远叶子节点的路径上的节点总数;
  • 树的深度:所有节点深度中的最大值;
  • 树的高度:同上;
  • 树的深度:等于树的高度;
  • 有序树:树中任意节点之间有顺序关系;
  • 无序树:树中任意子节点之间没有顺序关系(自由树)
  • 森林:由m(m>=0)颗互不相交的树组成的集合;

二叉树:

  • 特点:
    每个节点的度最大为2(最多拥有两颗子树)
    左子树和右子树是有顺序的
    即使某节点只有一颗子树,也要区分左右子树
  • 性质:
  1. 非空二叉树的第i层,最多有2^i-1个节点(i >= 1)
  2. 在高度为h的二叉树上最多有(2^h)-1个节点(h >= 1);
1. 真二叉树(Proper Binary Tree)

所有节点的度要么为0,要么为2;
在这里插入图片描述

2. 满二叉树(Full Binary Tree)

最后一层节点的度都为0,其他节点度都为2
在这里插入图片描述

3. 完全二叉树(Complete Binary Tree)

对节点从上至下,从左至右开始编号,其所有编号都能与相同高度的满二叉树中的编号对应;

叶子节点只会出现在最后两层,最后一层的叶子节点都靠左对齐;
完全二叉树从根节点至倒数第二层是一颗满二叉树;
满二叉树一定是完全二叉树,反之不易然;
在这里插入图片描述

  • 性质:
  1. 度为1的节点只有左子树;
  2. 度为1的节点要么是一个,要么是0个;
  3. 同样节点数量的二叉树,完全二叉树的高度最小;
  4. 假设完全二叉树的高度为h(h>=1),那么:
    至少有2 ^(h-1)个节点
    最多有(2^h)-1个节点;
    在这里插入图片描述
    在这里插入图片描述

二叉树的遍历:

1. 前序遍历(Preorder Traversal):
  • 访问顺序:根节点,前序遍历左子树,前序遍历右子树;
    在这里插入图片描述
2. 中序遍历(Inorder Traversal):

访问顺序:中序遍历左子树,根节点,中序遍历右子树;
在这里插入图片描述

3. 后序遍历(Postorder Traversal):

访问顺序:后序遍历左子树,后序遍历右子树,根节点;
在这里插入图片描述

4. 层序遍历(Level Order Traversal):

访问顺序:从上到下,从左到右依次访问每一个节点;
在这里插入图片描述

二叉搜索树(Binary Search Tree):

  • 性质:
  1. 任意一个节点的值都大于其左子树所有节点的值;
  2. 任意一个节点的值都小于其右子树所有节点的值;
  3. 他的左右子树也是一颗二叉搜索树;

平衡二叉搜索树( Balanced Binary Search Tree):

  • 平衡:
    当节点数量固定时,左右子树的高度越接近,这颗二叉树越平衡;
    最理想的平衡就是想像完全二叉树,满二叉树那样,高度是最小的;
    常见的BBST有:AVL树,红黑树;

AVL树:

AVL树:

AVL树是最早发明的自平衡二叉搜索树之一;

  • 平衡因子(Balance Factor):某节点左右子树的高度差;
  • AVL树的特点:
  1. 每个节点的平衡因子只可能是1,0,-1(即绝对值 <= 1,如果不在该范围内称之为失衡);
  2. 搜索,添加,删除的时间复杂度是O(log n);
简单的继承结构:

在这里插入图片描述

添加导致失衡:

往以下的AVL树中添加13,会导致失衡;
最坏情况:可能导致所有的祖先节点都失衡;
父节点,非祖先节点都不可能失衡;
在这里插入图片描述

删除导致失衡:

可能会导致父节点或祖先节点失衡(只有一个节点会失衡),其他节点都不会影响;

总结:
  • 添加:
  1. 可能会导致所有祖先节点都失衡;
  2. 只要让高度最低的失衡节点恢复平衡,整棵树就恢复平衡(仅需O(1)次调整);
  • 删除:
  1. 可能会导致父节点或祖先节点失衡(只有一个节点会失衡);
  2. 恢复平衡后,可能会导致更高层的祖先节点失衡(最多需要O(log n)次调整);

B树:

B树是一种平衡的多路搜索树,多用于文件系统,数据库的实现;

m阶B树的性质(m >= 2):

令一个节点存储的元素个数为x:
根节点:1 <= x <= m - 1
非根节点:「m / 2⌉ - 1 <= x <= m - 1 (向上取整)
如果有子节点,子节点个数 y = x + 1;
根节点:2 <= y <= m
非根节点:「m / 2⌉ <= y <= m

搜索:

跟二叉树的搜索类似:

  1. 先在节点内部从小到大开始搜索元素;
  2. 如果命中,搜索结束;
  3. 如果未命中,再去对应的子节点中搜索元素,重复步骤1;
添加:

新添加的元素必定是添加到叶子节点

添加 - 上溢:

假设m = 5阶;
假设上溢节点中最中间元素的位置为k;
上溢节点的元素个数必然等于5;

  1. 将k位置的元素向上与父节点合并;
  2. 将【0,k-1】和【k + 1,m - 1】位置的元素分裂成两个子节点;
    一次分裂完毕后,有可能导致父节点上溢,依然按照上述方法解决;最极端的情可能一直分裂到根节点;
删除:
  • 删除的如果为叶子节点,那么直接删除即可;
  • 删除非叶子节点:
    先找到前驱或后继元素,覆盖所需删除的元素的值,再把前驱或后继元素删除;
    非叶子节点的前驱或后继元素必定在叶子节点中;
删除 - 下溢:

叶子节点被删掉一个元素后,元素个数可能会低于最低限制(>= 「m / 2⌉ - 1)
下溢节点的元素数量必然等于「m / 2⌉ - 2;

  • 如果下溢节点的临近兄弟节点有至少「m / 2⌉ 个元素,可以向其借一个元素;
  1. 将父节点的元素b插入到下溢节点的0位置(最小位置)
  2. 用兄弟节点的元素a(最大元素)代替父节点元素b;
    在这里插入图片描述
  • 如果下溢节点临近兄弟节点只有「m / 2⌉ - 1 个元素;
  1. 将父节点的元素b挪下来跟左右子节点进行合并;
  2. 合并后的节点元素个数等于「m / 2⌉ + 「m / 2⌉ - 2,不超过m - 1;
  3. 这个操作可能会导致父节点下溢,依然重复上述解决方法;
    在这里插入图片描述

红黑树(RBTree):

红黑树也是一种自平衡二叉搜索树,由AVL树优化而来;

红黑树的5条性质:
  1. 节点是RED或者BLACK;
  2. 根节点是BLACK;
  3. 叶子节点(外部节点,空节点)都是BLACK;
  4. RED节点的子节点都是BLACK,RED节点的父节点都是BLACK,从根节点到叶子节点的所有路径上不能有两个连续的RED节点;
  5. 从任一节点到叶子节点的所有路径都包含相同数目的BLACK节点;
红黑树与四阶B树:

红黑树与四阶B树具有等价性,BLACK和他的RED子节点融合在一起,形成一个B树节点,红黑树的BLACK节点个数4阶B树的节点总数相等;
在这里插入图片描述

添加:

红黑树将继承一个艾薇儿树(AVLT)也继承的类(BBST),这个类中封装了旋转方法,而BBST继承二叉搜索树(BST),BST又继承二叉树(BT),所以红黑树的添加方法直接来自于二叉搜索树BST,添加后所需要的有关红黑树独特的特性的调整再进行处理(afteradd);BST中主要封装了添加,删除,比较器方法;BT中主要封装了前序遍历,中序遍历,后序遍历,层次遍历,树高度等方法;

  • 已知条件:
    B树中新元素必定添加到叶子节点中;
    4阶B树的所有节点元素个数x都符合 1 <= x <= 3;
  • 添加的所有情况(共12种):
    前4种(满足4阶B树的性质)parent为BLACK:
    1 ~ 4. 不需要做任何处理;
    在这里插入图片描述
    后8种(不满足4阶B树的性质)parent为RED:
    在这里插入图片描述
    5 ~ 8. 后8种的前4种属于B树节点上溢;

判定条件:uncle不是RED:(LL/RR)

parent染成BLACK,grand染成RED,grand进行单旋操作(LL右旋/RR左旋);
在这里插入图片描述
6. 8.

  • 判定条件:uncle不是RED:(LR/RL)

自己染成BLACK,grand染成RED,进行双旋操作;(LR:parent 左旋,grand右旋 / RL:parent右旋,grand左旋)
在这里插入图片描述
7. 10. 11. 12.

  • 判定条件:uncle是RED《B树上溢》:(LL/RR/LR/RL)

parent,uncle染成BLACK,grand向上合并;
grand向上合并时可能继续发生上溢,若上溢持续到根节点,只需将根节点染成BLACK即可;
LL
在这里插入图片描述
RR
在这里插入图片描述
LR
在这里插入图片描述
RL
在这里插入图片描述

删除:

红黑树的删除和添加类似,删除的具体方法都封装在BST(二叉搜索树)中,且在B树中,最后真正被删除的元素都在叶子节点中,所以删除红黑树元素时,调用的BST中的删除方法只会处理叶子节点的元素(非叶子节点的删除会找到该节点的前驱节点或后继节点,然后用前驱或后继的值去覆盖要删除节点的值,再删除前驱或后继节点,所以在红黑树封装的类中,afterremove只需要处理被删除的叶子节点中的元素);

  1. 删除RED节点:
    直接删除后不做任何处理;
    在这里插入图片描述
  2. 删除BLACK节点(1)拥有1个RED子节点的BLACK节点:
  • 判定条件:用以替代的子节点是RED

将替代的子节点染成BLACK即可保持红黑树的性质;
在这里插入图片描述

  1. 删除BLACK节点(2)sibling(兄弟节点)为BLACK,且sibling至少有1个RED子节点:

BLACK叶子节点被删除后,会导致B树节点下溢;
进行旋转操作,旋转后的中心节点继承parent的颜色,旋转后的左右节点染成BLACK;
在这里插入图片描述

  1. 删除BLACK节点(3)sibling(兄弟节点)为BLACK,且sibling没有一个RED子节点:
    若parent为RED,将sibling染成RED,parent染成BLACK即可;
    若parent为BLACK,会导致parent也下溢,这是只需要把parent也当做被删除的节点再次传给afterRemove即可;

在这里插入图片描述

  1. 删除BLACK节点(4)sibling(兄弟节点)为RED:
    sibling染成BLACK,parent染成RED,进行旋转,于是变成了sibling是BLACK的情况;
    在这里插入图片描述
红黑树的平衡:

在这里插入图片描述
红黑树的五条性质保证了红黑树等价于4阶B树,
相比于AVL树,红黑树的平衡标准比较宽松:没有一条路径的长度会大于其他路径的2倍;
红黑树的最大高度是2 * log2(n + 1),依然是O(log n)级别;

集合(Set)

  • 集合的特点:
    不存放重复的元素;
    常用于去重:存放新增IP,统计新增IP。存放词汇,统计词汇量;
    集合的底层可以用红黑树实现,效率相当高;

映射(Map)

  • Map中的每一个key都是唯一的,同样不存放重复元素;
  • Map的所有key组合在一起,其实就是一个Set;

哈希表(Hash)

哈希表(HashTable)

在这里插入图片描述
添加,删除,搜索都需要利用哈希函数生成key对应的index,然后在根据index操作指定数组元素;

哈希冲突(Hash Collision)

也叫哈希碰撞:两个不同的key,经过哈希函数计算出相同的结果,也就是index相同,如下图:
在这里插入图片描述
处理哈希冲突的常见方法:

  1. 开放地址法:按照一定的规则向其他地址探测,直到遇到空桶;
  2. 再哈希法:设计多个哈希函数;
  3. 链地址法:比如通过链表将同一index的元素串起来;

JDK1.8的哈希冲突解决方法:

  • 默认使用单向链表将元素串起来;
  • 在添加元素时,可能会由单向链表转化为红黑树;(比如官方为哈希表容量 >= 64,且单向链表的节点数量大于8时);
  • 当红黑树的节点数量少到一定程度,又会变回单向链表;
哈希函数:
  • 哈希表中的哈希函数实现步骤:
    先生成key的哈希值(必须是整数)
    再让key的哈希值跟数组的大小进行相关运算,生成一个索引值

  • 生成key的哈希值的方法:
    尽量让每个key的哈希值是唯一的;
    尽量让key的所有信息参与运算;

在java中,HashMap的key必须实现hashCode,equals方法,也允许key为null;

装填因子:

Load Factor: = 节点总数量/哈希表桶数组长度
在JDK1.8中,若装填因子超过0.75,就扩容为原来的2倍;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值