科普文:算法和数据结构系列【查找算法:常见7种查找算法的原理、应用、以及java实现】

概叙

科普文:算法和数据结构系列【算法和数据结构概叙】-CSDN博客

科普文:算法和数据结构系列【排序算法:常见10种排序算法的原理、应用、以及java实现】-CSDN博客

前面梳理完10种排序算法,我们再看看常用的7种查找算法:线性查找、二分查找、二叉搜索树查找、插值查找、斐波那契查找、哈希查找、分块查找等。

7种查找算法

查找算法是在数据集合中查找某一特定元素的算法,是计算机科学中的一项基本操作。

不同的查找算法适用于不同的数据结构和应用场景,以下是几种常见的查找算法:

  1. 线性查找(Linear Search)

    • 简介‌:线性查找是最基本的查找算法,它按照数据序列的顺序,逐个比较每个元素,直到找到目标元素或查找完整个序列。
    • 特点‌:实现简单,但查找效率较低,特别是在数据量较大的情况下。
  2. 二分查找(Binary Search)

    • 简介‌:二分查找是一种高效的查找算法,它要求数据必须是有序的。通过每次将查找范围缩小一半,可以快速定位目标元素或确定目标元素不存在。
    • 特点‌:查找效率高,时间复杂度为O(log n),但要求数据必须是有序的。
  3. 二叉搜索树查找(Binary Search Tree Search)

    • 简介‌:二叉搜索树查找是利用二叉搜索树数据结构实现的查找算法。二叉搜索树是一种特殊的二叉树,其中每个节点的左子树所有节点的值都小于该节点的值,右子树所有节点的值都大于该节点的值。
    • 特点‌:平均查找效率高,时间复杂度为O(log n),但最坏情况下可能退化为O(n)(当树退化成链表时)。
  4. 插值查找(Interpolation Search)

    • 简介‌:插值查找是一种改良版的二分查找算法。它根据要查找的值在有序数组中的大致位置进行估计,从而缩小搜索范围,提高查找效率。
    • 特点‌:特别适用于有序且均匀分布的数据集,时间复杂度在最优情况下可以达到O(log log n),但在不均匀分布的数据上可能退化为线性查找。
  5. 斐波那契查找(Fibonacci Search)

    • 简介‌:斐波那契查找是一种基于斐波那契数列的搜索算法。它利用斐波那契数列的特性,在有序序列中快速定位目标元素的位置。
    • 特点‌:适用于有序序列,时间复杂度为O(log n),且不需要直接访问数组的中间元素,因此在某些情况下比二分查找更灵活。
  6. 哈希查找(Hash Table Search)

    • 简介‌:哈希查找是利用哈希表数据结构实现的查找算法。它通过计算目标元素的哈希值,将元素映射到哈希表中的一个位置,从而实现快速查找。
    • 特点‌:查找效率高,平均时间复杂度为O(1),但需要额外的空间来存储哈希表,且需要处理哈希冲突。
  7. 分块查找(Block Search)

    • 简介‌:分块查找是顺序查找和二分查找的一种改进方法。它将数据序列分成若干块,块内元素无序,但块间有序。查找时先在索引表中进行查找,确定目标元素所在的块,然后在块内进行顺序查找。
    • 特点‌:查找速度比顺序查找快,同时不需要对全部节点进行排序,但实现相对复杂,需要维护一个索引表。

7种查找算法对比

7种查找算法的性能排序(从高到低):

  1. 哈希查找(Hash Table Search)

    • 平均时间复杂度‌:O(1)
    • 特点‌:在理想情况下,哈希查找可以在常数时间内找到目标元素,是性能最高的查找算法之一。但需要额外的空间来存储哈希表,并需要处理哈希冲突。
  2. 二分查找(Binary Search)

    • 时间复杂度‌:O(log n)
    • 特点‌:二分查找要求数据必须是有序的,通过每次将查找范围缩小一半,可以快速定位目标元素。时间复杂度为对数级别,性能非常高效。
  3. 斐波那契查找(Fibonacci Search)

    • 时间复杂度‌:O(log n)
    • 特点‌:斐波那契查找利用斐波那契数列的特性,在有序序列中快速定位目标元素。时间复杂度与二分查找相似,但在某些情况下可能更快。
  4. 插值查找(Interpolation Search)

    • 平均时间复杂度‌:O(log log n)
    • 特点‌:插值查找是二分查找的改进版本,特别适用于有序且均匀分布的数据集。其性能优于二分查找,但要求数据分布均匀。
  5. 二叉搜索树查找(Binary Search Tree Search)

    • 平均时间复杂度‌:O(log n)
    • 特点‌:在平衡二叉搜索树中,查找效率与二分查找相似。但在不平衡的树中,查找效率可能退化为O(n)。
  6. 分块查找(Block Search)

    • 平均时间复杂度‌:O(√n)
    • 特点‌:分块查找将数据序列分成若干块,块内无序但块间有序。查找时先定位到块,再在块内查找,性能优于线性查找。
  7. 线性查找(Linear Search)

    • 时间复杂度‌:O(n)
    • 特点‌:线性查找逐个比较数据序列中的元素,直到找到目标元素或查找完整个序列。性能最低,适用于数据量较小或无法排序的情况。

请注意,上述性能排序是基于平均时间复杂度的理论比较。

在实际应用中,查找算法的性能还受到数据分布、数据结构的具体实现、硬件环境等多种因素的影响。因此,在选择查找算法时,需要综合考虑多种因素。

搜索前,排序的重要性:除了线性查找和哈希查找,其他五种查找算法,均要求有序;否则找不到数据。

public static void main(String[] args) {
        //模拟产生100个待排序的数组
        int[] arr = new int[10];
        for (int i = 0; i < 10; i++) {
            arr[i] = (int) (Math.random() * 8000); // 生成一个[0, 8000000) 数
        }
        int target=(int) (Math.random() * 8000);
        System.out.println("查找目标: " + target +" 查询数组:"+ Arrays.toString(arr));
        int result = linearSearch(arr, target);
        System.out.println("线性查找结果: " + (result != -1 ? result : "未找到"));

        result = binarySearch(arr, target);
        System.out.println("二分查找结果: " + (result != -1 ? result : "未找到"));

        TreeNode treeNode = binarySearchTreeSearch(arr, target);
        System.out.println("二叉搜索树查找结果: " + (treeNode != null ? treeNode.val : "未找到"));

        result = interpolationSearch(arr, target);
        System.out.println("插值查找结果: " + (result != -1 ? result : "未找到"));

        result = FibonacciSearch.fibSearch(arr, target);
        System.out.println("斐波那契查找结果: " + (result != -1 ? result : "未找到"));

        result = hashSearch(arr, target);
        System.out.println("哈希查找结果: " + (result != -1 ? result : "未找到"));

        result = blockSearch(arr, 3, target);
        System.out.println("分块查找结果: " + (result != -1 ? result : "未找到"));

        // 查一定存在
        target=arr[5];
        System.out.println("查找目标(无序): " + target +" 查询数组:"+ Arrays.toString(arr));
        int[] arr1=Arrays.copyOf(arr,arr.length);
        SortList.bubbleSort(arr1);//排序
        System.out.println("查找目标(有序): " + target +" 查询数组:"+ Arrays.toString(arr1));
        result = linearSearch(arr, target);
        System.out.println("线性查找结果: " + (result != -1 ? result : "未找到"));

        result = binarySearch(arr, target);
        System.out.println("二分查找结果(无序): " + (result != -1 ? result : "未找到"));
        result = binarySearch(arr1, target);
        System.out.println("二分查找结果(有序): " + (result != -1 ? result : "未找到"));
        result = binarySearch1(arr1, target);
        System.out.println("[递归]二分查找结果(有序): " + (result != -1 ? result : "未找到"));

        treeNode = binarySearchTreeSearch(arr, target);
        System.out.println("二叉搜索树查找结果: " + (treeNode != null ? treeNode.val : "未找到"));

        result = interpolationSearch(arr, target);
        System.out.println("插值查找结果(无序): " + (result != -1 ? result : "未找到"));
        result = interpolationSearch(arr1, target);
        System.out.println("插值查找结果(有序): " + (result != -1 ? result : "未找到"));

        result =  FibonacciSearch.fibSearch(arr, target);
        System.out.println("斐波那契查找结果(无序): " + (result != -1 ? result : "未找到"));
        result =  FibonacciSearch.fibSearch(arr1, target);
        System.out.println("斐波那契查找结果(有序): " + (result != -1 ? result : "未找到"));
        result =  fibonacciSearch(arr1, target);
        System.out.println("斐波那契查找结果(有序): " + (result != -1 ? result : "未找到"));

        result = hashSearch(arr, target);
        System.out.println("哈希查找结果: " + (result != -1 ? result : "未找到"));

        result = blockSearch(arr, 3, target);
        System.out.println("分块查找结果(无序): " + (result != -1 ? result : "未找到"));
        result = blockSearch(arr1, 3, target);
        System.out.println("分块查找结果(有序): " + (result != -1 ? result : "未找到"));
    }

7种查找算法总结

1. 线性查找(顺序查找)

顺序查找是最基本的查找算法,按照序列原有顺序对数组进行遍历比较查询‌。

    //线性查找(顺序查找)
    public static int linearSearch(int[] arr, int target) {
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == target) {
                return i;
            }
        }
        return -1; // 如果未找到目标值,返回-1
    }
  • 原理:从数组的第一个元素开始,逐个与给定值进行比较,直到找到目标或遍历完整个数组。
  • 优点:实现简单。适用于任何类型的列表。
  • 缺点:效率低下,在最坏的情况下需要检查每个元素。
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 应用场景:数据集较小。未排序的数据集。
  • 注意事项:不适合大规模数据的高效搜索、在数据量较大时,顺序查找的效率会非常低,应尽量避免使用‌ 。

2. 二分查找

二分查找是一种高效的查找算法,前提是数据必须是有序的‌。

 //二分查找
    public static int binarySearch(int[] arr, int target) {
        int left = 0;
        int right = arr.length - 1;
        while (left <= right) {// todo 必须
            int mid = left + (right - left) / 2;
            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        return -1; // 如果未找到目标值,返回-1
    }
    //二分查找:递归
    public static int binarySearch1(int[] arr, int target) {
        return binarySearch(arr,0,arr.length-1,target);
    }
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
        // 当 left > right 时,说明递归整个数组,但是没有找到
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        int midVal = arr[mid];

        if (findVal > midVal) { // 向 右递归
            return binarySearch(arr, mid + 1, right, findVal);
        } else if (findVal < midVal) { // 向左递归
            return binarySearch(arr, left, mid - 1, findVal);
        } else {

            return mid;
        }

    }
  • 原理:基于有序数组,每次都将待查区间分为两半,并确定下一步在哪个子区间继续查找。
  • 优点:非常快速地缩小搜索范围。
  • 缺点:要求输入必须是有序序列。
  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)
  • 应用场景:已排序的大规模数据集。
  • 注意事项:确保数组已经排序。对于动态变化的数据集,维护排序状态可能会消耗额外资源。

3. 二叉搜索树查找

二叉搜索树查找是利用二叉搜索树数据结构实现的查找算法,具有较高的查找效率‌。

    //二叉搜索树查找:依赖二叉树数据结构
    public static TreeNode binarySearchTreeSearch(int[] arr, int target) {
        TreeNode root = null;
        root= BinarySearchTree.insert(root,arr[0]);
        for(int i=1;i<arr.length;i++){
            BinarySearchTree.insert(root,arr[i]);
        }
        return BinarySearchTree.search(root, target);
    }
  • 原理:利用二叉搜索树的性质(左子树的所有节点小于根节点,右子树的所有节点大于根节点)进行递归查找。
  • 优点:平均情况下查找效率较高,为O(log n)。支持高效的插入和删除操作。
  • 缺点:如果树不平衡,最坏情况下的查找效率可能退化到O(n)。
  • 时间复杂度:平均O(log n),最坏O(n)
  • 空间复杂度:O(n)
  • 应用场景:频繁插入删除操作且支持快速查找的场合。
  • 注意事项:保持树的平衡可以提高性能,如使用AVL树或红黑树。

4. 插值查找

二叉搜索树查找是利用二叉搜索树数据结构实现的查找算法,具有较高的查找效率‌。

//插值查找
    public static int interpolationSearch(int[] arr, int target) {
        int low = 0;
        int high = arr.length - 1;
        while (low <= high && target >= arr[low] && target <= arr[high]) {
            if (low == high) {
                if (arr[low] == target) {return low;}
                return -1;
            }
            int pos = low + ((target - arr[low]) * (high - low)) / (arr[high] - arr[low]);
            if (arr[pos] == target) {
                return pos;
            } else if (arr[pos] < target) {
                low = pos + 1;
            } else {
                high = pos - 1;
            }
        }
        return -1; // 如果未找到目标值,返回-1
    }
  • 原理:改进版的二分查找,通过估计目标值的位置来选择分割点,而不是固定地取中间位置。
  • 优点:对于分布均匀的数据可以更快定位到目标值附近。
  • 缺点:如果数据分布不均,则可能比二分查找更慢。需要维护二叉搜索树的结构‌。
  • 时间复杂度:最好情况下为O(log log n),最坏情况下退化至O(n)。
  • 空间复杂度:O(1)
  • 应用场景:键值分布较为均匀的情况。
  • 注意事项:对非均匀分布的数据效果不佳。

5. 斐波那契查找

斐波那契查找是一种基于斐波那契数列的搜索算法。它利用斐波那契数列的特性,在有序序列中快速定位目标元素的位置。

//斐波那契查找
    public static int fibonacciSearch(int[] arr, int target) {
        // 获取大于或等于数组长度的最小的斐波那契数
        int fibMMm2 = 0;  // (m-2)'th Fibonacci No.
        int fibMMm1 = 1;  // (m-1)'th Fibonacci No.
        int fibM = fibMMm2 + fibMMm1;  // m'th Fibonacci

        while (fibM < arr.length) {
            fibMMm2 = fibMMm1;
            fibMMm1 = fibM;
            fibM = fibMMm2 + fibMMm1;
        }

        // 将数组扩展到斐波那契数的长度
        int[] extendedArray = new int[fibM];
        for (int i = 0; i < arr.length; i++) {
            extendedArray[i] = arr[i];
        }
        for (int i = arr.length; i < fibM; i++) {
            extendedArray[i] = arr[arr.length - 1]; // 填充最后一个元素
        }

        // 开始斐波那契查找
        int offset = -1;

        while (fibM > 1) {
            int i = Math.min(offset + fibMMm2, arr.length - 1);

            if (target < extendedArray[i]) {
                fibM = fibMMm2;
                fibMMm1 = fibMMm1 - fibMMm2;
                fibMMm2 = fibM - fibMMm1;
            } else if (target > extendedArray[i]) {
                fibM = fibMMm1;
                fibMMm1 = fibMMm2;
                fibMMm2 = fibM - fibMMm1;
                offset = i;
            } else {
                return Math.min(i, arr.length - 1); // 返回实际数组中的索引
            }
        }

        // 如果未找到目标值,返回-1
        if (fibMMm1 == 1 && extendedArray[offset + 1] == target) {
            return Math.min(offset + 1, arr.length - 1);
        }

        return -1;
    }
  • 原理:利用斐波那契数列作为索引间隔来进行跳跃式搜索。
  • 优点:相比二分查找,在某些特定条件下性能更好。
  • 缺点:计算过程稍微复杂一些。
  • 时间复杂度:平均O(log n)
  • 空间复杂度:O(1)
  • 应用场景:类似于二分查找,但可以在某些特定类型的数据上提供更好的性能。
  • 注意事项:同样需要数据预先排序。

6. 哈希查找

哈希查找是利用哈希表数据结构实现的查找算法。它通过计算目标元素的哈希值,将元素映射到哈希表中的一个位置,从而实现快速查找。

    //哈希查找:依赖哈希表
    public static int hashSearch(int[] arr, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], i);
        }
        return map.getOrDefault(target, -1); // 如果未找到目标值,返回-1
    }
  • 原理:通过哈希函数将键转换成固定范围内的索引来访问元素。
  • 优点:理想情况下能够达到O(1)的时间复杂度。支持高效的插入和删除操作。
  • 缺点:存在哈希冲突问题。设计良好的哈希函数较难。
  • 时间复杂度:平均O(1),最坏O(n)
  • 空间复杂度:取决于哈希表大小及负载因子
  • 应用场景:需要频繁插入删除操作且支持快速查找的场合。
  • 注意事项:合理设置哈希表大小以减少碰撞概率。选择合适的哈希函数至关重要。

解决冲突常用的手法

当两个不同的数据元素的哈希值相同时,就会发生冲突。

解决冲突常用的手法有2种:开放地址法、链接法

  1. 开放地址法:如果两个数据元素的哈希值相同,则在哈希表中为后插入的数据元素另外选择一个表项。当程序查找哈希表时,如果没有在第一个对应的哈希表项中找到符合查找要求的数据元素,程序就会继续往后查找,直到找到一个符合查找要求的数据元素,或者遇到一个空的表项。
  2. 链接法(哈希表采用这种方法):将哈希值相同的数据元素存放在一个链表中,在查找哈希表的过程中,当查找到这个链表时,必须采用线性查找方法。

科普文:Java基础之算法系列【哈希算法性能和哈希冲突测试:CRC、BKDR、APHash、DJB、JSHash、RSHash、SDBM、PJWHash、ELFHash、Murmur、FNV等】-CSDN博客

科普文:Java基础之算法系列【一文搞懂哈希Hash、及其应用】_哈希算法-CSDN博客

科普文:Java基础之算法系列【一文搞懂CRC32哈希、及其应用】_java中计算sha-3-CSDN博客

科普文:Java基础之算法系列【再哈希法(Rehashing):用SHA256+CRC32来手搓一个HashTable】-CSDN博客

科普文:Java基础之算法系列【升级版:再哈希法(Rehashing)+链地址法(Chaining):用SHA256+CRC32来手搓一个HashTable】-CSDN博客

科普文:【哈希算法的扩展应用:HashRing哈希环、哈希切分、SimHash局部敏感哈希算法、GeoHash经纬度哈希、BloomFilter布隆过滤器、CuckooFilter布谷鸟过滤器等】_hash环增加节点时旧数据处理-CSDN博客

7. 分块查找

分块查找是顺序查找和二分查找的一种改进方法。它将数据序列分成若干块,块内元素无序,但块间有序。查找时先在索引表中进行查找,确定目标元素所在的块,然后在块内进行顺序查找。

 //分块查找
    public static int blockSearch(int[] arr, int blockSize, int target) {
        int n = arr.length;
        int numBlocks = (n + blockSize - 1) / blockSize;

        // 创建块的最大值数组
        int[] maxOfBlock = new int[numBlocks];
        for (int i = 0; i < n; i++) {
            if (i % blockSize == 0) {
                maxOfBlock[i / blockSize] = arr[i];
            } else {
                maxOfBlock[i / blockSize] = Math.max(maxOfBlock[i / blockSize], arr[i]);
            }
        }

        // 在块的最大值数组中进行二分查找
        int blockIndex = -1;
        int left = 0;
        int right = numBlocks - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (maxOfBlock[mid] >= target) {
                blockIndex = mid;
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }

        if (blockIndex == -1) {
            return -1;
        }

        // 在找到的块内进行线性查找
        int start = blockIndex * blockSize;
        int end = Math.min(start + blockSize - 1, n - 1);
        for (int i = start; i <= end; i++) {
            if (arr[i] == target) {
                return i;
            }
        }

        return -1; // 如果未找到目标值,返回-1
    }
  • 原理:将数据分成多个小块,每个块内部有序。首先在块间进行二分查找,确定目标值所在的块,然后在该块内进行线性查找。
  • 优点:结合了二分查找和线性查找的优点,减少了整体查找时间。
  • 缺点:需要额外的空间存储块的最大值信息。
  • 时间复杂度:O(√n)
  • 空间复杂度:O(√n)
  • 应用场景:中等规模的数据集。数据部分有序时。
  • 注意事项:块的大小需要适当选择,以平衡查找时间和额外空间开销。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

01Byte空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值