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

news2024/11/25 1:29:42

目录

前言

直接插入排序(Insertion Sort)

一、概念及其介绍

二、过程图示

三、代码

四、复杂度

希尔排序(Shell Sort)

一、概念

二、实现思路

三、图示过程

 四、代码

4.1代码

4.2运行结果

4.3解释

五、复杂度

堆排序(Heap Sort)

一、概念

1.1什么是堆

二、排序思想

三、图示过程

 四、代码

4.1代码

4.2运行结果

4.3解释

直接选择排序(Selection Sort)

一、概念

二、实现思路

三、图示过程

四、代码

4.1代码

 4.2运行结果

五、代码优化

5.1代码方案一

 5.2代码方案二

六、复杂度

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

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


前言

在计算机科学领域中,排序算法是最基础和最重要的算法之一。

排序算法可以将一个无序的数据序列按照一定的规则进行排序,使得数据更加有序,方便后续的数据处理。常见的排序算法有八大经典算法:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序和基数排序。每个算法都有其独特的思想和性能特点。对于一个合适的排序算法来说,它既要保证排序的正确性,又要具备高效的时间和空间复杂度。

本文将分别介绍八大排序算法的原理、实现和优缺点。对于有一定算法基础的读者,本文也提供了几种算法的优化方法,可以帮助他们更好地应用这些排序算法。

直接插入排序(Insertion Sort)

一、概念及其介绍

插入排序(InsertionSort),一般也被称为直接插入排序。

对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表。

在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。

二、过程图示

假设前面 n-1(其中 n>=2)个数已经是排好顺序的,现将第 n 个数插到前面已经排好的序列中,然后找到合适自己的位置,使得插入第n个数的这个序列也是排好顺序的。

按照此法对所有元素进行插入,直到整个序列排为有序的过程,称为插入排序。

从小到大的插入排序整个过程如图示:

第一轮:从第二位置的 6 开始比较,比前面 7 小,交换位置。

第二轮:第三位置的 9 比前一位置的 7 大,无需交换位置。

第三轮:第四位置的 3 比前一位置的 9 小交换位置,依次往前比较。

第四轮:第五位置的 1 比前一位置的 9 小,交换位置,再依次往前比较。

三、代码

对[1,9,6,5,7,6,8,2,4]进行直接排序

public static void sort(int[] a) {

        for (int i = 1; i < a.length; i++) { //a[0]不用排序

            int temp = a[i]; //记录待排序元素的值

            for (int j = i - 1; j >=0; j--) {

                if (temp < a[j]) {

                    a[j + 1] = a[j];

                } else {

                    break;

                }

                a[j] = temp;

            }

            System.out.println("第" + i + "轮排序的结果为" + Arrays.toString(a));

        }

    }

public class InsertSort {

    //核心代码---开始

    public static void sort(Comparable[] arr){



        int n = arr.length;

        for (int i = 0; i < n; i++) {

            // 寻找元素 arr[i] 合适的插入位置

            for( int j = i ; j > 0 ; j -- )

                if( arr[j].compareTo( arr[j-1] ) < 0 )

                    swap( arr, j , j-1 );

                else

                    break;

        }

    }

    //核心代码---结束

    private static void swap(Object[] arr, int i, int j) {

        Object t = arr[i];

        arr[i] = arr[j];

        arr[j] = t;

    }



    public static void main(String[] args) {



        Integer[] arr = {1,9,6,5,7,6,8,2,4};

        sort(arr);

        for( int i = 0 ; i < arr.length ; i ++ ){

            System.out.print(arr[i]);

            System.out.print(' ');

        }

    }

}

四、复杂度

时间复杂度也是 O(n^2),空间复杂度为常数阶 O(1)

希尔排序(Shell Sort)

一、概念

希尔排序(Shell's Sort)是插入排序的一种,是插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。

二、实现思路

  1. 先分组:找到一个间隔,每隔一定的间隔把这些元素排成一组(在希尔排序当中对间隔没有明确的要求,可以任意找间隔,通常会取总长度的一半)
  2. 对组内元素进行插入排序
  3. 重新设置间隔、分组,在原来的间隔基础之上减半
  4. 组内元素排序
  5. 直到间隔为1,间隔为1以为着所有的元素为一组,此时进行最后一次组内排序

三、图示过程

 四、代码

4.1代码

public class ShellSort {
    public static void main(String[] args) {
        int[] arr = { 8, 9, 1, 7, 2, 3, 5, 4, 6, 0 };
        System.out.println("排序前的数组为:" + Arrays.toString(arr));
        shellSort(arr);
        System.out.println("排序后的数组为:" + Arrays.toString(arr));
    }

    public static void shellSort(int[] arr) {
        int n = arr.length;
        // 初始步长,可以根据实际情况调整
        int gap = n / 2;
        while (gap > 0) {
            // 从数组第gap个元素开始,逐个对其所在组进行直接插入排序操作
            for (int i = gap; i < n; i++) {
                int j = i;
                int temp = arr[j];
                if (arr[j] < arr[j - gap]) {
                    while (j - gap >= 0 && arr[j - gap] > temp) {
                        // 插入排序采用交换法
                        arr[j] = arr[j - gap];
                        j -= gap;
                    }
                    arr[j] = temp;
                }
            }
            // 得到新的步长
            gap = gap / 2;
        }
    }
}

4.2运行结果

排序前的数组为:[8, 9, 1, 7, 2, 3, 5, 4, 6, 0]

排序后的数组为:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

4.3解释

希尔排序是插入排序的一种改进,其核心思想是将原序列分成若干个子序列,对每个子序列进行插入排序,随着排序过程的进行,逐渐缩小子序列的长度,直到整个序列变为有序。希尔排序的关键在于步长的选择,不同的步长序列会影响排序的效率。

在上面的代码中,我们采用的是初始步长为数组长度的一半,然后不断将步长除以2,直到步长为1时结束排序。在每个步长下,我们将数组分成若干个子序列,对每个子序列进行插入排序。具体来说,我们从第gap个元素开始,逐个对其所在组进行插入排序操作。在插入排序过程中,我们采用交换法,即将当前元素与其前面的元素进行比较,如果前面的元素比当前元素大,则将前面的元素后移,直到当前元素找到合适的位置插入。

最终,当步长为1时,整个序列就变成了有序序列,排序结束。希尔排序的时间复杂度与步长序列的选择有关,最坏情况下为O(n^2),但是在大部分情况下,其时间复杂度都能达到O(nlogn)的级别,比插入排序要快很多。

五、复杂度

不稳定,时间复杂度是O(nlogn)~O(n^2),空间复杂度是O(1)

堆排序(Heap Sort)

一、概念

1.1什么是堆

  • 分为大顶堆和小顶堆
  • 符合完全二叉树
  • 父节点大于(或小于)子节点
  • 第一个非叶子节点:n/2-1(向下取整)

二、排序思想

  1. 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
  2. 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
  3. 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组

三、图示过程

 

 最大值和末尾的最小值交换位置

 

 重复以上操作

 四、代码

4.1代码

public class HeapSort {
    public static void heapSort(int[] arr) {
        // 构建初始大根堆
        buildMaxHeap(arr);
        // 从最后一个非叶子节点开始,依次向上调整堆
        for (int i = arr.length - 1; i > 0; i--) {
            swap(arr, 0, i); // 将堆顶元素(最大值)与最后一个元素交换
            maxHeapify(arr, 0, i); // 调整堆
        }
    }

    private static void buildMaxHeap(int[] arr) {
        // 从最后一个非叶子节点开始,依次向上调整堆
        for (int i = arr.length / 2 - 1; i >= 0; i--) {
            maxHeapify(arr, i, arr.length);
        }
    }

    private static void maxHeapify(int[] arr, int i, int heapSize) {
        int left = 2 * i + 1; // 左子节点下标
        int right = 2 * i + 2; // 右子节点下标
        int largest = i; // 最大值下标
        // 找到左、右子节点中的最大值
        if (left < heapSize && arr[left] > arr[largest]) {
            largest = left;
        }
        if (right < heapSize && arr[right] > arr[largest]) {
            largest = right;
        }
        // 如果最大值不是当前节点,交换最大值和当前节点,继续向下调整
        if (largest != i) {
            swap(arr, i, largest);
            maxHeapify(arr, largest, heapSize);
        }
    }

    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 = {5, 2, 6, 0, 3, 9, 1, 7, 4, 8};
        heapSort(arr);
        for (int i : arr) {
            System.out.print(i + " ");
        }
    }
}

4.2运行结果

排序前的数组为:[5, 2, 6, 0, 3, 9, 1, 7, 4, 8]

排序后的数组为:[0, 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8, 9]

4.3解释

代码中的buildMaxHeap()方法用于构建初始大根堆,maxHeapify()方法用于调整堆,heapSort()方法实现了堆排序算法。main()方法中用一个示例数组进行测试,并输出排序结果

直接选择排序(Selection Sort)

一、概念

  1. 选择排序是一种简单直观的排序算法
  2. 无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。
  3. 唯一的好处可能就是不占用额外的内存空间

二、实现思路

  1. 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  2. 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  3. 重复第二步,直到所有元素均排序完毕。

三、图示过程

四、代码

4.1代码

//Selection Sort选择排序
public class Selection {
    public static void main(String[] args) {

     int[] arr = {23,34,21,243,67,432,23,34};
        System.out.println(Arrays.toString(selection(arr)));
    }
    //选择排序
    public static int[] selection(int[] arr) {
        int temp = 0; //定义数据交换时的第三方变量
        for (int i = 0; i < arr.length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[min] > arr[j]) { //如果arr[j]的值小于arr[min]
                    min = j; //保存j的索引
                }
            }
            temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
            System.out.println("第" + (i + 1) + "轮插入" + Arrays.toString(arr));
        }


        return arr;
    }
}

 4.2运行结果

排序前的数组为:[23,34,21,243,67,432,23,34]

排序后的数组为:[21,23,23,34,34,67,243,432]

五、代码优化

5.1代码方案一

第一种:使用一个if判断 i == arr.length-2 ,直接去掉最后一轮对比

问题,如果倒数第二轮排序 最后一位元素比倒数第二位元素小,那么去掉最后一轮排序,最终结果是没有排序完成的

//Selection Sort选择排序
public class Selection {
    public static void main(String[] args) {

     int[] arr = {23,34,21,243,67,432,23,34};
        System.out.println(Arrays.toString(selection(arr)));
    }
    //选择排序
    public static int[] selection(int[] arr) {
        int temp = 0; //定义数据交换时的第三方变量
        for (int i = 0; i < arr.length - 1; i++) {
            if( i == arr.length-2){ //如果i=数组的最后两个值
                break;  //退出循环
            }
            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[min] > arr[j]) { //如果arr[j]的值小于arr[min]
                    min = j; //保存j的索引
                }
            }
            temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
            System.out.println("第" + (i + 1) + "轮插入" + Arrays.toString(arr));
        }


        return arr;
    }
}

 5.2代码方案二

第二种优化:

if(i != min ) { //判断只有在min=i时才会运行下面的代码

这种优化方法省略了很多不必要的代码运行,并优化最后一轮是否需要对比

//Selection Sort选择排序
public class Selection {
    public static void main(String[] args) {

     int[] arr = {23,34,21,243,67,432,23,34};
        System.out.println(Arrays.toString(selection(arr)));
    }
    //选择排序
    public static int[] selection(int[] arr) {
        int temp = 0; //定义数据交换时的第三方变量
        for (int i = 0; i < arr.length - 1; i++) {

            int min = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[min] > arr[j]) { //如果arr[j]的值小于arr[min]
                    min = j; //保存j的索引
                }
            }
            if(i != min ) { //判断只有在min=i时才会运行下面的代码
                temp = arr[i];
                arr[i] = arr[min];
                arr[min] = temp;
                System.out.println("第" + (i + 1) + "轮插入" + Arrays.toString(arr));
            }
        }


        return arr;
    }
}

六、复杂度

是一种不稳定排序,时间复杂度是O(n^2),空间复杂度是O(1)

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

排序算法分类规则稳定性时间复杂度空间复杂度优点缺点
插入排序(Insertion Sort)插入排序将元素插入到有序序列中稳定O(n^2)O(1)对小规模数据排序效率高

大规模数据效率低

希尔排序(Shell Sort)插入排序的升级版,先进行分组插入排序不稳定O(nlogn)~O(n^2)O(1)对大规模数据排序效率高

步长选择不当时效率低

冒泡排序(Bubble Sort)交换排序相邻元素比较稳定O(n^2)O(1)实现简单,空间复杂度低

大规模数据效率低,

时间复杂度高
选择排序(Selection Sort)找到最小/大元素不稳定O(n^2)O(1)简单,不占用额外空间时间复杂度高
归并排序(Merge Sort)分治思想,将序列不断拆分成子序列再合并稳定O(nlogn)O(n)对大规模数据排序效率高占用额外空间
快速排序(Quick Sort)分治思想,选取一个基准值进行比较交换不稳定O(nlogn)~O(n^2)O(logn)~O(n)对大规模数据排序效率高最坏时间复杂度较高
堆排序(Heap Sort)将元素构建成最大/小堆,每次取堆顶元素不稳定O(nlogn)O(1)对大规模数据排序效率高空间复杂度高
基数排序(Radix Sort)将元素按位数进行排序稳定O(d(n+k))O(n+k)适用于对多关键字排序数据集分布不均匀时效率低

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

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

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

相关文章

华为诺亚实验室VanillaNet学习笔记

华为诺亚实验室VanillaNet学习笔记 VanillaNet: the Power of Minimalism in Deep Learning Abstract 基础模型的核心理念是“多而不同”&#xff0c;计算机视觉和自然语言处理领域的惊人成功就是例证。然而&#xff0c;优化的挑战和变压器模型固有的复杂性要求范式向简单性…

Linux:finalshell虚拟机与真实机的上传和下载;rz、sz

使用命令rz上传的速度会比拖拽上传的速度慢

Day3:Windows网络编程-UDP

对比TCP与UDP的通信区别 UDP Server没有listen()和accept() TCP Server #include <iostream> #include <WinSock2.h> // 包含网络库 #pragma comment(lib,"ws2_32.lib") using namespace std;int main() {// 1. 初始化套接字 初始化套接字库cout <&l…

Qt编写视频监控系统76-Onvif跨网段组播搜索和单播搜索的实现

一、前言 在视频监控行业一般会用国际onvif工具来测试设备是否支持onvif协议&#xff0c;工具的名字叫ONVIF Device Manager&#xff08;还有个工具叫ONVIF Device Test Tool&#xff0c;专用于程序员测试各种数据交互&#xff09;&#xff0c;可以自行搜索下载&#xff0c;此…

linux网桥简单理解和持久化配置

文章目录 前言一、Linux 网桥是什么&#xff1f;二、网桥主要作用三、网桥配置命令及安装(CentOS系统)1 网桥配置命令2.持久化网桥配置 前言 linux bridge是网络虚拟化中非常重要的一种设备&#xff0c;今天就来学习下linux bridge的相关知识 一、Linux 网桥是什么&#xff1…

Allegro操作规范

光绘输出操作规范 1.1添加钻孔表 添加钻孔表的具体步骤为: 1.通过屏幕右边的Visibility选项的Views列表,将Drill层打开 2.将Visibility选项中的PIN和Via选项都选中,见下图所示: 1.2添加钻孔文件 参数设好之后关闭NC Drill/Parameters窗口,输出数控机床钻孔文件的命…

企业域名与企业邮箱:提升企业形象的必备选择

在商业中&#xff0c;你最不想做的就是把注意力从业务上移开&#xff0c;去担心一些小事——比如电子邮件。大多数免费的企业电子邮件账户偷工减料&#xff0c;因此无法交付货物。在Zoho我们围绕您的需求设计了我们的平台&#xff0c;以创建优于其他工作电子邮件地址的电子邮件…

jQuery-从左到右、从右到左

<!DOCTYPE html> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetUTF-8"> <title>从左到右、从右到左</title> <style type"text/css"> select { …

采购申请审批测试

采购申请审批的配置并不难&#xff0c;但是总会有原因导致业务无审批策略&#xff0c;而且这个配置也比较脆弱&#xff0c;有时同步也会出现问题&#xff0c;小编利用这篇操作记录下测试结果。 1、项目类别的审批策略分类 下图是审批策略分类-项目类别不给值&#xff0c;测试…

LAMP架构中的安装论坛

前言&#xff1a;本次操作依赖于LAMP的环境已经配置完全&#xff0c;网站也是依赖于开发人员现有的网站包框架&#xff0c;实施在LAMP已搭建好的环境进行安装部署 目录 一、对mysql进行操作 创建数据库&#xff0c;并进行授权 二、解压论坛压缩包 三、初步访问安装 解决…

少年,你可听说过MVCC?

&#xff1a;切&#xff01;这谁没听过&#xff0c;不就是多版本并发控制么~ 早在亘古时期&#xff0c;修真界就流传着一门mysql功法&#xff0c;将其修至小乘境界&#xff0c;足以纵横一方。。。不乏也有走火入魔者&#xff0c;为祸一方~ Serializable篇 强制事务排序&#…

【MCS-51】中断系统原理及应用

中断是单片机中一个十分重要的功能&#xff0c;它的出现能够让我们的单片机在顺序执行命令时&#xff0c;具备应对特殊情况的能力。 目录 &#x1f319;通信方式 &#x1f343;无条件传送 &#x1f343;有条件传送 &#x1f343;DMA通信 &#x1f343;中断传送 &#x1…

python计算程序开始、结束以及运行的时间

python 计算程序运行的耗时&#xff0c;主要有三种方法&#xff1a; time.time() 、 time.perf_counter() 和 datetime.datetime.now()方法 使用方法如下&#xff1a; 1. time.time() import time start time.time() # 程序开始时间,单位为秒 print("start_time:&…

C++ [STL之list的使用]

本文已收录至《C语言和高级数据结构》专栏&#xff01; 作者&#xff1a;ARMCSKGT 前言 vector是一片连续的空间&#xff0c;在数据访问上性能较好&#xff0c;但是任意位置插入删除性能较低&#xff0c;头插头删性能亦是如此&#xff1b;此时在这种需要频繁插入的场景下&…

PCL计算法向量结果不对的问题

一、点云&#xff08;高程渲染。CC计算的法向量&#xff0c;Z分量渲染&#xff09; 高程渲染 法向量Z分量的绝对值渲染 二、PCL计算法向量 下面是PCL计算法向量的代码&#xff08;点类型自己设置的&#xff09; 计算出的法向量有正有负 void pclNormalEstimation(pcl::Point…

【C++】构造函数初始化列表的特性以及注意事项

文章目录 一、1.1构造函数体赋值&#xff1a;二、 初始化列表2.1初始化基本形式&#xff0c;注意&#xff1a;3. 尽量使用初始化列表初始化&#xff0c;因为不管你是否使用初始化列表&#xff0c;对于自定义类型成员变量&#xff0c;一定会先使用初始化列表初始化。4. 成员变量…

JavaScript教程(二)

BOM浏览器对象模型 什么是BOM BOM&#xff08;Browser Object Model&#xff09;即浏览器对象模型&#xff0c;它提供了独立于内容而与浏览器窗口进行交互的对象&#xff0c;其核心对象是 window&#xff1b;BOM由一系列相关的对象构成&#xff0c;并且每个对象都提供了很多方…

deepfloyd/IF

Stable Diffusion团队放大招&#xff01;新绘画模型直出AI海报&#xff0c;实现像素级图像生成AI画文字终于能画对了https://mp.weixin.qq.com/s/_pwBD4-wLA9zNHBpD6WdNgDeepFloyd IF — DeepFloydhttps://deepfloyd.ai/deepfloyd-ifhttps://colab.research.google.com/#scroll…

微服务开发系列 第八篇:Elasticsearch

总概 A、技术栈 开发语言&#xff1a;Java 1.8数据库&#xff1a;MySQL、Redis、MongoDB、Elasticsearch微服务框架&#xff1a;Spring Cloud Alibaba微服务网关&#xff1a;Spring Cloud Gateway服务注册和配置中心&#xff1a;Nacos分布式事务&#xff1a;Seata链路追踪框架…

gradle快速入门

1.Gradle 入门 1.1 Gradle 简介 Gradle 是一款Google 推出的基于JVM、通用灵活的项目构建工具&#xff0c;支持Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而使用简洁的、支持多种语言(例如&#xff1a;java、groovy 等…