Java数据结构 -- 常见算法分析(查找算法、排序算法)精解详解!!!

简介: Java数据结构 -- 常见算法分析(查找算法、排序算法)精解详解!!!

前言

本文主要讲了常见的查找算法和排序算法,内容通俗易懂,相信各位小伙伴定会有一定的收获。

一、查找算法

1.1基本查找

基本查找也叫顺序查找核心思想是从0下标开始挨个往后查找

public class Test {
    public static void main(String[] args) {
        //算法:基本查找
        //需求:定义一个方法利用基本查找判断一个元素是否存在
        int[] arr = {12,89,24,26,100,215,48,94};
        System.out.println(basicSearch(arr, 12));
    }
    /**
     * 基本查找
     * @param array 数组
     * @param number 要查找的元素
     * @return 元素是否存在
     */
    public static boolean basicSearch(int[] array,int number) {
        for (int i = 0; i < array.length; i++) {
            if(array[i] == number) {
                return true;
            }
        }
        return false;
    }
}

练习1

定义一个方法,利用基本查找,查询某个元素在数组中的索引

要求:不需要考虑数组中元素是否重复

public static int basicSearchIndex(int[] array,int number) {
        for (int i = 0; i < array.length; i++) {
            if(array[i] == number) {
                return i;
            }
        }
        return -1;
    }

练习2

定义一个方法,利用基本查找,查询某个元素在数组中的索引

要求:需要考虑数组中元素是否重复

public static ArrayList<Integer> basicSearchIndex2(int[] array,int number) {
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < array.length; i++) {
                if(array[i] == number) {
                    list.add(i);
                }
        }
        return list;
    }

1.2二分查找/折半查找

前提:数组arr中的数据必须是有序的

核心:每次排除一半的查找范围

定义三个变量:min,max,mid

min:表示每次查找的0下标

max:表示每次查找的最大下标

mid:表示每次查找的数组的范围的中间值下标,即mid = (min + max)/2

要查找的数:num

在进行查找时,每次查找将要查找的数与mid下标的元素进行比较

1.若arr[mid] > num,则max = mid - 1,mid = (min + max)/2,继续查找

2.若arr[mid] < num,则min = mid + 1,mid = (min + max)/2,继续查找

直到arr[mid] == num 时查找结束。

查找不到的情况:

若要查找的数在数组中不存在,这时代码的逻辑是min>max(或max

public static int binarySearch(int[] array,int number) {
        //1.定义变量记录范围
        int min = 0;
        int max = array.length - 1;
        //2.利用循环查找数据
        while (true) {
            if(min>max) {
                return -1;
            }
            //3.找到min和max的中间位置mid
            int mid = (min + max) >>> 1;
            //4.比较
            //4.1 number在mid左边
            if(array[mid] > number) {
                max = mid - 1;
                mid = (min + max) >>> 1;
            }
            //4.2 number在mid右边
            else if(array[mid] < number){
                min = mid + 1;
                mid = (min + max) >>> 1;
            }else{
                //4.3 number与mid下标的值一样
                return mid;
            }
        }
    }

1.3插值查找

二分查找是将每次查找的中间值与要查的元素比较确定是否找到和下一次的查找范围,插值查找是二分查找的一种改进方式,插值查找是尽可能将中间值接近要查找的元素。

通过套用公式mid = min + (num - arr[min]) / (arr[max] - arr[min] ) * (max - min)可以使每次得到的中间值更接近要查找的元素。

1.num - arr[min]指的是数组中要查找的值与当前范围最小索引值的距离

2.arr[max] - arr[min]指的是数组中当前范围最大索引值与最小索引值的距离

3.(num - arr[min]) / (arr[max] - arr[min] ) 指的是要查找的元素在数组中位置的占比,乘以数组的长度 (max - min)再加上最小偏移量min得到查找的元素在数组中的位置。

优点:计算中间值mid时效率更高。

缺陷:要保证数组中的数据分布要比较均匀,否则效率会降低。

/**
     * 插值查找
     * @param array
     * @param number
     * @return
     */
    public static int binarySearch2(int[] array,int number) {
        //1.定义变量记录范围
        int min = 0;
        int max = array.length - 1;
        //2.利用循环查找数据
        while (true) {
            if(min>max) {
                return -1;
            }
            //3.找到min和max的中间位置mid
            int mid = (min + max) >>> 1;
            //4.比较
            //4.1 number在mid左边
            if(array[mid] > number) {
                max = mid - 1;
                mid = min + (number - array[min]) / (array[max] - array[min]) * (max - min);
            }
            //4.2 number在mid右边
            else if(array[mid] < number){
                min = mid + 1;
                mid = min + (number - array[min]) / (array[max] - array[min]) * (max - min);
            }else{
                //4.3 number与mid下标的值一样
                return mid;
            }
        }
    }

1.4分块查找

将一组数据分成好几个小块,每个块内的数据都是无序的,但块与块间是有一定顺序的。

分块的原则:

前一个块中的最大数都小于紧相挨着它的下一个块中的所有数(块内无序,块间有序)

块数数量一般等于数字的个数开根号。

在查找时,先确定要找的元素位于哪一块,然后在块内挨个查找。

代码分析:

在书写代码时,要先创建一个类,表示块,需要多少个块则分别创建多少个块的对象,然后将这些块对象放到一个数组中统一管理,这个数组也叫索引表

若要查找某个数,通过索引表中的每个最大值与该数进行比较得出该数在哪一块内。

package com.practice;
/**
 * @Author YJ
 * @Date 2023/7/15 12:06
 * Description: 分块查找
 */
public class BlockSearch {
    public static void main(String[] args) {
        /**
         * 分块查找
         * 块内无序,块间有序
         *
         */
        int[] arr = {16, 5, 9, 12, 21, 18,
                32, 23, 37, 26, 45, 34,
                50, 48, 61, 52, 73, 66};
        //创建3个Block对象
        Block b1 = new Block(21, 0, 5);
        Block b2 = new Block(45, 6, 11);
        Block b3 = new Block(73, 12, 17);
        //定义数组管理三个块的对象(索引表)
        Block[] blocks = {b1, b2, b3};
        //定义一个变量,记录查找的元素
        int num = 50;
        //调用方法  打印
        System.out.println(getIndex(blocks, arr, num));
    }
    public static int getIndex(Block[] blocks, int[] array, int number) {
        //1.确定number在哪一块中
        int indexBlock = findIndexBlock(blocks, number);
        if(indexBlock == -1) {
            return -1;
        }
        //数据在数组中
        //2.获取这一块的起始索引和结束索引
        int startIndex = blocks[indexBlock].getStartIndex();
        int endIndex = blocks[indexBlock].getEndIndex();
        //3.遍历
        for (int i = startIndex; i <= endIndex; i++) {
            if(array[i] == number) {
                return i;
            }
        }
        return -1;
    }
    //定义一个方法,确定number在哪一块中
    public static int findIndexBlock(Block[] blocks, int number) {
        //Block b1 = new Block(21, 0, 5);    -------- 0
        //Block b2 = new Block(45, 6, 11);   -------- 1
        //Block b3 = new Block(73, 12, 17);  -------- 2
        //通过基本查找确定number在哪一块中
        //从0索引遍历blocks数组,如果number小于max,说明number在这一块中
        for (int i = 0; i < blocks.length; i++) {
            if (number <= blocks[i].getMax()) {
                return i;
            }
        }
        return -1;
    }
}
//定义块的类
class Block {
    private int max;//块内最大值
    private int startIndex;//起始索引
    private int endIndex;//结束索引
    public Block() {
    }
    public Block(int max, int startIndex, int endIndex) {
        this.max = max;
        this.startIndex = startIndex;
        this.endIndex = endIndex;
    }
    /**
     * 获取
     *
     * @return max
     */
    public int getMax() {
        return max;
    }
    /**
     * 设置
     *
     * @param max
     */
    public void setMax(int max) {
        this.max = max;
    }
    /**
     * 获取
     *
     * @return startIndex
     */
    public int getStartIndex() {
        return startIndex;
    }
    /**
     * 设置
     *
     * @param startIndex
     */
    public void setStartIndex(int startIndex) {
        this.startIndex = startIndex;
    }
    /**
     * 获取
     *
     * @return endIndex
     */
    public int getEndIndex() {
        return endIndex;
    }
    /**
     * 设置
     *
     * @param endIndex
     */
    public void setEndIndex(int endIndex) {
        this.endIndex = endIndex;
    }
    public String toString() {
        return "Block{max = " + max + ", startIndex = " + startIndex + ", endIndex = " + endIndex + "}";
    }
}

二、排序算法

2.1冒泡排序

规则:相邻的数据两两比较,小的放前面,大的放后面

在进行排序的时候,按照相邻数据比较,小的放左边,大的放右边,当第一轮的数据比较结束后,最大值找到,且最大值在数组的最右边,除了最大值外,前面的其他数据还是无序的,所以还需要接着进行新一轮的排序,在剩余的数据中再次找到最大值,第二轮结束后,找到了次大值,继续再剩余的数据中进行新一轮的比较,再次找到最大值。

进行循环比较时,一轮比较完毕后,后一轮就可以少循环一次,执行的轮数为数组中的数据个数n - 1

package com.practice;
/**
 * @Author YJ
 * @Date 2023/7/15 17:12
 * Description:冒泡排序
 */
public class BubbleSortDemo {
    public static void main(String[] args) {
        //1.定义数组
        int[] arr = {2, 4, 5, 3, 1};
        //2.冒泡排序
        //第一轮:结束后最大值在数组的最右边
        //多轮比较:
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //i表示数组当中的索引 0 1 2 3 4
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                }
            }
        }
        //3.遍历数组
        printArr(arr);
    }
    /**
     * 遍历数组
     * @param arr
     */
    public static void printArr(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

2.2选择排序

选择排序:从0索引开始,拿着每一个索引上的元素跟后面的元素一次比较,小的放前面,大的放后面。

跟冒泡排序类似,选择排序从0索引开始,跟后面的元素一一比较,小的放前面,大的放后面,每一轮循环结束后,最小值就可以确定,接着以此从剩下的元素进行循环比较。

package com.practice;
/**
 * @Author YJ
 * @Date 2023/7/15 18:15
 * Description:
 */
public class SelectionDemo {
    public static void main(String[] args) {
        //1.定义数组
        int[] arr = {2, 4, 5, 3, 1};
        //开始比较
        //i:表示这一轮中,哪个索引上的数据跟后面的数据进行比较并交换
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if(arr[i] > arr[j]) {
                    int tmp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = tmp;
                }
            }
        }
        printArr(arr);
    }
    /**
     * 遍历数组
     * @param arr
     */
    public static void printArr(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

2.3插入排序

将0索引的元素到N索引的元素看作是有序的,把N+1索引的元素到最后一个当成是无序的。遍历无序的数据,将遍历到的元素插入到有序序列中适当的位置,如遇到相同数据,插在后面。

N的范围:0~最大索引

代码分析:在进行插入排序时,首先遍历无序的一组数据,拿到无序数据中的第一个,将这个元素与有序数据中倒着依次比较,并进行插入。

/**
 * @Author YJ
 * @Date 2023/7/16 6:54
 * Description:
 */
public class InsertSortDemo {
    public static void main(String[] args) {
        //数组
        int[] arr = {3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48};
        //1.找到无序的那一组数据是从哪个索引开始的
        int startIndex = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > arr[i + 1]) {
                startIndex = i + 1;//表示有序的数据到i下标就结束了
                break;
            }
        }
        //2.遍历从startIndex开始到最后一个元素,依次得到无序的那一组数据中的每一个数据
        for (int i = startIndex; i < arr.length; i++) {
            //记录当前要插入数据的索引
            int cur = i;
            while (cur > 0 && arr[cur] < arr[cur - 1]) {
                //交换位置
                int tmp = arr[cur];
                arr[cur] = arr[cur - 1];
                arr[cur - 1] = tmp;
                cur--;
            }
        }
        printArr(arr);
    }
    private static void printArr(int[] array) {
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}

2.4快速排序

在进行快速排序前,我们还要用到递归算法

递归指的是方法中调用方法本身的现象。

注意:递归一定要有出口,否则就会出现内存溢出。

作用:把一个复杂的问题层层转换为一个与原问题相似的规模较小的问题来求解。

递归只需少量的程序就可描述出解题过程中所需要的多次重复计算。

核心

1.找出口:什么时候不再调用方法。

2.找规则:如何把大问题变成规模较小的问题。

2.4.1递归求和

求1-100之间的和

/**
 * @Author YJ
 * @Date 2023/7/16 7:22
 * Description:
 */
public class RecursionDemo {
    public static void main(String[] args) {
        //把大问题拆成小问题
        //1-100之间的和 = 100 + (1-99之间的和)
        //1-99之间的和 = 99 + (1-98之间的和)
        //...
        //1-2之间的和 + 2 + (1-1之间的和)
        //1-1之间的和 = 1(递归的出口)
        System.out.println(getSum(100));
    }
    public static int getSum(int number) {
        if(number == 1) {
            return 1;
        }
        return number + getSum(number - 1);
    }
}

2.4.2快速排序

第一轮:把0索引的数字作为基准数,确定基准数在数组中正确的位置。

结束后比基准数小的全部在左边,比基准数大的全部在右边。



要想实现上面的效果,要将6作为基准数,定义start和end作为数组的起始和结束位置,从start开始与基准数6依次比较,找出比6大的数,从end开始往后与基准数6比较,找出比6小的数,分别找到后将这两个数交换位置,然后继续找,当start和end指向同一数时,此时的位置就是基准数应存入的位置,这个也叫基准数归位。

/**
 * @Author YJ
 * @Date 2023/7/16 7:42
 * Description:快速排序
 */
public class QuickSort {
    public static void main(String[] args) {
        //数组
        int[] arr = {6, 1, 2, 7, 9, 3, 4, 5, 10, 8};
        quickSort(arr, 0, arr.length - 1);
        //打印结果
        for (int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
    /**
     * 快速排序
     *
     * @param arr 要排序的数组
     * @param i   起始索引
     * @param j   结束索引
     */
    public static void quickSort(int[] arr, int i, int j) {
        //1.定义变量记录要查找的范围
        int start = i;
        int end = j;
        if(start > end) {
            return;
        }
        //2.定义变量记录基准数
        int baseNumber = arr[i];
        //3.利用循环找到要交换的数据
        while (start != end) {
            //利用end,从后往前找比基准数小的数
            while (true) {
                if(end <= start || arr[end] < baseNumber) {
                    break;
                }
                end--;
            }
            //利用start,从前往后找比基准数大的数
            while (true) {
                if(start >= end || arr[start] > baseNumber) {
                    break;
                }
                start++;
            }
            //交换
            int tmp = arr[start];
            arr[start] = arr[end];
            arr[end] = tmp;
        }
        //当start和end指向了同一个元素时,循环结束
        //表示已经找到了基准数在数组中应存入的位置
        //基准数归位
        int tmp = arr[i];
        arr[i] = arr[start];
        arr[start] = tmp;
        //确定6左边的数,重复排序
        quickSort(arr,i,start - 1);
        //确定6右边的数,重复排序
        quickSort(arr,start + 1,j);
    }
}

总结

经过这一番讲解,我们学到了最基本的查找和排序算法,欢迎各位的持续关注!!!


相关文章
|
6月前
|
存储 搜索推荐 算法
加密算法、排序算法、字符串处理及搜索算法详解
本文涵盖四大类核心技术知识。加密算法部分介绍了对称加密(如 AES)、非对称加密(如 RSA)、哈希摘要(如 SHA-2)、签名算法的特点及密码存储方案(加盐、BCrypt 等)。 排序算法部分分类讲解了比较排序(冒泡、选择、插入、归并、快排、堆排序)和非比较排序(计数、桶、基数排序)的时间复杂度、适用场景及实现思路,强调混合排序的工业应用。 字符串处理部分包括字符串反转的双指针法,及项目中用正则进行表单校验、网页爬取、日志处理的实例。 搜索算法部分详解了二分查找的实现(双指针与中间索引计算)和回溯算法的概念(递归 + 剪枝),以 N 皇后问题为例说明回溯应用。内容全面覆盖算法原理与实践
219 0
|
6月前
|
存储 监控 安全
企业上网监控系统中红黑树数据结构的 Python 算法实现与应用研究
企业上网监控系统需高效处理海量数据,传统数据结构存在性能瓶颈。红黑树通过自平衡机制,确保查找、插入、删除操作的时间复杂度稳定在 O(log n),适用于网络记录存储、设备信息维护及安全事件排序等场景。本文分析红黑树的理论基础、应用场景及 Python 实现,并探讨其在企业监控系统中的实践价值,提升系统性能与稳定性。
192 1
|
6月前
|
存储 监控 算法
基于跳表数据结构的企业局域网监控异常连接实时检测 C++ 算法研究
跳表(Skip List)是一种基于概率的数据结构,适用于企业局域网监控中海量连接记录的高效处理。其通过多层索引机制实现快速查找、插入和删除操作,时间复杂度为 $O(\log n)$,优于链表和平衡树。跳表在异常连接识别、黑名单管理和历史记录溯源等场景中表现出色,具备实现简单、支持范围查询等优势,是企业网络监控中动态数据管理的理想选择。
189 0
|
7月前
|
存储 安全 Java
Java 集合面试题从数据结构到 HashMap 源码剖析详解及长尾考点梳理
本文深入解析Java集合框架,涵盖基础概念、常见集合类型及HashMap的底层数据结构与源码实现。从Collection、Map到Iterator接口,逐一剖析其特性与应用场景。重点解读HashMap在JDK1.7与1.8中的数据结构演变,包括数组+链表+红黑树优化,以及put方法和扩容机制的实现细节。结合订单管理与用户权限管理等实际案例,展示集合框架的应用价值,助你全面掌握相关知识,轻松应对面试与开发需求。
379 3
|
9月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
304 1
|
9月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
920 1
|
10月前
|
JavaScript 前端开发 算法
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
10月前
|
算法 Java
算法系列之数据结构-Huffman树
Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。
329 3
 算法系列之数据结构-Huffman树
|
10月前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
453 22
|
10月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
340 10
 算法系列之数据结构-二叉树