1.二叉搜索树
1.1概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者遵循以下特征:
1.如果它的左子树不为空,则左子树上节点的所有值都要小于根节点. 2.如果它的左子树不为空,则左子树上节点的所有值都要小于根节点. 3.左右子树也同样是二叉搜索树,二叉搜索树中的节点不能有相同的值. |
1.2 二叉树的查找
二叉搜索树查找方式:如果我们要查找6这个数字,那么我们从根节点开始,遇到根节点值为8,比我们要查找的值大,根据二叉搜索树的性质,左子树的值都比根节点小,所以我们往左边查找,左子树根节点是5,6>5,所以我们要往右搜寻,6就被找到了.
public TreeNode searchBST(TreeNode root, int val) {
if(root==null) return null;
TreeNode curr=root;
while(curr!=null){
if(curr.val==val){
return curr;
}else if(curr.val<val){
curr=curr.right;
}else{
curr=curr.left;
}
}
//循环外面说明没有找到
return null;
}
1.3 二叉搜索树插入操作
根节点为空,则待插入元素作为根节点.
根节点非空,我们需要根据val和节点的val大小来判断是插入根节点的左子树还是右子树.
public class Solution {
public TreeNode insertToBST (TreeNode root, int val) {
// write code here
if(root==null) return new TreeNode(val);
TreeNode curr=root;
TreeNode parent=null;
while(curr!=null){
if(curr.val==val){
break;
}else if(curr.val<val){
parent=curr;
curr=curr.right;
}else{
parent=curr;
curr=curr.left;
}
}
if(curr==parent.left){
parent.left=new TreeNode(val);
}else{
parent.right=new TreeNode(val);
}
return root;
}
}
1.4 二叉搜索树节点删除
二叉搜索树的删除可以分为以下三种情况:
1.要删除的节点的左右子节点都为空,直接删除该节点即可 2.要删除节点的左右子节点有一个为空,当前节点没有左子树:删除节点,让右子树的节点顶替其位置,当前节点没有右子树: 删除该节点,其左子树的节点顶替其位置 3.要删除节点的左右子节点都不为空 |
public void remove(int key) {
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
if(cur.key == key) {
removeNode(parent,cur);
return;
}else if(cur.key < key) {
parent = cur;
cur = cur.right;
}else {
parent = cur;
cur = cur.left;
}
}
}
private void removeNode(TreeNode parent,TreeNode 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 {
TreeNode target = cur.right;
TreeNode targetParent = cur;
while (target.left != null) {
targetParent = target;
target = target.left;
}
cur.key = target.key;
if(target == targetParent.left) {
targetParent.left = target.right;
}else {
targetParent.right = target.right;
}
}
}
}
二.TreeMap
Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复。
Map 的常用方法
1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap. 2. Map中存放键值对的Key是唯一的,value是可以重复的 3. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复) 4. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复) 5. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入 |
三.Set
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key
1. Set是继承自Collection的一个接口类. 2. Set中只存储了key,并且要求key一定要唯一. 3. Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的. 4. Set最大的功能就是对集合中的元素进行去重. 5. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入. 6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入. 7. Set中不能插入null的key. |
3.1 哈希表
散列表(Hash table,也叫哈希表)是根据关键码值(Key value)而直接进行访问的数据结构,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表.
数据集合{1,7,6,4,5,9}
哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小.
3.2 冲突
不同关键字通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞,把具有不同关键码而具有相同哈希地址的数据元素称为"同义词".
如下图,16和6使用哈希函数得出相同的值,就冲突了
3.3 避免冲突
由于哈希表底层数组的容量往往是小于实际要存储的关键字的数量的,这就导致冲突的发生是必然的,但我们可以尽量的降低冲突率.
引起哈希冲突的一个原因可能是,哈希函数设计不够合理,所以有几种常见的设计哈希函数的方法:
1.直接定址法:取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B ,适合查找比较小且连续的情况 2.除留余数法:设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数Hash(key) = key% p(p<=m),将关键码转换成哈希地址. 3.闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去. |
3.4 解决冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
散列表的载荷因子定义为:填入表中的元素个数 /散列表的长度
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去.
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中.
hash(key) = key % capacity;
// key-value 模型
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;
public HashBucket() {
// write code here
array=new Node[DEFAULT_SIZE];
}
private int size; // 当前的数据个数
private static final double LOAD_FACTOR = 0.75;
private static final int DEFAULT_SIZE = 8;//默认桶的大小
public void put(int key, int value) {
// write code here
//获取下标
int index=key%array.length;
Node cur=array[index];
//找位置,如果有相同的就覆盖
while (cur!=null){
if(cur.key==key){
cur.value=value;
return;
}
cur=cur.next;
}
//进行插入(头插)
Node head=new Node(key,value);
head.next=array[index];
array[index]=head;
size++;
//负载因子大于0.75时扩容
if(loadFactor()>=0.75){
resize();
}
}
private void resize() {
// write code here
Node[]newArray=new Node[2*array.length];
//对原array中的每一个数据进行重新哈希
for(int i=0;i<array.length;i++){
Node cur=array[i];
while(cur!=null){
Node curNext=cur.next; //保存后续结点
int newIndex=cur.key%newArray.length;
cur.next=newArray[newIndex];
newArray[newIndex]=cur;
cur=curNext;
}
}
array=newArray;//重新赋值
}
private double loadFactor() {
return size * 1.0 / array.length;
}
public int get(int key) {
// write code here
int index=key%array.length;
Node cur=array[index];
//找位置
while (cur!=null){
if(cur.key==key){
return cur.value;
}
cur=cur.next;
}
return -1;
}
}