査找表:是由同一类型的数据元素(或记录)构成的集合。
关键字:是数据元素中某个数据项的值,又称为键值,用它可以标识一个数据元素。也可以标识一个记录的某个数据项(字段),我们称为关键码。
查找,就是根据给定的某个值,在査找表中碥定一个其关键字等于给定值的数据元素(或记录)。
数组:
链表:
查找:
插入:
线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。
public class BinarySearch {
public static int binart_search(int[] arr,int key){
int index = -1,low = 0,high = arr.length-1,mid = 0;
while (low<=high)
{
mid = (low+high)/2; //中间值
if(arr[mid]>key) //比中间的小,修改high
high = mid-1;
else if(arr[mid]
适合关键字极端,表长较小。
根据要査找的关键字key与査找表中最大最小记录的关键字比较后的査找方法,
原来:mid = low+ (high-low) * 1/2
改进:mid = low+ (high-low) * ( key-a[low] ) / ( a [high]-a [low]);
适合关键字分布均匀,表长较大。
采用最接近查找长度和查找个数的斐波那契数值来确定拆分点。
定义:索引是为了加快査找速度而设计的一种数据结构。索引就是把一个关键字与它对应的记录相关联的过程。
组成:一个索引由若干个索引项构成,每个索引项至少应包含关键字和其对应的记录在存储器中的位置等信息。
索引按照结构可以分为线性索引、树形索引和多级索引。所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。
用一张索引表,将数据集中的每个记录对应一个索引项,索引项按照关键码有序排列。
使用空间换时间,索引项和数据集的记录个数相同,适合比较小的数据集。
对数据集进行分块,使其分块有序。
然后再用一张索引表对每一块建立一个索引项,从而减少索引项的个数。
根据属性(或字段、次关键码)的值来査找记录。
查找速度快,但是记录号不定长
查找:
//获取值
public V get(K key) {
return get(root, key);
}
private V get(TreeNode node, K key) {
if (node == null)
return null;
if (key.compareTo(node.getKey()) > 0)
return get(node.getRight(), key);
else if (key.compareTo(node.getKey()) < 0)
return get(node.getLeft(), key);
else
return node.getValue();
}
插入:
//插入或更新值
public void put(K key, V value) {
put(root, key, value);
}
private TreeNode put(TreeNode node, K key, V value) {
if (node == null)
return new TreeNode<>(key, value,1);
else if (key.compareTo(node.getKey()) > 0)
node.setRight( put(node.getRight(), key, value));
else if (key.compareTo(node.getKey()) < 0)
node.setLeft( put(node.getLeft(), key, value));
else
node.setValue(value);
node.setSize(size(node.getLeft())+size(node.getRight())+1); //维护结点数量
return node;
}
删除
//获取最小值,也就是最左边的树
private TreeNode min(TreeNode node){
if (node == null)
return null;
if (node.getLeft() == null)
return node;
return min(node.getLeft());
}
//删除最小值
private TreeNode delMin(TreeNode node){
if(node == null)
return null;
if(node.getLeft() == null) //如果没有左子树,则当前结点为最小值,删除当前结点,用比顶点大的值代替
return node.getRight();
node.setLeft(delMin(node.getLeft())); //如果有左子树,则需要删除最左边的结点,用左子树的第二个大的结点代替
node.setSize(size(node.getLeft())+size(node.getLeft())+1); //重新更新结点个数
return node;
}
//删除结点
public void delete(K key){
delete(root,key);
}
private TreeNode delete(TreeNode node,K key){
if(node == null)
return null;
if (node.getKey().compareTo(key) > 0)
node.setLeft(delete(node.getLeft(),key));
else if (node.getKey().compareTo(key) < 0)
node.setRight(delete(node.getRight(),key));
else {
//用一个孩子顶替
if (node.getLeft() == null)
return node.getRight();
else if (node.getRight() == null)
return node.getLeft();
//找出孩子中较大的顶替,也就是右孩子中的最左结点,把原来的左子树作为左子树,右孩子的剩余结点作为右子树
else
{
TreeNode temp = node;
node = min(temp.getRight());
node.setRight(delMin(temp.getRight()));
node.setLeft(temp.getLeft());
}
}
node.setSize(node.getLeft().getSize()+node.getRight().getSize()+1);
return node;
}
2-3树的每一个结点都具有两个孩子(2结点)或三个孩子(3结点)。并且2-3树中所有的叶子都在同一层次上。
在大小为N的2-3树中,查找和插入的次数小于等于lgN次
左斜的红色的链接连接2结点A,黑色的链接指向普通的2结点B。AB就是一个3结点
在构建二叉排序树的过程中,每当插入一个结点时,先检査是否因插入而破坏了树的平衡性。若是,则找出最小不平衡子树。
在保持二叉排序树特性的前提下,调整最小不平衡子树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
(1)旋转:主要是把中间结点从一个结点的孩子转为另一个结点的孩子
//左旋转
private RedBlackNode rotateLeft(RedBlackNode node){
//转换根节点
RedBlackNode temp = node.getRight();
node.setRight(temp.getLeft());
temp.setLeft(node);
//维护链接颜色
temp.setColor(node.isColor());
node.setColor(RedBlackNode.RED);
//维护结点
temp.setSize(node.getSize());
node.setSize(size(node.getLeft())+size(node.getRight())+1);
return temp;
}
//右旋转
private RedBlackNode rotateRight(RedBlackNode node){
//转换根节点
RedBlackNode temp = node.getLeft();
node.setLeft(temp.getRight());
temp.setRight(node);
//维护链接颜色
temp.setColor(node.isColor());
node.setColor(RedBlackNode.RED);
//维护结点
temp.setSize(node.getSize());
node.setSize(size(node.getLeft())+size(node.getRight())+1);
return temp;
}
(2)颜色转化:当一个结点同时具有两个红色链接并且该结点是中间值,可以直接转化为黑色链接
//转换颜色,把两个孩子变为黑色链接,同时自己变为红色链接
private void changeColor(RedBlackNode node){
node.setColor(RedBlackNode.RED);
RedBlackNode temp = node.getLeft();
temp.setColor(RedBlackNode.BLACK);
temp = node.getRight();
temp.setColor(RedBlackNode.BLACK);
}
(3):插入
//插入,初始化根节点颜色
public void put(K key,V value){
root = put(key,value,root);
root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode put(K key,V value,RedBlackNode node){
//创建新结点
if(node == null)
return new RedBlackNode<>(key,value,1,RedBlackNode.RED);
//查找结点
if(key.compareTo(node.getKey()) < 0)
node.setLeft(put(key,value,node.getLeft()));
else if(key.compareTo(node.getKey()) > 0)
node.setRight(put(key,value,node.getRight()));
else
node.setValue(value);
//平衡树
if(isRed(node.getRight()) && !isRed(node.getLeft()))
node = rotateLeft(node);
if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft()))
node = rotateRight(node);
if(isRed(node.getRight()) && isRed(node.getLeft()))
changeColor(node);
node.setSize(size(node.getLeft()) + size(node.getRight()) + 1);
return node;
}
(1)转换:主要是把子节点加上红色链接升级,父节点加上黑色链接降级
//升级结点为3结点
private RedBlackNode moveRedLeft(RedBlackNode node){
if(node == null)
return null;
if(!isRed(node) || isRed(node.getLeft()) || isRed(node.getLeft().getLeft()))
return null;
//父节点与孩子结合成为4节点
changeColor(node);
//如果父节点的右孩子是3节点,则可以借键给左孩子
if (node.getRight() != null && isRed(node.getRight().getLeft()))
{
//父节点向右孩子借键,也就是把右孩子中比较小的结点移入父节点
node.setRight(rotateRight(node.getRight()));//右旋转,使得小节点作为父节点。再移入父节点
//左孩子向父节点借键,也就是把父节点移入左孩子
node = rotateLeft(node);
}
return node;
}
//升级结点为3结点
private RedBlackNode moveRedRight(RedBlackNode node){
if(node == null)
return null;
if(!isRed(node) || isRed(node.getRight()) || isRed(node.getRight().getLeft()))
return null;
//父节点与孩子结合成为4节点
changeColor(node);
//如果父节点的左孩子是3结点,可以借键给右孩子
//右孩子向父节点借键,也就是把父节点移入右孩子
if (isRed(node.getLeft().getLeft()))
node = rotateRight(node); //不用旋转,因为右边肯定是大节点
return node;
}
(2)平衡:删除结点之后调整树
//平衡树
private RedBlackNode balance(RedBlackNode node) {
//右斜调整
if (isRed(node.getRight()))
node = rotateLeft(node);
//连续两条左斜链接
if (isRed(node.getLeft()) && isRed(node.getLeft().getLeft()))
node = rotateRight(node);
//4结点调整
if (isRed(node.getLeft()) && isRed(node.getRight()))
changeColor(node);
node.setSize(size(node.getLeft()) + size(node.getRight()) + 1);
return node;
}
(3):删除
//删除最小值
public void delMin(){
if(isEmpty())
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delMin(root);
//再初始化根节点的链接
if (!isEmpty()) root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode delMin(RedBlackNode node){
if(node.getLeft() == null) //如果没有左子树,则当前结点为最小值,直接删除
return null;
//当前结点不是3结点,结点的左孩子也不是3结点,先升级
if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft()))
node = moveRedLeft(node);
//递归删除
node.setLeft(delMin(node.getLeft()));
//重新调整树
return balance(node);
}
//删除最大值
public void delMax(){
if(isEmpty())
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delMax(root);
//再初始化根节点的链接
if (!isEmpty()) root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode delMax(RedBlackNode node){
//如果左孩子结点为红结点,先将所有的红链接右斜,转换为右孩子结点为红结点
if(isRed(node.getLeft()))
node = rotateRight(node);
if(node.getRight() == null) //如果没有右子树,则当前结点为最小值,直接删除
return null;
//再把右孩子的红链接左斜
if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft()))
node = moveRedRight(node);
//递归删除
node.setRight(delMax(node.getRight()));
//重新调整树
return balance(node);
}
删除:
//删除
public void delete(K key){
if (!contains(key))
return;
//先将根节点升级为3结点
if (!isRed(root.getLeft()) && !isRed(root.getRight()))
root.setColor(RedBlackNode.RED);
root = delete(root,key);
if(!isEmpty())
root.setColor(RedBlackNode.BLACK);
}
private RedBlackNode delete(RedBlackNode node,K key){
//关键字在左子树,先把左结点由上至下升级
if (node.getKey().compareTo(key) >= 0){
if (!isRed(node.getLeft()) && !isRed(node.getLeft().getLeft()))
node = moveRedLeft(node);
node.setLeft(delete(node.getLeft(),key));
}
//关键字在右子树,先把右结点由上至下升级
else
{
//升级前先右斜
if (isRed(node.getLeft()))
node = rotateRight(node);
//找到了,并且在叶子结点可以直接删除,在向下查找的过程中,已经保证了结点不可能是2-结点
if (node.getKey().compareTo(key) == 0 && (node.getRight() == null))
return null;
//把右结点由上至下升级
if (!isRed(node.getRight()) && !isRed(node.getRight().getLeft()))
node = moveRedRight(node);
//找到了,并且不在叶子节点,用后面的结点代替
if (node.getKey().compareTo(key) == 0) {
RedBlackNode right = min(node.getRight());
node.setKey(right.getKey());
node.setValue(right.getValue());
node.setRight(delMax(node.getRight()));
}
//没找到,继续在右子树找
else
node.setRight(delete(node.getRight(),key));
}
//平衡树
return balance(node);
}
在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f (key)。查找时,根据这个确定的对应关系找到给定值key的映射 f(key), 若査找集合中存在这个记录,则必定在f(key)的位置上。
对应关系 f 称为散列函数,又称为哈希函数。
记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。
关键字对应的记录存储位置我们称为散列地址。
平均查找长度L=散列的次数/散列的个数
取关键字的某个线性函数值为散列地址,f ( key ) =a * key+b ( a、b 为常数)
抽取关键字的一部分来计算散列存储位置
对关键字进行平方,再取中间的几位
将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
对关键字取模,f ( key) = key mod p ( p<=m )
若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
public int hash(int prime,K target){
return (target.hashCode() & 0x7fffffff) % prime;
}
public int hash(K target){
return (target.hashCode() & 0x7fffffff) % 31;
}
//整数
public static int hashInteger(int prime,int num){
return num % prime;
}
//字符串
public static int hashString(int R,int prime,String target){
int hash = 0;
for (int i = 0; i < target.length(); i++) {
hash = (R * hash + target.charAt(i)) % prime;
}
return hash;
}
//浮点数
public static int hashDouble(int prime,double target) throws Exception {
String binary = AllToBinary(target);
return hashString(31,31,binary);
}
//二进制转换
public static String IntegerToBinary(int target){
StringBuffer buffer = new StringBuffer();
while (target != 0)
{
buffer.append(target % 2);
target = target / 2;
}
return buffer.reverse().toString();
}
public static String DoubleToBinary(double target)throws Exception {
return DoubleToBinary(target,8);
}
public static String DoubleToBinary(double target,int count)throws Exception{
StringBuffer buffer = new StringBuffer();
if (count > 32 || count < 0)
throw new Exception("Error Bits!");
while (count >= 0)
{
target *= 2;
if (target >= 1)
{
target -= 1;
buffer.append(1);
}
else
buffer.append(0);
count--;
}
return buffer.toString();
}
public static String AllToBinary(double target)throws Exception{
int inte = (int) target;
double doub = target - inte;
StringBuffer buffer = new StringBuffer();
buffer.append(IntegerToBinary(inte));
buffer.append(".");
buffer.append(DoubleToBinary(doub));
return buffer.toString();
}
选择一个随机数,取关键字的随机函数值为它的散列地址,f (key)=random (key)
将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子
表,在散列表中只存储所有同义词子表的头指针。
public V get(K key){
if (isEmpty())
return null;
int index = hash(key);
V value = (V) table[index].get(key);
return value;
}
一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
特点:
//查找键值对
public V get(K key){
if (isEmpty())
return null;
int index = hash(key);
while (keys[index] != null) {
if (keys[index].compareTo(key) == 0) //命中
return values[index];
index = (index + 1) % keys.length; //线性勘测法
}
return null; //未命中
}
毎当发生散列地址冲突时,就换一个散列函数计算
在査找时,对给定值通过散列函败计算出散列地址后,先与基本表的相应位置进
行比对,如果相等,则査找成功;如果不相等,则到溢出表进行顺序査找。