七大查找算法——分块查找(五)

分块查找

定义: 分块查找是二分查找不懂二分查找点这里)和顺序查找不懂顺序查找点这里)的一种改进方法,分块查找由于只要求索引表是有序的,对块内节点没有排序要求,因此特别适合于节点动态变化的情况。

块有序

分块查找要求的是块与块之间有序。我们可以把一个查找数组分成若干部分,每一部分就可以称为一个块。块里面的元素可以是无序的,但块与块之间有序,即第一块中所有的元素都一定小于第二块中最小的元素,以此类推:第i块中的所有元素都小于第i+1块中最小的元素。
分块查找

索引表

索引表类似一个二维数组,它要记录每一块的最大元素和该块的起始索引位置。通过索引表可以快速定位要查找的数据在哪一块中。
分块查找

查找方法

假设我们有一组数据[4,1,3,14,18,13,6,20,25,22,19,55,78],现在要目标元素是13。

1.用二分查找(索引表长度较小时,可以用顺序查找)查找索引表中记录每一块最大值的那一行。
这里索引表中记录每一块的最大值的一行是:[4,8,25,78]。那么搜索结果就是25(我们要找到大于目标元素的最小值,因为第二块最大元素是8,第三块最大元素是25,那么第三块的元素范围就是9~25 , 所以我们要去第三块内查找。)

2.用顺序查找目标块内的元素。直到找到目标元素,返回其索引。如果遍历完没有,则返回-1,说明没找到。
这里我们已知第一步查找的结果是25,与25对应的起始块索引在索引表中记录的是7,而下一块起始的索引是11 ,所以第三块内的元素就是索引7~10的元素 。我们从索引7开始,一直遍历到索引10,最后就能找到目标元素13对应的索引为9 。

具体实现步骤

1.初始化四个指针,left = 0,right = length - 1;mid = 0;begin = -1;前面三个是二分查找用的,length是索引表的长度,后面一个begin用来记录查找索引表后,得到的块内元素起始索引。

2.用二分查找去搜索索引表中存储各块最大元素那一行。当left<=right时,循环继续。

3.mid = (left+right)/ 2 ; 如果目标元素等于索引为mid的元素,那么将begin = mid;跳出循环。

4.不相等,
若 索引为mid的元素小于目标元素,那么left = mid + 1;然后判断此时索引为left的元素是否大于等于目标元素,若是,则begin = left;跳出循环,不是则跳回第2步。(因为如果data[mid]=10,target = 11,而data[mid+1]=12,那么此时就应该返回mid+1)
若 索引为mid的元素大于目标元素,那么right= mid - 1;然后判断此时索引为right的元素是否小于目标元素,若是,则begin = mid;跳出循环,不是则跳回第2步。(因为如果data[mid]=10,target = 9,而data[mid-1]=8,那么此时就应该返回mid)

5.判断begin是否等于-1且right<0, 如果是,则begin = 0 。(因为假设第一块最大的元素是5,而目标元素是4,那么通过前面4步的查找,有可能begin会没赋上值,还是-1,但第一块内有可能包含目标元素,第一块只说了最大值是5,没说最小值。而第一块的起始索引为0 。)

6.确定块内元素的范围,begin是块内元素的起始索引,要找块内的结束索引。如果该块不是最后一块,那么该块的后一块的起始索引-1,就是要找的结束索引,如果是最后一块,那么查找数组的最后一个索引就是结束索引。

7.顺序查找块内元素。就是遍历,挨个找,找不到返回-1 。

复杂度分析

期望时间复杂度为:O( l o g 2 log_2 log2N)~O(N)

适用场景:数据是按块有序的,且记录有索引表。

代码实现

这里基于Java,jdk1.8实现。

1.初始化四个指针,left = 0,right = length - 1;mid = 0;begin = -1;前面三个是二分查找用的,length是索引表的长度,后面一个begin用来记录查找索引表后,得到的块内元素起始索引。

        int left = 0;
        int right = index.length - 1;
        int mid = 0;
        //        记录索引表的二分查找结果
        int begin = -1;

2.用二分查找搜索索引表 , 确定目标元素在哪一块中,并得到该块起始索引。(索引表我用了二维数组,第一列记录起始索引,第二列记录最大值),这里与普通二分查找有所不同。
若索引为mid的元素与目标元素不相等,那么
(1)若 索引为mid的元素小于目标元素,那么left = mid + 1;然后判断此时索引为left的元素是否大于等于目标元素,若是,则begin = left;跳出循环,不是则跳回第2步。(因为如果data[mid]=10,target = 11,而data[mid+1]=12,那么此时就应该返回mid+1)
(2)若 索引为mid的元素大于目标元素,那么right= mid - 1;然后判断此时索引为right的元素是否小于目标元素,若是,则begin = mid;跳出循环,不是则跳回第2步。(因为如果data[mid]=10,target = 9,而data[mid-1]=8,那么此时就应该返回mid)

        //        二分查找索引表
        while (left <= right) {
            mid = (left + right) / 2;
            if (index[mid][1] == target) {
                begin = mid;
                break;
            } else if (index[mid][0] < target) {
                left = mid + 1;
                if (left < index.length && index[left][0] >= target) {
                    begin = left;
                    break;
                }
            } else {
                right = mid - 1;
                if (right >= 0 && index[right][0] < target) {
                    begin = mid;
                    break;
                }
            }
        }

3.判断begin是否等于-1且right<0, 如果是,则begin = 0 。(因为假设第一块最大的元素是5,而目标元素是4,那么通过前面4步的查找,有可能begin会没赋上值,还是-1,但第一块内有可能包含目标元素,第一块只说了最大值是5,没说最小值。而第一块的起始索引为0 。)
如果begin = -1,但right > = 0,那说明目标元素比该查找数组的最大值还大,那直接返回-1表示没有。

        if (begin == -1) {
            if (right < 0) {
                begin = 0;
            } else {
                //                说明此时是left>right,target比data中最大的元素还大,直接返回-1
                return -1;
            }
        }

4.确定块内元素的范围,begin是块内元素的起始索引,要找块内的结束索引。如果该块不是最后一块,那么该块的后一块的起始索引-1,就是要找的结束索引,如果是最后一块,那么查找数组的最后一个索引就是结束索引。

        int length =
                begin + 1 < index.length ? index[begin + 1][1] : data.length;

5.顺序查找块内元素。就是遍历,挨个找,找不到返回-1 。

        for (int i = index[begin][1]; i < length; i++) {
            if (data[i] == target)
                return i;
        }
        return -1;

完整代码

//分块查找
//O(log2n)~O(n),块内无序,块与块有序。
//分块查找又称索引顺序查找,它是顺序查找的一种改进方法。
//          算法思想:将n个数据元素"按块有序"划分为m块(m ≤
//        n)。每一块中的结点不必有序,但块与块之间必须"按块有序";即第1块中任一元素的关键字都必须小于第2块中任一元素的关键字;而第2
//        块中任一元素又都必须小于第3块中的任一元素,……
//          算法流程:
//          step1 先选取各块中的最大关键字构成一个索引表;
//          step2 查找分两个部分:先对索引表进行二分查找或顺序查找,以确定待查记录在哪一块中;然后,在已确定的块中用顺序法进行查找。
public class BlockSearch {
    //    index索引表,第一列存块内最大元素,第二列存开始索引
    public int blockSearch(int[] data, int[][] index, int target) {
        int left = 0;
        int right = index.length - 1;
        int mid = 0;
        //        记录索引表的二分查找结果
        int begin = -1;
        //        二分查找索引表
        while (left <= right) {
            mid = (left + right) / 2;
            if (index[mid][1] == target) {
                begin = mid;
                break;
            } else if (index[mid][0] < target) {
                left = mid + 1;
                if (left < index.length && index[left][0] >= target) {
                    begin = left;
                    break;
                }
            } else {
                right = mid - 1;
                if (right >= 0 && index[right][0] < target) {
                    begin = mid;
                    break;
                }
            }
        }
        if (begin == -1) {
            if (right < 0) {
                begin = 0;
            } else {
                //                说明此时是left>right,target比data中最大的元素还大,直接返回-1
                return -1;
            }
        }
        int length =
                begin + 1 < index.length ? index[begin + 1][1] : data.length;
        //        顺序查找块内元素
        for (int i = index[begin][1]; i < length; i++) {
            if (data[i] == target)
                return i;
        }
        return -1;
    }
}
  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值