栈(stack)
栈(stack)是限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈顶 (top)。它是后进先出(LIFO)的。对栈的基本操作只有 push(进栈)和 pop(出栈)两种, 前者相当于插入,后者相当于删除最后的元素。
队列(queue)
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的 后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为 队尾,进行删除操作的端称为队头。
链表 (link)
散列表(Hash Table)
散列表(Hash table,也叫哈希表)是一种查找算法,散列表算法希望能尽量做到不经过任何比较,通过一次存取就能得到所查找的数据元素。
- 直接定址法: 取关键字或关键字的某个线性函数值为散列地址。
即:h(key) = key 或 h(key) = a * key + b,其中 a 和 b 为常数。 - 数字分析法
- 平方取值法: 取关键字平方后的中间几位为散列地址。
- 折叠法:将关键字分割成位数相同的几部分,然后取这几部分的叠加和作为散列地址。
- 除留余数法:取关键字被某个不大于散列表表长 m 的数 p 除后所得的余数为散列地址, 即:h(key)=keyMODp p≤m
- 随机数法:选择一个随机函数,取关键字的随机函数值为它的散列地址, 即:h(key) = random(key)
其中除留余数法就是我们Map中使用的逻辑,Map中获取table下标的逻辑是:
hash & (table.length -1) 等价于 hash%table.length ,等价的原因是length是2^n
这种时间查询复杂度在没有冲突的情况下是O(1)
排序二叉树
首先如果普通二叉树每个节点满足:左子树所有节点值小于它的根节点值,且右子树所有节点值
大于它的根节点值,则这样的二叉树就是排序二叉树。
插入
删除
查询
查找操作的主要流程为:先和根节点比较,如果相同就返回,如果小于根节点则到左子树中 递归查找,如果大于根节点则到右子树中递归查找。因此在排序二叉树中可以很容易获取最 大(最右最深子节点)和最小(最左最深子节点)值。
红黑树
我们看到上面的图,二叉树在某些特殊情况下,有可能变成链表。为了解决这个问题,出现了红黑树。
特征
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL 或 NULL)的叶子节点!]
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
细节内容大家可以看别人的文章:
红黑树(一)之 原理和算法详细介绍
红黑树(五)之 Java的实现
B-Tree 和 B+Tree
由于二叉树的树的高度问题,衍生出了B-Tree。关于B树和B+树,看下面的三阶树:
对比差异:
- 都是3阶的B-Tree和B+Tree,B-Tree一个节点只有2个关键字,而B+Tree有3个关键字。
- B+Tree叶子结点包含了所有信息,而B-Tree叶子结点没有全部信息
- B+Tree比B-Tree存储的数据更少
- 在范围查询方面,B+Tree可以直接查询叶子结点,而B-Tree不可以,所以范围查询B+Tree更有优势
- 对于等值查询,B+Tree查询效率比较平衡,因为最终都要落到叶子结点上,而B-Tree,有可能在根结点就查询到数据。可以直接返回内容,速度更快。
位图
位图的原理就是用一个 bit 来标识一个数字是否存在,采用一个 bit 来存储一个数据,所以这样可 以大大的节省空间
package com.jarvis.java;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author jinzhaopo
* @date 2024-01-26 15:39
*/
public class BitMap {
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
private static class User {
/**
* 判断新用户:0 不是,1 是
*/
private Integer bolNew;
/**
* 是否启用:0 不启用,1 启用
*/
private Integer bolEnable;
/**
* 是否超级管理员:0 不是超级管理员 , 1 是超级管理员
*/
private Integer bolSuper;
/**
* 标示数据库存储的值
*/
private Integer dbData;
public User(Integer bolNew, Integer bolEnable, Integer bolSuper) {
this.bolNew = bolNew;
this.bolEnable = bolEnable;
this.bolSuper = bolSuper;
dbData = Integer.parseInt("" + bolNew + bolEnable + bolSuper, 2);
}
public void setDbData(Integer dbData) {
this.dbData = dbData;
String s = Integer.toBinaryString(dbData);
int length = s.length();
Function<Integer, Integer> function = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer object) {
int index = length - object;
return index < 0 ? 0 : Integer.valueOf(s.charAt(index) + "");
}
};
bolNew = function.apply(3);
bolEnable = function.apply(2);
bolSuper = function.apply(1);
}
public User(Integer dbData) {
setDbData(dbData);
}
}
public static void main(String[] args) {
// 针对User类中的三个字段,我们会对应数据库表的三个字段。在某种特殊的情况下,为了节省存储,那么需要将3个字段融合成一个?
// 我们可以通过位,指定每一位代表的意思,假如顺序是:bolNew - bolEnable - bolSuper
// 我们通过组合可以得到:000 001 010 011 100 101 110 111
List<User> list = new ArrayList<>(8);
Collections.addAll(
list,
new User(0),
new User(1),
new User(2),
new User(3),
new User(4),
new User(5),
new User(6),
new User(7)
);
// 如何查询数据库里面是新用户的的数据呢?新用户有100 111 101 110 ,数据 & 100 = 0 就是非新用户, 数据 & 100 = 4 就是新用户
System.out.println("----------------新用户--------------------");
list.stream().filter(x -> (x.getDbData() & 4) == 4)
.collect(Collectors.toList())
.forEach(x -> {
System.out.println("新用户:" + x);
});
// 如何查询即是 新用户,又是超级管理员? 数据为:1?1
System.out.println("----------------新用户和超级管理员--------------------");
list.stream().filter(x -> (x.getDbData() & 5) == 5)
.collect(Collectors.toList())
.forEach(x -> {
System.out.println("新用户和超级管理员:" + x);
});
// 如何更新数据呢?比如将用户都设置成新用户?我们可以找到非新用户,011 010 001 000,将最前面的位改成1
System.out.println("----------------非新用户--------------------");
List<User> collect = list.stream().filter(x -> (x.getDbData() & 4) == 0).collect(Collectors.toList());
for (User user : collect) {
System.out.println("非新用户:" + user);
}
collect.stream().forEach(x -> {
x.setDbData(x.getDbData() | 4);
});
System.out.println("--------------修改后的非新用户,就是新用户--------------------");
for (User user : collect) {
System.out.println("非新用户:" + user);
}
}
}