【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


相关文章
|
3天前
|
监控 算法 网络协议
Java 实现局域网电脑屏幕监控算法揭秘
在数字化办公环境中,局域网电脑屏幕监控至关重要。本文介绍用Java实现这一功能的算法,涵盖图像采集、数据传输和监控端显示三个关键环节。通过Java的AWT/Swing库和Robot类抓取屏幕图像,使用Socket进行TCP/IP通信传输图像数据,并利用ImageIO类在监控端展示图像。整个过程确保高效、实时和准确,为提升数字化管理提供了技术基础。
35 15
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
49 1
|
3月前
|
存储 Java
Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。
【10月更文挑战第19天】本文详细介绍了Java中的HashMap和TreeMap,通过具体示例展示了它们在处理复杂数据结构问题时的应用。HashMap以其高效的插入、查找和删除操作著称,而TreeMap则擅长于保持元素的自然排序或自定义排序,两者各具优势,适用于不同的开发场景。
55 1
|
9天前
|
缓存 算法 搜索推荐
Java中的算法优化与复杂度分析
在Java开发中,理解和优化算法的时间复杂度和空间复杂度是提升程序性能的关键。通过合理选择数据结构、避免重复计算、应用分治法等策略,可以显著提高算法效率。在实际开发中,应该根据具体需求和场景,选择合适的优化方法,从而编写出高效、可靠的代码。
24 6
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
20天前
|
存储 缓存 安全
Java 集合江湖:底层数据结构的大揭秘!
小米是一位热爱技术分享的程序员,本文详细解析了Java面试中常见的List、Set、Map的区别。不仅介绍了它们的基本特性和实现类,还深入探讨了各自的使用场景和面试技巧,帮助读者更好地理解和应对相关问题。
37 5
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
110 23
|
2月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
62 20
|
2月前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
57 0

热门文章

最新文章