目录
1.2,查找的性能衡量指标-平均查找长度(Average Search Length,ASL)
1,查找算法概述
1.1,查找算法的分类
- 静态查找和动态查找:
- 静态或者动态都是针对查找表而言的。动态表指查找表中有删除和插入操作的表。
- 无序查找和有序查找:
- 无序查找:被查找数列有序无序均可;
- 有序查找:被查找数列必须为有序数列。
1.2,查找的性能衡量指标-平均查找长度(Average Search Length,ASL)
需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。
- 对于含有n个数据元素的查找表,查找成功的平均查找长度为:
ASL = Pi*Ci
的和。- Pi:查找表中第i个数据元素的概率。
- Ci:找到第i个数据元素时已经比较过的次数。
1.3,查找性能
- 顺序查找,时间复杂度O(N)
- 分块查找,时间复杂度O(logN+N/m);
- 二分查找,时间复杂度O(logN)
- Fibonacci查找,时间复杂度O(logN)
- 差值查找,时间复杂度O(log(logN))
- 哈希查找,时间复杂度O(1)
2,查找算法实现
2.1,顺序查找
属于有序查找,顺序查找适合于存储结构为顺序存储或链接存储的线性表,元素可以没有顺序(也就是无序)。
2.1.1,顺序查找复杂度分析
- 查找成功时的平均查找长度为:
- (假设每个数据元素的概率相等)
ASL = 1/n(1+2+3+…+n) = (n+1)/2 ;
- (假设每个数据元素的概率相等)
- 当查找不成功时,需要n+1次比较,时间复杂度为O(n);
- 顺序查找的时间复杂度为O(n)。
2.1.2,代码实现
/**
* 线性查找算法
* @param arr 待查找序列
* @param value 需要查找的值
*/
public static int Search(int []arr,int value){
for(int i=0;i<arr.length;i++){
if(arr[i] == value){
return i;
}
}
return -1;
}
- 测试代码
public class SearchTest {
public static void main(String[] args) {
int []arr={45,32,7,54,90,67};
int index=Search(arr,54);
System.out.println(index);
}
}
2.2,二分查找算法
也称为是折半查找,属于有序查找算法。用给定值k先与中间结点的关键字比较,中间结点把线形表分成两个子表,若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
2.2.1,复杂度分析
- 最坏情况下,关键词比较次数为log2(n+1),且期望时间复杂度为O(log2n);对于一个有1024个元素的数组,在最坏的情况下,二分查找法只需要比较log2n + 1= 11次,而在最坏的情况下线性查找要比较1023次。
- 二分查找算法对应的查找序列是有序查找序列,所以在查找的时候会生成一个查找树,查找成功的平均路径长度就是每一层节点的数目乘以每一个节点查找成功的路径长度除以总的节点数,但是查找不成功的平均查找路径长度是对每一个叶节点添加外节点,计算外节点的查找路径总长度除以(n+1)就是查找不成功的平均路径长度。
2.2.2,二分查找代码实现
- 递归实现
/**
* 二分查找算法, 前提是数组序列已经有序
* @param arr 待查找数组
* @param left
* @param right
* @param value 待查找的值
* @return 如果找到就返回索引,否则就返回-1
*/
public static int Search(int []arr,int left,int right,int value){
if(left > right)
return -1;
int mid=(left+right)/2;
if(arr[mid] > value){
return Search(arr,left,mid-1,value);
}else if(arr[mid] < value){
return Search(arr,mid+1,right,value);
}else {
return mid;
}
// return -1;
}
- 非递归实现
public static int searchPlus(int [] arr,int left,int right,int value){
while (left<right){
int mid=(left+right)/2;
if(arr[mid]>value){
right=mid;
}else if(arr[mid]<value){
left=mid+1;
}else if(arr[mid] == value){
return mid;
}
}
// 如果查不到就返回-1
return -1;
}
2.2.3,算法改进
如果一个序列中查找的那个元素有多个,全部输出其索引
/**
* 二分查找算法改进,如果一个序列中待查找的元素有多个的情况
* 法改进,如果一个序列中有多个数值相同,如何找到全部的索引
* @param arr 带查找数组
* @param left 数组左边界
* @param right 数组右边界
* @param value 待查找的值
* @return 返回所有查到元素的下标
*/
public static List<Integer> BinarySearchPlus(int []arr, int left, int right, int value){
ArrayList<Integer> arrayList=new ArrayList<Integer>();
if(left > right)//返回一个空的arraylist
return new ArrayList<Integer>();
int mid=(left+right)/2;
if(arr[mid] > value){
return BinarySearchPlus(arr,left,mid-1,value);
}else if(arr[mid] < value){
return BinarySearchPlus(arr,mid+1,right,value);
}else {
// 前向查找
int temp=mid-1;
while (true){
if((temp<0)||(arr[temp]!=value)){
break;
}
arrayList.add(temp);
temp-=1;
}
arrayList.add(mid);
// 后向查找
temp=mid+1;
while (true){
if((temp>arr.length-1)||(arr[temp]!=value)){
break;
}
arrayList.add(temp);
temp+=1;
}
return arrayList;
}
}
- 测试代码
public class BinarySearch {
public static void main(String[] args) {
int []arr={45,52,75,547,980,980,980,980,6758};
ArrayList arrayList=(ArrayList)BinarySearchPlus(arr,0,arr.length-1,980);
System.out.println(arrayList);
}
2.3,插值查找算法
- 插值查找算法类似于二分查找,不同的是插值查找每次从自适应 mid 处开始查找。
- 将折半查找中的求 mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right.,key 就是前面我们讲的 findVal。
int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;/*插值索引*/
int mid = left + (right – left) * (findVal – arr[left]) / (arr[right] – arr[left])
- 插值算法案例演示:
- 插值算法注意事项
-
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
-
关键字分布不均匀的情况下,该方法不一定比折半查找要好
-
2.3.1,代码实现
/**
* 插值查找算法
* @param arr 待查找数组
* @param left 数组左边索引
* @param right 数组右边索引
* @param value 待查找的值
*/
public static int InsertValueSearch(int [] arr,int left,int right,int value){
// 后面的两个条件防止在求mid的时候发生越界
if((left>right)||(arr[left]>value)||(arr[right]<value))
{
return -1;
}
int mid=left+(right-left)*((value-arr[left])/(arr[right]-arr[left]));
// 找到中间的值
int midVal=arr[mid];
if(midVal<value){
// 向左边递归
return InsertValueSearch(arr,mid+1,right,value);
}else if(midVal> value){
// 向左边递归
return InsertValueSearch(arr,left,mid-1,value);
}else{
// 查找到该数值
return mid;
}
// return -1;
}
2.4,斐波那契(黄金分割法)查找算法
2.4.1,斐波那锲查找算法原理
- 黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位 数字的近似值是 0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神 奇的数字,会带来意向不大的效果。
- 斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的两个相邻数 的比例,无限接近 黄金分割值0.618
- 斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid)的位置,mid 不再是中间或插值得到,而是位 于黄金分割点附近,即 mid=low+F(k-1)-1(F 代表斐波那契数列)
- 对 F(k-1)-1 的理解:
-
由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到 (F[k]-1)=(F[k-1]-1)+(F[k-2]-1)+1 。该式说明: 只要顺序表的长度为 F[k]-1,则可以将该表分成长度为 F[k-1]-1 和 F[k-2]-1 的两段,即如上图所示。从而中间位置为 mid=low+F(k-1)-1。
-
类似的,每一子段也可以用相同的方式分割。
-
但顺序表长度 n 不一定刚好等于 F[k]-1,所以需要将原来的顺序表长度 n 增加至 F[k]-1。这里的 k 值只要能使得 F[k]-1 恰好大于或等于 n 即可,由以下代码得到,顺序表长度增加后,新增的位置(从 n+1 到 F[k]-1 位置),都赋为 n 位置的值即可。
-
while(n>fib(k)-1)
k++;
2.4.2,斐波那锲查找算法的实现
/**
* 获取斐波那锲数列
* @return 返回数列
*/
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 arr
* @param value
* @return
*/
public static int FibonacciSearch(int []arr,int value){
// 因为mid=low+F(k-1)-1,所以需要先创建一个斐波那锲数列
int low=0;
int high=arr.length-1;
int k=0;//标示斐波那锲分割数值的下标
int mid=0;
int []f=fib();
// 获取斐波那锲数值的下标,只有当high小于等于f[k]-1的时候才找到下标
while(high > f[k]-1){
k++;
}
// 因为f[k]的值可能大于我们arr数组的长度,所以我们使用Arrays工具类赋值一个新的
// 数组,并且指向arr数组
int []temp= Arrays.copyOf(arr,f[k]);
// 实际上需要使用arr数组的最后元素对temp数组最后进行填充,因为arr数组长度
// 可能小于f[k]里面的值
for(int i=high+1;i<f[k];i++)
{
// 使用最后元素进行填充
temp[i]=arr[high];
}
// 使用循环来找到我们的key
while (low<=high){
mid=low+f[k-1]-1;
// 现在可以和mid下标对应的值进行比较
if(value<temp[mid]){
// 说明需要向数组的左边查找
high=mid-1;
k--;
// 为什么是k--
// 1,全部元素=前面元素+后面元素
// 2,f[k]=f[k-1]+f[k-2]
// 因为前面有k-1个数值,所以可以继续拆分
// f[k-1]=f[k-2]+f[k-3],也就是在f[k-1]前面继续查找
// 所以说下次循环,mid=f[k-1-1]-1
}else if(value > temp[mid]){
low=mid+1;
k-=2;
// 为什么是k-2呢?
// 1,全部元素=前面元素+后面元素
// 因为后面我们有f[k-2]个元素
// f[k-1]=f[k-3]+f[k-4]
// 即我们在f[k-2]前面可以继续进行查找
// 也就是在下次循环,我们的mid=f[k-1-2]-1
}else {
// 需要确定返回的是哪一个下标
if(mid<=high){
return mid;
}else {
return high;
}
}
}
return -1;
}
2.5,测试代码
public class FibonacciSearchDemo {
public static int maxSize=20;
public static void main(String[] args) {
int arr[]={1,65,76,87,89,999};
int index=FibonacciSearch(arr,999);
System.out.println(index);
}
}
- 结果展示