折半查找
前提:数据有序。
思想:将key与数组中间值arr[middle]比较,若key>arr[middle],说明目标值在数组右边部分,继续在数组右半边查找,同理,key<arr[middle],继续在数组左半边查找。重复以上过程,直至key = arr[middle]。找到目标数据。
时间复杂度:O(logn)
/** * @return 关键字在数组中的位置 */ public static int b_search(int[] arr,int key){ int location = -1; int length = arr.length; int low = 0; int high = length - 1; int mid; //需要取=,否则最后一个数据找不到 while (low <= high){ mid = (low+high)/2; if (key < arr[mid]){ high = mid - 1; }else if (key > arr[mid]){ low = mid + 1; }else { location = mid; break; } } return location; }
插值查找
为什么一定要折半而不是折四分之一或者折更多?
eg. 1 在英文字典中查找“apple”,我们会从靠前的位置开始查找。查“zoo”会从靠后的位置查找。而不会从中间开始查起。
eg.2 在取值范围1 ~ 10000 之间 100 个元素从小到大均匀分布的数组中查找5, 我们自然会考虑从数组下标较小的开始查找。
所以插值查找与折半查找的不同处在于每次不是从中间切分,而是根据要查找的关键字key与查找表中的最大最小记录的关键字比较后,使middle的选择尽量靠近key。
折半查找中:middle = (low + high)/2;
插值查找:middle = low + [(key - arr[low])/arr[hign] - arr[low]]/(hign - low)
时间复杂度:O(logn),对数据较多且关键字分布较均匀的场景,插值查找的平均性能优于折半查找。
斐波拉契查找
斐波拉契数列:1,1,2,3,5,8,13,21,34,55,89 ...
利用斐波拉契数列F(n) = F(n-1) + F(n-2) (其中,n >=2)的性质来分割数组。即当前位置值 = 前两个数之和。2=1+1,3=2+1,5=3+2 ...
每次分割,数据总个数 = F(n),左边数据个数 = F(n-1),右边数据个数F(n-2)。n表示斐波拉数列的下标。
时间复杂度O(logn)
/** * @return 关键字在数组中的位置 */ public static int fibonacci_search(int[] arr,int key){ int[] f = {1,1,2,3,5,8,13,21,34,55,89};//斐波拉契数,实际应用中可以抽取一个函数,通过传参构建一个特定长度的斐波拉契数列 int location = -1,mid,k=0,length = arr.length,low = 0,high = length-1; //找到一个最小的斐波拉契数,使数组的长度<=该数。即若数组长度是4,该数是5,数组长度是8,该数是8 while (length > f[k]){ k++; } //当找到的斐波拉契数>数组长度,需要将多出的数据位填上数据(填数组中最大的数) int last_v = arr[length-1];//最后一个数据值 //将数组扩展到f[k]长度 int[] temp = Arrays.copyOf(arr, f[k]); //填充 for (int i = length; i < f[k];i++){ temp[i] = last_v; } //左边:0,f(k-1)-1;右边f(k-1),f[k]-1 while (low <= high){ mid = low + f[k-1] - 1;//减1是因为下标从0开始。 if (key < temp[mid]){ high = mid -1; k = k-1;//在左部分继续找 }else if (key > temp[mid]){ low = mid + 1; k = k-2;//在右部分继续找,下标mid+1~f(k)-1 }else { if (mid < length-1){ location = mid; }else { //mid>length-1, 说明找到的是补全的数 location = length-1; } break; } } return location; }
参考:《大话数据结构》