【Java数据结构】想进大厂必须牢记于心的——常见八大排序算法(下)

简介: 笔记

⭐交换排序


🎄5. 冒泡排序


思路:


比较 相邻的两个元素。如果第一个比第二个大则交换他们的位置(升序排列,降序则反过来)。

从列表的开始一直到结尾,依次对每一对相邻元素都进行比较。这样,值最大的元素就通过交换“冒泡”到了列表的结尾,完成第一轮“冒泡”。

重复上一步,继续从列表开头依次对相邻元素进行比较。已经“冒泡”出来的元素不用比较(一直比较到结尾也可以,已经“冒泡”到后面的元素即使比较也不需要交换,不比较可以减少步骤)。

继续从列表开始进行比较,每轮比较会有一个元素“冒泡”成功。每轮需要比较的元素个数会递减,一直到只剩一个元素没有“冒泡”时(没有任何一对元素需要比较),则列表排序完成。

算法优化

如若在一轮排序中没有发生位置的交换,那么说明数据已经有序,不用继续进行后边的排序了

图解:

26.gif


代码实现:

/**
     * 时间复杂度:
     *         最好最坏都是O(n^2)  但是:如果优化了 ,有序的时候就是O(n)
     * 空间复杂度:O(1)
     * 稳定性:稳定的排序
     *      冒泡  直接插入
     * @param array
     */
    public static void bubbleSort(int[] array) {
        for (int i = 0 ;i < array.length-1; i++){//外循环只用length-1趟
            boolean flg = false;//记录当前这一趟是否有换位子
            for (int j = 0 ; j <array.length-1-i ; j++){//内循环array.length-1-i趟
                if (array[j] > array[j+1]){
                    int tmp = array[j];
                    array[j] = array[j+1];
                    array[j+1] = tmp;
                    flg = true;
                }
            }
            if (flg==false) {//如果当前趟没换位置,说明已经有序,不需要再排序了
                break;
            }
        }
    }


🎄6. 快速排序


思路:

快速排序使用 分治策略 来把一个序列(list)分为两个子序列(sub-lists)。步骤为:


从数列中挑出一个元素,称为"基准"(pivot)。

①选择边上(左或者右)默认用这种方式

②随机选择

③几数取中(例如三数取中):array[left], array[mid], array[right] 大小是中间的为基准值

重新排序数列(挖坑法),所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的 中间位置。这个称为分区(partition)操作。

递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。

递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

图解:

60.gif


代码实现:

/**
     * 时间复杂度:
     *         最好:O(n*logn)  均匀的分割下
     *         最坏:o(n^2)     数据有序的时候
     * 空间复杂度:
     *        最好:logn
     *        最坏:O(n)
     * 稳定性:不稳定的排序
     *
     * k*n*logn
     * 2
     * 1.2
     * @param array
     */
public static void quickSort(int[] array) {
            sort(array, 0, array.length - 1);
        }
        private static void sort(int[] array, int low, int high) {
            int i = low;
            int j = high;
            if (array.length <= 1) {
                return;
            }
            if (i >= j) {
                return;
            }
            int pivot = array[i];
            //跳出while循环之后,因为循环的条件是i<j,所以,跳出循环时,i和j是相等的
            while (i < j) {
                //哨兵i从左往右找
                while (i < j && array[j] >= pivot){
                    j--;
                }
                //哨兵j从右往左找
                while (i < j && array[i] <= pivot){
                    i++;
                }
                //如果满足条件则交换两个数在数组中的位置
                if (i<j) {//当哨兵i和哨兵j没有相遇时
                    int t = array[j];
                    array[j] = array[i];
                    array[i] = t;
                }
            }
            //最终基准数归位
            array[low] = array[i];//一开始low位置的数就是基准数
            array[i] = pivot;//i下标值就是pivot基准值,由此可以递归左右两边的序列
            sort(array, low, i - 1);//继续处理左边的,这里是递归的过程
            sort(array, i + 1, high);//继续处理右边的,这里是递归的过程
        }

非递归实现快速排序重点掌握递归实现

/**
     * 非递归实现快速排序
     * @param array
     */
    public static void quickSort_FDG(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        int pivot = partition(array,start,end);
        //左边有2个元素及以上
        if(pivot > start+1) {
            stack.push(0);
            stack.push(pivot-1);
        }
        if(pivot < end-1) {
            stack.push(pivot+1);
            stack.push(end);
        }
        while (!stack.empty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //左边有2个元素及以上
            if(pivot > start+1) {
                stack.push(0);
                stack.push(pivot-1);
            }
            if(pivot < end-1) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }
    }

⭐🎄7. 归并排序·


思路:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

图解:


分而治之

60.png


可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。“分” 阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。


2.合并相邻有序子序列

再来看看 “治” 阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。

61.png


代码实现:

    public static void merge(int[] array,int low,int mid,int high) {
        int s1 = low;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = high;
        int[] tmp = new int[high-low+1];
        int k = 0;//代表tmp数组的下标
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmp[k++] = array[s1++];
                //k++;
                //s1++;
            }else {
                tmp[k++] = array[s2++];
            }
        }
        //有2种情况
        while (s1 <= e1){
            //说明第2个归并段没有了数据 把第1个归并段剩下的数据 全部拷贝过来
            tmp[k++] = array[s1++];
        }
        while (s2 <= e2) {
            //说明第1个归并段没有了数据 把第2个归并段剩下的数据 全部拷贝过来
            tmp[k++] = array[s2++];
        }
        //tmp数组当中 存储的就是当前归并好的数据,现在还需要拷贝到原数组中
        //这样的代码是错误的
        /*for (int i = 0; i < array.length; i++) {
            array[i] = tmp[i];
        }*/
        for (int i = 0; i < tmp.length; i++) {
            array[i+low] = tmp[i];//加上对应的low,用来处理第二个归并段,如果是第一个归并段,low=0
        }
    }
    public static void mergeSortInternal(int[] array,int low,int high) {
        if(low >= high) {
            return;
        }
        int mid = (low+high) / 2;
        mergeSortInternal(array,low,mid);//分解前半段
        mergeSortInternal(array,mid+1,high);//分解后半段
        //合并的过程
        merge(array,low,mid,high);
    }
    /**
     * 时间复杂度: O(N*log n)
     * 空间复杂度:O(N)
     * 稳定性:稳定的
     * @param array
     */
    public static void mergeSort(int[] array) {
        mergeSortInternal(array, 0,array.length-1);
    }

非递归实现归并排序(了解即可,重点掌握递归实现):

/**
     * 非递归实现 归并排序
     * @param array
     * @param gap
     */
    public static void merge2(int[] array,int gap) {
        int[] tmp = new int[array.length];
        int k = 0;
        int s1 = 0;
        int e1 = s1+gap-1;
        int s2 = e1+1;
        //int e2 = s2+gap-1 >= array.length ? array.length-1 : s2+gap-1;
        int e2 = s2+gap-1 < array.length ?  s2+gap-1 : array.length-1;
        //保证有两个归并段
        while (s2 < array.length) {
            while (s1 <= e1 && s2 <= e2) {
                if(array[s1] <= array[s2]) {
                    tmp[k++] = array[s1++];
                }else {
                    tmp[k++] = array[s2++];
                }
            }
            while (s1 <= e1) {
                tmp[k++] = array[s1++];
            }
            while (s2 <= e2) {
                tmp[k++] = array[s2++];
            }
            //一组完了 确定新的区间的开始和结束
            s1 = e2+1;
            e1 = s1+gap-1;
            s2 = e1+1;
            e2 = s2+gap-1 < array.length ?  s2+gap-1 : array.length-1;
        }
        //e2 > array.length
        while (s1 <= array.length-1) {
            tmp[k++] = array[s1++];
        }
        for (int i = 0; i < tmp.length; i++) {
            array[i] = tmp[i];
        }
    }
    public static void mergeSort_FDG(int[] array) {
        for (int i = 1; i < array.length; i*=2) {
            merge2(array,i);
        }
    }

🛸非基于比较的排序


🎄8. 基排序

思路:


基数排序的思想就是先排好 个位,然后 排好个位的基础上排十位,以此类推,直到遍历最高位 次,排序结束(仔细理解最后一句话)

基数排序不是比较排序,而是通过分配和收集的过程来实现排序

初始化10个桶(固定的),桶下标为0-9

通过得到待排序数字的个十百等位的数字,把这个数字对应的item放到对应的桶中

基数排序有两种排序方式:LSD和MSD,最小位优先(从右边开始)和最大位优先(从左边开始)

图解:

70.png


代码实现:

public static void radixSort(int[] array) {
    ArrayList<ArrayList<Integer>> queue = new ArrayList<>();
    for (int i = 0; i <10 ; i++) {
        queue.add(new ArrayList<>());// 创建一个基数从0---9 每个数字上都是一个list
    }
    // 找到最大值,并判断最大值是几位数
    int max = array[0];
    for (int i = 1; i < array.length; i++) {
        if (max < array[i]) {
            max = array[i];
        }
    }
    int time = 0;
    while (max > 0) {
        max /= 10;
        time++;
    }
    for (int i = 0; i < time; i++) {// 循环每一个位数(个位、十位、百位)
        for (int j = 0; j < array.length; j++) {// 循环数组,取每一个值
            int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
            ArrayList<Integer> queue3 = queue.get(x);
            queue3.add(array[j]);
            queue.set(x, queue3);
        }
        int count = 0;
        for (int k = 0; k < 10; k++) {
            while (queue.get(k).size() > 0) {
                ArrayList<Integer> queue4 = queue.get(k);
                array[count] = queue4.get(0);
                queue4.remove(0);
                count++;
            }
        }
    }
}

🗽总结


一个稳定的排序,可以变成不稳定的排序
但是一个不稳定的排序,不可能变成稳定的排序

72.png

71.png


相关文章
|
21天前
|
监控 算法 网络协议
Java 实现局域网电脑屏幕监控算法揭秘
在数字化办公环境中,局域网电脑屏幕监控至关重要。本文介绍用Java实现这一功能的算法,涵盖图像采集、数据传输和监控端显示三个关键环节。通过Java的AWT/Swing库和Robot类抓取屏幕图像,使用Socket进行TCP/IP通信传输图像数据,并利用ImageIO类在监控端展示图像。整个过程确保高效、实时和准确,为提升数字化管理提供了技术基础。
59 15
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
68 1
|
13天前
|
运维 监控 算法
企业局域网监控软件中 Java 优先队列算法的核心优势
企业局域网监控软件是数字化时代企业网络安全与高效运营的基石,犹如一位洞察秋毫的卫士。通过Java实现的优先队列算法,它能依据事件优先级排序,确保关键网络事件如异常流量、数据泄露等被优先处理,保障系统稳定与安全。代码示例展示了如何定义网络事件类并使用PriorityQueue处理高优先级事件,尤其在面对疑似风险时迅速启动应急措施。这一核心技术助力企业在复杂网络环境中稳健前行,护航业务腾飞。
57 32
|
4天前
|
存储 监控 算法
剖析基于Java算法驱动的智能局域网管控之道
本文探讨了基于Java语言的局域网控制方案,结合链表数据结构与令牌桶算法,解决设备管理和流量调度难题。通过链表灵活存储网络设备信息,实现高效设备管理;令牌桶算法则精准控制流量,确保网络平稳运行。二者相辅相成,为校园、企业等局域网提供稳固高效的控制体系,保障业务连续性和数据安全。
|
1天前
|
算法 搜索推荐 Java
【潜意识Java】深度解析黑马项目《苍穹外卖》与蓝桥杯算法的结合问题
本文探讨了如何将算法学习与实际项目相结合,以提升编程竞赛中的解题能力。通过《苍穹外卖》项目,介绍了订单配送路径规划(基于动态规划解决旅行商问题)和商品推荐系统(基于贪心算法)。这些实例不仅展示了算法在实际业务中的应用,还帮助读者更好地准备蓝桥杯等编程竞赛。结合具体代码实现和解析,文章详细说明了如何运用算法优化项目功能,提高解决问题的能力。
25 6
|
1天前
|
算法 Java C++
【潜意识Java】蓝桥杯算法有关的动态规划求解背包问题
本文介绍了经典的0/1背包问题及其动态规划解法。
25 5
|
12天前
|
存储 监控 算法
探秘局域网桌面监控:深入剖析 Java 语言核心算法
在数字化办公时代,局域网桌面监控如同企业的“智慧鹰眼”,确保工作效率与数据安全。本文以Java为载体,揭示哈希表在监控中的关键应用。通过高效的数据结构和算法,哈希表能快速索引设备连接信息,大幅提升监控的时效性和响应速度。代码示例展示了如何用Java实现设备网络连接监控,结合未来技术如AI、大数据,展望更智能的监控体系,助力企业在数字化浪潮中稳健前行。
|
12天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
37 2
|
27天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
35 6
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用