八大排序算法-直接插入排序、希尔排序、直接选择排序、冒泡排序、堆排序、快速排序、归并排序、基数排序(下)

news2024/11/27 23:55:10

目录

  • 前言
  • 冒泡排序(Bubble Sort)
    • 一、概念
    • 二、实现思路
    • 三、图示过程
    • 四、案例分析
      • 1、图示过程
      • 2、第一趟排序示例
    • 五、代码
      • 1、代码示例
      • 2、代码解释
      • 3、运行结果
    • 六、复杂度
  • 快速排序(QuickSort)
    • 一、概念
    • 二、实现思路
    • 三、图示过程
    • 四、代码
      • 1、代码示例
      • 2、代码解释
      • 3、运行结果
    • 五、复杂度
  • 归并排序(MergeSort)
    • 一、概念
    • 二、实现思路
    • 三、图示过程
    • 四、代码
      • 1、代码示例
      • 2、代码解释
      • 3、运行结果
    • 五、复杂度
  • 基数排序(Radix Sort)
    • 一、概念
    • 二、实现思路
    • 三、图示过程
    • 四、代码
      • 1、代码示例
      • 2、代码解释
      • 3、运行结果
    • 五、复杂度
  • 结构化:各个排序对比表格
  • 如果本篇博客对您有一定的帮助,请您留下宝贵的三连:==留言+点赞+收藏==哦。

前言

八大排序-直接插入排序、希尔排序、直接选择排序、冒泡排序、堆排序、快速排序、归并排序、基数排序(上)

书接上回,咱们继续来看下面四个排序。

下面所有代码段都以升序为例,数组的下标均从0开始。

排序的稳定性即:任意两个相等的数据,排序前后的相对位置不发生变化。

冒泡排序(Bubble Sort)

一、概念

    它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

    这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

二、实现思路

    • 比较相邻的元素。如果第一个比第二个大,就交换它们两个;

    • 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;

    • 针对所有的元素重复以上的步骤,除了最后一个;

    • 重复步骤1~3,直到排序完成。

三、图示过程

在这里插入图片描述

四、案例分析

    将几个无序的数:3,6,4,2,11,10,5 使用冒泡排序法将其排成一个从小到大的有序数列。

1、图示过程

在这里插入图片描述

2、第一趟排序示例

    第1次比较:3与6进行比较,3 < 6 所以不交换位置。得到3,6,4,2,11,10,5

    第2次比较:6与4比较,6 > 4,6与4交换位置,如果相邻的元素逆序就交换。得到3,4,6,2,11,10,5

    第3次比较:交换完位置后两个索引又同时移动让 6 与 2比较,6 > 2,6与2交换位置。得到3,4,2,6,11,10,5

    第4次比较:6与11进行比较,6 < 11 所以不交换位置。得到 3,4,2,6,11,10,5

    第5次比较:11与10进行比较,11 > 10 所以进行位置交换。得到 3,4,2,6,10,11,5

    最后将11与5进行比较,11 > 5 所以进行位置交换。得到3,4,2,6,10,5,11

五、代码

1、代码示例

public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = { 64, 34, 25, 12, 22, 11, 90 };
        int n = arr.length;

        // 外层循环控制排序轮数
        for (int i = 0; i < n - 1; i++) {
            // 内层循环控制每轮排序的次数
            for (int j = 0; j < n - i - 1; j++) {
                // 如果前面的元素大于后面的元素,则交换它们的位置
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }

        // 输出排序后的结果
        System.out.println("排序后的结果为:");
        for (int i = 0; i < n; ++i) {
            System.out.print(arr[i] + " ");
        }
    }
}

2、代码解释

    1、int[] arr = { 64, 34, 25, 12, 22, 11, 90 }; 定义了一个数组 arr,用于存储待排序的数据。

    2、int n = arr.length; 获取数组的长度,即待排序数据的个数。

    3、for (int i = 0; i < n - 1; i++) 外层循环控制排序轮数,i 的取值范围是 0 到 n-2。

    4、for (int j = 0; j < n - i - 1; j++) 内层循环控制每轮排序的次数,j 的取值范围是 0 到 n-i-2。

    5、if (arr[j] > arr[j + 1]) 如果前面的元素大于后面的元素,则交换它们的位置。

    6、System.out.print(arr[i] + " "); 输出排序后的结果,每个元素之间用空格分隔。

3、运行结果

排序后的结果为:
11 12 22 25 34 64 90

六、复杂度

    冒泡排序最好的时间复杂度是O(n),最坏和平均时间复杂度为O(n^2),空间复杂度为O(1)。是一种稳定排序。

快速排序(QuickSort)

一、概念

    快速排序(QuickSort)是一种基于比较的排序算法,它也是最常用的排序算法之一。快速排序的基本思想是通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再按此方法对这两部分记录分别进行快速排序,以达到整个序列有序的目的。

二、实现思路

    1、选出一个key,一般是最左边或是最右边的。

    2、定义一个begin和一个end,begin从左向右走,end从右向左走。(需要注意的是:若选择最左边的数据作为key,则需要end先走;若选择最右边的数据作为key,则需要bengin先走)。

    3、在走的过程中,若end遇到小于key的数,则停下,begin开始走,直到begin遇到一个大于key的数时,将begin和right的内容交换,end再次开始走,如此进行下去,直到begin和end最终相遇,此时将相遇点的内容与key交换即可。(选取最左边的值作为key)

    4.此时key的左边都是小于key的数,key的右边都是大于key的数

    5.将key的左序列和右序列再次进行这种单趟排序,如此反复操作下去,直到左右序列只有一个数据,或是左右序列不存在时,便停止操作,此时此部分已有序

三、图示过程

在这里插入图片描述

四、代码

1、代码示例

public class QuickSort {
    public static void quickSort(int[] arr, int left, int right) {
        if (left < right) {
            int pivot = partition(arr, left, right); // 分区操作,返回 pivot 的索引
            quickSort(arr, left, pivot - 1);  // 对左边进行递归排序
            quickSort(arr, pivot + 1, right); // 对右边进行递归排序
        }
    }

    private static int partition(int[] arr, int left, int right) {
        int pivot = left; // 设定基准值(pivot)
        int index = pivot + 1;
        for (int i = index; i <= right; i++) {
            if (arr[i] < arr[pivot]) {
                swap(arr, i, index);
                index++;
            }
        }
        swap(arr, pivot, index - 1);
        return index - 1;
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        int n = arr.length;
        quickSort(arr, 0, n-1);
        System.out.println("排序后的数组:");
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}

2、代码解释

    1、quickSort 方法:实现快速排序算法。参数 arr 为待排序数组,left 和 right 为待排序区间的左右端点(初始值为数组的起始和终止下标)。

    2、partition 方法:实现分区操作,即将数组中小于基准值的元素放在基准值的左边,大于等于基准值的元素放在基准值的右边。参数 arr 为待排序数组,left 和 right 为待排序区间的左右端点(初始值为数组的起始和终止下标)。

    3、swap 方法:实现数组中两个元素的交换。参数 arr 为待排序数组,i 和 j 分别为两个元素的下标。

    4、main 方法:测试快速排序算法。先定义一个待排序数组 arr,然后调用 quickSort 方法对其进行排序,最后输出排序后的数组。

3、运行结果

排序后的数组:
1 5 7 8 9 10

五、复杂度

    快速排序最好和平均的时间复杂度是O(nlogn),最坏时间复杂度为O(n^2),空间复杂度为O(logn)。是一种不稳定排序。

归并排序(MergeSort)

一、概念

    归并排序是一种基于分治思想的排序算法,它将待排序序列分成若干个子序列,每个子序列都是有序的,然后再将子序列合并为整体有序的序列。

    归并排序是用分治思想,分治模式在每一层递归上有三个步骤:

        • 分解(Divide):将n个元素分成个含n/2个元素的子序列。

        • 解决(Conquer):用合并排序法对两个子序列递归的排序。

        • 合并(Combine):合并两个已排序的子序列已得到排序结果。

二、实现思路

    1、申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

    2、设定两个指针,最初位置分别为两个已经排序序列的起始位置

    3、比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

    4、重复步骤③直到某一指针到达序列尾

    5、将另一序列剩下的所有元素直接复制到合并序列尾

三、图示过程

在这里插入图片描述

四、代码

1、代码示例

import java.util.Arrays;

public class MergeSort {
    public static void main(String[] args) {
        int[] arr = { 5, 3, 7, 6, 2, 1, 4 };
        System.out.println("原始数组:" + Arrays.toString(arr));
        mergeSort(arr, 0, arr.length - 1);
        System.out.println("排序后数组:" + Arrays.toString(arr));
    }

    /**
     * 归并排序
     * 
     * @param arr   待排序数组
     * @param left  左边界
     * @param right 右边界
     */
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2; // 计算中间位置
            mergeSort(arr, left, mid); // 对左半部分进行归并排序
            mergeSort(arr, mid + 1, right); // 对右半部分进行归并排序
            merge(arr, left, mid, right); // 合并左右两部分
        }
    }

    /**
     * 合并左右两部分
     * 
     * @param arr   待合并数组
     * @param left  左边界
     * @param mid   中间位置
     * @param right 右边界
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] tmp = new int[arr.length]; // 用于存储归并后的临时数组
        int i = left; // 左半部分起始位置
        int j = mid + 1; // 右半部分起始位置
        int k = left; // 临时数组起始位置

        while (i <= mid && j <= right) {
            // 如果左半部分当前元素小于右半部分当前元素,就将左半部分当前元素放入临时数组中
            // 否则,就将右半部分当前元素放入临时数组中
            if (arr[i] < arr[j]) {
                tmp[k++] = arr[i++];
            } else {
                tmp[k++] = arr[j++];
            }
        }

        // 将左半部分剩余元素依次放入临时数组中
        while (i <= mid) {
            tmp[k++] = arr[i++];
        }

        // 将右半部分剩余元素依次放入临时数组中
        while (j <= right) {
            tmp[k++] = arr[j++];
        }

        // 将临时数组中的元素复制回原数组中
        for (int p = left; p <= right; p++) {
            arr[p] = tmp[p];
        }
    }
}

2、代码解释

    1、首先定义了一个数组 arr,然后调用 mergeSort 函数对数组进行归并排序,最后输出排序后的数组。

    2、mergeSort 函数是归并排序的主要实现,它采用递归的方式将数组分成左右两部分,分别进行归并排序,然后将左右两部分合并成一个有序数组。具体实现时,如果左边界小于右边界,就计算出中间位置 mid,然后对左半部分和右半部分分别进行归并排序,最后将左右两部分合并成一个有序数组。

    3、merge 函数是将左右两部分合并成一个有序数组的实现,它先创建一个临时数组 tmp,然后将左右两部分中较小的元素放入临时数组中,直到某一部分的元素已经全部放入临时数组中为止。最后将剩余元素依次放入临时数组中,并将临时数组中的元素复制回原数组中。

3、运行结果

原始数组:[5, 3, 7, 6, 2, 1, 4]
排序后数组:[1, 2, 3, 4, 5, 6, 7]

五、复杂度

    归并排序时间复杂度是O(nlogn),空间复杂度为O(n)。是一种稳定排序。

基数排序(Radix Sort)

一、概念

    基数排序将整数按照位数逐个进行比较排序。对于有 n 个整数的数组,假设整数的最大值有 k 位数,则基数排序的时间复杂度为 O(kn),其中 k 是常数。

二、实现思路

    基数排序有两种实现方式,一种是 LSD(Least Significant Digit)排序,即从最低位开始排序;

    另一种是 MSD(Most Significant Digit)排序,即从最高位开始排序。LSD 是基数排序的经典实现方式,而 MSD 可以优化一些情况,比如在字符串排序中。今天主要介绍LSD排序。

    基数排序的思路是从个位开始,按照位数逐个进行排序。首先按照个位数进行排序,将每个数放到对应的桶中,再从桶中依次取出每个数,重新排列为新的数组。然后按照十位数进行排序,以此类推,直到最高位排序完成,数组就变成有序的了。

三、图示过程

在这里插入图片描述

四、代码

1、代码示例

import java.util.Arrays;

public class RadixSort {
    public static void main(String[] args) {
        int[] arr = {53, 3, 542, 748, 14, 214};
        radixSort(arr);
        System.out.println(Arrays.toString(arr));
    }

    public static void radixSort(int[] arr) {
        // 获取数组中最大值的位数
        int maxDigit = getMaxDigit(arr);

        // 对每一位进行排序
        for (int i = 1; i <= maxDigit; i++) {
            // 用桶来存储每个位上的数字
            int[][] buckets = new int[10][arr.length];
            int[] count = new int[10];

            // 将数组中的数字放入对应的桶中
            for (int j = 0; j < arr.length; j++) {
                int digit = getDigit(arr[j], i);
                buckets[digit][count[digit]] = arr[j];
                count[digit]++;
            }

            // 将桶中的数字按顺序放回原数组中
            int index = 0;
            for (int j = 0; j < count.length; j++) {
                for (int k = 0; k < count[j]; k++) {
                    arr[index] = buckets[j][k];
                    index++;
                }
            }
        }
    }

    // 获取数字的某一位上的数字
    private static int getDigit(int num, int digit) {
        return (num / (int) Math.pow(10, digit - 1)) % 10;
    }

    // 获取数组中最大值的位数
    private static int getMaxDigit(int[] arr) {
        int max = Integer.MIN_VALUE;
        for (int num : arr) {
            max = Math.max(max, num);
        }
        return String.valueOf(max).length();
    }
}

2、代码解释

    1、先获取数组中最大值的位数。

    2、对每一位进行排序,用桶来存储每个位上的数字。先遍历数组,将数组中的数字放入对应的桶中。

    3、将桶中的数字按顺序放回原数组中。

    4、重复步骤2和步骤3,直到排完最高位为止。

3、运行结果

排序后的数组:[3, 14, 53, 214, 542, 748]

五、复杂度

    本示例中,时间复杂度是O(d(n+k)),其中d为位数,n为待排序元素的数量,k为桶的大小,本例中k=10。空间复杂度是O(n+k),即桶的大小加上一个计数数组。由于本例中使用的是固定大小的桶,所以空间复杂度可以简化为O(n)。

结构化:各个排序对比表格

在这里插入图片描述

如果本篇博客对您有一定的帮助,请您留下宝贵的三连:留言+点赞+收藏哦。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/577112.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++学习第十二讲】C++ 常量

文章目录 一、前言二、整数常量三、浮点常量四、布尔常量五、字符常量六、字符串常量七、定义常量7.1 #define 预处理器7.2 const 关键字 一、前言 常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 常量可以是任何的基本数据类型&a…

202312读书笔记|《赶时间的人》——灰暗的从前会成为照亮未来的光,艰难的生活里,诗歌是那陡峭的另一面

202312读书笔记|《赶时间的人》——灰暗的从前会成为照亮未来的光&#xff0c;艰难的生活里&#xff0c;诗歌是那陡峭的另一面 《赶时间的人》 作者王计兵&#xff0c;一个外卖员的诗&#xff0c;饱含对生活的热情&#xff0c;向上的力量&#xff0c;仿若身在炼狱&#xff0c;心…

【014】C++数组之一维字符数组和二维字符数组

C数组之一维字符数组和二维字符数组 引言一、一维字符数组1.1、一维字符数组的初始化1.2、字符数组的遍历1.3、从键盘获取字符串1.4、使用示例 二、二维字符数组2.1、定义2.2、初始化2.3、访问 总结 引言 &#x1f4a1; 作者简介&#xff1a;专注于C/C高性能程序设计和开发&…

KVM虚拟化技术学习-基础入门

1.虚拟化技术概述 虚拟化[Virtualization]技术最早出现在 20 世纪 60 年代的 IBM ⼤型机系统&#xff0c;在70年代的 System 370 系列中逐渐流⾏起来&#xff0c;这些机器通过⼀种叫虚拟机监控器[Virtual Machine Monitor&#xff0c;VMM]的程序 在物理硬件之上⽣成许多可以运⾏…

Windows下Pycharm2022如何使用Centos7中的虚拟环境 venv 实现文件实时同步

前期准备 Windows 与 Centos 怎么搞共享文件夹&#xff0c;之前写了一篇&#xff0c;这里直接引用 hyperf 关于配置yasd调试器进行远程调试 swoole sdebug调试 windowlinux 共享文件夹开发汇总_森叶的博客-CSDN博客yasd github下载链接&#xff1a;https://github.com/swoole…

redis优化

一)优雅的key结构: redis中的key虽然可以自定义&#xff0c;但是最好遵循下面的几个最佳实践约定: 1)遵循基本格式:业务名称:数据名字:ID&#xff1b; 2)长度不要超过44字节&#xff0c;key所占的字节数越小&#xff0c;占用空间越小&#xff0c;越短越好&#xff1b; 3)不要包…

this 内存原理

&#x1f49f;这里是CS大白话专场&#xff0c;让枯燥的学习变得有趣&#xff01; &#x1f49f;没有对象不要怕&#xff0c;我们new一个出来&#xff0c;每天对ta说不尽情话&#xff01; &#x1f49f;好记性不如烂键盘&#xff0c;自己总结不如收藏别人&#xff01; &#x1f…

Spring Cloud Alibaba - Nacos

目录 一、Spring Cloud Alibaba 1、简介 二、Nacos 1、Nacos介绍 2、什么是Nacos&#xff1f; 3、为何使用Nacos&#xff1f; 4、Nacos下载和安装 4.1、启动 Linux/Unix/Mac Windows 5、Nacos代替Eureka 6、Nacos服务注册中心 一、Spring Cloud Alibaba Spring Cl…

【循环自相关和循环谱系列7】OFDM循环自相关推导分析、时间参数估计原理仿真及某无人机实际图传信号验证(含矩形/非矩形、有无循环前缀等情况)

重要声明:为防止爬虫和盗版贩卖,文章中的核心代码可凭【CSDN订阅截图或公z号付费截图】私信免费领取,一律不认其他渠道付费截图! 说明:本博客含大量公式推导分析,比较烧脑,需要有一定的数学基础,高等数学、信号与系统等! 这是循环自相关和循环谱系列的第七篇文章了…

基于SpringBoot+Vue测试用例管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着信息技术的不断发…

【Vue】学习笔记-Vue中的Ajax配置代理

回顾 常用的发送Ajax请求的方法有哪些&#xff1f; xhr​​ new XMLHttpRequest() xhr.open()、xhr.send()(真正开发中很少用到&#xff0c;太麻烦了&#xff0c;我们一般使用的都是他的二次封装) ​jQuery​​ 其对xhr有二次封装 . g e t ( ) 、 .get()、 .get()、.post ​…

【一起啃书】《机器学习》第八章集成学习

文章目录 第八章 集成学习8.1 个体与集成8.2 Boosting8.3 Bagging与随机森林8.4 结合策略8.5 多样性 第八章 集成学习 8.1 个体与集成 集成学习通过构建并结合多个学习器来完成学习任务&#xff0c;有时也被称为多分类器系统、基于委员会的学习等&#xff0c;下面是集成学习的…

Mac下好用的日记、电子书阅读器、RSS订阅软件​

Mac下好用的日记笔记本、电子书阅读器和RSS订阅、播客订阅等软件推荐。我们收录到 Mac下好用的日记、电子书阅读器、RSS订阅软件​http://www.webhub123.com/#/home/detail?pLZPL-2ofIu 收录效果如下 ​也可以使用分组视图来查看各类软件网址 ​ 登录后可一键保存全部软件网址…

Java 集合 - Queue 接口

文章目录 1.Queue 接口2.LinkedList3.ArrayDeque4.PriorityQueue5.总结 队列是一种特殊的线性数据结构&#xff0c;在数据的尾部插入元素&#xff0c;在数据的头部删除元素。通常以 FIFO&#xff08;先进先出&#xff09;的方式存储和访问数据。Java 中提供了 Queue 接口来实现…

element-ui对话框dialog详解

效果展示 先给大家展示一下大致的样式 代码 <el-dialog draggable destroy-on-close v-model"dialogAddVisible" title"添加用户" width"35%" center><el-form :inline"true" :model"addFormInfo" status-icon …

Java中的String数据类型,String类(字符串)详解

目录 第一章、String概述1&#xff09;String是什么2&#xff09;String长什么样3&#xff09;String的构造方法(声明方式) 第二章、String类的详解1&#xff09;String底层是什么2&#xff09;字符串存储的内存原理/字符串常量池(String Constant Pool&#xff09;3&#xff0…

C语言-【指针二】-【指针运算/指针和数组】

好久不见吖&#xff0c;好啦&#xff0c;言归正传&#xff0c;这篇文章接着上篇文章的尾巴接着介绍指针相关知识哦&#xff01; 一.指针运算 &#xff08;1&#xff09;指针-整数 &#xff08;2&#xff09;指针-指针 &#xff08;3&#xff09;指针的关系运算 接下来&…

Apache Kafka - 高性能原因探究

文章目录 概述图解 概述 Kafka 的高性能主要依赖于以下几个关键因素: 分布式架构:Kafka 采用分布式集群架构,可以水平扩展到上万个节点,支持每秒处理百万级消息。持久化存储:Kafka 使用文件系统持久化存储消息,避免了数据库成为性能瓶颈,大大提高了吞吐量。顺序读写:Kafka 的…

计算机视觉:卷积核的运行过程

本文重点 我们前面从直观角度理解了卷积神经网络的卷积在特征提取的作用,本节课程我们从数学角度来看一下,卷积是如何计算的? 计算步骤 1. 将卷积核与输入图像的某一部分进行逐元素相乘。 2. 将相乘后的结果求和,得到卷积核在该部分的输出值。 3. 重复以上步骤,将卷积核…

达梦8逻辑备份导出导入dexp/dimp

逻辑导出&#xff08;dexp&#xff09;和逻辑导入&#xff08;dimp&#xff09;是 DM 数据库的两个命令行工具&#xff0c;分别用来实现对 DM 数据库的逻辑备份和逻辑还原。逻辑备份和逻辑还原都是在联机方式下完成&#xff0c;联机方式是指数据库服务器正常运行过程中进行的备…