目录:
1. 顺序表查找(算法&优化)
2. 有序表查找
2.1 折半查找
2.2 插值查找
2.3 斐波那契查找
3. 线性索引查找
3.1 稠密索引
3.2 分块索引
3.3 倒排索引
4. 二叉排序树
4.1 平衡二叉树(AVL树)
4.2 多路查找树(B树)
4.3 二叉排序树
5. 散列表查找(哈希表)
查找概述:查找就是根据给定的某个值,在查找表中确定一个其关键字=给定值的数据元素(或记录)。根据操作方式可分为:静态查找表(只作查找操作的查找表)和动态查找表(查找过程中,插入或删除某个数据元素)。
静态查找表有:顺序查找算法,折半查找等
动态查找表:二叉排序树。
1.顺序查找
又叫线性查找,最基本的查找技术。查找过程: 从表中第一个(或最后一个)记录开始,逐个进行记录的关键字与给定值比较,若相等则查找成功;若表中没有查找记录,则查找不成功。
private static void Ordersearch(int[] arr,int num) {
for (int i = 0; i < arr.length; i++) {
if (arr[i]==num) {
System.out.println(arr[i]);
return;
}
}
System.out.println("not found ");
衡量一个查找算法效率优劣的标准: 查找过程中对关键字的平均比较次数,也叫平均查找长度(ASL)。
算法最好情况:1次就找到给定值。最坏情况:查找n次。平均查找次数:(n+1)/2。时间复杂度:o(n)
优化方案:上面的方式每次循环都要判断i是否越界,可以通过设置哨兵解决这个问题。
private static void Ordersearch(int[] arr,int num) {
int count=arr.length-1;
int min=arr[0];
arr[0]=num;
while(arr[count]!=num){
count--;
}
if (min==arr[count]) {
System.out.println(arr[count]);
}else {
System.out.println("not found");
}
}
2. 有序表查找
2.1 折半查找
又称为二分查找,是最经典的有序表查找,它的前提是线性表中的记录必须是有序的(通常从小到大排列),线性表采用顺序存储的方式。其基本思路是:将关键字key与中间记录((low+high)/2)进行比较;若相等,则查找成功;若关键字小于中间记录,则说明关键字可能在下半区;若大于,则说明关键字可能在上半区;不断重复上述过程,直到查找成功或失败。
public class JavaTest1 {
public static void main(String[] args) {
int[] num = { 1, 2, 3, 4, 5, 6 };//必须有序
int index = Binary_Search(num, 5);
System.out.print(index);
}
/* num:有序表(由小到大排列)
* key:要查找的关键字
* return:还回查找到关键字的下标,没有找到则还回-1
*/
private static int Binary_Search(int[] num, int key) {
int low, high, mid;
low = 0;
high = num.length - 1;
while (low <= high) {
mid = (low + high) / 2;
if (key < num[mid])
high = mid - 1;
else if (key > num[mid])
low = mid + 1;
else// 如果等于则直接还回下标值
return mid;
}
return -1;
}
}
2.2 插值查找
对二分法查找进行改进,将要查找的关键字key与查找表中的最大最小值记录进行比较后,再确定查找的范围。在二分法查找中,是以中间记录作为查找分区的,即将表一分为二,分为上下两个查找分区:
插值查找采用插值公式的方法,来确定查找分区。可简单这样理解,比如有100个数其值在0~1000范围之间从小到大排序,你要查找关键字为5的位置下标,若采用二分法,则大概在500的地方往下查找,但采用插值的方法,可以通过插值计算出5这个关键字应该在靠近0的地方,因此查找时从50往下开始查找,从而提高效率:
public class JavaTest1 {
public static void main(String[] args) {
int[] num = { 1, 2, 3, 4, 5, 6 };// 必须有序
int index = Insert_Search(num, 5);
System.out.print(index);
}
/*
* num:有序表(由小到大排列) key:要查找的关键字 return:还回查找到关键字的下标,没有找到则还回-1
*/
private static int Insert_Search(int[] num, int key) {
int low, high, mid;
low = 0;
high = num.length - 1;
while (low <= high) {
// mid = (low + high) / 2;//二分查找
mid = low + (high - low) * (key - num[low])/ (num[high] - num[low]); // 插值查找
if (key < num[mid])
high = mid - 1;
else if (key > num[mid])
low = mid + 1;
else
// 如果等于则直接还回下标值
return mid;
}
return -1;
}
}
2.3 斐波纳契数列
又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……在数学上,斐波纳契数列以如下被以递归的方法定义:F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)。
算法步骤:
1)确定K值。数据索引n的值位于斐波那契数列中的位置。若F(k-1)< n < F(k),则K值确定。
2)比较所要查找的数key与a[mid]的值。
a. 若key=a[mid] ,查找成功;
b. 若key>a[mid],low=mid+1,k=k-2;
c. 若key<a[mid],high=mid-1,k=k-1;
时间复杂度为O(logn),但平均性能优于折半查找,如果是最坏情况,查找效率低于折半查找。
另外,斐波那契查找只包含简单的加减法,在海量数据的查找过程中,查找效率好。
public class MainClass {
public static long fibonacci(long number) {
if ((number == 0) || (number == 1))
return number;
else
return fibonacci(number - 1) + fibonacci(number - 2);
}
public static void main(String[] args) {
for (int counter = 0; counter <= 10; counter++){
System.out.printf("Fibonacci of %d is: %d\n",
counter, fibonacci(counter));
}
}
}
3. 线性索引查找
索引:一本书的内容通常是无序的,如果没有目录,要想快速找到某一张,则需要将一本书按顺序翻过去,这是非常耗时的。计算机的索引跟图书目录类似,它记录了关键词和数据的位置。索引按照结构可以分为线性索引、树形索引、多级索引。实际上他们的命名是根据索引数据结构来区分的,线性索引是指索引项集合组织为线性结构。常用的线性索引有:稠密索引、分块索引、倒排序索引。
3.1 稠密查找
稠密索引是指每一条数据都有一个索引和其对应,这个索引记录了关键词和指针,并且该索引是排序的。稠密索引的查找非常快,但遗憾的是,因为为每一条记录都创建了索引,所以维护这个索引需要大量的内容空间以及性能。
3.2 分块索引
分块索引类似于图书馆的书架,每一个书架放了某个编号范围的图书。分块索引有以下几个特点:
· 块内无序
· 块间有序
· 每个块拥有一个最大关键码
· 存储了块的记录个数
· 拥有指向块首数据的指针
3.3 倒排索引
假如有N篇文章:
A.i am a good boy.
B.i love eat beef.
…….
假设这两篇文章非常长,如果我们要搜索good这个单词在哪篇文章里,正常情况下,我们要遍历所有的文章,并且找出结果,这样做的话,搜索的范围就会非常大。
倒排索引给了我们优化解决的方法,它先创建一个排好序的关键词表,关键词表对应的是文章列表地址。然后解析每一篇文章,如果含有某一个关键词,就将该文章的地址加入到关键词表对应的搜索结果里去。通过这种方式,我们只需要先找到关键词,然后再找到关键词对应的文章列表,再遍历这个列表就能找到这篇文章。如果我们只查找一次,肯定不划算的,通常情况下,这种技术用在查找频繁的搜索引擎中。
4. 二叉排序树(Binary Sort Tree)
顺序存储的插入和删除可以通过在表末端插入和删除元素同最后一个元素互换来保持高效率,但是无序造成查找的效率很低。
如果查找的数据表是有序线性表,并且是顺序存储的,查找可以折半、插值、斐波那契等查找算法来实现,但是因为有序在插入和删除操作上,就需要耗费大量时间。
二叉排序树可以使得插入和删除的效率不错,又可以比较高效率地实现查找。
二叉排序树(Binary Sort Tree),又称为二叉查找树。它或者是一棵空树,或者是具有下列性质的二叉树
1.若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
2.若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
3.它的左、右子树也分别为二叉排序树。
二叉排序树的查找有递归和非递归两种方法:
public class BinarySortTree {
/**
* 根结点
*/
private TreeNode rootNode;
/**
* 获取rootNode
* @return
*/
public TreeNode getRootNode() {
return this.rootNode;
}
/**
* 创建二叉排序树 的第一种方法(通过insertBST创建)
* @param array
* @return
*/
public void createBinarySortTree1(int[] array) {
for (int i = 0; i <array.length; i++) {
if (!insertBST(array[i])) {
System.out.println("已存在" + array[i] + ",不再插入二叉排序树");
}
}
}
/**
* 创建二叉排序树 的第二种方法
* @param array
* @return
*/
public void createBinarySortTree2(int[] array) {
for (int i = 0; i < array.length; i++) {
if (i == 0) {
rootNode = new TreeNode(array[i]);
continue;
}
TreeNode node = rootNode;
while (true) {
if (array[i] < node.getData()) {
if (node.getLchild() == null) {
node.setLchild(new TreeNode(array[i]));
break;
} else {
node = node.getLchild();
}
} else if (array[i] == node.getData()) {
System.out.println("已存在" + array[i] + ",不再插入二叉排序树");
break;
} else {
if (node.getRchild() == null) {
node.setRchild(new TreeNode(array[i]));
break;
} else {
node = node.getRchild();
}
}
}
}
}
/**
* 中序遍历
* @param node
*/
public void inOrderTraverse(TreeNode node) {
if (node != null) {
inOrderTraverse(node.getLchild());
System.out.print(node.getData() + " ");
inOrderTraverse(node.getRchild());
}
}
/**
* 二叉排序树的查找 (递归实现)
*
* 查找可以有两种实现方法,一种是递归,一种是while 循环。
* 在插入和删除操作中也首先需要查询操作,这时使用while 循环比较好,可以知道要查询结点的双亲结点
* @param node
* @param key
* @return 返回
*/
public boolean searchBST(TreeNode node, int key) {
if (node == null) {
return false;
} else if (key == node.getData()) {
return true;
} else if (key < node.getData()) {
return searchBST(node.getLchild(), key);
} else {
return searchBST(node.getRchild(), key);
}
}
/**
* 二叉排序树的插入
* @param node
* @param key
*/
public boolean insertBST(int key) {
//相当于 search 用非递归实现 -----begin
TreeNode node = rootNode;
//查找 最后访问的 node
TreeNode searchLastNode = null;
while (node != null) {
searchLastNode = node;
if (key < node.getData()) {
node = node.getLchild();
} else if (key > node.getData()) {
node = node.getRchild();
} else {
return false;
}
}
// search ----end
TreeNode newNode = new TreeNode(key);
if (rootNode == null) {
rootNode = newNode;
} else if (key < searchLastNode.getData()) {
searchLastNode.setLchild(newNode);
} else {
searchLastNode.setRchild(newNode);
}
return true;
}
public boolean deleteBST(int key) {
//相当于 search 用非递归实现 -----begin
TreeNode node = rootNode;
//存储 其双亲结点,如果是根节点,则双亲结点为null
TreeNode parentNode = null;
while (node != null) {
if (key < node.getData()) {
parentNode = node;
node = node.getLchild();
} else if (key > node.getData()) {
parentNode = node;
node = node.getRchild();
} else {
//相等时已找到
break ;
}
}
// search ----end
//没有找到要删除的node
if (node == null) {
return false;
}
// 右子树为空 ,重接左子树
if (node.getRchild() == null) {
if (parentNode != null) {
if (parentNode.getLchild() == node)
parentNode.setLchild(node.getLchild());
else
parentNode.setRchild(node.getLchild());
} else {
rootNode = node.getLchild();
}
// 左子树为空,重接右子树
} else if (node.getLchild() == null) {
if (parentNode != null) {
if (parentNode.getLchild() == node)
parentNode.setLchild(node.getRchild());
else
parentNode.setRchild(node.getRchild());
} else {
rootNode = node.getRchild();
}
// 左右子树都不为空 ,可以将 要删除结点 按中序遍历的前驱或后继 替代 要删除结点的位置,此处取前驱
} else {
//取前驱结点 ,先转左,然后一直到最右
TreeNode replaceNode = node.getLchild();
TreeNode replaceParentNode = node;
if (replaceNode.getRchild() != null) {
replaceParentNode = replaceNode;
replaceNode = replaceNode.getRchild();
}
//获取到前驱结点极其双亲结点
//如果前驱结点的双亲结点是 要删除的结点
if (replaceParentNode == node)
replaceParentNode.setLchild(replaceNode.getLchild());
else
replaceParentNode.setRchild(replaceNode.getLchild());
node.setData(replaceNode.getData());
}
return true;
}
public static void main(String[] args) {
/* int[] array = new int[15];
System.out.print("随机数:");
for (int i = 0; i < 15; i++) {
array[i] = (int) (Math.random() * 100);
System.out.print(array[i] + " ");
*/ }
int[] array = {4, 10, 1, 13, 3, 15, 9, 11, 8, 5, 14, 2, 6, 7, 12};
BinarySortTree binarySortTree = new BinarySortTree();
//创建二叉排序树
binarySortTree.createBinarySortTree1(array);
//中序遍历 即是 从小到大的排序
System.out.print("\n中序遍历结果:");
binarySortTree.inOrderTraverse(binarySortTree.getRootNode());
//二叉排序树的查找
boolean searchResult = binarySortTree.searchBST(binarySortTree.getRootNode(), 2);
if (searchResult)
System.out.println("\n查到");
else
System.out.println("\n查不到");
//二叉排序树的删除
binarySortTree.deleteBST(2);
binarySortTree.deleteBST(1);
binarySortTree.deleteBST(3);
binarySortTree.deleteBST(4);
binarySortTree.deleteBST(10);
binarySortTree.inOrderTraverse(binarySortTree.getRootNode());
}
}
/**
* 二叉树结点
*
*
*/
class TreeNode {
private int data;
private TreeNode lchild;
private TreeNode rchild;
public TreeNode(int data) {
this.data = data;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public TreeNode getLchild() {
return lchild;
}
public void setLchild(TreeNode lchild) {
this.lchild = lchild;
}
public TreeNode getRchild() {
return rchild;
}
public void setRchild(TreeNode rchild) {
this.rchild = rchild;
}
@Override
public String toString() {
return "TreeNode [data=" + data + ", lchild=" + lchild + ", rchild=" + rchild + "]";
}
}
代码所示二叉树为:
中序遍历结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
查找:5 6 7 8 9 11 12 13 14 15