目录
一、全局看关系
继承自Iterable的TreeSet和HashSet可以进行继承和迭代,TreeMap和HashMap方法则没有。
TreeSet和TreeMap用到了红黑树,时间复杂度为O(logN);
HashSet和HashMap用到了哈希桶,时间复杂度为O(1)。
二、搜索树
概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
查找
1.先判断根节点,与根节点相等则找到
2.不相等时判断左右子树,比根节点小找左子树,比根节点大找右子树,由此重复操作,直到找到为止。
public boolean search(int value) {
//没有值
if(root==null){
return false;
}
//遍历树
TreeNode current=root;
while(current!=null){
//根节点为所找的值
if(current.value==value){
return true;
}
//如果比根节点值大,向右子树移动
if (current.value<value){
current=current.left;
}
//如果比根节点值小,向左子树移动
if (current.value>value){
current=current.right;
}
}
//遍历所有值没有找到返回false
return false;
}
插入
1.如果树为空,证明数中没有元素,直接插入
2.树不为空时,先判断与根节点大小,大向右子树移动,小向左子树移动,直到找到合适的位置插入,相同则插入失败。
public boolean insert(int value) {
//定义插入的数值为新的节点
TreeNode node=new TreeNode(value);
//根节点为空直接插入
if(root==null){
root=node;
return true;
}
//遍历节点
TreeNode current=root;
//用来保存根节点
TreeNode prev=null;
while (current!=null){
//如果根节点的值和插入的值相等,则不能插入
if (current.value==value){
return false;
}
prev=current;
//如果比根节点的值大,向右子树移动
if(current.value<value){
current=current.right;
}
//如果比根节点的值小,向左子树移动
if (current.value>value){
current=current.left;
}
}
if (value<prev.value){
node=prev.left;
}else {
node=prev.right;
}
return true;
}
删除
设待删除结点为 cur, 待删除结点的双亲结点为 parent
1. cur.left == null
1. cur 是 root,则 root = cur.right
2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.right
3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.right
2. cur.right == null
1. cur 是 root,则 root = cur.left
2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.left
3. cur.left != null && cur.right != null
1. 需要使用替换法进行删除,即在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被
删除节点中,再来处理该结点的删除问题
public boolean remove(int value) {
if (root == null) {
return false;
}
// 找到要删除元素
TreeNode current = root;
TreeNode parent = null;
while (current != null) {
if (current.value == value) {
removeNode(parent, current);
return true;
}
// 记录父节点
parent = current;
if (value < current.value) {
current = current.left;
} else {
current = current.right;
}
}
return false;
}
private void removeNode(TreeNode parent, TreeNode current) {
if (current.left == null) {
// 当左孩子节点为空时进入
if (current == root) {
// 把当前要删除节点的右孩子赋给root
root = current.right;
} else if (current == parent.left) {
// 当前节点是父节点的左孩子节点时
parent.left = current.right;
} else {
// 当前节点是父节点的右孩子节点时
parent.right = current.right;
}
} else if (current.right == null) {
// 当前节点是根节点时
if (current == root) {
root = current.left;
} else if (current == parent.left) {
// 当前节点是父点的左孩子节点时
parent.left = current.left;
} else {
// 当前节点是父节点的右孩子节点时
parent.right = current.left;
}
} else {
// 定义用来遍历的几个变量
TreeNode target = current.right;
TreeNode parentTarget = current;
// 向左去找最小值
while (target.left != null) {
parentTarget = target;
target = target.left;
}
// 到达叶子节点时
current.value = target.value;
// 删除target节点
if (target == parentTarget.left) {
parentTarget.left = target.right;
} else {
parentTarget.right = target.right;
}
}
}
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为: log2N
三、Map
概念:
使用方式:
![](https://img-blog.csdnimg.cn/4c89094efc254e6386809bcb4fdcff08.png)
底层结构图解:
四、Set
Set与map最大的不同:
1.set继承自collection的接口类
2.set只存储key,而map存储key,value。
常见的方法:
底层结构图解
注:
五、哈希表
概念:
当向该结构中:
插入元素根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放搜索元素对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功该方式即为哈希 ( 散列 ) 方法, 哈希方法中使用的转换函数称为哈希 ( 散列 ) 函数,构造出来的结构称为哈希表 (Hash Table)( 或者称散列表 )
![](https://img-blog.csdnimg.cn/6b1dea5a1b6f43e79ef2547c29b4777e.png)
冲突
在运用上述方法进行查找位置时,难免会出现存储位置相同的情况,这就产生了冲突。
不同关键字通过相同哈 希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
由于我们哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致一
个问题, 冲突的发生是必然的 ,但我们能做的应该是尽量的 降低冲突率 。
由此,我们可以通过设计更合理的哈希函数来降低冲突率
用哈希函数降低冲突率:
负载因子调节(重点)
负载因子概念:
闭散列
开散列(重点)
概念:开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
实现哈希桶
public class HashBucket {
private static class Node {
private int key;
private int value;
Node next;
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
private Node[] array;
private int size; // 当前的数据个数
private static final double LOAD_FACTOR = 0.75;
public int put(int key, int value) {
int index = key % array.length;
// 在链表中查找 key 所在的结点
// 如果找到了,更新
// 所有结点都不是 key,插入一个新的结点
for (Node cur = array[index]; cur != null; cur = cur.next) {
if (key == cur.key) {
int oldValue = cur.value;
cur.value = value;
return oldValue;
}
}
Node node = new Node(key, value);
node.next = array[index];
array[index] = node;
size++;
if (loadFactor() >= LOAD_FACTOR) {
resize();
}
return -1;
}
private void resize() {
Node[] newArray = new Node[array.length*2];
for (int i = 0; i < array.length; i++) {
Node next;
for (Node cur = array[i]; cur != null; cur = next) {
next = cur.next;
int index = cur.key % newArray.length;
cur.next = newArray[index];
newArray[index] = cur;
}
}
array = newArray;
}
private double loadFactor() {
return size * 1.0 / array.length;
}
public HashBucket() {
array = new Node[8];
size = 0;
}
public int get(int key) {
int index = key % array.length;
Node head = array[index];
for (Node cur = head; cur != null; cur = cur.next) {
if (key == cur.key) {
return cur.value;
}
}
return -1;
}
}