一文带你秒懂十大排序

news2024/11/27 6:28:39

目录

一、排序的概述  

二、插入排序

1、直接插入排序 

2、希尔排序 

二、选择排序 

1、直接选择排序

2、堆排序 

三、交换排序 

1、冒泡排序

2、快速排序 

四、归并排序

五、计数排序 

六、基数排序

七、桶排序 

八、排序总结


一、排序的概述  

排序就是将一组乱序的数据集合变得有序

排序可以分为:

内部排序:数据全部存放在内存中进行排序。

外部排序:数据太多而不能全部存放在内存,整个排序·过程需要在内外存之间多次交换数据才能进行。

根据排序的策略分为:插入排序、选择排序、交换排序和归并排序。

衡量排序的性能指标:

  • 时间复杂度。
  • 空间复杂度。
  • 稳定性:就是排序前后两个相同元素的相对位置保持不变。一个稳定的算法可以变得不稳定,但是一个不稳定的算法不能变得稳定。

排序在我们日常生活中有很多的应用,例如在购物页面可以选择价格从低到高排序,导航时路程可以根据预测花费时间进行排序。

对于排序算法的时间性能测试,采用如下代码:

public static long testTime(int[] array){
        int[] nums = Arrays.copyOf(array, array.length);
        long begin=System.currentTimeMillis();
        sort(nums);//排序算法
        long end=System.currentTimeMillis();
        return end - begin;
    }

二、插入排序

1、直接插入排序 

算法思想:就是将待排序的元逐个插入到已将排好序的序列的合适位置,该算法就像扑克牌游戏,每次拿到牌就将牌插入到已排好序的牌的合适位置。

动图演示:

代码实现:

public static void insertSort(int[] array){
        for(int i=1;i<array.length;i++){
            int temp=array[i];
            int j=i-1;
            for(;j>=0;j--){
                if(array[j]>temp){
                    array[j+1]=array[j];
                }else{
                    break;
                }
            }
            array[j+1]=temp;
        }
    }

性能分析:

  • 时间复杂度:O(n^2)。
  • 空间复杂度:O(1)。
  • 稳定性:稳定,因为当待插入的元素小于排好序的序列中的元素时才会进行交换,如果相等就不会进行交换,那么连个相同元素的相对位置保持不变。 

2、希尔排序 

算法思想:按照下标的一定增量进行的分组,对每组使用直接插入排序,增量逐渐减少,当增量减少为1时,整体再进行一次直接插入排序。

 过程演示:

动图演示:

代码实现:

// 希尔排序
public static void shellSort(int[] array){
        int gap=array.length/3+1;
        while(gap>1){
            shell(array,gap);
            gap=gap/3+1;
        }
        shell(array,gap);

    }
private static void shell(int[] array,int gap){
        for(int i=gap;i<array.length;i++){
            int temp=array[i];
            int j=i-gap;
            for(;j>=0;j=j-gap){
                if(array[j]>temp){
                    array[j+gap] = array[j];
                }else{
                    break;
                }
            }
            array[j+gap]=temp;
        }
    }

性能分析:

  • 时间复杂度:O(n^1.3)~O(n^1.5)。
  • 空间复杂度:O(1)
  • 稳定性:不稳定,因为是先分组排序,两个值相等的元素不在一组那么相对顺序就可能发生改变。

二、选择排序 

1、直接选择排序

算法思想:每次从待排序的序列中选出一个最小的元素与与序列的起始位置进行交换,直到全部元素排序完成。

动图演示:

代码实现:

public static void selectSort(int[] array){
        for(int i=0;i<array.length;i++){
            int minIndex=i;
            for(int j=i+1;j<array.length;j++){
                if(array[j]<array[minIndex]){
                    minIndex=j;
                }
            }
            int temp=array[i];
            array[i]=array[minIndex];
            array[minIndex]=temp;
        }
    }

性能分析

  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定,因为选择排序会和序列的起始位置发生交换,就可能导致不稳定。

2、堆排序 

算法思想:对数组内部进行排序,不能弹出堆顶元素来进行排序,所以如果要使元素从小到大进行排序就将堆顶元素与最后一个元素交换,每次交换完之后最后一个元素就不再改变,指向下调整堆之后,堆顶与上一个最后元素的前一个元素进行交换,依次类推,直到所有位置的元素都已确定。

动图演示:

代码实现:

// 堆排序
public static void heapSort(int[] array){
        createHeap(array);
        int end=array.length-1;
        for(int i=end;i>0;i--){
            int temp=array[i];
            array[i]=array[0];
            array[0]=temp;
            shiftDown(array,0,end);
            end--;
        }
    }
//创建堆
private static void createHeap(int[] array){
        for(int i=(array.length-1-1)/2;i>=0;i--){
            shiftDown(array,i,array.length);
        }
    }
//向下调整
private static void shiftDown(int[] array,int parent,int len){
        int child=2*parent+1;
        while (child < len){
            if(child+1 < len &&array[child+1] > array[child]){
                child++;
            }
            if(array[child]>array[parent]){
                int temp=array[child];
                array[child]=array[parent];
                array[parent]=temp;
                parent=child;
                child=2*parent+1;
            }else{
                break;
            }
        }
    }

性能分析

  • 时间复杂度:O(nlog2 n)。
  • 空间复杂度:O(1)。
  • 稳定性:不稳定。

三、交换排序 

1、冒泡排序

算法思想:相邻元素两两进行比较,若发生逆序就进行交换,直至待排序的元素序列中没有逆序。

动图演示:

代码实现:

public static void bubbleSort(int[] array){
        for(int i=0;i<array.length;i++){
            boolean flag=true;
            for(int j=0;j<array.length-i-1;j++){
                if(array[j]>array[j+1]){
                    int temp=array[j];
                    array[j]=array[j+1];
                    array[j+1]=temp;
                    flag=false;
                }
            }
            if(flag){
                break;
            }
        }
    }

性能分析

  • 时间复杂度:O(n^2)。
  • 空间复杂度:O(1)。
  •  稳定性:稳定。

2、快速排序 

算法思想:在待排序的序列中选择一个基准,按照基准将元素分成左右两个子序列,使得左边的子序列的元素都小于基准元素,使得右边的子序列都大于基准元素,对左右子序列继续重复上述步骤,使得每个子序列的长度为1为止。

动图演示:

代码实现:

public static void quickSort(int[] array){
        
        quick(array,0,array.length - 1);
    }
    public static void quick(int[] array,int left,int right){
        if(left >= right){
            return;
        }
        int part = partition(array,left,right);
        quick(array,left,part -1);
        quick(array,part + 1,right);
    }
    public static int partition(int[] array,int start,int end){
        int temp=array[start];
        while(start < end){
            while(start < end && array[end] >= temp){
                end--;
            }
            array[start] = array[end];
            while (start < end && array[start] <= temp){
                start++;
            }
            array[end] = array[start];
        }
        array[start] = temp;
        return start;
    }

性能分析:

  • 时间复杂度:O(nlog2 n)。
  • 空间复杂度:O(log2 n)。
  • 稳定性:稳定。

上述快速排序的时间复杂度是在最好情况下的,最坏情况下整个待排序元素是顺序或是逆序的,时间复杂度高达O(n^2),并且当数据量很大的时候,空间复杂度也是很大的,可能会出现栈溢出,所以需要进行优化:

每次选择基准元素时采用三数取中法,就是将right坐标、left坐标的值和mid中间值约束,以确保每次的基准元素不是最大值或最小值,还有当子序列长度不太大时,可以使用直接插入排序来提高效率。 

public static void quickSort(int[] array){
        quick(array,0,array.length - 1);
    }
    public static void quick(int[] array,int left,int right){
        if(left >= right){
            return;
        }
        if(right-left <=15){
            insertSort(array,left,right);
            return;
        }
        int index = midThree(array,left,right);
        int temp = array[index];
        array[index] = array[left];
        array[left] = temp;
        int part = partition(array,left,right);
        quick(array,left,part -1);
        quick(array,part + 1,right);
    }
    public static int partition(int[] array,int start,int end){
        int temp=array[start];
        while(start < end){
            while(start < end && array[end] >= temp){
                end--;
            }
            array[start] = array[end];
            while (start < end && array[start] <= temp){
                start++;
            }
            array[end] = array[start];
        }
        array[start] = temp;
        return start;
    }
    public static void insertSort(int[] array,int left,int right){
        for(int i = left+1;i <= right;i++){
            int temp = array[i];
            int j = i-1;
            for(;j >=left;j--){
                if(array[j] > temp){
                    array[j+1] = array[j];
                }else{
                    break;
                }
            }
            array[j+1] = temp;
        }
    }
    public static  int midThree(int[] array,int left,int right){
        int mid=(left+right) / 2;
        if(array[left] < array[right]){
            if(array[mid] > array[left]){
                return mid;
            }else{
                return left;
            }
        }else{
            if(array[mid] > array[right]){
                return mid;
            }else{
                return right;
            }
        }
    }

快速排序也可以非递归实现: 利用栈将每次划分的左右子序列的下标入栈,只要栈不为空,每次弹出两个元素,作为调整区间的下标。直到子序列只有一个元素的时候就停止入栈。

代码实现:

public static void quickSort2(int[] array){
        Deque<Integer> stack = new LinkedList<>();
        int left=0;
        int right=array.length-1;
        stack.push(left);
        stack.push(right);
        while (!stack.isEmpty()){
            right=stack.pop();
            left=stack.pop();
            int part=partition(array,left,right);
            if(part > left+1){
                stack.push(left);
                stack.push(part-1);
            }
            if(part < right-1){
                stack.push(part+1);
                stack.push(right);
            }

        }
    }

 四、归并排序

算法思想:采用的是分治法的思想,先把待排序的序列分解成若干个子序列,先让子序列有序,然后将有序的子序列合并是整个待排序的序列。

动图演示:

代码实现:

public static void mergeSort(int[] array){
        divideMerge(array,0,array.length-1);
    }
    public static void divideMerge(int[] array,int left,int right){
        if(left >= right){
            return;
        }
        int mid=(left+right) / 2;
        divideMerge(array,left,mid);
        divideMerge(array,mid+1,right);
        merge(array,left,right,mid);
    }
    public static void merge(int[] array,int left,int right,int mid){
        int s1=left;
        int s2=mid+1;
        int[] nums=new int[right-left+1];
        int k=0;
        while(s1 <= mid && s2 <= right){
            if(array[s1]<array[s2]){
                nums[k++] = array[s1++];
            }else{
                nums[k++] = array[s2++];
            }
        }
        while(s1 <= mid){
            nums[k++] = array[s1++];
        }
        while (s2 <= right){
            nums[k++] = array[s2++];
        }
        for(int i=0;i< k;i++){
            array[i+left]=nums[i];
        }
    }

性能分析:

  • 时间复杂度:O(nlog2 n)。
  • 空间复杂度:O(n)。
  • 稳定性:稳定 。

同样,归并排序也可以进行非递归实现。 

 public static void mergeSort2(int[] array) {
        int gap = 1;
        while (gap < array.length) {
            for (int i = 0; i < array.length; i = i + gap * 2) {
                int left = i;
                int mid = left + gap - 1;
                if (mid >= array.length) {
                    mid = array.length - 1;
                }
                int right = mid + gap;
                if (right >= array.length) {
                    right = array.length - 1;
                }
                merge(array, left, right, mid);
            }
            gap *= 2;
        }
    }

海量数据的排序问题: 

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G ,需要排序的数据有 100G
解决方案:因为待排序的数据有100G而内存只有1G,那么就可以将待排序的数据等分为200份,每份数据大小为512M,然后将每份数据存入内存中排好序,然后对这200份数据在内存外进行再进行归并排序即可。

五、计数排序 

算法思想:利用鸽巢原理,开辟一段较大的空间的数组,数组中默认元素为0,将待排序的元素对应到下标所对应的元素值加一,计数排序主要应用于待排序的序列在某个范围内。

动图演示:

代码实现: 

public static void countingSort(int[] array){
        int min = array[0];
        int max = array[0];
        for(int i = 1;i < array.length;i++){
            if(array[i] < min){
                min = array[i];
            }
            if(array[i] > max){
                max = array[i];
            }
        }
        int[] nums = new int[max-min+1];
        for(int i = 0;i < array.length;i++){
            nums[array[i]-min]++;
        }
        int k=0;
        for(int i = 0;i < nums.length;i++){
            while(nums[i] != 0){
                array[k++] = min+i;
                nums[i]--;
            }
        }
    }

性能分析:

  • 时间复杂度:O(max(n,数组范围))。
  • 空间复杂度:O(数组范围)。
  • 稳定性:稳定。

六、基数排序

算法思想:基数排序是对数字的每一位进行排序的,最大数字的位数决定了排列的次数,每次先排列,然后再收集,重复上述步骤,直到最高位排序完成。

动图演示:

代码实现:

 //基数排序
    public static void baseSort(int[] array){
        int max = array[0];
        for(int i = 1;i < array.length;i++){
            if(array[i] > max){
                max = array[i];
            }
        }
        int count=countByte(max);
        LinkedList<LinkedList<Integer>> lists = new LinkedList<>();
        for(int i = 0;i < 10;i++){
            lists.add(new LinkedList<>());
        }
        int k=1;
        while(k <= count){
            for(int i = 0;i < array.length;i++){
                int num = getByteNum(array[i],k);
                lists.get(num).add(array[i]);
            }
            int j = 0;
            for(int i = 0;i < lists.size();i++){
                while(!lists.get(i).isEmpty()){
                    array[j++] =lists.get(i).poll();
                }
            }
            k++;

        }
    }
    //计算出数字的位数
    public static int countByte(int num){
        int count = 0;
        while(num != 0){
            num=num/10;
            count++;
        }
        return count;
    }
    //获取到指定位的数字
    public static int getByteNum(int num,int index){
        String s = String.valueOf(num);
        if(index <= s.length()){
            return (int)(s.charAt(s.length() - index) - '0');
        }
        return 0;

    }

性能分析:

  • 时间复杂度:O(n*k)。
  • 空间复杂度:O(n+k)。
  • 稳定性: 稳定。

七、桶排序 

算法思想:将待排序的序列划分为多个范围大小相同的区间,将元素分别放入到对应的区间,对每个子区间进行排序,最后整个序列变得有序。

动图演示:

代码实现:

public static void bucketSort(int[] array){
        int min = array[0];
        int max = array[0];
        for(int i = 1;i < array.length;i++){
            if(array[i] < min){
                min = array[i];
            }
            if(array[i] > max){
                max = array[i];
            }
        }
        int size = (max-min) / array.length + 1;
        LinkedList<LinkedList<Integer>> lists = new LinkedList<>();
        for(int i = 0;i < size;i++){
            lists.add(new LinkedList<>());
        }
        for(int i = 0;i < array.length;i++){
            int num = (array[i] - min) / size;
            lists.get(num).add(array[i]);
        }
        int k=0;
        for(int i = 0;i < size;i++){
            lists.get(i).sort(new Comparator<Integer>() {
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1-o2;
                }
            });
            while(!lists.get(i).isEmpty()){
                array[k++]=lists.get(i).poll();
            }
        }
    }

性能分析

  • 时间复杂度:O(n+k)。
  • 空间复杂度:O(n+k)。
  • 稳定性:稳定。

八、排序总结

算法名称时间复杂度空间复杂度稳定性
直接插入排序O(n^2)O(1)稳定
希尔排序O(n^1.3)O(1)不稳定
直接选择排序O(n^2)O(1)不稳定
堆排序O(nlog2 n)O(1)不稳定
冒泡排序O(n^2)O(1)稳定
快速排序

O(nlog2 n)

O(log2 n)不稳定
归并排序O(nlog2 n)O(n)稳定
计数排序

O(nlog2 n)

O(n)稳定
基数排序

O(n*k)

O(n+k)不稳定
桶排序O(n+k)O(n+k)稳定

注意点:

序列基本有序时,快排退化成冒泡排序,直接插入排序最快。

各排序算法中,最坏情况下时间复杂度最低的是堆排序。

初始数据集的排列顺序对算法的性能无影响的有堆排序、归并排序、选择排序。

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

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

相关文章

pod私有库

私有库制作步骤 1、在gitlab上创建一个空项目&#xff0c;并用source tree导到本地&#xff0c;便于后面代码更新上传 2、cd 到项目下 执行pod lib create 【组件名】如&#xff1a;pod lib create TDAlertView 输入命令后会显示下载模板&#xff0c;会有几秒钟等待 Cloni…

一文搞懂 python 中的 classmethod、staticmethod和普通的实例方法的使用场景

什么是类方法&#xff08;classmethod&#xff09;/静态方法&#xff08;staticmethod&#xff09;和普通成员方法&#xff1f; 首先看这样一个例子&#xff1a; class A(object):def m1(self, n):# 属于实例对象&#xff0c;self 指代实例对象&#xff0c;print("self:…

Allegro如何更改钻孔孔符以及大小操作指导

Allegro如何更改钻孔孔符以及大小操作指导 PCB设计完成时,需要放出整板的钻孔表来,有的钻孔孔符以及大小并不是需要的,Allegro支持更改钻孔符以及大小,如下图 需要更改孔符以及大小, 具体操作如下 选择Manufacture选择NC

aws parallelcluster 理解 parallelcluster 集群的配置和使用

参考资料 Setup AWS ParallelCluster 3.0 with AWS Cloud9 200 HPC For Public Sector Customers 200 HPC pcluster workshop 200 Running CFD on AWS ParallelCluster at scale 400 Tutorial on how to run CFD on AWS ParallelCluster 400 Running CFD on AWS ParallelC…

CSS 伪元素也可以被用于反爬案例?来学习一下。26

先说一下什么是 CSS 中的伪元素&#xff0c;CSS 伪元素的概念是指在 CSS 中使用的一些特殊的元素&#xff0c;它们不存在于 HTML 文档中&#xff0c;而是由浏览器生成的元素&#xff0c;用于提供额外的样式控制。这些伪元素在 HTML 代码中不存在&#xff0c;但可以在 CSS 中通过…

[idekCTF 2023] Malbolge I Gluttony,Typop,Cleithrophobia,Megalophobia

这些题名字我都不认识&#xff0c;这是什么语呀。这个比赛感觉太难了&#xff0c;加上春节将近比较忙&#xff0c;仅作了4个简单题。记录一下。Misc/Malbolge I Gluttony这是个虚拟机的题&#xff0c;放入misc感觉有点不可思忆&#xff0c;题目给了7个命令&#xff0c;有"…

【云原生进阶之容器】第五章容器运行时5.1节--容器运行时总述

1 Kubernetes引言 Kubernetes 已经成为容器编排调度领域的事实标准,其优良的架构不仅保证了丰富的容器编排调度功能,同时也提供了各个层次的扩展接口以满足用户的定制化需求。其中,容器运行时作为 Kubernetes 管理和运行容器的关键组件,当然也提供了简便易用的扩展…

图解二叉树的构造 | 中序 + 后序

中序后续构造二叉树 https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/ 递归思路 递归思路很简单, 因为无论是构造一棵大树还是一棵小树, 都是重复的子问题, 思路主要麻烦在边界上 如下图所示 上述是中序和后续序列 我们要递归, 需…

BetaFlight飞控AOCODARC-F7MINI固件编译

BetaFlight飞控AOCODARC-F7MINI固件编译1. 编译目标&#xff08;AOCODARC-F7MINI&#xff09;2. 编译步骤Step 1 软件配置环境准备Step 2 获取开源代码Step 3 构建命令介绍Step 4 构建命令准备Step 5 厂家目标板查询Step 6 目标固件编译Step 7 目标固件清理3. 参考资料BetaFlig…

MyBatis中TypeHandler的使用教程

一.TypeHandler作用及其使用场景在我们平常开发操作数据库时&#xff0c;查询、插入数据等操作行为&#xff0c;有时会报数据类型不匹配异常&#xff0c;就可以得知数据的类型是不唯一的必然是多种不同的数据类型。并且我们必须要明确的一点就是java作为一门编程语言有自己的数…

如何使用ElementUI的table组件来实现单元格的行合并

前言 最近在编写一个值班的排班表&#xff0c;然后中间涉及到了表格应用。并且还要做出类似这种效果的行合并效果: 然后就开始找组件了。Html的table是有rowsSpan和colsSpan的属性来实现行合并和列合并的。然后就在网上找资料&#xff0c;发现没有几篇能把这两个属性将好的&a…

LeetCode刷题模版:111 - 120

目录 简介111. 二叉树的最小深度112. 路径总和113. 路径总和 II114. 二叉树展开为链表115. 不同的子序列116. 填充每个节点的下一个右侧节点指针117. 填充每个节点的下一个右侧节点指针 II118. 杨辉三角119. 杨辉三角 II120. 三角形最小路径和结语简介 Hello! 非常感谢您阅读海…

SWPUCTF 2022新生赛 web部分wp

&#x1f60b;大家好&#xff0c;我是YAy_17&#xff0c;是一枚爱好网安的小白。 本人水平有限&#xff0c;欢迎各位大佬指点&#xff0c;一起学习&#x1f497;&#xff0c;一起进步⭐️。⭐️此后如竟没有炬火&#xff0c;我便是唯一的光。⭐️ 目录 [SWPUCTF 2022 新生赛]…

linux中使用KubeSphere和集群k8s 部署springboot项目

上期已经介绍了单体k8s部署springboot项目&#xff0c;这期讲解集群k8s部署springboot项目 因为部署方式已经在单体中讲过&#xff0c;现在大体粗略讲一下首先看下集群节点&#xff0c;如下所示&#xff1a; 第一步&#xff1a;创建项目----》按照做的项目名称建 创建后&…

MyCat实现单库分表+代理所有表

MyCAT支持水平分片与垂直分片&#xff1a; 水平分片&#xff1a;一个表格的数据分割到多个节点上&#xff0c;按照行分隔。 垂直分片&#xff1a;一个数据库中多个表格A&#xff0c;B&#xff0c;C&#xff0c;A存储到节点1上&#xff0c;B存储到节点2上&#xff0c;C存储到…

Unity 过场工具(Cutscene)设计(二)

Unity 过场工具(Cutscene)设计&#xff08;二&#xff09; 本章主要分析一下过场一般的必要组成元素&#xff0c;以及在Unity中的制作方案 镜头 通常来说一个表现要求比较高的过场&#xff0c;需要专业的导演进行运镜操作的。 在Unity中官方有一个很好的镜头插件 Cinemachine…

基于Leaflet的VideoOverlay视频图层叠加实战

前言在基于二维的场景中&#xff0c;也许会遇到以下的需求。在某交通路口或者重要的监控点&#xff0c;需要将实时或者录制的视频信息叠加在地图上。更有甚者&#xff0c;随着设备通讯方式的增强&#xff0c;无人机等设备可以采集实时数据&#xff0c;实时回传到控制终端&#…

纵有疾风起,Petterp与他的2022

引言 每逢年末&#xff0c;都要来聊一聊关于今年的各种事情&#xff0c;今昔也不例外:) 与往年不同的是&#xff0c;今天刚搬完家&#xff0c;现在是晚上 1:44 ,正是忙碌一天后比较头痛的时刻。 此刻写点东西&#xff0c;脑子也许会放松一下。&#x1f916; 坐在桌子前&…

RFID技术应用在服装门店管理

服装行业是一个高度一体化的行业&#xff0c;集设计研发、成衣生产、运输、销售于一体。在这些过程中&#xff0c;传统的服装供应链往往消耗巨大的人力、物力和资金成本&#xff0c;但效果一般。当今市场消费者的需求变幻莫测&#xff0c;时尚潮流日新月异。稍有延误&#xff0…

从零编写MDK的FLM烧录算法

文章目录前言一、将代码中的图片资源下载到外部flash1. 修改分散加载文件2. 添加外部flash算法二、制作FLM文件步骤三、使用STM32CubeMX新建工程前言 上文讲过&#xff0c;当我们要下载编译好的镜像到Flash时&#xff0c;首先要做的一步就是选择合适的Flash下载算法&#xff0…