二叉搜索树意思是:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树。
二叉树的一个插入:
static class Node{
public int val;
public Node left;
public Node right;
public Node(int val) {
this.val = val;
}
}
public Node root =null;
public void insert (int key){
Node node=new Node(key);
if(root == null) {
root = node;
return;
}
Node cur = root;
Node parent=null;
while (cur != null) {
if (cur.val==key){
return;
}
if (cur.val>key){
parent=cur;
cur=cur.right;
}else {
parent=cur;
cur=cur.left;
}
//退出循环现在我们所代表的是已经找到了插入数值的父节点
//判断父节点与key的关系
if (parent.val<key){
parent.right=node;
}else {
parent.left=node;
}
}
}
二叉树的查询
public Node search(int key){
Node cur=root;
while (cur!=null){
if (cur.val==key){
return cur;
}else if (cur.val<key){
cur=cur.right;
}else {
cur=cur.left;
}
//循环结束表示自己是没有找到的
}return null;
}
删除操作:
代码描述
public void remove(int key){
Node cur=root;
Node parent=null;
while (cur!=null){
if (cur.val==key){
//我们进行删除操作
removeNode(parent,cur);
}else if (cur.val<key){
parent=cur;
cur=cur.right;
}else {
parent=cur;
cur=cur.left;
}
}
}
/*
* cur代表需要删除的节点
* parent代表的是删除节点的父亲节点
* */
public void removeNode(Node parent,Node cur){
if (cur.left==null){
if (cur==root){
root=cur.right;
}else if (cur==parent.left){
parent.left=cur.right;
}else {
parent.right=cur.right;
}
}else if (cur.right==null){
if (cur==root){
root=cur.left;
}else if (cur==parent.left){
parent.left=cur.left;
}else {
parent.right=cur.left;
}
}else {
//目前我们面临的就是左右子树都有数据
//我们需要使用替代法进行删除,两种方法1.是在我们要删除节点的左子树找最大值
//2.在右子树找最小值替代掉cur
Node targetParent=cur;
Node target=cur.right;
//我们在记录parent和寻找替代cur的值
while (target.left!=null){
targetParent=target;
target=target.left;
}
//进行替换
cur.val=target.val;
//替换完成我们需要进行删除掉target的节点
//前提条件我们是在右子树找到最小的值表示的是 代表的意思就是左边都是空
//需要判断一下它现在是属于parent的left还是right
if (target==targetParent.left){
targetParent.left=target.right;
}else {
targetParent.right=target.right;
}
}
}
补充测试代码:
public class Test {
public static void main(String[] args) {
BinarySearchTreee binarySearchTreee=new
BinarySearchTreee();
int []array={10,7,8,2,11,15,9};
for (int i = 0; i <array.length ; i++) {
binarySearchTreee.insert(array[i]);
}
//根据前序和中序确定一颗二叉树
binarySearchTreee.prevOrder(binarySearchTreee.root);
System.out.println();
binarySearchTreee.inOrder(binarySearchTreee.root);
System.out.println();
try {
BinarySearchTreee.Node ret = binarySearchTreee.search(10);
System.out.println(ret.val);
} catch (NullPointerException e) {
e.printStackTrace();
System.out.println("空指针异常");
}
binarySearchTreee.remove(10);
binarySearchTreee.prevOrder(binarySearchTreee.root);
System.out.println();
binarySearchTreee.inOrder(binarySearchTreee.root);
}
}
哈希表
1.内部是一个数组
2.关键字经过变换(hash函数)得到int类型的值
3.int类型的值变成一个合法的下标
4.把关键字放到这个下标的位置
哈希冲突:
不同关键字通过相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
冲突-避免-哈希函数的设计
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。 哈希函数设计原则:
哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1
之间
哈希函数计算出来的地址能均匀分布在整个空间中
哈希函数应该比较简单
常见的哈希函数:
- 直接定制法–(常用)
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B 优点:简单、均匀 缺点:需要事先知道关
键字的分布情况 使用场景:适合查找比较小且连续的情况 - 除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:
Hash(key) = key% p(p<=m),将关键码转换成哈希地址
解决冲突/避免冲突:
负载因子:有效的数据个数/数组的长度
7%10=0.7 8%10=0.8
扩容 我们的负载因子和冲突率是正比的
所以减小负载因子的比率就会抑制冲突率 减小负载因子的方法是增大数组长度
闭散列:
1 . 线性探测:线性探测会一个一个的往后找,找到第一个为空的地方进行放入
2 .二次探测 : hash(key)+i^2 i代表的是次数
区别:线性探测会尽可能的最后把所有冲突的元素集中放在一起,二次探测相比较来说更加分散一点。
闭散列的缺陷是空间利用率比较低。
开散列/哈希桶也叫做链地址法。
冲突严重时的解决办法:
哈希桶其实可以看作将大集合的搜索问题转化为小集合的搜索问题了,那如果冲突严重,就意味
着小集合的搜索性能其实也时不佳的,这个时候我们就可以将这个所谓的小集合搜索问题继续进行转化,例如:
1 . 每个桶的背后是另一个哈希表
2 . 每个桶的背后是一棵搜索树
性能分析
虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的,
也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是O(1) 。
实现put;
public class HashBuck {
static class Node{
//定义我们的单链表
public int key;
public int value;
public Node next;
public Node(int key,int val){
this.key=key;
this.value=val;
}
}
//现在定义的是我们数组的信息
public Node []array;
public int usedSize;
public HashBuck(){
this.array=new Node[10];
this.usedSize=0;
}
//put
public void put(int key,int val){
int index=key%array.length;
for (Node cur=array[index]; cur!=null; cur=cur.next) {
//我们现在是进行遍历数组中为index 下标位置开始的单链表
if (cur.key==key){
cur.value=val;
return;
}
}
//头插法进行插入头结点
Node node=new Node(key, val);
node.next=array[index];
array[index]=node;
this.usedSize++;
if (loadFactor()>=0.75){
resize();
}
}
//扩容在桶中一开始的默认容量是16 二倍方式扩容
public void resize(){
Node []newArray=new Node[array.length*2];
//我们先进行遍历我们的数组
for (int i = 0; i <array.length ; i++) {
Node curNext;//我们需要在遍历的过程中每次去记录我们的上一个节点。
//然后在cur!=null下遍历我们的单链表
for (Node cur=array[i]; cur!=null ; cur=curNext) {
curNext=cur.next;//功能在于我们可以进行遍历单链表否则我们会丢失前驱
//现在的index就是我们将要放入新数组中的位置
int index=cur.key%newArray.length;
//头插法插入
cur.next=newArray[index];
newArray[index]=cur;
}
}
array=newArray;//newArray为临时的数组,
// 否则我们无法完成扩容Node []newArray=new Node[array.length*2];
}
//计算负载因子
private double loadFactor(){
return this.usedSize*1.0/array.length;
}
}
测试代码
public class TestDemo1 {
public static void main(String[] args) {
HashBuck hashBuck= new HashBuck();
hashBuck.put(1, 66);
hashBuck.put(11, 88);
hashBuck.put(4, 400);
hashBuck.put(26, 900);
hashBuck.put(21, 8888);
int c=hashBuck.get(21);
System.out.println(c);
//如果在扩容后所有的数据都会重新进行一个反射
}
}
我们现在在哈希表中写自定义类型元素 一定要写hash.Code equlas方法
import java.util.Objects;
//Map<K-person,V-姓名>
class Person{
String id;//学号
public Person(String id){
this.id =id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(id, person.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
class HashBuck2<K,V>{
static class Node<K,V>{
K key;
V val;
Node<K,V> next;
public Node(K key,V val){
this.key=key;
this.val=val;
}
}
public Node<K,V>[]array;
public int usedSize=0;
public HashBuck2(){
this.array=(Node<K,V>[])new Node[8];//强转 new 泛型会报警告
}
//push的方法
public void push(K key, V val){
int hash=key.hashCode();//我们先去获取一个int的哈希值,
// 这样我们才可以在数组中定位它的index
int index =hash%array.length;
for (Node<K,V> cur =array[index];cur!=null ;cur=cur.next) {
if (cur.key.equals(key)){
cur.val=val;
return;
}
}
Node<K,V> node=new Node<>(key,val);
node.next=array[index];
array[index]=node;
this.usedSize++;
//后面就是扩容了判断负载因子的值
}
public V get(K key){
int hash=key.hashCode();
int index=hash%array.length;
for (Node<K,V> cur=array[index]; cur != null ; cur=cur.next) {
if (cur.key.equals(key)){
return cur.val;
}
}
return null;
}
}
public class TestDemo {
//一定要重写方法
public static void main(String[] args) {
Person person1=new Person("11101");
Person person2=new Person("11101");
HashBuck2<Person,String> hashBuck2=new
HashBuck2<>();
hashBuck2.push(person1,"张三");
//通过引用我们去获取id相同即name相同
String ret=hashBuck2.get(person2);
System.out.println(ret);
}
}