一、线性查找
1、算法说明
线性查找又称顺序查找,是一种最简单的查找方法。查找是对具有相同属性的数据元素(记录)的集合(数据对象)进行的,称之为表或文件,也称字典。对表的查找,若仅对表进行查找操作,而不能改变表中的数据元素,为静态查找;对表除了进行查找操作外,还可能对表进行插入或删除操作,则为动态查找。
2、算法思路
它的基本思想是从第一个记录开始,逐个比较记录的关键字,直到和给定的K值相等,则查找成功;若比较结果与文件中n个记录的关键字都不等,则查找失败。
3、算法的代码描述
public class SeqSearch {
public static void main(String[] args) {
int[] arr = { 5, 18, 10, 6, 1, 15, 7, 33};
int value = 10;
System.out.println(seqSearch(arr, value));
}
public static int seqSearch(int[] arr, int value) {
//直接遍历整个数组,找到相应的值,返回该数的下标即可
for (int i = 0; i < arr.length; i++) {
if(arr[i] == value) {
return i;
}
}
return -1;//找不到就返回-1
}
}
二、二分查找
二分査找法是另一种常见的对数值列表的査找方法。使用二分査找法的前提条件是数组中的元素必须已经排好序。假设数组已按升序排列。二分査找法首先将关键字与数组的中间元素进行比较。
考虑下面三种情况:
• 如果关键字小于中间元素, 只需要在数组的前一半元素中继续査找关键字。
• 如果关键字和中间元素相等, 则匹配成功, 査找结束。
• 如果关键字大于中间元素, 只需要在数组的后一半元素中继续査找关键字。
1、递归算法
(1)思路分析
①先确定一下中间元素的位置(即他的下标);
②再是要考虑上面的三种情况;
③找出递归的出口条件:找到就结束递归;没找到,返回-1。
(2)代码描述
public class BinarySearch {
public static void main(String[] args) {
int[] list = {2,12,15,45,53,86,98,156,450};
int key = 45;
int index = binarySearch(list, 0, list.length - 1, key);
System.out.println("该数的在数组中的位置为:" + index);
}
/**
*
* @param list 待查找的数组
* @param low 左边的索引值
* @param high 右边的索引值
* @param key 要查找的值
* @return 若找着了,则对应的下标;没找着,则返回-1
*/
public static int binarySearch(int[] list, int low, int high, int key) {
if(high < low) {
//在high < low的时候,说明递归了整个数组,但是没找着
return -1;
}
int mid = (high + low) / 2;
if(key < list[mid]) {//往mid的左边递归
return binarySearch(list, low, mid - 1, key);
} else if(key > list[mid]) {//往mid的右边递归
return binarySearch(list, mid + 1, high, key);
} else {
return mid;
}
}
}
2、非递归算法
(1)算法的分析说明
二分查找法的时间复杂度为O(log2n),也就是说他找到目标位置时最多只需要log2n步。在一个已经排序的数组中用二分査找法査找一个元素,即使是最坏的情况,也只需要 log2n+1次比较。对于一个有 1024 个元素的数组在最坏情况下,二分査找法只需要比较 11 次,而在最坏的情况下线性査找要比较 1023 次。
(2)代码描述
public class BinarySearch {
public static void main(String[] args) {
//给定一个数组和要查找的数
int[] list = {2,12,15,45,53,86,98,156,450};
int key = 45;
int index = binarySearch(list, key);
System.out.println("该数的在数组中的位置为:" + index);
}
/**
*
* @param list 待查找的数组
* @param key 将要查找的数
* @return 若找着了,则对应的下标;没找着,则返回-1
*/
public static int binarySearch(int[] list, int key) {
int low = 0;
int high = list.length - 1;
while(high >= low) {//说明要在该数组范围内查找数
int mid = (high + low) / 2;
if(key < list[mid]) {//往mid的左边查找
high = mid - 1;
} else if(key == list[mid]) {
return mid;
} else {//往mid的右边查找
low = mid + 1;
}
}
return -1;
}
}
(3)【返回值的改进】
当没有找到这个关键字时,low 就是一个插入点,这个位置将插入关键字以保持列表的有序性。一种更实用的方法是返回插入点减去1。这个方法必须返回一个负值,表明这个关键字不在该序列中。可以只返回 -low 吗?答案是:不可以。如果关键字小于 list[0], 那么low 就是 0, -0 也是 0。这就表明关键字匹配 list[0]。一个好的选择是,如果关键字不在该序列中,方法返回 -low-1。返 回 不 仅 表 明 关 键 字 不 在 序 列 中,而 且 还 给 出 了 关 键 字应该插人的地方。
三、斐波那契查找
1、算法介绍
斐波那契搜索(Fibonacci search) ,又称斐波那契查找,是区间中单峰函数的搜索技术。
斐波那契搜索就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为Fn,完成后进行斐波那契分割,即F[n]个元素分割为前半部分F[n-1]个元素,后半部分F[n-2]个元素,找出要查找的元素在那一部分并递归,直到找到。
2、算法思路说明
F(k)=F(k-1)+F(k-2) ==> F(k)-1=(F(k-1)-1)+(F(k-2)-1)+1
mid=low+F(k-1)-1
斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,及n=F(k)-1;开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种:
(1)相等,则mid位置的元素即为所求;
(2)>,则low=mid+1,k-=2;
说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))=Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找。
(3)<,则high=mid-1,k-=1。
说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归的应用斐波那契查找。
3、算法的代码描述
import java.util.Arrays;
public class FibonacciSearch {
public static int maxSize = 20;
public static void main(String[] args) {
int[] arr = { 9, 25, 34, 89, 1000, 1645};
int key = 34;
System.out.println(fibSearch(arr, key));
}
//用非递归的方法获取一个斐波那契数列
public static int[] fib() {
int[] f = new int[maxSize];
f[0] = 1;
f[1] = 1;
for (int i = 2; i < maxSize; i++) {
f[i] = f[i - 1] +f[i - 2];
}
return f;
}
//编写斐波纳挈查找法
/**
*
* @param a 数组
* @param key 待查找的关键值
* @return 若找到则返回对应下标,没找到,则返回-1
*/
public static int fibSearch(int[] a, int key) {
int low = 0;
int high = a.length - 1;
int k = 0;//表示斐波纳挈分割数值的下标
int mid = 0;//存放mid的值
int[] f = fib();//获取斐波那契数列
//获取到斐波那契而数列的下标
while(high > f[k] - 1) {
k ++;
}
//f[k]的值可能会大于数组a的长度,所以需要新定义一个数组,指向temp
//不足的部分系统自动用0填充
//temp={ 9, 25, 34, 89, 1000, 1645, 0, 0}
//实际上我们需要用原数组的最后一个数填充0的地方
//temp={ 9, 25, 34, 89, 1000, 1645, 0, 0} ==> temp={ 9, 25, 34, 89, 1000, 1645, 1645, 1645}
int[] temp = Arrays.copyOf(a,f[k]);
for (int i = high + 1; i < temp.length; i++) {
temp[i] = a[high];
}
//找我们的关键数
while(low <= high) {
mid = low + f[k - 1] - 1;
//全部的元素=前面的元素+后面的元素
//f[k]=f[k-1]+f[k-2]
//前面有f[k-1]个元素,继续拆分为f[k-1]=f[k-2]+f[k-3],即在f[k-1]的前面继续查找,k--
//后面有f[k-2]个元素,继续拆分为f[k-1]=f[k-3]+f[k-4],即在f[k-2]的前面继续查找,k=k-2
if(key < temp[mid]) {
high = mid - 1;
k--;
//即下次循环时mid=f[k-1-1]-1
} else if(key > temp[mid]) {
low = mid + 1;
k -= 2;
//即下次循环时mid=f[k-1-2]-1
} else {
//找到了,但是要确定是哪个下标
if(mid <= high) {
return mid;
} else {
return high;
}
}
}
return -1;
}
}
4、算法的复杂度分析
在最坏情况下,斐波那契查找的时间复杂度还是O(log2n),且其期望复杂度也为O(log2n),但是与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。