七大排序算法和计数排序

news2024/11/25 5:49:15

文章目录

  • 一、直接插入排序
  • 二、希尔排序
  • 三、直接选择排序
  • 四、堆排序
  • 五、冒泡排序
  • 六、快速排序
    • 6.1递归实现快速排序
    • 6.2非递归实现快速排序
  • 七、归并排序
    • 7.1递归实现归并排序
    • 7.2非递归实现归并排序
  • 八、计数排序

以下排序以从小到大排序为例

一、直接插入排序

时间复杂度:
最好情况:完全有序的情况 1 2 3 4 5 O(N)
最坏情况:完全逆序的情况 5 4 3 2 1 O(N^2)(相当于等差数列求和)
空间复杂度:O(1)
稳定性:稳定
当所给的数据越有序,直接插入排序越快
有一组基本有序的数据时,用直接插入排序较好

public static void insertSort(int[] array) {
        for(int i = 1; i < array.length; i++) {
            int j = i - 1;
            int tmp = array[i];
            for(; j >= 0; j--) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

二、希尔排序

希尔排序是对直接插入排序的优化,跳跃式的分组可能会将更小的元素尽可能往前方
增量为多少(gap为多少),则被分为多少组
时间复杂度:N^1.3 ~ N^1.5
空间复杂度:O(1)
稳定性:不稳定

public static void shellSort(int[] array) {
        int gap = array.length;//增量为多少(gap为多少),则被分为多少组
        while(gap > 1) {//里面已经包括了排序gap为1的情况
            gap /= 2;
            shell(array, gap);
        }
    }
    private static void shell(int[] array, int gap) {
        for(int i = gap; i < array.length; i++) {//i+=gap也行,因为gap最后会变为1
            int j = i - gap;
            int tmp = array[i];
            for(; j >= 0; j -= gap) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + gap] = array[j];
                }else {
                    break;
                }
            }
            array[j + gap] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }

三、直接选择排序

时间复杂度:不管情况是好还是坏,下面两种写法都是O(N^2)(相当于等差数列求和)
空间复杂度:O(1)
稳定性:不稳定

public static void selectSort1(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;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                }
            }
            swap(array, i, minIndex);
        }
    }
    private static void swap(int[]array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    //写法二
    public static void selectSort2(int[] array){
        int left = 0;
        int right = array.length - 1;
        while(left < right) {//与写法一相比将写法一最外层的for循环改为了while循环
                int minIndex = left;
                int maxIndex = left;//maxIndex一定是left,因为下面的j下标是从left+1开始从前往后遍历
                for(int i = left + 1; i <= right; i++) {
                    if(array[i] < array[minIndex]) {
                        minIndex = i;//在所有的j下标元素中找比array[minIndex]还要小的元素的下标
                    }
                    if(array[i] > array[maxIndex]) {
                        maxIndex = i;//在所有的j下标元素中找比array[maxIndex]还要大的元素的下标
                    }
                }
                swap(array, left, minIndex);
                //当最大值原来刚好在最小值的位置(left位置)时,则上一步已经将最小值与left交换,此时最大值在原来最小值的位置,
                if(maxIndex == left) {//所以要做这一步操作
                    maxIndex = minIndex;
                }
                swap(array, right, maxIndex);
                left--;
                right++;
        }
    }

四、堆排序

时间复杂度:O(N)(创建大根堆)+ O(NlogN) (堆的每个节点进行向下调整) 即O(NlogN)
空间复杂度:O(1)
稳定性:不稳定
数据量非常大的时候,堆排序一定比希尔排序快,因为希尔排序的时间复杂度为N^1.3 ~ N^1.4,堆排是对数希尔排是指数

public static void heapSort(int[] array){
        creatBigHeap(array);
        int end = array.length - 1;
        while(end > 0) {
            swap(array, 0, end);//因为是大堆,堆顶的元素最大,将堆顶元素与队尾元素交换,使队尾元素变成队内最大元素
            shiftDown(array, 0, end);//在这里end=array.length-1,而在shiftDown中end代表数组的总长度,
            end--;                          //相当于去掉队尾元素再进行向下调整
        }
    }
    private static void creatBigHeap(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 end) {
        int child = 2 * parent + 1;
        while(child < end) {//因为end传的是array.length,所以条件用<而不用<=
            if(child + 1 < end && array[child] < array[child + 1]) {
                child++;
            }
            if(array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;
            }else {
                break;
            }
        }
    }

五、冒泡排序

时间复杂度:O(N^2) 加了优化之后,最好的情况(只比较一趟)则是O(N)
空间复杂度: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) {
                return;
            }
        }
    }

六、快速排序

快速排序(相当于以基准为根创建二叉树)
时间复杂度:
最好情况:O(N*logN) 满二叉树/完全二叉树
最坏情况:O(N^2)(相当于等差数列求和) 单分支的树
空间复杂度:
最好情况:O(logN) 满二叉树/完全二叉树的高度
最坏情况:O(N) 单分支的树创建N个节点
稳定性:不稳定
三种求基准的方法:Hoare法,挖坑法,前后指针法

6.1递归实现快速排序

在这里递归实现的快速排序做了两个优化,第一个优化是使用三数取中法求原始基准,使创建出来的树更像满二叉树,降低了树的高度,降低了空间复杂度
在这里插入图片描述
第二个优化是递归到一定程度时对每个区间使用插入排序,因为区间越来越小且区间的内容越来越有序,这时这部分区间可以用插入排序以减少递归次数

public static void quickSort(int[] array){
        quickSortF(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void quickSortF(int[] array, int left, int right) {
        if(left >= right) return;//这是递归的结束条件,有=,因为left=right时不用再创建节点了,这个节点已经有序
        //递归到后面的较小区间时用插入法
        if(right - left + 1 <= 7) {//随着快速排序的进行,整个数据正在趋于有序,当区间越来越小且区间的内容越来越有序时,
            insertSort1(array, left, right);//这部分区间可以用插入排序,这样做可以减少递归的次数
            return;
        }
        //三数取中法,降低了二叉树的高度,降低了空间复杂度,使空间复杂度变为O(logn)
        int midIndex = midOfTree(array, left, right);
        swap(array, midIndex, left);//交换完之后保证left下标是三个数中中间大的数字
        int pivot = partition2(array, left, right);//找到一次基准相当于排好了当前基准这个元素在数组中的顺序,找到基准后,基准左边都是比基准小的,基准右边都是比基准大的
        //先递归左边,递归完左边再递归右边
        quickSortF(array, left, pivot - 1);
        quickSortF(array, pivot + 1, right);
    }
    //插入法
    private static void insertSort1(int[] array, int left, int right) {
        for(int i = left + 1; i <= right; i++) {
            int j = i - 1;
            int tmp = array[i];
            for(; j >= left; j--) {//跳出循环有两种情况,1.j<0 2.array[j]<=tmp
                if(array[j] > tmp) {//如果条件变为array[j]>=tmp,则排序变为不稳定
                    array[j + 1] = array[j];
                }else {
                    break;
                }
            }
            array[j + 1] = tmp;//每次把tmp插入数组后,都会重新获取i和j的位置
        }
    }
    //在三数中找到中间大小数的下标
    private static int midOfTree(int[] array, int left, int right) {
        int mid = (left + right) / 2;
        if(array[left] < array[right]) {
            if(array[mid] < array[left]) {
                return left;
            }else if(array[mid] > array[right]) {
                return right;
            }else {
                return mid;
            }
        }else {
            if(array[mid] > array[left]) {
                return left;
            }else if(array[mid] < array[right]) {
                return right;
            }else {
                return mid;
            }
        }
    }
    //三种找基准的方法,在排序的过程中序列可能会不一样,但最终排好序的结果一样,建议优先使用挖坑法,right参数传的都是array.length-1
    //Hoare法找基准
    private static int partition1(int[] array, int left, int right) {
         int key = array[left];
         int i = left;//将基准的下标保存到i中,因为后面要用left与right相交位置的元素与基准进行交换,这里的交换函数只传下标
        while(left < right) {
            while (left < right && array[right] >= key) {//left<right这个条件很重要,因为right不断--,此时再判断array[right]>=key时array[right]有可能会越界
                right--;//先走right,因为一开始基准为left,先走right,left和right相遇的地方才能是比基准小的,才能把比基准小的与基准交换放在基准前
            }
            while (left < right && array[left] <= key) {//array[left]<=key或上面array[right]>=key的=一定要有,否则可能会进入死循环(如当left和right的值相同时)
                left++;
            }
            swap(array, left, right);
        }
        swap(array, left, i);
        return left;
    }
    //挖坑法找基准
    private static int partition2(int[] array, int left, int right) {
        int key = array[left];
        while(left < right) {
            while (left < right && array[right] >= key) {
                right--;
            }
            array[left] = array[right];
            while (left < right && array[left] <= key) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = key;
        return left;
    }
    //前后指针法找基准
    private static int partition3(int[] array, int left, int right) {
       int prev = left;
       int cur = left + 1;
       while(cur <= right) {
           if(array[cur] < array[left] && array[++prev] != array[cur]) {//cur在前面找比基准小的,prev保持在cur找到比基准小的之后要跟prev交换的那个位置,prev是前置++
               swap(array, prev, cur);//代码走到这说明cur和prev拉开了距离且cur<left,prev>=left
           }
           cur++;//array[cur]>=array[left]时cur直接往前走,与prev拉开距离
       }
       swap(array, prev, left);
       return prev;
    }

6.2非递归实现快速排序

public static void quickSortNor(int[] array) {
        Stack<Integer> stack = new Stack<>();
        int left = 0;
        int right = array.length - 1;
        int piovt = partition2(array, left, right);
        if(piovt - 1 > left) {
            stack.push(left);//往栈上先放left后放right,则出栈时先出right后出left
            stack.push(piovt - 1);
        }
        if(piovt + 1 < right) {
            stack.push(piovt + 1);
            stack.push(right);
        }
        while(!stack.isEmpty()) {//因为这里出栈时先出right后出left,根据出出来的right和left找新的基准,所以相当于先递归二叉树右边,再递归二叉树左边
            right = stack.pop();
            left = stack.pop();
            piovt = partition2(array, left, right);
            if(piovt - 1 > left) {
                stack.push(left);
                stack.push(piovt - 1);
            }
            if(piovt + 1 < right) {
                stack.push(piovt + 1);
                stack.push(right);
            }
        }
    }

七、归并排序

时间复杂度:O(N*logN) 归并排序在合并过程中每层都要遍历N次,一共logN层
空间复杂度:O(N) 归并排序在最后合并的时候要额外申请与原来数组一模一样大小的数组
归并排序的缺点是空间复杂度过大
稳定性:稳定
原始分开的区间从0下标开始和原始分开的区间不从0下标开始的合并过程:
在这里插入图片描述
在这里插入图片描述

7.1递归实现归并排序

public static void mergeSort(int[] array) {
        mergeSort(array, 0, array.length - 1);//参数right传的是array.length-1
    }
    private static void mergeSort(int[] array, int left, int right) {
        if(left >= right) return;
        int mid = (left + right) / 2;
        //分裂左边
        mergeSort(array, left, mid);
        //分裂右边
        mergeSort(array, mid + 1, right);
        //合并,在合并的过程中进行了排序
        merge(array, left, right, mid);
    }
    private static void merge(int[] array, int left, int right, int mid) {
        int s1 = left;
        int s2 = mid + 1;
        int[] tmpArr = new int[right - left + 1];//申请一个新的数组,大小为right-left+1
        int k = 0;
        while(s1 <= mid && s2 <= right) {//满足这个循环条件说明s1~mid和s2~right这两个区间都同时有数据
            if(array[s2] < array[s1]) {//若条件改为array[s2]<=array[s1]则排序变为不稳定
                tmpArr[k++] = array[s2++];
            }else {
                tmpArr[k++] = array[s1++];
            }
        }
        while(s1 <= mid) {
            tmpArr[k++] = array[s1++];
        }
        while(s2 <= right) {
            tmpArr[k++] = array[s2++];
        }
        for (int i = 0; i < tmpArr.length; i++) {
            array[i + left] = tmpArr[i];//将tmpArr数组拷回原来数组时,赋给array[i+left],因为原来数组的left有可能不是0
        }
    }

7.2非递归实现归并排序

在合并之前分的组数不断减少,每组元素不断增多,一个一个为一组(gap为1),再到两个两个为一组(gap为2),如此类推
在这里插入图片描述
合并之前mid和right有可能会越界,此时要做出调整
在这里插入图片描述

public static void mergeSortNor(int[] array) {
        int gap = 1;
        while(gap < array.length) {
            for (int i = 0; i < array.length; i += 2*gap) {
                int left = i;
                int mid = left + gap - 1;
                int right = mid + gap;
                if(mid >= array.length) {//mid有可能会越界,越界时要做出调整
                    mid = array.length - 1;
                }
                if(right >= array.length) {//right有可能会越界,越界时要做出调整
                    right = array.length - 1;
                }
                merge(array, left, right, mid);
            }
            gap *= 2;
        }
    }

八、计数排序

时间复杂度:O(2N+数据范围) 即O(MAX(N,数据范围))
空间复杂度:O(数据范围)
稳定性:稳定,但以下代码实现的计数排序是不稳定的
计数排序适合排序某个区间内且集中的数据

   public static void countSort(int[] array) {
        int minVal = array[0];
        int maxVal = array[0];
        //求数组中的最大值和最小值
        for (int i = 0; i < array.length; i++) {
            if(array[i] < minVal) {
                minVal = array[i];
            }
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
        }
        int[] count = new int[maxVal - minVal + 1];//依据最大值和最小值来确定计数数组的大小
        //遍历原来的数组进行计数
        for (int i = 0; i < array.length; i++) {
            count[array[i] - minVal]++;//计数数组的下标相当于要排序数组的元素内容
        }
        //遍历count,把当前元素写回array
        int index = 0;
        for (int i = 0; i < count.length; i++) {
            while(count[i] != 0) {
                array[index] = i + minVal;
                index++;
                count[i]--;
            }
        }
    }

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

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

相关文章

如何从gitee上下载项目并把它在本地运行起来

有时候我们会想到在gitee上下载下来项目&#xff0c;那么怎么把项目下载到本地并跑起来呢&#xff1f; 第一步&#xff1a;在git上找到你想要克隆下来的项目&#xff0c;按照如下操作复制项目地址连接&#xff0c;如下图&#xff1a; 以上可以选择HTTPS和SSH两种形式。 第二步…

在SPringBoot中整合Mybatis-plus以及mybatis-puls的基本使用

创建SPringBoot项目 1.选择创建项目 2.创建SPringBoot项目 3.选择SPringBoot的版本和依赖 4.导入mysql,druid,mybatis-plus和lombok的依赖,导入后记得更新依赖 <dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId…

Mybatis单元测试,不使用spring

平时开发过程中需要对mybatis的Mapper类做单元测试&#xff0c;主要是验证语法是否正确&#xff0c;尤其是一些复杂的动态sql&#xff0c;一般项目都集成了spring或springboot&#xff0c;当项比较大时&#xff0c;每次单元测试启动相当慢&#xff0c;可能需要好几分钟&#xf…

Mac 四大常用清理软件推荐,软件特色下载教程横向评测

Mac 一般来说基本是不会中毒的&#xff0c;而且像 现在的 windows 也是很少中毒&#xff0c;但我们可能还是需要一款杀毒清理软件&#xff0c;主要是为了清理垃圾&#xff0c;统一查看并管理软件开机自启、权限信息等&#xff0c;统一卸载清理等功能&#xff0c;另外我们可能还…

【机器学习】PyTorch手动实现Logistic回归算法

参考地址&#xff1a;点击打开 计算较为繁琐&#xff0c;需要用到sigmoid函数和梯度下降算法&#xff0c;步骤主要如下&#xff1a; 二项分布概率公式表示最大似然估计和对数化计算求道带入梯度下降算法计算和优化 代码&#xff1a; import numpy as np import matplotlib.py…

05.计算机网络——TCP协议

文章目录 TCP协议段格式TCP交付过程TCP解包过程确认应答机制\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kDvQFCTM-1689855767485)(C:\Users\11794\AppData\Roaming\Typora\typora-user-images\image-20230719204622485.png)\] 32位序号/32位确认…

深度学习anaconda+pycharm+虚拟环境迁移

一、下载好anaconda和pycharm安装包。 下载anaconda:Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror pycharm汉化包 二、安装anaconda 深度学习环境配置-Anaconda以及pytorch1.2.0的环境配置&#xff08;Bubbliiiing 深度学习 教程&…

Pycharm远程服务器连接教程

第一步 只有Pycharm专业版才能远程连接服务器 第二步&#xff1a;远程连接部分 点击左上角的号新建一个连接&#xff0c;起一个名字&#xff0c;比如叫dilab191&#xff1a; 设置SSH参数 Tools-Development-Options 第三步, 添加远程服务器解释器部分 File-settings-Project …

spring复习:(50)@Configuration注解配置的singleton的bean是什么时候被创建出来并缓存到容器的?

一、主类&#xff1a; 二、配置类&#xff1a; 三、singleton bean的创建流程 运行到context.refresh(); 进入refresh方法&#xff1a; 向下运行到红线位置时&#xff1a; 会实例化所有的singleton bean.进入finisheBeanFactoryInitialization方法&#xff1a; 向下拖动代…

旧版Xcode文件较大导致下载总是失败但又不能断点续传重新开始的解决方法

问题&#xff1a; 旧版mac下载旧版Xcode时需要进入https://developer.apple.com/download/all/?qxcode下载&#xff0c;但是下载这些文件需要登录。登录后下载中途很容易失败&#xff0c;失败后又必须重新下载。 解决方案&#xff1a; 下载这里面的内容都需要登录&#xff0…

华为、阿里巴巴、字节跳动 100+ Python 面试问题总结(五)

系列文章目录 个人简介&#xff1a;机电专业在读研究生&#xff0c;CSDN内容合伙人&#xff0c;博主个人首页 Python面试专栏&#xff1a;《Python面试》此专栏面向准备面试的2024届毕业生。欢迎阅读&#xff0c;一起进步&#xff01;&#x1f31f;&#x1f31f;&#x1f31f; …

苹果手机IOS自带科学计算器冷门功能使用

前言 事件是这样的&#xff0c;前几天有人想买个斜坡枕&#xff0c;斜坡枕是个直角三角形&#xff0c;已知短直角边长度是14CM&#xff0c;长直角边长度是80CM&#xff0c;他想知道这个斜坡是多少度&#xff0c;我说这个不是很简单吗&#xff1f;计算一下 a r c t a n ( 14 80…

C# List 详解七

目录 42.Sort() 43.ToArray() 44.ToString() 45.TrimExcess() 46.TrueForAll(Predicate) C# List 详解一 1.Add(T)&#xff0c;2.AddRange(IEnumerable)&#xff0c;3.AsReadOnly()&#xff0c;4.BinarySearch(T)&#xff0c; C# List 详解二 5.Cl…

Matlab 刚性问题求解器-ode23s

1、ode23s介绍 ode23s&#xff08;stiff differential equation solver&#xff09;是MATLAB中的一种求解刚性&#xff08;stiff&#xff09;微分方程的数值方法。刚性微分方程通常具有多个时间尺度差异较大的变量&#xff0c;并且其中至少有一个变量具有快速变化的特性。 od…

Antv G6 force分布式布局 icon“+“ “-“收缩自定义,关系图子节点

子节点收缩 const collapseIcon (x, y, r) > {// 折叠return [[M, x - r, y],[a, r, r, 0, 1, 0, r * 2, 0],[a, r, r, 0, 1, 0, -r * 2, 0],[M, x - r 4, y],[L, x - r 2 * r - 4, y]]}const expandIcon (x, y, r) > {// 拓展return [[M, x - r, y],[a, r, r, 0, 1,…

SQL优化——插入数据优化(load指令的使用)

插入数据时的优化主键优化order by优化group by优化limit优化count优化update优化 1.插入数据时的优化 批量插入数据时最好最多别超过一千条&#xff0c;如果一次批量插入几万条数据&#xff0c;可以将其分割成多条insert语句进行插入。 mysql的事务提交方式是默认自动提交的…

Linux 下centos 查看 -std 是否支持 C17

实际工作中&#xff0c;可能会遇到c的一些高级特性&#xff0c;例如std::invoke&#xff0c;此函数是c17才引入的&#xff0c;如何判断当前的gcc是否支持c17呢&#xff0c;这里提供两种办法。 1.根据gcc的版本号来推断 gcc --version&#xff0c;可以查看版本号&#xff0c;笔者…

15.矩阵运算与img2col方式的卷积

使用矩阵计算卷积 GEMM算法 矩阵乘法运算(General Matrix Multiplication)&#xff0c;形如&#xff1a; C A B , A ∈ R m k , B ∈ R k n , C ∈ R m n C AB, A\in \mathbb{R}^{m\times k},B\in \mathbb{R}^{k\times n},C\in \mathbb{R}^{m\times n} CAB,A∈Rmk,B∈Rk…

vite4.x+vue3.x中使用装饰器语法,eslint校验不识别@的报错处理方法

在项目中&#xff0c;使用了pre-commit校验代码&#xff0c;eslint校验无法识别,导致一直无法提交代码&#xff0c;查找了资料&#xff0c;eslint版本过低&#xff0c;不能解决现在遇到的问题 最终正确的配置方法&#xff1a; 装饰器配置文件babel.config.js module.exports …

了解应用层

应用层 1. 概述2. 应用程序组织方式2.1 C/S方式2.1 P2P方式 3. 动态主机配置协议DHCP3.1 DHCP工作流程 4. 域名系统DNS4.1 域名结构4.2 域名分类4.3 域名服务器4.3.1 分类 4.4 DNS域名解析过程 5. 文件传输协议FTP5.1 FTP工作流程 6. 电子邮件系统6.1 邮件信息格式6.2 简单邮件…