顺序查找
顺序查找也称为线形查找,属于无序查找算法。从 线形表的一端开始,顺序扫描,依次将扫描到的结点关键字与给定值 相比较,若相等则表示查找成功;若扫描结束仍没有找到关键字与查找值的结点,表示查找失败。其时间复杂度为O(n) 属于最简单的一种查找
优点:简单,对表的结构无任何要求
缺点 : 查找效率低,因此,当n较大时不宜采用顺序查找。
顺序查找适合于存储结构为顺序存储或链接存储的线性表。
public static int seqSearch(int[] arr,int searchValue){
for (int i = 0; i < arr.length; i++) {
if (arr[i] == searchValue) {
return i;
}
}
return -1;
}
分块查找
块查找又称索引顺序查找,它是顺序查找的一种改进方法。
将n个数据元素"按块有序"划分为m块(m ≤ n)。
每一块中的结点不必有序,但块与块之间必须"按块有序";
即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;
而第2块中任一元素又都必须小于第3块中的任一元素,……
主要步骤
- 1、先选取各块中的最大关键字构成一个索引表—索引表中每个元素含有各个块的最大关键字和各个块的第一个元素地址,索引表按关键字有序排列
- 2、查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;
- 3、在已确定的块中用顺序法进行查找
优点 适用动态变化的序列,方便插入和删除数据,
缺点:需要额外增加一个索引表的存储空间并对其排序
时间复杂度:O(log(m)+N/m)
public class BlockSearch {
public static void main(String[] args) {
int[] array = new int[] { 22, 12, 13, 8, 9, 41,44, 33, 42, 44, 38, 24, 45,48, 60, 44,74, 74, 49, 86, 53};
int blockNum=3;
Block [] blocks = new Block[blockNum];
int index=array.length/blockNum;
int arrNum = 0;
for (int i = 0,len=array.length/blockNum-1; i <blockNum; i++,len+=index) {
if (i==blockNum-1){
len=array.length-1;
}
int max=array[len];
for( int j=len;j>(len-index);j--){
if (max<array[j]){
max=array[j];
}
}
blocks[arrNum++]=new Block(len-index+1,len, max);
}
System.out.println(blockSearch(array,blocks,blockNum,74));
}
private static ArrayList<Integer> getBlockIndex( Block[] arr,int blockNum,int searchValue) {
ArrayList<Integer> list = new ArrayList<>();
int i=0;
while(i<blockNum ) {
if (arr[i].maxNum>=searchValue)
list.add(i );
i++;
}
return list;
}
public static List<Integer> blockSearch(int[] arr, Block[] blocks, int blockNum,int searchValue){
List<Integer> searchList = getBlockIndex(blocks,blockNum, searchValue);
if (searchList.size()==0) {
return new ArrayList<>();
}
List<Integer> list = new ArrayList<>();
for (Integer integer : searchList) {
int start = blocks[integer].startIndex;
int end = blocks[integer].endIndex;
for (int j = start; j <= end; j++) {
if (arr[j] == searchValue) {
list.add(j);
}
}
}
return list;
}
public static class Block {
int maxNum;// 块中的最大值
int startIndex;// 块在数组中的起始索引位置
int endIndex;// 块在数组中的结束索引位置
public Block(int startIndex, int endIndex, int maxNum) {
this.startIndex = startIndex;
this.endIndex = endIndex;
this.maxNum = maxNum;
}
@Override
public String toString() {
return "[" + startIndex + "~" + endIndex + "]" +
" maxNum=" + maxNum;
}
}
}
折半查找
基本思想: 有序查找算法。将有序表分成两部分,用给定值k先与中间结点的关键字比较, 若相等则查找成功;若不相等,再根据k与该中间结点关键字的比较结果确定下一步查找哪个子表,这样递归进行,直到查找到或查找结束发现表中没有这样的结点。
要求:数组需要有序
时间复杂度:O(logN)
步骤:
-
1 确定数组中间的下标middle=(left+right)>>1
-
2 searchValue>arr[middle] searchValue在middle位置的右边则需要从middle+1向右开始查找
-
3 searchValue< arr[middle] searchValue在middle位置的左边则需要从middle-1向左开始查找
-
4 searchValue== arr[middle] searchValue已经找到
折半查找实现:非递归
//折半查找非递归方式
public static ArrayList<Integer> halfSearch(int[] arr, int searchValue){
int left=0,right =arr.length-1;
ArrayList<Integer> list = new ArrayList<>();
while (left<=right){
int middle=(left+right)>>1;
if (arr[middle] == searchValue) {
list.add(middle);
}
if (arr[middle]>searchValue){
right=middle-1;
}
else {
left=middle+1;
}
}
return list;
}
折半查找实现: 递归
//折半查找递归方式
public static List<Integer> halfSearch1(int[] arr, int leftIndex, int rightIndex, int searchValue) {
int middle=(leftIndex + rightIndex)>>1;
if (leftIndex>rightIndex){//rightIndex<0||leftIndex>arr.length-1
return new ArrayList<>();
}
if (arr[middle]>searchValue) return halfSearch1(arr, leftIndex, middle-1, searchValue);
else if (arr[middle]<searchValue) return halfSearch1(arr, middle+1, rightIndex, searchValue);
else {
//searchValue==arr[middle],找到了但不一定找完全,因为递归满足条件就会返回,不会继续递归寻找
ArrayList<Integer> list = new ArrayList<>();
int lIndex = middle - 1;
while (lIndex > 0) {//向左扫描将剩余满足条件的元素索引加入集合
if (searchValue == arr[lIndex]) {
list.add(lIndex);
}
lIndex--;
}
list.add(middle);
int rIndex = middle + 1;
while (rIndex < arr.length) {//向右扫描将剩余满足条件的元素索引加入集合
if (searchValue == arr[rIndex]) {
list.add(rIndex);
}
rIndex++;
}
return list;
}
}
插值查找
插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找。
mid=(height+low)/2---->
low+(height-low)/2—>
low+((key-arr[low])/(arr[height]-arr[low]))(height-low) —>
low+(key-arr[low])(height-low)/(arr[height]-arr[low])
对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
关键字分布不均匀的情况下(跳跃性比较大),该方法不一定比折半查找要好
要求:数组需要有序
时间复杂度: O(log2(log2n))
public class InsertSearch {
public static void main(String[] args) {
int[] array = new int[] { 8, 9, 12, 13, 22, 24, 33, 38, 41, 42, 44, 44, 44, 45, 48, 49, 53, 60, 74, 74, 86 };
System.out.println(insertSearch(array, 44));
System.out.println(insertSearch1(array, 0, array.length-1,44));
}
public static List<Integer> insertSearch(int[] arr, int searchValue){
int left=0,right = arr.length-1;
List<Integer> list = new ArrayList<>();
while (left <=right) {
int middle=left+(searchValue-arr[left])*(right-left)/(arr[right]-arr[left]);
if (arr[middle] == searchValue) {
list.add(middle);
left=middle+1;
}
else if (arr[middle]>searchValue){
right=middle-1;
}
else {
left=middle+1;
}
}
return list;
}
public static List<Integer> insertSearch1(int[] arr,int left,int right, int searchValue){
//因为middle自适应需要防止越界及递归终止条件
if(searchValue<arr[0]||searchValue>arr[arr.length-1]||left>right){
return new ArrayList<>();
}
//自适应
int middle=left+(searchValue-arr[left])*(right-left)/(arr[right] - arr[left]);
if (arr[middle]>searchValue) {
return insertSearch1(arr, left, middle-1, searchValue);
}
else if (arr[middle]<searchValue) {
return insertSearch1(arr, middle+1, right, searchValue);
}
else {
List<Integer> list = new ArrayList<>();
int leftIndex=middle-1;
int rightIndex=middle+1;
while (leftIndex >=0 ) {
if (arr[leftIndex] == searchValue) {
list.add(leftIndex);
}
leftIndex--;
}
list.add(middle);
while (rightIndex <=right ) {
if (arr[rightIndex] == searchValue) {
list.add(rightIndex);
}
rightIndex++;
}
return list;
}
}
}
斐波那契查找
斐波那契查找是利用斐波那契数列的特性将数列映射到待查找数组索引中,查找的mid则是在黄金分割点附近
斐波那契查找同样是查找算法家族中的一员,它要求数据是有序的(升序或降序)。斐波那契查找采用和二分查找/插值查找相似的区间分割策略,都是通过不断的分割区间缩小搜索的范围。
在斐波那契数列中 ,从第三项开始,每一项都等于前两项之和:F(n)=F(n-1)+F(n-2)(n>=2)
其中n的取值是任意长度的,即对任意长度的数组都能找到对应的斐波那契数
要求:数组需要有序
时间复杂度: O(log2n)
解析
由斐波那契数列 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 。 数组长度为F(k) - 1;左区间长度为F(k - 1) - 1 右区间长度为F(k - 2) - 1; middle节点只有一个,长度为1
即F(k) - 1 = (F(k - 1) - 1)(左区间元素个数) + 1(middle元素个数) + (F(k - 2) - 1)(右区间元素个数)
mid=low+F(k-1)-1 的推导:
-
假定数组为[1 3 5 7 11 12]—对应斐波那契数列[0 1 1 2 3 5 8]
数组长度为6 没有合适的斐波那契数匹配,则扩容[1 3 5 7 11 12 12 12]至符合斐波那契数8 -
现在数组长度是8,对应斐波那契数F(k=5))=8即F(5),
F(5)=F(5-1)+F(5-2),
则F(5-1)=F(4)即为黄金分割点 映射到对应数组索引长度则为
F(5)-1=F(5-1)-1+F(k-2)-1+1(?因为数组从0开始所以需要稍微变形)
对应数组中的黄金分割点值为 F(5-1)-1=F(4)-1
则根据斐波那契规则会将数组索引分成[0–4]和[5-7]两部分
即分别为F(k-1)-1和F(k-2)-1两部分 -
我们对应的middle则为数组首索引+对应黄金分割点长度F(4)-1 即:middle=0+F(4)-1–>middle=low+F(5-1)-1---->middle=low+F(k-1)-1
斐波那契查找的整个过程可以分为:
- 1 构建斐波那契数列;
- 2 计算数组长度对应的斐波那契数列元素个数–在斐波那契数列找一个等于或略大于查找表中元素个数的数F[n]
- 3 对数组进行填充;----如果不等于则将原查找表扩展为长度为 F[n]](如果要补充元素,则补充重复最后一个元素,直到满足F[n]个元素)。
- 4 循环进行区间分割,查找中间值;
- 5 判断中间值和目标值的关系,确定更新策略;
中间值和目标值有三种大小关系,分别对应三种处理方式:
相等,则查找成功,返回中间位置即可;
中间值小于目标值,则说明目标值位于中间值到右边界之间(即右区间),右区间含有F(n-2)个元素,所以n应该更新为n=n-2;
中间值大于目标值,这说明目标值位于左边界和中间值之间(即左区间),左区间含有F(n-1)个元素,所以n应更新为n=n-1;
public class FibonacciSearch {
static int[] fibonacciArray;
static int fibonacciNumber = 0;
public static void main(String[] args) {
int[] array=new int[]{1, 2, 4, 6, 7, 9, 13,16, 17, 21 , 23, 25,27,27,27, 27,31, 45, 56, 58,60, 61};
System.out.println(fibonacciSearch(array, 61));
System.out.println(fibonacciSearch1(array, 61));
}
/*
* @param: [array]
* @return: int[]
* @author: Jerssy
* @data: 2021/3/24 14:15
* @description: 创建一个斐波那契数列并使数组与之匹配,返回匹配数组
*/
private static int[] createFibonacci(int[] array) {
//与数组匹配的斐波那契数列公式:F(k) - 1 = F(k - 1) - 1 + 1 + F(k - 2) - 1
fibonacciArray=new int[array.length];
fibonacciArray[0]=fibonacciArray[1]=1;
for (int i = 2; i < array.length; i++) {
fibonacciArray[i]=fibonacciArray[i-1]+fibonacciArray[i-2];
if (fibonacciArray[i]>=array.length) {//创建数的列长度只需满足数列里的某个值大于等于数组长度即可,这样数列的最后一个数就是匹配的数组长度的斐波那契数
fibonacciArray=Arrays.stream(fibonacciArray).takeWhile(e->e!=0).toArray();//删除数组为0的元素节省空间
break;
}
}
//匹配数组长度的斐波那契数
fibonacciNumber = fibonacciArray.length-1;
int[] copyArray;
if (fibonacciArray[fibonacciNumber]>array.length){//如果数列最后一个斐波那契数大于数组长度,说明没有合适的数列值等于数组长度,需要对数组进行填充
copyArray=Arrays.copyOf(array, fibonacciArray[fibonacciNumber]);
Arrays.fill(copyArray, array.length, copyArray.length,array[array.length - 1]);
}
else copyArray=array;//如果相等直接操作原数组
return copyArray;
}
/*
* @param: [arr,searchValue]
* @return: java.util.ArrayList<java.lang.Integer>
* @author: Jerssy
* @dateTime: 2021/3/24 14:16
* @description: 斐波那契非递归查找
*/
public static ArrayList<Integer> fibonacciSearch(int[] arr, int searchValue){
ArrayList<Integer> list = new ArrayList<>();
if (arr==null||arr.length == 0) return null;
if ( arr.length ==1) {
if(arr[0]==searchValue){
list.add(0);
}
return list;
}
//创建一个符和的斐波那契数列
int[] copyArray = createFibonacci(arr);
int left=0,right = arr.length-1;
while (left <= right) {
int middle=left+fibonacciArray[Math.max(fibonacciNumber-1,0)]-1;//黄金分割点F(k-1)-1
if (copyArray[middle]==searchValue) {
int leftIndex=left;
while ( leftIndex <= right) {//因为步长不是1,如果所查找的元素位于黄金分割点前面则会将当前符和条件的middle的前面元素给过滤掉.继续查找left- right的剩余是否满足
if( copyArray[leftIndex] == searchValue){
list.add(leftIndex);
}
leftIndex++;
}
break;
}
if (copyArray[middle] >searchValue) {
right=middle-1;
fibonacciNumber-=1;//在左半区间F(n-1),需要在f[k-1] 的前面进行查找 k -=1,则下一次middle=F(k-1-1)-1
}
else {
left = middle +1;
fibonacciNumber-=2;//在右半区间F(n-2),需要在f[k-2] 的后面进行查找 k -=2,则下一次middle=F(k-2-2)-1
}
}
return list;
}
/*
* @param: [arr,searchValue]
* @return: java.util.List<java.lang.Integer>
* @author: Jerssy
* @dateTime:2021/3/24 14:16
* @description: 斐波那契递归查找
*/
public static List<Integer> fibonacciSearch1(int[] arr, int searchValue){
if (arr==null||arr.length == 0) return null;
if ( arr.length ==1) {
List<Integer> list = new ArrayList<>();
if(arr[0]==searchValue){
list.add(0);
}
return list;
}
//创建一个符和的斐波那契数列
return fibonacciSearch(createFibonacci(arr),0,arr.length-1,searchValue);
}
/*
* @param: [arr, left, right, searchValue]
* @return: java.util.List<java.lang.Integer>
* @author: Jerssy
* @dateTime: 2021/3/24 14:31
* @description:递归查找,将满足查找的索引放入集合List
*/
private static List<Integer> fibonacciSearch(int[] arr,int left,int right,int searchValue){
if (left>right){
return new ArrayList<>();
}
int middle=left+fibonacciArray[Math.max(fibonacciNumber-1,0)]-1;
if (arr[middle]>searchValue){
fibonacciNumber-=1;
return fibonacciSearch(arr, left, middle-1, searchValue);
}
else if (arr[middle]<searchValue){
fibonacciNumber-=2;
return fibonacciSearch(arr, middle+1, right, searchValue);
}
else {
List<Integer> list = new ArrayList<>();
middle=Math.min(middle,right);
int leftIndex=middle-1,rightIndex = middle+1;
while (leftIndex>=0) {
if (searchValue==arr[leftIndex]){
list.add(leftIndex);
}
leftIndex--;
}
list.add(middle);
while (rightIndex<=right) {
if (searchValue==arr[rightIndex]) list.add(rightIndex);
rightIndex++;
}
return list;
}
}
}