【数据结构】七大排序

news2025/1/4 21:33:14

目录

一、什么是稳定性

二、七大排序

2.1基于选择的思想

2.1.1直接选择排序

2.1.2堆排序

2.2基于插入的思想

2.2.1直接插入排序

2.2.2希尔排序

 2.3归并排序

2.4基于交换的思想

2.4.1冒泡排序

2.4.2快速排序

三、外部排序


排序就是将一组无序的数据经过一定的算法调整为有序的数据。

一、什么是稳定性

七大排序中只有直接插入排序,冒泡排序,归并排序时稳定的。

待排序的序列中,若存在值相等的元素,经过排序之后,相等元素之间原有的顺序保持不变,这种特性称为排序算法的稳定性。

在进行多轮排序(依照的属性不同)时,后面的排序不对前面的排序产生影响,我们就优先使用稳定性的排序算法。

若都是对同一属性的排序,则稳定性没有什么影响。

二、七大排序

以下七种排序都是基于比较的排序(内部排序(数据都放在内存中))。

2.1基于选择的思想

每次从无序区间中选择最小或者最大值,放在无序区间的最开始或最后面,直到整个数组有序。

2.1.1直接选择排序

时间复杂度为O(n^2)。不管数组有序无序正序倒序两个循环都要走到头。空间复杂度为O(1),并且不稳定。

在整个无序区间中选择最小值放在无序区间的最开始,每次都将当前区间的最小值放在数组最开始的位置,每次选一个元素,这个元素就放在了正确的位置上。

有序区间[0..i],无序区间[i..n),外层循环表示需要走的内层循环趟数,每走一次内层循环,就有一个元素(当前最小值)放在正确位置然后内层循环每次选取当前无序区间的最小值放在无序区间的最开始位置,用min保存最小值的索引,每次都假设是无序区间第一个元素,如果array中对应索引为 j对应的元素比min小,更新min的指向 min = j。 此时min就保存了最小值索引,交换array中对应索引为i和min的元素即可。

    public static void selectSort(int[] array){
        for(int i = 0;i<array.length-1;i++){
            int min = i;
            for (int j = i+1; j < array.length; j++) {
                if(array[min]>array[j]){
                    min = j;
                }
            }
            int tmp = array[min];
            array[min] = array[i];
            array[i] = tmp;
        }
    }
    public static void main(String[] args) {
        int[] arr = new int[] {1,5,3,2,4,10,7,9,8,30,25,26,21,15};
        selectSort(arr);
        System.out.println(Arrays.toString(arr));
    }

2.1.2堆排序

每次从最大堆中选取最大值放在无序区间的末尾,直到整个数组有序。

先将任意数组调整为最大堆,然后不断交换array[0]和当前为排序的最后一个元素array[i],每次交换都能将未排序区间的最大值放在最终位置,外层每循环一次,就能将当前未排序数组的最大值放在正确的位置,每次进行一趟循环,就有一个元素放在了最终位置,有序区间+1,当i==0,时,未排序区间[0]已排序区间[1...n-1] => 整个数组已经有序。

    public static void heapSort(int[] arr) {
        for (int i = (arr.length - 1 - 1) / 2; i >= 0; i--) {
            siftDown(arr,i,arr.length);
        }
        for (int i = arr.length - 1; i > 0; i--) {
            swap(arr,0,i);
            siftDown(arr,0,i);
        }
    }
    private static void siftDown(int[] arr, int k, int length) {
        while (2 * k + 1 < length) {
            int j = 2 * k + 1;
            if (j + 1 < length && arr[j + 1] > arr[j]) {
                j = j + 1;
            }
            if (arr[k] >= arr[j]) {
                break;
            }else {
                swap(arr,k,j);
                k = j;
            }
        }
    }

堆为完全二叉树,每次调整的时间最坏为logn,所以其时间复杂度最坏为nlogn,时间复杂度为O(nlogn)。空间复杂度O(1),也是不稳定的(下沉过程中没法保证相等元素顺序不交换)。 

2.2基于插入的思想

灵感来源于打扑克牌,每次在无序曲建忠选择第一个元素,插入在有序区间的合适位置,直到整个数组有序。

2.2.1直接插入排序

无序区间是[i...n),有序区间是[0..i) , i就是有序区间和无序区间的分水岭,i之前的元素全部有序,i之后(包括i)是无序区间。只有当arr[j] < arr[j - 1]才进行元素交换,若arr[j] >= arr[j - 1] ,说明j已经放在正确位置,则跳出循环。

    public static void insertSort(int[] array){
        for (int i = 1; i < array.length; i++) {
            for (int j = i; j >= 1 && array[j] <array[j-1]; j--) {
                int tmp = array[j];
                array[j] = array[j-1];
                array[j-1] = tmp;
            }
        }
    }

 时间复杂度为O(n^2)。但是当最好的情况时,内层循环只是做了简单的元素对比且只对比一次然后全部直接退出,那么时间复杂度为O(n)。因此插入排序经常作为其他高阶排序的辅助手段。最坏情况,就是内外层都全部执行O(n^2)。空间复杂度还是O(1)。是一个稳定的排序算法。

2.2.2希尔排序

插入排序就是在近乎有序的数组上性能非常好或者在小数据规模的集合中性能也很好,希尔排序就是针对于直接插入排序的优化。所以希尔排序的思路就是先将数组调整的近乎有序,最终在整个近乎有序的数组上来一次插入排序。

具体操作:先选定一个整数(gap),将待排序的集合中按照gap分组(所有距离为gap的元素在同一组),对同一组的元素进行排序,不断缩小这个gap的长度(gap/=2,gap/=3),重复上述的分组与排序过程,当gap==1,时整个数组已经近乎有序,最终来以此全数组的插入排序,整个集合就有序了。

当gap > 1都是分组,组内进行插入排序,当gap == 1时,其实整个数组近乎有序,整体来一次插入排序即可。组内进行的插入排序就相当于将插入排序中的1改为gap。

    public static void shellSort(int[] array){
        int gap = array.length/2;
        while(gap>1){
            insertSortByGap(array,gap);
            gap = gap>>1;
        }
        insertSortByGap(array,1);
    }
    public static void insertSortByGap(int[] array,int gap){
        for (int i = gap; i < array.length; i++) {
            for (int j = i; j >= gap && array[j] < array[j - gap]; j-=gap) {
                int tmp = array[j];
                array[j] = array[j-gap];
                array[j-gap] = tmp;
            }
        }
    }

一次一次按照gap分组,第一个gap = 7,分成七组,两两一组通过选择排序组内比较。然后第二次分组再比较,最后gap= 1,此刻已经近乎有序了,整体进行选择排序。 

 时间复杂度为O(n^(1.2-1.3)),具体看gap分组的长度。 

 2.3归并排序

将待排序的数组分为如下两个阶段。

归而为一:阶段一,首先不断将数组一分为二,知道每个子数组一分为二,直到每个子数组只剩下一个元素,拆分阶段结束(此时每个子数组都是有序数组)。

 并而为整:阶段二,不断地将相邻的两个有序子数组,合并为一个大的有序数组,知道合并为完整的数组,此时整个数组有序。

首先要创建一个方法mergeSortInternal用于递归分组,在这个方法中当l>=r时,则只有一个元素或者没有元素则不需要再进行递归直接返回即可,然后定义变量int mid = l + ((r - l) >> 1),存储中间值。然后先进行左半数组l-mid中继续进行归并排序,再在右半数组mid+1-r进行归并排序,将每个小数组都有序之后再将左右两个有序子数组进行排序调用merge方法。

在这个方法中有两个优化,1、首先是在方法最后调用merge方法这里的优化,只有当两个子数组部分元素还存在乱序,才需要合并。2、在小数组区间上使用插入排序进行优化,减少了很多小数组的递归合并过程,小数组即l  -r <= 15的情况下(是经过实验的)。

归并排序的核心在于merge函数(合并过程)。

在merge方法中设置两个变量int i = l,int j = mid+1。(r为数组最后一个元素下标,l为数组起点下标)先创一个大小为r - l +1 的临时数组aux,将两个子数组的内容复制到aux,进行两个子数组的比较合并过程直到两个子数组完成。

情况1:i > mid说明子数组1的所有元素已经处理完毕。直接将子数组2的剩余元素覆盖即可。

情况2:j > r说明子数组2的所有元素处理完毕,将子数组1的剩余元素进行覆盖。

剩余的情况就进行两个子数组的元素内容大小比较,如果aux[i-1] <= [j-1]选择子数组1的元素进行覆盖,保证了相等元素的稳定性。

    public static void mergeSort(int[] array){
        mergeSortInternal(array,0,array.length-1);
    }
    private static void merge(int[] arr, int l, int mid, int r) {
        int[] aux = new int[r - l + 1];
        System.arraycopy(arr, l, aux, 0, (r - l + 1));
        int i = l,j = mid+1;
        for (int k = l; k <= r; k++) {
            if(i > mid){
                arr[k] = aux[j - l];
                j ++;
            }else if (j > r) {
                arr[k] = aux[i - l];
                i ++;
            }else if (aux[i - l] <= aux[j - l]) {
                arr[k] = aux[i - l];
                i ++;
            }else {
                arr[k] = aux[j - l];
                j ++;
            }
        }
    }
    private static void mergeSortInternal(int[] array, int l, int r) {
        if (r - l <= 15) {
            insertionSortPart(array,l,r);
            return;
        }
        int mid = l + ((r - l) >> 1);
        mergeSortInternal(array,l,mid);
        mergeSortInternal(array,mid+1,r);
        if(array[mid]> array[mid+1]){
            merge(array,l,mid,r);
        }
    }
    private static void insertionSortPart(int[] arr, int l, int r) {
        for (int i = l + 1; i <= r; i++) {
            for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
                swap(arr,j,j - 1);
            }
        }
    }

不断将原数组拆分递归的过程本质就是一颗二叉树, 递归的次数其实就是树的高度,递归的时间复杂度就是O(logn),merge方法的时间复杂度就是O(n)。创建了临时数组,空间复杂度为O(n)。

所以归并排序的时间复杂度为O(nlogn),并且是稳定的。

2.4基于交换的思想

2.4.1冒泡排序

冒泡排序是从头开始两个两个元素之间进行比较,若前面的元素大于后面的元素的值,那么两者交换,这样最大的元素就会移动到最后面,然后重复上述步骤为了快速则忽略掉已经排好序的后面元素。

    private static void bubbleSort(int[] num) {
        for(int i = 0;i<num.length;i++) {
            for (int j = 0; j < num.length - 1 - i; j++) {
                if (num[j] > num[j + 1]) {
                    int tmp = num[j];
                    num[j] = num[j + 1];
                    num[j + 1] = tmp;
                }
            }
        }
    }

时间复杂度为O(n^2)。是稳定的排序方法。 

2.4.2快速排序

现在无序数组中选取一个分区点(数组中的一个元素),扫描整个集合,将比当前分区点小的元素放在分区点数值的左侧,比分区点大的元素放在分区点的右侧,那么分区点数值就放在了正确的位置。=》重复上述过程继续在左半区间和右半区间进行快速排序。

快速排序的核心在于分区函数的实现:针对分区函数的实现有N种操作。

(1)Hoare方法

默认第一个元素或最后一个元素为分区点,其中i索引指向开始位置,j指向终止为止,先让j从后向前扫描,碰到第一个<v值停止,然后再让i从前向后扫描碰到第一个>v的值停止,让两者进行交换,不断重复,直到 i>=j 停止。

(2)挖坑法=》针对Hoare方法的改进

默认将第一个位置值作为分取值,即将他取出,其中i索引指向开始位置,j指向终止为止,先让j从后向前扫描,碰到第一个<v值将他填到分区值的位置上,然后再让i从前向后扫描碰到第一个>v的值停止再填到j的位置上,不断重复,直到 i>=j 停止,然后将分区值填到这个位置。

    public static void quickSort(int[] arr) {
        quickSortInternalByHell(arr,0,arr.length - 1);
    }
    public static void quickSortInternalByHell(int[] arr,int l,int r){
        if(l>=r){
            return ;
        }
        int pivot = partitionByHell(arr,l,r);
        quickSortInternalByHell(arr,l,pivot-1);
        quickSortInternalByHell(arr,l +1 ,r);
    }
    private static int partitionByHell(int[] arr, int l, int r) {
        int v = arr[l];
        int i = l;
        int j = r;
        while (i < j) {
            while (i<j &&arr[j]>=v){
                j--;
            }
            arr[i] = arr[j];
            while (i<j&&arr[i]<=v){
                i++;
            }
            arr[j] = arr[i];
        }
        arr[i] = v;
        return i;
    }

正常情况为nlogn,情况最坏为n^2。时间复杂度为O(nlogn)。

如果是递归算法,所递归的深度大概为二叉树的深度,即logn。如果是非递归算法,需要模拟递归的过程,即需要保存子区间的索引,每次都会成对的保存,最多保存的索引也和二叉树的高度有关:2 * logn所以空间复杂度为O(logn)。

但是快速排序在近乎有序的数组中排序时间会很慢,第一种为三数取中法,选一个最左侧元素、中间位置元素,最右侧元素,选择值在中间的作为分区点。第二种就是解决方法就是随机生成一个区间数。

三、外部排序

大多需要借助外存。时间复杂度近乎于O(n)。

对于数据的特点要求非常高,应用场景很有限,只能那个在特定的数据集上使用。

(1)桶排序,一个省的中考或高考成绩就可以用桶排序,学生的成绩区间是固定的,因此就可以按照分数段将原先的数据先划分为若干个桶。

(2)计数排序

(3)基数排序

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

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

相关文章

Linux系统:root用户 登录失败

问题 在Linux系统上&#xff0c;从root用户切换到oracle用户时报错 su: cannot open session: Permission denied 如下&#xff1a; 分析 定位原因1 分析登录日志&#xff0c;可以看到时登录的时候limit中的 memlock 设置失败&#xff0c;导致用户登录失败&#xff1a; limi…

[GO] Gin入门

1. Gin基本使用 1.1 Gin入门 Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,具有快速灵活,容错方便等特点对于Golang而言,web框架的依赖要远比Python,Java之类要小,自身的net/http足够简单,性能也非常不错借助框架开发,不仅可以省去很多常用的封装带来的时…

为本地web服务配置使用固定的二级子域名【内网穿透】

由于使用免费的cpolar所生成的公网地址为随机临时地址&#xff0c;该地址24小时内会发生变化&#xff0c;对于需要长期访问的用户来讲比较不方便。 不过我们可以为其配置cpolar固定的二级子域名&#xff08;该二级子域名可自定义&#xff09;&#xff0c;该地址不会随机变化&a…

芯片漫游指南(2)-- UVM结构

目录&#xff1a;1 组件家族1.1概述1.2 uvm_driver1.2.1 概述1.2.2 示例1.3 uvm_monitor1.3.1 概述1.3.2 示例1.4 uvm_sequencer1.4.1 概述1.4.2 示例1.5 uvm_agent1.5.1 概述1.5.2 示例1.6 uvm_scoreboard1.6.1 概述1.6.2 示例1.7 uvm_env1.7.1 概述1.7.2 示例1.8 uvm_test1.8…

天翎携手群晖助力电商行业文档管理

编者按&#xff1a;电商行业的文档管理怎么做&#xff1f;本文根据电商行业文档管理中存在的一些难点&#xff0c;提出天翎文档管理系统和群晖NAS结合的解决方案。 关键词&#xff1a;免安装&#xff0c;免维护&#xff0c;文件分类&#xff0c;权限设置&#xff0c;文件同步&…

【拿捏链表(Ⅱ)】—Leetcode删除排序链表中的重复元素

目录删除排序链表中的重复元素(Ⅰ)删除排序链表中的重复元素(Ⅱ)删除排序链表中的重复元素(Ⅰ) 题目&#xff1a; 给定一个已排序的链表的头 head &#xff0c;删除所有重复的元素&#xff0c;使每个元素只出现一次 。返回 已排序的链表 。 思路&#xff1a;这里的思路很简单&…

Navigation--导航算法(局部视野导航)--DWA、TAB

DWA 动态窗口法&#xff08;dynamic window approach&#xff0c;DWA&#xff09;目前与A*一样都是ROS导航包中提供的基本路径规划算法。DWA是一种贪心的算法&#xff0c;通过可选速度、可选角速度的组合&#xff0c;模拟出很多局部轨迹&#xff0c;然后选择最优的。这种方法时…

Android dex动态加载(Kotlin版)

前言 环境 语言–KotlinJDK11SDK33AndroidStudio版本 Android Studio Dolphin | 2021.3.1 Patch 1 Build #AI-213.7172.25.2113.9123335, built on September 30, 2022概述 libaray项目打包成jarjar通过dx命令行工具转为dex.jardex.jar放到assets目录下App启动读取assets中…

外贸业务12年,我想和大家分享这几点感受

如今再回看这段经历&#xff0c;很庆幸我的三观一直都很正确&#xff0c;那就是买家第一。 不管是什么原因&#xff0c;只要你想退&#xff0c;我都可以接受退&#xff0c;我不能退回上级供应商的那我就自己留着&#xff0c;只为了不想因为这一次拒绝而失去这个买家&#xff1…

springboot集成security(鉴权)

本文承接上一章节内容&#xff1a;springboot集成security&#xff08;认证&#xff09; 上一章节&#xff1a; https://blog.csdn.net/m0_54355172/article/details/128239128 1. 授予静态资源访问权限 因为我的演示案例涉及到多个页面&#xff0c;所以先说一下如何给静态资源…

数据挖掘——关联规则(Association Rule)Apriori算法和python代码实现

关联规则&#xff08;Association Rule&#xff09;什么是关联规则一些基本概念任务是什么Apriori 算法核心思想步骤与流程图如何找到候选集python代码实现什么是关联规则 关联规则(Association Rules)是反映一个事物与其他事物之间的相互依存性和关联性&#xff0c;是数据挖掘…

线程2的深度剖析

加锁 synchronized 1.修饰方法&#xff08;普通方法&#xff0c;静态方法&#xff09;普通方法实际上加到了this上&#xff0c;静态方法加到了类对象上。 2.修饰代码块 手动指定加到那个对象上 明确锁对象针对那个对象加锁&#xff0c;如果两个线程针对同一个对象加锁&am…

(1)AWD入门攻略大纲

1.比赛介绍 (1)比赛环境 (2)常见服务器信息介绍 比赛名称 白名单&#xff1a;一般用于防止外部恶意攻击&#xff0c;如果赛方发现名单以外IP可能会进行封禁处理。 服务器账号密码 Token和虚拟IP&#xff1a;token为提交答案的凭证&#xff0c;绑定了队伍&#xff1b;虚拟IP为…

SpringCloud中Feign注解@FeignClient参数一览表

写在前面 Feign是微服务中服务间调用的优选组件&#xff0c;后来的OpenFeign也是基于此来开展的。 为什么要梳理一下Feign注解FeignClient中的各个参数&#xff1f; 踩坑太多面试总问 参数一栏表 FeignClient的源码示例图如下&#xff1a; 今天我们接着来说最后的几个参数。…

Java面试题(六)美团JVM夺命7连问(灵魂拷问)

0.来看一道美团的面试题 这题直接把人给问懵逼了&#xff0c;你能全部答出来吗&#xff1f; Object o new Object();请解释对象的创建过程&#xff1f;DCL要不要加volatile问题&#xff1f;对象在内存中的存储布局&#xff1f;什么是指针压缩&#xff1f;对象头具体包含哪些…

生成树问题汇总

生成树问题汇总注1、最小(大)生成树思路代码例子&#xff1a;1、最小生成树结果是2、最大生成树结果2、在最小生成树中再加一条边&#xff0c;求新的最小生成树思路代码核心代码全部代码例子3、次小生成树思路:在上一个功能基础上进一步扩充代码核心代码全部代码例子4、判断最小…

一个轻量级的分布式日志标记追踪神器,十分钟接入,非常好用!

TLog简介 1、TLog通过对日志打标签完成企业级微服务的日志追踪。它不收集日志&#xff0c;使用简单&#xff0c; 产生全局唯一的追踪码。除了追踪码以外&#xff0c;TLog还支持SpanId和上下游服务信息 标签的追加。 2、为用户使用方便而设计&#xff0c;提供完全零侵入式接入…

es入门(上)

笔记来源于学习 b站中的【IT李老师】的elasticsearch课程 自己在实习做的es模块中的理解。 后续会有 中&#xff0c;下篇笔记更新&#xff0c;目前这一篇是上篇。 目录 Elastic Stack简介 1.1简介 1.2特色 1.3组件介绍 2.Elasticsearch的接收与核心概念 2.1搜索是什么…

【Keras+计算机视觉+Tensorflow】OCR文字识别实战(附源码和数据集 超详细必看)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、OCR文字识别简介 利用计算机自动识别字符的技术&#xff0c;是模式识别应用的一个重要领域。人们在生产和生活中&#xff0c;要处理大量的文字、报表和文本。为了减轻人们的劳动&#xff0c;提高处理效率&#xff0c;从…

[python]初步练习脚本

之前练习的python&#xff0c;编写的脚本&#xff0c;现在作为记录&#xff0c;方便查看~ python 初步练习脚本基础部分的练习脚本脚本代码1、helloworld.py&#xff0c;有for循环语句2、main.py3、range—test.py&#xff0c;范围4、RE.py&#xff0c;花式输出内容5、turtle练…