1.概念
1.1概述
HashMap/TreeMap/LinedHashMap
HashMap是基于哈希表+红黑树的结构
1.2 HashMap、TreeMap、LinkedHashMap
HashMap的保存顺序与元素的插入顺序无关,key和value都可以为空。TreeMap是基于红黑树的结构,TreeMap的元素保存顺序与元素的插入顺序无关,key不能为null,value可以为null. (使用TreeMap保存元素时,元素必须是Comparable子类或者传入比较器)
LinkedHashMap就是在HashMap的基础上维护了一个链表来记录元素的插入先后,可以按照元素的插入顺序来保存元素。
Set集合:去重
遍历set集合,直接使用for each循环即可
只要是lterable接口的子类,都可以直接使用for each循环
如图,遍历Map集合时,需要将Map->Set进行for each遍历
for(Map,entrt<具体类型>entry:map.entrySet())
1.3 Comparable 和 Compator有啥区别?
当一个类实现了Comparable接口,说明该类具备了可比较大小的能力,类本身具备可比较的能力
//Freq这个类就本身具备可比较的能力,Freq对象的大小关系由camparaTo方法决定
class Freq implements Comparable{
public int comparaTo(Freq o){}
}
//此时FreqTwo本身是不可比较的,关于Freq类的对象比较交给第三方的类,
FreqDesc这个类专门就是比较FreqTwo类的大小关系。
class FrqDesc implements Compartor{
public int compare(FreqTwo o1,FreqTwo o2){}
}
class FreqTwo{}
2.二分搜索树
二叉搜索树:又称二叉排序树,
特点:若左数不为空,则左子树上所有节点的值都小于根节点的值
若右数不为空,则柚子树上所有结点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
1.是个二叉树(每个节点最多有两个子节点)
2.对于这棵树中的节点的节点值,左子树的所有节点值<根节点<右子树的所有节点值
最大特点:也是判断是否为搜索树的方法,对该书进行中序遍历,就可以得到一个升序集合。
二分查找的时间复杂度:O(log N)–>树结构
2.1 插入结点、判断是否包含结点
package bin_tree.heap.searchtree;
public class BST {
private TreeNode root;
private class TreeNode{
private int val;
private TreeNode left;
private TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
private int size;
private TreeNode node;
public void add(int val){
root = add(root,val);
}
/**
* 判断当前树中是否存在val
* @param val
* @return
*/
public boolean contains(int val){
return contains(root,val);
}
/**
* 判断当前以root为根的BST中是否包含了val
* @param root
* @param val
* @return
*/
private boolean contains(TreeNode 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.right,val);
}
}
/**
* 向以root为根的BST中插入一个新的结点val
* @param root
* @param val
* @return
*/
private TreeNode add(TreeNode root,int val){
TreeNode newNode =new TreeNode(val);
//创建新结点
if(root == null){
//当前值就是根节点
size++;
return newNode;
}
if(val < root.val){
//左树中插入
root.left = add(root.left,val);
}
if(val > root.val){
//右数中插入
root.right = add(root.left,val);
}
return root;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
generateBSTString(root,0,sb);
return sb.toString();
}
/**
* 先序遍历,以root为根节点的BST,将节点值存储在sb中
* @param root
* @param height
* @param sb
*/
private void generateBSTString(TreeNode 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.right,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();
}
}
2.2 删除最小值所在的节点
//删除当前最小值
public int removeMin(){
//先找到最小值
int min = findMin();
root = removeMin(root);
return min;
}
/**
* 在当前以root为根的BST中,删除最小值所在的节点,返回删除后的树根
* @param root
* @return
*/
private TreeNode removeMin(TreeNode root) {
if(root.left == null){
//当前的root即为最小值,即为待删除的节点
//先暂存一下当前root的右子树
TreeNode right = root.right;
//删掉最小节点root
root.right=root=null;
size--;
return right;//返回给上方第132行的root,原最小值root删除后,它的右子树节点顶替它的位置,成为新的root
}
//若此时当前root不是最小值,则去左子树找寻最小值并删除
root.left = removeMin(root.left);
return root;
}
2.3删除最大值所在的节点
//删除最大值所在的节点
public int removeMax(){
int max = findMax();
root=removeMax(root);
return max;//返回要删除的节点值
}
/**
* 在当前以root为根的BST中,删除最大值所在的节点,返回删除后的树根
* @param root
* @return
*/
private TreeNode removeMax(TreeNode root) {
if(root.right == null){
//说明此时的root即为待删除的节点
TreeNode left = root.left;
//断开root连接
root.left = root =null;
size--;
return left;
}
//此时不是最小值
root.right = removeMax(root.right);
return root;//返回删除后的树根
}
2.4 删除任意值所在的节点(重要)
思路:
1.查找到值为val的节点:
若val < root.val,则去根节点的左子树中寻找
若 val > root.val 则去根节点的右子树寻找
2.若val == root.val,则:
若此时根节点无左子树,则直接返回根节点的右子树
若此时根节点无右子树,则直接返回根节点的左子树
3.若此时待删除的root既有左子树也有右子树,例如:
此时待删节点为:58
此时应该寻找找左子树的最大节点或者右子树的最小节点作为后继节点successor.
此时使用右子树的最小节点59为successor,则先暂存58 的左子树。
后继节点successor要作为新的root的话,他的右子树应该为删除succesor这个节点之后的原右子树
如图:
TreeNode successor = minNode(root.right);
successor.right = removeMin(root.right);
successor.left = root.left;
此时两步的顺序不能交换(先连右后连左):
如果先执行的是successor.left = root.left,的话,再执行successor.right = removeMin(root.right)时,root.right的树结构发生改变
所以,这里的顺序不能变,只能先successo.right = removeMin(root.right),再successor.left = root.left.
/**
* 在当前以root为根的BST中,删除值为val的所有节点
* @param val
* @return
*/
public void remove(int val){
root = remove(root,val);
}
/**
* 在当前以root为根节点的BST中删除值为val的节点
* 返回删除后的新的根节点
* @param root
* @param val
* @return
*/
private TreeNode remove(TreeNode root, int val) {
//边界条件
if(root == null){
throw new NoSuchElementException("当前BST中没有值为"+val+"的节点!");
}else if(val < root.val){
//去左子树中查找值为val的节点并删除
root.left=remove(root.left,val);
return root;
}else if(val > root.val){
//在右子树中查找值为val的节点并删除
root.right = remove(root.right,val);
return root;
}else{
//此时root.val = val;
if(root.left == null){
//此时只需要返回右子树
TreeNode right = root.right;
root.right = root = null;
size--;
return right;
}
if(root.right == null){
TreeNode left = root.left;
root.right = root = null;
size--;
return left;
}
// 此时说明root.left 和 root.right 都不为空
// Hibbard Deletion,找左子树的最大节点或者右子树的最小节点作为新的root
//此处使用右子树的最小节点,succerssor->后继节点
TreeNode successor = minNode(root.right);
// 在右子树中删除后继节点的时候就已经size --了
//此时后继节点要作为新的root的话,他的右子树应该为删除succesor这个节点之后的原右子树
//注意:此时应该先拼接右子树,再拼接左子树,若先左后右,在进行removemin(root.right)时,擅长的并不是succesor这个值的节点
successor.right = removeMin(root.right);
successor.left = root.left;
// 将要删除的节点58,和BST断开关系
root.left = root.right = root = null;
return successor;
}
3.Hash函数
3.1.概念
所谓的哈希函数就是指将任意的数据类型转换为整型int,有整型后,就可以作为数组的索引,将原数组的元素和索引建立一个映射—>哈希函数
3.2 取模
[100,-2,1000]这类似的元素跨度大的集合,用空间换取时间,将原数组的元素和数据的索引建立一个映射,最常用的一种方法:取模
3.3哈希冲突
通过取模运算,将一个很大范围的数据集映射到一个小区间,有可能出现多个不同的key经过hash之后得到了相同的值–>哈希冲突
3.4设计原则
1.不同的key值经过Hash函数运算之后得到的结果分布越均匀越好。
2.稳定性,相同的数据经过N次hash函数之后,值应该保证相同,保证不变。
3.5 HashCode
JDK中,任意一个数据类型都可以使用HashCode方法,转换为int类型
问题:hashCode相同的对象,equals一定相同吗?
false,哈希冲突时,不同的key值对应同一个结果。
equals相同的对象hashCode相同吗?
key相同—>hashCode相同
4.冲突-解决
4.1 线性探测–闭散列
开放定位法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中还有空位置,可以把key存放在冲突位置的下一个空位置去。
如何找到这个空位置:线性探测,再哈希,二次探测,对冲突的key取一个不同的模。
4.2 开散列
冲突时,冲突的位置变为链表。
开散列方案:数组–>数组+链表的方式
当发生哈希碰撞时,就将对应的冲突位置的元素转换为链表,之后的查询和删除操作都是针对这个单链表来处理。
5.负载因子
5.1 定义
负载因子= 元素个数/数组长度
5.2负载因子与哈希冲突
负载因子越大,发生哈希冲突的概率越大。
数组长度就会偏小,节省空间。
负载因子越小,发生哈希冲突的概率越小。
但是数组长度大,浪费空间。
5.3解决冲突
当元素个数 / 负载因子 >= 数组长度,冲突严重,进行扩容解决冲突。
负载因子就是在空间和时间上求平衡。