二分搜索树—(RBTree)—TreeMap / TreeSet
哈希表 —HashSet / HashMap
Map接口(映射关系)
存储键值对。
Map接口常见子类添加问题
Set
披着Set外衣的Map,也是collection接口的子接口。
collection接口: 线性表接口一次保存一个元素 ,其子类的都可以进行for-each循环
和List集合区别: Set 集合保存的元素不能重复
【使用Set集合去重处理】
搜索树
1、搜索树有哪些?–>RBTree、AVL(平衡BST)、2-3树、B树等等
2、概念:
BST:二分搜索树/二叉搜索树/二叉排序树
1、是二叉树
2、节点严格要求:每个树的左子树值<树根节点值<所有右子树值
JDK中BST不存在重复节点!!!
3、存储的节点必须具备可比较的能力(实现Compara接口或者传入比较器)
3、向BST添加一个节点:
【新添加的数一定是在叶子节点操作】
时间复杂度:平均logn
最坏 n–单支数
/**
* 添加一个新节点
*
* @param val
*/
public void add(int val) {
root = add(root, val);
}
/**
* 向当前以root为根的BST中插入一个新元素 ,返回插入后的根
*
* @param root
* @param val
* @return
*/
private Node add(Node root, int val) {
if (root == null) {
Node node = new Node(val);
size++;
return node;
}
if (val < root.val) {
root.left = add(root.left, val);
}
if (val > root.val) {
root.rigth = add(root.rigth, val);
}
return root;
}
4、判断value是否在BST中
平均:logn
最坏:n
/**
* 判断BST中是否存在值为value的节点
*
* @param val
* @return
*/
public boolean contains(int val) {
return contains(root, val);
}
/**
* 传入一个根节点和val判断该节点是否存在
*
* @param root
* @param val
* @return
*/
private boolean contains(Node root, int val) {
if (root == null) {
return false;
}
if (root.val == val) {
return true;
} else if (val < root.val) {
return contains(root.left, val);
} else {
return contains(root.rigth, val);
}
}
5、按照先序遍历打印BST
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
generateBSTString(root, 0, sb);
return sb.toString();
}
/**
* 按照先序遍历,将BST节点值存于sb中
*
* @param root
* @param height
* @param sb
*/
private void generateBSTString(Node root, int height, StringBuilder sb) {
if (root == null) {
sb.append(generateHeightStr(height)).append("Null\n");
return;
}
sb.append(generateHeightStr(height)).append(root.val).append("\n");
generateBSTString(root.left, height + 1, sb);
generateBSTString(root.rigth, height + 1, sb);
}
/**
* 按照节点高度打印 --
* 树根的第一层 => --
* 第二层 ----
*
* @param height
* @return
*/
private String generateHeightStr(int height) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < height; i++) {
sb.append("--");
}
return sb.toString();
}
6、BST中最大最小值
先序遍历中。第一个左子树为空的节点–最小值
第一个右子树为空的节点–最大值
public int findMin() {
if (size == 0) {
throw new NoSuchElementException("bst is empty! no element!");
}
return min(root).val;
}
public int findMax() {
if (size == 0) {
throw new NoSuchElementException("bst is empty! no element!");
}
return max(root).val;
}
/**
* 以roor为根的BST找到最大值的节点
*
* @param root
* @return
*/
private Node max(Node root) {
if (root.rigth == null) {
return root;
}
return max(root.rigth);
}
/**
* 以roor为根的BST找到最小值的节点
*
* @param root
* @return
*/
private Node min(Node root) {
if (root.left == null) {
return root;
}
return min(root.left);
}
7、删除最大值、最小值
【最小值】第一个左子树为空的节点
【最大值】第一个右子树为空的节点
/**
* 删除BST中最小节点
*
* @return
*/
public int removeMin() {
if (size == 0) {
throw new NoSuchElementException("bst is empty! cannot remove!");
}
Node node = min(root);
root = removeMin(root);
return node.val;
}
/**
* 传入以root为根的BST 删除最小值的节点,返回删除后的根节点
*
* @param root
* @return
*/
private Node removeMin(Node root) {
if (root.left == null) {
Node right = root.rigth;
root.left = right.rigth = root = null;
size--;
return right;
}
root.left = removeMin(root.left);
return root;
}
/**
* 删除最大值
*
* @return
*/
public int removeMax() {
if (size == 0) {
throw new NoSuchElementException("bst is empty! cannot remove!");
}
Node node = max(root);
root = removeMax(root);
return node.val;
}
/**
* 传入根节点,返回删除最小值后的根节点
*
* @param root
* @return
*/
private Node removeMax(Node root) {
if (root.rigth == null) {
Node node = root.left;
root.rigth = root.left = root = null;
size--;
return node;
}
root.rigth = removeMax(root.rigth);
return root;
}
8、删除任意一个值为value的节点
【Hibbard 算法找前驱或者后继】
【前驱:要删除节点的左子树最大值】
【后继:要删除节点右子树最小值】
/**
* 删除值为value的节点
*
* @param val
*/
public void remove(int val) {
if (size == 0) {
throw new NoSuchElementException("bst is empty! cannot remove!");
}
root = remove(root, val);
}
/**
* 在当前 root 为根的BST,删除值为val的节点,返回删除后的根节点
*
* @param root
* @param val
* @return
*/
private Node remove(Node root, int val) {
if (root == null) {
//遍历树找不到值为val的节点
throw new NoSuchElementException("bst has not this node!");
} else if (val < root.val) {
root.left = remove(root.left, val);
return root;
} else if (val > root.val) {
root.rigth = remove(root.rigth, val);
return root;
} else {
//树不为空
//root就是待删除的节点
if (root.left == null) {
Node node = root.rigth;
root.rigth = root = null;
size--;
return node;
}
if (root.rigth == null) {
Node node = root.left;
root.left = root = null;
size--;
return node;
}
//左右 子树都不为空
//Hibbard Deletion
//找到当前右子树最小值节点
Node successor = min(root.rigth);
//1、先在右子树中删除最小值
successor.rigth = removeMin(root.rigth);
//2、连接root.left
successor.left = root.left;
root.left = root.rigth = root = null;
return successor;
}
}
哈希表
【一种方法将任意数据类型转为数组索引–哈希函数,用空间换时间】
【借鉴了数组的随机访问能力】
1、哈希冲突
不同key经过hash函数的计算得到了相同的值
2、hash函数的设计
哈希冲突在数学领域理论上一定存在
3、原则
尽可能在空间和时间上求平衡,利用最少的空间获取一个较为平均的数字分布

4、解决哈希冲突
1、闭散列(缺点:难查难删除、工程中很少使用)–线性探测
遇到位置不为空可以向后寻找第一个不为空的位置存入(或者二次哈希–再模一次)
【若整个哈希表冲突非常严重,此时查找一个元素从o(1)–>o(n)】
2、开散列
出现 冲突,就将此位置变为链表
冲突严重的情况解决办法:①针对整个数组扩容-----C++中的STL的Map
②将冲突严重的链表再次变为新的哈希表/二分搜索书-----JDK
5、MD5用于字符串计算hash值
特点:
①定长,无论输入数据多长,得到的MD5值长度固定
②分散 ,若输入稍有偏差则MD5值相差万里
③不可逆,通过MD5还原字符串很难
④稳定性,根据相同的数据计算的MD5稳定,不会变化—所有hash函数都要满足的
用途:
①作为hash计算
②用于加密
③对比文件内容
扩容
负载因子 loadFactor = 哈希表中有效元素/哈希表长度,值越大冲突越严重,值越小数组利用率低
1、扩容与否与负载因子相关:数组长度*负载因子 <= 有效元素个数
JDK默认负载因子=0.75
阿里巴巴实验数据 负载因子=10
public class MyHashMap {
private int size;
private Node[] hashTable;
private int m;
private static final double LOAD_FACTOR = 0.75;
public MyHashMap() {
this(16);
}
public MyHashMap(int x) {
this.hashTable = new Node[x];
this.m = x;
}
/**
* 对key求哈希
*
* @param key
* @return
*/
public int hash(int key) {
return Math.abs(key) % m;
}
/**
* 将键值对保存入哈希表中
* 既是新增又是修改
*
* @param key
* @param value
*/
public int put(int key, int value) {
int index = hash(key);
//遍历链表,查看key值是否存在
for (Node i = hashTable[index]; i != null; i = i.next) {
if (i.key == key) {
int oldVal = i.value;
i.value = value;
return oldVal;
}
}
Node node = new Node(key, value);
node.next = hashTable[index];
hashTable[index] = node;//头插
size++;
//添加之后判断是否扩容
if (size >= hashTable.length * LOAD_FACTOR) {
resize();
}
return value;
}
/**
* 哈希表扩容
* 新数组的长度变为原来一倍
*/
private void resize() {
Node[] newTable = new Node[hashTable.length << 1];//产生新数组
//元素搬移
this.m = newTable.length;
for (int i = 0; i < hashTable.length; i++) {
for (Node x = hashTable[i]; x != null; ) {
Node next = x.next;
int index = hash(x.key);
x.next = newTable[index];
newTable[index] = x;
}
}
hashTable = newTable;
}
/**
* 判断当前key是否在表中存在
*
* @param key
* @return
*/
public boolean containsKey(int key) {
int index = hash(key);
for (Node x = hashTable[index]; x != null; x = x.next) {
if (x.key == key) {
return true;
}
}
return false;
}
/**
* 判断value是否存在
*
* @param val
* @return
*/
public boolean containsVal(int val) {
for (Node node : hashTable) {
for (Node j = node; j != null; j = j.next) {
if (j.value == val) {
return true;
}
}
}
return false;
}
/**
* 判断键值对是否存在
*
* @param key
* @param val
* @return
*/
public boolean todd(int key, int val) {
int index = hash(key);
Node node = hashTable[index];
while (node != null) {
if (node.value == val) {
return true;
}
node = node.next;
}
return false;
}
/**
* 删除
* @param key
* @param val
* @return
*/
public boolean remove(int key, int val) {
int index = hash(key);
Node node = hashTable[index];
//头节点是待删除结点
if (node.key == key && node.value == val) {
hashTable[index] = node.next;
node = node.next = null;
size--;
return true;
}
Node prev = node;
while (prev.next != null) {
if (prev.next.key == key && prev.next.value == val) {
Node cur = prev.next;
prev.next = cur.next;
cur = cur.next = null;
size--;
return true;
} else {
prev = prev.next;
}
}
//遍历完链表都找不到,报错
throw new NoSuchElementException("can not find! remove error!");
}
}
class Node {
int key;//对Key值进行hash运算
int value;
Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
jdk8之后的HashMap为:数组+链表+红黑树
jdk8之前为:数组+链表
面试题
- 复制带随机指针的链表
LeetCode原题
public Node copyRandomList(Node head) {
Map<Node, Node> map = new HashMap<>();
//1、遍历原链表构造Map集合,维护原链表和新链表的映射关系
//原1 = 新1;
for (Node i = head; i != null; i = i.next) {
Node node = new Node(i.val);
map.put(i, node);
}
//通过原1找到新1,通过原random找到新random
for (Node i = head; i != null; i = i.next) {
//新链表的next=新链表的next
map.get(i).next = map.get(i.next);
//新链表的random=新链表的random
map.get(i).random = map.get(i.random);
}
return map.get(head);
}