七大排序算法

2023-11-16

目录

直接插入排序

希尔排序

直接选择排序

堆排序

冒泡排序

快速排序

快速排序优化

非递归实现快速排序

归并排序

非递归的归并排序


排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.

常见的排序算法有插入排序(直接插入排序和希尔排序),选择排序(选择排序和堆排序),交换排序(冒泡排序和快速排序)以及归并排序.

我们将从时间复杂度,空间复杂度,以及排序的稳定性来分析这七大排序.

排序的稳定性

假定在待排序的记录序列中,存在多个具有相同关键字的记录,若经过排序,这些记录的相对次序保持不变,则称这种排序是稳定的.

直接插入排序

基本思想:把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到 一个新的有序序列

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

时间复杂度:考虑最坏的情况下,就是全逆序的时候,此时时间复杂度为O(N^2).

最好的情况下,有序的时候,此时时间复杂度为O(N).得出一个结论:当数据量不多,且数据基本上是趋于有序的时候,此时直接插入排序是非常快的.

空间复杂度:O(1)

稳定性:稳定.一个本身就稳定的排序,可以实现为不稳定的排序;但是一个本身不稳定的排序,不能实现为稳定的排序.


希尔排序

希尔排序(缩小增量排序)是直接插入排序的一个优化.

基本思想:先将数据进行分组,将每一组内的数据先进行排序(这一过程叫做预排序);逐渐缩小组数,直到最后整体看作是一组,采用直接插入排序进行排序.

跳着分组的原因:尽可能的使小的数据靠前,大的数据靠后,从而使整体更加趋于有序.

public static void shellSort(int[] array){
        int gap = array.length;
        while (gap > 1){
            gap /= 2;
            shell(array,gap);
        }

    }

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

当gap>1时,都是预排序,目的是让数组更接近于有序.当gap==1时,此时数组已经接近有序了,这样进行插入排序就会很快.

希尔排序的时间复杂度不好计算,因为gap的取值方法有很多,导致很难去计算.目前还没有证明gap具体取多少是最快的.

时间复杂度:O(N^1.3),估计的时间复杂度.

空间复杂度:O(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[minIndex] > array[j]){
                    minIndex = j;
                }
            }
            //处理两个下标一样的情况
            if (i != minIndex) {
                int tmp = array[i];
                array[i] = array[minIndex];
                array[minIndex] = tmp;
            }
        }
    }

直接选择排序好理解,但是效率低下.

时间复杂度:O(N^2)

空间复杂度:O(1)

稳定性:不稳定


堆排序

排升序要建大堆,排降序建小堆.

升序,建大堆:堆顶元素和最后一个元素交换,将数组长度-1,在对堆顶元素进行向下调整,依次循环.

public static void heapSort(int[] array){
        createBigHeap(array);
        int end = array.length-1;
        while(end > 0){
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }
    //建立大根堆
    //从最后一颗子树的根节点开始,每一次都进行向下调整
    private static void createBigHeap(int[] array){
        for (int parent = (array.length-1-1)/2; parent >=0 ; parent--) {
            shiftDown(array,parent,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] < array[child+1]){
                child++;
            }
            if (array[child] > array[parent]){
                swap(array,child,parent);
                parent = child;
                child = (2*parent)+1;
            }else {
                break;
            }
        }
    }
 public static void swap(int[] array,int x,int y){
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

时间复杂度:O(n*logn)

空间复杂度:O(1)

稳定性:不稳定


冒泡排序

相邻元素之间的比较.

public static void bubbleSort(int[] array){
        //最外层控制的是趟数
        //五个数据需要比较四趟
        for (int i = 0; i < array.length-1; i++) {
            //加一个标志对冒泡排序进行优化
            boolean flg = false;
            for (int j = 0; j < array.length - 1 - i; j++) {
                if (array[j] > array[j+1]){
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            if (flg == false){
                break;
            }
        }
    }

时间复杂度:(不考虑优化)O(n^2)

空间复杂度:O(1)

稳定性:稳定


快速排序

快速排序是Hoare 1962 年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
public static void quickSort(int[] array){
        quick(array,0,array.length-1);
    }
    public static void quick(int[] array,int start,int end){
        //大于号 是预防1 2 3 4 5 6,直接没有左树或没有右树
        if (start >= end){
            return;
        }
        int pivot = partition(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }
    //找基准
    //Hoare版本
    private static int partition(int[] array,int left,int right){
        int i = left;
        int pivot = array[left];
        while (left < right){
            while (left < right && array[right] >= pivot){
                right--;
            }
            //right下标的值小于pivot

            while (left < right && array[left] <= pivot){
                left++;
            }
            //left下标的值大于pivot
            swap(array,left,right);
        }
        //循环走完,left和right相遇
        //交换 和原来的left
        swap(array,left,i);
        //返回基准
        return left;
    }

时间复杂度:O(n*logn)

此时间复杂度不是最坏情况下的时间复杂度,最坏情况下是有序的情况下,此时树的高度是n,时间复杂度是O(n^2),空间复杂度也变成了O(n).

空间复杂度:O(logn)

稳定性:不稳定

挖坑法找基准

private static int partition(int[] array,int left,int right){
        int pivot = array[left];
        while (left < right){
            while (left < right && array[right] >= pivot){
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= pivot){
                left++;
            }
            array[right] = array[left];
        }
        array[left] = pivot;
        //返回基准
        return left;
    }

挖坑法是找到合适的值直接交换.

需要注意的是挖坑法和hoare找基准的结果是不一样的但是最终都是有序的.


快速排序优化

当数据有序的时候,快速排序的时间复杂度达到最大,而且空间复杂度也会随之改变.

三数取中法

为了使二叉树的划分尽可能的均匀,我们在left,mid,right三个数中,取出中间大的值,来作为key(left).

比如1 2 3 4 5,如果不采用三数取中法,那么1作为key走下来,left和right在1相遇,基准就是1,此时划分的就是单分支的树;如果采用三数取中,将数组顺序调整为 3 2 1 4 5,3作为key,走下来,left和right在中间位置相遇,将3和1交换,变为 1 2 3 4 5,虽然又变回去了,但是此时的基准在中间位置3的地方,此时二叉树划分的将更加均匀.

public static void quick(int[] array,int start,int end){
        //大于号 是预防1 2 3 4 5 6,直接没有左树或没有右树
        if (start >= end){
            return;
        }
        //在找基准之前,进行三数取中的优化,尽量去解决划分不均匀的问题.
        //在left,mid,right三个数中找到中间大的数字做key
        int index = findMidValueOfIndex(array, start, end);
        swap(array,start,index);

        int pivot = partitionHoare(array,start,end);
        quick(array,start,pivot-1);
        quick(array,pivot+1,end);
    }
    //3个数中取中位数
    private static int findMidValueOfIndex(int[] array,int start,int end){
            int minIndex = (start+end)/2;
            if (array[start] > array[end]){
                if (array[minIndex] > array[start]){
                    return start;
                }else if (array[minIndex] < array[end]){
                    return end;
                }else {
                    return minIndex;
                }
            }else {
                if (array[minIndex] > array[end]){
                    return end;
                }else if (array[minIndex] < array[start]){
                    return start;
                }else {
                    return minIndex;
                }
            }
    }

我们除了采取这种优化之外,还可以在快速排序递归到小区间的时候,采用插入排序.

因为插入排序在数据趋于有序并且数据量小的时候,排序的速度非常快.


非递归实现快速排序

 public static void quickSort2(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int start = 0;
        int end = array.length-1;
        int pivot = partition(array,start,end);
        //1.判断左边是不是有2个元素
        if(pivot > start+1) {
            stack.push(start);
            stack.push(pivot-1);
        }
        //2.判断右边是不是有2个元素
        if(pivot < end-1) {
            stack.push(pivot+1);
            stack.push(end);
        }
        while (!stack.isEmpty()) {
            end = stack.pop();
            start = stack.pop();
            pivot = partition(array,start,end);
            //3.判断左边是不是有2个元素
            if(pivot > start+1) {
                stack.push(start);
                stack.push(pivot-1);
            }
            //4.判断右边是不是有2个元素
            if(pivot < end-1) {
                stack.push(pivot+1);
                stack.push(end);
            }
        }

归并排序

先分解,后合并.

将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并.

public static void mergeSort1(int[] array) {
        mergeSortChild(array,0,array.length-1);
    }

    private static void mergeSortChild(int[] array,int left,int right) {
        if(left == right) {
            return;
        }
        int mid = (left+right) / 2;
        mergeSortChild(array,left,mid);
        mergeSortChild(array,mid+1,right);
        //合并
        merge(array,left,mid,right);
    }

    private static void merge(int[] array,int left,int mid,int right) {
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArr = new int[right-left+1];
        int k = 0;//表示tmpArr 的下标
        while (s1 <= e1  && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmpArr[k++] = array[s1++];
            }else{
                tmpArr[k++] = array[s2++];
            }
        }
        while (s1 <= e1) {
            tmpArr[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmpArr[k++] = array[s2++];
        }
        //tmpArr当中 的数据 是right  left 之间有序的数据
        for (int i = 0; i < k; i++) {
            array[i+left] = tmpArr[i];
        }
    }

时间复杂度:O(n*logn).

空间复杂度:O(n)

稳定性:稳定


非递归的归并排序

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

七大排序算法 的相关文章

随机推荐

  • windows忘记mysql5.7密码修改密码

    1 打开第一个cmd窗口执行 net stop mysql57 2 在第一个cmd窗口执行 mysqld defaults file C ProgramData MySQL MySQL Server 5 7 my ini skip gran
  • 机器学习笔记 soft-DTW(论文笔记 A differentiable loss function for time-series)

    1 soft DTW来由 DTW 算法通过动态规划求解了两个序列的相似度 这个过程1是离散的 不可微的 如果要将其应用作为神经网络的损失函数 这是不行的 因为神经网络通过对损失函数结果进行梯度下降的方法 更新参数 要求损失函数可微 2 符号
  • sqlmap详细使用介绍

    SQLmap介绍 sqlmap是一个自动化的SQL注入工具 其主要功能是扫描 发现并利用给定的URL进行SQL注入 目前支持的数据库有MySql Oracle Access PostageSQL SQL Server IBM DB2 SQL
  • redis之单机多节点集群,javaee教程网上购书系统

    bind 192 168 40 142 5 将redis cluster redis01文件复制5份到redis cluster目录下 redis02 redis06 创建6个redis实例 模拟Redis集群的6个节点 然后将其余5个文件
  • html flex 兼容ie9,flex布局及其兼容解决方案_蓝戒的博客

    导语 布局的传统解决方案 基于盒状模型 依赖 display属性 position属性 float属性 它对于那些特殊布局非常不方便 比如 垂直居中就不容易实现 2009年 W3C提出了一种新的方案 Flex布局 可以简便 完整 响应式地实
  • LevelDB源码分析之内存管理类arena

    LevelDB源码分析之内存管理类arena Leveldb的大部分内存管理依赖于C 语言的默认实现 也就是不对内存进行管理 只是在memtable的实现中用到了一个简单的内存管理器 arena 因为memtable的内部实现skip li
  • ElasticSearch6.x 之路由规则

    1 创建文档指定路由 语法规则 http elasticsearch 服务器访问地址 索引名称 文档名称 文档主键编号 routing 路由名称 Put请求 携带文档属性参数 实列 http 192 168 1 74 9200 shoppi
  • @Override异常

    文章目录 异常 异常 异常现象 导入一个新的maven项目发现很奇怪的一个bug 提示错误 Override is not allowed when implementing interface method 异常原因 Override从j
  • PAT乙级1057 数零壹 (20 分)

    1057 数零壹 20 分 一 问题描述 给定一串长度不超过 10 5 的字符串 本题要求你将其中所有英文字母的序号 字母 a z 对应序号 1 26 不分大小写 相加 得到整数 N 然后再分析一下 N 的二进制表示中有多少 0 多少 1
  • 使用Java实现JDBC 驱动程序,连接本地文件

    要使用Java实现JDBC驱动程序以连接您的本地文件 您可以使用H2数据库提供的嵌入式数据库引擎 import java sql import java util Properties public class LocalFileDrive
  • 回归评估指标:MSE、R2

    原数据标签 预测结果 平均值 1 均方误差 MSE Mean Squared Error 2 均方根误差 RMSE 对MSE开平方 3 R2 R Square 注 R2一般取 0 1 0表示拟合效果不好 如果出现负值 首先考虑数据集是否有问
  • 读书笔记 -《Python 黑帽子》 ( 二 )

    读书笔记系列文章 一直都在读书 读了忘 忘了再读 不如把每次学到的东西都写下来 第三章 网络 原始套接字和流量嗅探 我的工作内容就是用C 语言写嗅探工具和 DPI 基本的工作原理和本章的内容是非常相似的 所以理解起来会比较容易一些 arp
  • Java计算当天剩余秒、当月剩余天

    日常开发中会遇到关于日期的计算比如 当天剩余的秒数 当月的天数 当月剩余天数等等 实现思路 获取当天剩余的秒数 获取当月的天数 获取当天是是这个月的第几天 计算两个时间的差值 代码如下 LocalDateTime midnight Loca
  • ubuntu安装ElasticSearch-head插件

    插件安装 1 下载插件 默认你已经安装git git clone https github com mobz elasticsearch head git 2 检查是否安装node node v 如果没有安装 先安装python sudo
  • 0.43 版本frp 穿透后 404,内网访问正常

    解决办法 把 frps ini 中 common 块中加的 vhost http port 6001 删除就好 nginx 配置 6001 端口 然后 frpc ini 配置如下 web type http local ip 127 0 0
  • ConcurrentHashMap原理,jdk7和jdk8版本的区别

    jdk7 分段锁 数据结构 ReentrantLock Segment HashEntry 一个Segment中包含一个HashEntry数组 每个 HashEntry又是一个链表结构 元素查询 二次hash 第一次Hash定位到Segme
  • 记录一次优化运行时间的经验,QTableWidget竟有这么大的坑

    前两天接到一个任务 一个VS2015 qt5 osgEarth实现的项目 在向osgEarth场景中添加卫星时 用时过长 首先看一下代码逻辑 点击 添加 按钮并选择要添加的卫星后 我选择了七百多颗卫星 先将卫星相关参数添加到QTableWi
  • JS document.write()换行

    一开始想到的是用 n 未能达到换行效果 通过多个参数实现换行效果 通过传递多个参数 即可实现换行效果 document write br ar 效果 示例源码
  • Qt实战 信号槽有哪些连接方式?

    相信大多数面试过Qt的同学都会被问的问题 是的 因为在Qt的世界中 这简直太太太基础啦 而你只知道Qt AutoConnection 从未关心过其他连接方式 如果被我说中了 那就耐心看完吧 Qt AutoConnection 自动连接 这是
  • 七大排序算法

    目录 直接插入排序 希尔排序 直接选择排序 堆排序 冒泡排序 快速排序 快速排序优化 非递归实现快速排序 归并排序 非递归的归并排序 排序 所谓排序 就是使一串记录 按照其中的某个或某些关键字的大小 递增或递减的排列起来的操作 常见的排序算