二叉搜索樹意思是:
若它的左子樹不爲空,則左子樹上所有節點的值都小於根節點的值
若它的右子樹不爲空,則右子樹上所有節點的值都大於根節點的值
它的左右子樹也分別爲二叉搜索樹。
二叉樹的一個插入:
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);
}
}