- 查找概论
查找表(Search Table) :是由同一类型的数据元素(或记录)构成的集合。
关键字(Key):是数据元素中某个数据项的值。
主关键字(Primary Key):若关键字可以唯一的表示一个记录,则称关键字为主关键字。
次关键字(Secondary Key) :对于那些可以识别多个数据元素(或记录),我们称为次关键字。
静态查找表(Static Search Table) :只作查找操作的查找表。
动态查找表(Dynamic Search Table) : 在查找过程中同事插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。
查找(Searching)就是根据给定的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录)。
2. 顺序表查找
顺序查找(Sequential Search) 又叫
线性查找,是最基本的查找技术,它的查找过程:从表中第一个(或最后一个)记录开始,逐个进行记录的关键字和给定值比较,若某个记录关键字和给定值相等,则查找成功,找到所查的记录。反之则不成功。
2.1 顺序表查找算法
//顺序表查找
public static int sequenceSearch(char[] chars, int length, char key) {
for (int i = 0; i < length; i++) {
if (key == chars[i])
return i;
}
return -1;
}
2.2 顺序表查找优化
(2.1)中每次循环都需要对数组是否越界进行判断,通过设置哨兵的技巧,可优化代码。
public static int sequenceSearch2(char[] chars,int n,char key){
chars[n] = key; //将chars[n]设为关键字值,即哨兵
int i = 0;
while(chars[i] != key){
i++;
}
return i;//i = n 则代表查找失败
}
3. 有序表查找
3.1 折半查找
折半查找(Binary Search)技术,又称为二分查找。
前提:线性表中的记录必须是关键码有序,线性表必须采取顺序存储。
基本思想:在有序表中,取中间记录作为比较对象,若与关键词相等,则成功;若给定值小于中间记录关键词,则在中间记录的左半区查找;同理大于则在右边去查找。不断重复直到查找成功,或所有查找区域无记录,查找失败为止
public static int binaySearch(int[] a, int n, int key) {
int low = 1, high = n, mid;
while (low <= high) {
mid = (low + high) / 2;
if (key < a[mid])
high = mid - 1;
else if (key > a[mid])
low = mid + 1;
else {
System.out.println("found key at index :" + mid);
return mid;
}
}
System.out.println("not found");
return 0;
}
3.2 插值查找(Interpolation Search)
插值算法是在折半算法上的改进,核心在于插值的计算公式 mid = low + ((high - low)* (key - a[low])/(a[high] - a[low])),将这个计算公式替换原折半查找公式中的mid =(low + high)/2。
对于关键词分布均匀的查找表而言,插值查找的效率会高于折半查找。
3.3 斐波那契查找(Fibonacci Search)
斐波那契算法也是基于折半查找的改进,代码如下:public static int fibonacciSearch(int[] a, int n, int key) {
int low = 0, high = n - 1, mid, k = 0;
int F[] = getFibonacciArray(20);//获取斐波那契数列
while (n > (F[k] - 1))
k++;//计算n位于斐波那契数列中的位置,记为k
int[] t = new int[F[k]];
System.arraycopy(a, 0, t, 0, a.length);
for (int i = n; i < F[k] - 1; i++)
t[i] = t[n];//F【k】比t【n】大,所以补全t【n】
while (low <= high) {
mid = low + F[k - 1] - 1;//斐波那契核心算法
if (key < t[mid]) {
high = mid - 1;
k = k - 1;
} else if (key > t[mid]) {
low = mid + 1;
k = k - 2;
} else {
if (mid <= n) {
return mid;
} else {
return n;
}
}
}
return 0;
}
4. 线性索引查找
索引就是把一个关键字与它对应的记录相关联的过程。
所谓线性索引就是将索引项集合组织为线性结构,也称为
索引表
4.1 稠密索引
稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项。
对于稠密索引这个索引表来说,索引项一定是按照关键码
有序的排列。
4.2 分块索引
分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件: 块内无序,块间有序。
分块索引的索引项结构分三个数据项:
最大关键码,存储了
块中的个数,用于指向块
首数据元素的指针。
4.2 倒排索引
倒排索引的索引项通用结构是: 次关键码, 记录号表。5. 二叉排序树(Binary Sort Tree)
二叉排序树,又称为二叉查找树。它或者是一颗空树,或者是具有下列性质的二叉树。- 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值。
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。
- 它的左、右子树也分别为二叉排序树。
5.1 查找
查找核心代码:
public void search(Node node,int key){
if (node == null){
System.out.println("没找到");
}
else if ( key == node.data){
System.out.println("找到了");
}
else if ( key < node.getData()){
search(node.getLeftNode(),key);
}
else if (key > node.getData()){
search(node.getRightNode(),key);
}
}
5.1 插入
先执行查找,如果已存要直接拼接至父节小则左子树。5.2 删除
删除需要考虑3种情况:- 删除节点为叶子节点。
- 删除节点仅有左或右子树。
- 删除节点均有左右子树。
前两种情况简单,只需要直接拼接至父节点即可,第三种情况需要获取左子树最靠右的结点,或右子树最靠左的结点重新成为根节点。
二叉排序树实现代码: https://github.com/xu509/Java-practise/blob/master/src/SearchAndSort/bean/Tree.java
6 平衡二叉树(AVL树)
定义:平衡二叉树是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1。
平衡因子:我们将二叉树上节点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor)。
最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树。
平衡树的需实现左转,右转,左平衡,右平衡操作。而实现平衡二叉树的过程也是在不断的平衡后获取的。
7 多路查找树(B树)
多路查找树,其每一个结点的孩子数可以多于两个,且每一个结点处可以存储多个元素。
7.1 2-3树
2-3树是这样的一颗多路查找树:其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)。
一个
2 结点包含一个元素和两个孩子。与二叉排序树相似,左子树包含的元素小于该元素,右子树包含的元素大于该元素。2 结点要么没有孩子,要有就有两个,不能只有一个孩子。
一个
3 结点包含
一小一大两个元素和三个孩子(或没有孩子),一个 3 结点要么没有孩子,要么具有 3 个孩子。
2-3树中所有的叶子都在同一层次上。2 - 3树在插入和删除的时候需要考虑多种情况
7.2 B树
B树是一种平衡的多路查找树,2-3树,2-3-4树是B树的特例。结点最大的孩子数目称为B树的阶(order)。2-3树既是3阶B树。
7.3 B+树
在原有的B树结构基础上,加上了新的元素组织方式,这就是 B+ 树。
出现分支结点的元素会被当做它们在该分支结点位置的中序后继者中再次列出。每一个叶子结点都会保存一个指向后一叶子结点的指针。
8 散列表查找
散列技术是在记录存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。f称为散列函数,又称为哈希函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续空间称为散列表或哈希表。散列技术最适合的求解问题是查找与给定值相等的记录。
8.1 散列函数的构造方法
好的散列函数的原则: 1、计算简单 2、散列地址分布均匀
8.1.1 直接定值法
取关键字的某个线性函数值为散列地址,即 f ( key ) = a * key + b ,a,b为常数。这样的散列函数有点少简单、均匀,也不会产生冲突。但问题是这需要事先知道关键字的分布情况,适合查找表较小且连续的情况。
8.1.2 数字分析法
通过抽取数字的方法产生散列函数,适合关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。
8.1.3 平方取中法
假设关键字为1234,平方后则是1522756,再抽取中间的3位就是227,用做散列地址。适合于不知道关键字的分布,而位数又不是很大的情况。
8.1.4 折叠法
假设关键字为9876543210,散列表表长为三位,我们将它分成四组,987|654|321|0,然后叠加求和1962,再取后三位962为散列地址。适用情况与平方取中法一样。
8.1.5 除留余数法
此方法为最常用的构造散列函数方法。对于散列表长为m的散列公式为:f (key) = key mod p (p<=m)。
8.1.5 随机数法
正如名字一样,取随机值取得随机数。
8.2 处理散列冲突的方法
8.2.1开放地址法
开放地址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
8.2.2 再散列函数法
事先准备多个散列函数,如果产生冲突,则更好散列函数,直到不产生聚集。
8.2.3 链地址法
散列表存储同义词子表的头指针,该指针指向下一个同义词。
8.2.4 公共溢出区法
为所有冲突的关键字建立了一个公共溢出区来存放。
8.2 散列表查找实现
java实现在github中。