七大排序---详细介绍

news2024/12/25 2:21:24

插入排序

从第二个数,往前面进行插入,默认第一个数字有序,插入第二个,则前两个都有序了,一个一个往后选择数字,不断向前进行插入

直接插入排序

时间复杂度:

  • 最好情况:全部有序,此时只需遍历一次,最好的时间复杂度为O ( n )

  • 最坏情况:全部反序,内层每次遍历已排序部分,最坏时间复杂度为 O(N^2)

  • 综上,因此直接插入排序的平均时间复杂度为O(N^2)

空间复杂度:

  • 没有使用多余的空间,是在原数组上进行操作,所以时间复杂度为O(1)

稳定性:

  • 稳定的,相同大小的数字前后位置不变

思路举例:

代码详细实现:

    public void sort(int[] arr) {
        if (arr == null || arr.length == 0) return;
        //由于起始的时候,第一个数已经排好,从后面开始插入即可,所以这个循环从1开始即可
        for (int i = 1; i < arr.length; i++) {
            int temp = arr[i];//暂时存放需要插入的数
            int j;
            //由于需要与前面排好序的进行比较 且 从后往前比较,所以j从i-1开始到0结束
            for (j = i - 1; j >= 0; j--) {
                if (arr[j] > temp) {
                    //如果前面的数大于temp 则这个前面的数向后移动一下
                    arr[j + 1] = arr[j];
                } else {
                    //出现<=temp的数字直接跳出循环
                    break;
                }
            }
            //跳出循环之后,把temp放入j+1的位置,temp就插入好了
            arr[j + 1] = temp;
        }
    }

希尔排序

希尔排序其实是直接插入排序的升级版本,不过是把 数组进行分组然后进行插入排序

时间复杂度:大约在O(n ^ 1.25)到O(1.6 * n ^ 1.25)取决于增量gap的值

稳定性:不稳定

空间复杂度:O(1)

适用场景:相比直接插入排序,希尔排序更适合无序的数据,尤其数据量大的时候,能节省很多运行时间

思路举例:选取增量,进行分组排序,其实相比于直接插入排序就是多了一个分组增量,在代码中体现就是多套一个while循环来改变增量的值




代码实现:

    public void sort(int[] arr) {
        //这里图简单 我们直接使用gap每次/2来确定gap的值
        int gap = arr.length;
        while (gap != 1) {
            gap /= 2;
            //下面的步骤其实和 直接插入排序一样  不过是每次增加gap
            for (int i = 1; i < arr.length; i += gap) {
                int j;
                int temp = arr[i];
                for (j = i - 1; j >= 0; j--) {
                    if (arr[j] > temp) {
                        arr[j + 1] = arr[j - 1];
                    } else {
                        return;
                    }
                }
                arr[j + 1] = temp;
            }
        }
    }

选择排序

每次从待定元素中,选出最小的那个,然后放在序列的起始位置,继续排序后面的。
直接选择是每个找一遍选择,堆排序是利用大根堆,找最大的放到后面

直接选择排序

时间复杂度:O(N^2)(对数据是否有序不敏感)

空间复杂度:O(1)

稳定性:不稳定过程 每次选出一个最小值放到数据的第一个位置

过程:不断选出最小的,分别放到第一个位置,第二个位置

代码:

//普通版本:找到最小的放到左边
publicvoidselectSort(int[] array) {
    for (inti=0; i < array.length; i++) {
        intminIndex= i;
        for (intj= i + 1; j < array.length; j++) {
            if (array[j] < array[minIndex]) 
                { minIndex = j;}
        }
        swap(array, minIndex, i);
    }
}
//升级优化版本:左右两边同时进行,同时找出最大和最小的
publicvoidselectSortPro(int[] array) {
    int left, right;
    for (left = 0, right = array.length - 1; left < right; left++, right--) {
        intminIndex= left;
        intmaxIndex= right;
        for (intj= left ; j <= right; j++) {
            if (array[j] < array[minIndex])      {   minIndex = j;} 
            elseif (array[j] > array[maxIndex]) {   maxIndex = j;}
        }
        swap(array, left, minIndex);
//如果left位置存放的是最大值,则下一步maxIndex的内容被掉包了,要有个if判断一下if (left == maxIndex) { maxIndex = minIndex; }
        swap(array, right, maxIndex);
    }
}

堆排序

是直接选择排序的优化,相较于直接选择排序, 通过堆的方式 选择 最小值放在前面

时间复杂度:O(N*logN)(大/小根堆调整一次的时间复杂度是 logN)

空间复杂度: O (1)

稳定性: 不稳定

思路过程:加入堆的数据结构,升序使用大根堆,降序使用小根堆

代码:

public void heapSort(int[] arr) {
    createdHeap(arr);//把原来的函数变成大根堆
    for (int i= arr.length - 1; i > 0; i--) {
        swap(arr, 0, i);//大根堆 arr[0]最大 把他挪到最后面
        shiftDown(arr, 0, i);//再继续对 arr
    }
}
//建立大根树的方法
private void createdHeap(int[] arr) {
    int len= arr.length;
    for (int i= len - 1; i >= 0; i--) {  shiftDown(arr, i, len);  }
}
//向下寻找 ----  注意这个代码一定要立马能写出来
private void shiftDown(int[] arr, int parent, int len) {//能够操作的界限是  parent 到 len-1
    int son= parent * 2 + 1;//左孩子的位置
    while (son < len) {
        if (son+1 < len  &&  arr[son] < arr[son+1]){  son++;  }//找到左右孩子比较大的那一个大孩子
        else {
            if (arr[parent] < arr[son]) {//如果大孩子 比 父亲大   那么就交换
                swap(arr, parent, son);
                parent = son;
                son = parent * 2 + 1;
            } else {  break; }
        }
    }
}

交换排序

选出一个较大的数和一个较小的数,交换位置。将小数放前面,大数放到后面。

冒泡排序

是一个很简单的排序,编写最简单代码且不要求什么的时候可以使用。

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

空间复杂度:O(1)

稳定性:稳定

思路:每次交换把大的数都换到后面。每个循环能把最大的数,交换到最后面的位置。

代码:

public void bubbleSort(int[] arr) {
//记住两个for的结束条件
    for (int i=0; i < arr.length - 1; i++) {
        for (int j=0; j < arr.length - 1 - i; j++) {
            if (arr[j]>arr[j+1]){  swap(arr,j,j+1);  }
        }
    }
}`

快速排序

时间复杂度:O(N*logN)

空间复杂度:O(log2n)~O(n) ( 有序数据 的空间复杂度是O(N))

稳定性:不稳定

适用场景:相较于希尔排序,更适合无序,因为是递归有序的情况可能造成堆满

方法:递归,基本写法和树类似

  • 先写一个sort函数,脱裤子放屁,让使用者只传入一个数组即可

  • 写quicklySort函数,参数有 arr,left,right,负责递归,只要right还小于left就不断进行递归执行。

  • 写核心代码 参数依旧是arr,left,right,但返回值为root,负责排序实现。具体的实现方法有下面三种,Hoare版本,挖坑法(个人认为最简答的),双指针法。

代码:

Hoare版

任意选取待排序序列中的一个元素作为基准值,按照该排序码将待排序集合分割成两个子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后再对左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
//脱裤子放屁,让使用者只需要传入一个数组就可以排序
public void quicklySort(int[] arr) {
    quicklySort(arr, 0, arr.length - 1);//传入数组和数组的左右边界
}
private void quicklySort(int[] arr, int left, int right) {
    if (left >= right) return;
    int root = hoare(arr, left, right);
    quicklySort(arr, root + 1, right);//操作左子序列
    quicklySort(arr, left, root - 1);
}
private int hoare(int[] arr, int left, int right) {
    int root = left;
    while (right > left) {
        //为啥要取等号?不取就死循环了,一直交换
        while (right > left && arr[right] >= arr[root]) {
            right--;
        }
        while (right > left && arr[left] <= arr[root]) {
            left++;
        }
        swap(arr, left, right);//这里使用交换,跟挖坑法有所区别
    }
    swap(arr, root, left);
    return left;
}

挖坑法

挖坑法大体思路与hoare法思路相同
基本思想是:将基准值保存到标记位中,这样最右侧位置就形成了一个坑位,然后左侧标注位往右遍历找比基准值大的元素,找到后将该元素填入右侧坑位中,该位置就又形成了一个新的坑位,坑位不断左右变换,重复上述过程直到左右标志位相遇,最后将基准值放入最后的坑位中,最对左右子序列重复上述过程直到整个序列排序完成。
public void digQuicklySort(int[] arr){
    digQuicklySort(arr,0,arr.length-1);
}
private int digQuicklySort(int[] arr,int left,int right){
    int root = dig(arr,left,right);
    digQuickluSort(arr,root+1,right);
    digQuickluSort(arr,left,root+1);
    return root;
}
private int dig(int[] arr,int left,int right){
    int root= arr[left];//key存放基
    while(left<right){//注意比较挖坑法与Hoare的区别
        while(left<right && arr[right]>=root){  right--; }
        arr[left] = arr[right];//这时候右边较大值,放到了左边,而坑到了右边
        while(left<right && arr[left]<= root){   left++; }
        arr[right] = arr[left];//这时候左边较小值又把右边的坑填了,而左边有了坑
    }
    arr[left] = root;//把root基 补到中间的坑
    return left;
}

面试:手写快排.....如何能优化一下?.....

快速排序优化

优化1.中位数做基

问题:快速排序不适合基本有序数据

原因:当元素有序的时候,会一直递归,递归的深度过深,使递归速度较慢。

解决方式:选取一个中位数作为 基,下面以挖坑法为例:

public void digQuicklySort(int[] arr){
    digQuicklySort(arr,0,arr.length-1);
}  
privateintdigQuicklySort(int[] arr,int left,int right){
    int root= findMid(arr, left, right);//找到中位数
    swap(arr, root, left);//将这三个数 的中位数 ,放到最左边,作为基使用。
    root = dig(arr,left,right);
    digQuickluSort(arr,root+1,right);
    digQuickluSort(arr,left,root+1);
    return root;
}
//findMid并不是找这个数组的中位数,而是选择 left right left+right/2 这三个位置中间大小的数
private int findMid(int[] arr, int left, int right) {
    int mid= (left + right) / 2;
    //寻找左边为  left mid right 这三个数的下标
    if (arr[right] > arr[left]) {
        if (arr[mid] > arr[right]) return right;
        else if (arr[mid] < arr[left]) return left;
        else return mid;
    } else {
        if (arr[mid] > arr[left]) return left;
        else if (arr[mid] < arr[right]) return right;
        else return mid;
    }
}

优化2.减少递归

问题:递归过多会造成栈满

解决思路:叶子很多,假如到最后几层我们是不是就可以直接使用 插入排序,而且最后的元素也刚好基本趋于稳定

解决方式 :加一个if语句判断是继续递归还是直接插入排序,下面以挖坑法为例:

非递归实现快排

方法: 使用栈,方法基本参考非递归解决二叉树的问题

速度: 不用很多优化,但代码相对不好想。

优化??:可以尝试加入三数取中,插入排序的优化,参考3.1 3.2

代码:

public void quicklySortNoRecursion(int[] arr){
        if (arr.length<=1) return;
        Stack<Integer> stack = newStack<>();
        int left=0,right = arr.length-1;
        stack.push(left);
        stack.push(right);
        while(!stack.isEmpty()){
            right = stack.pop();//记得一定要先弹出right 因为right是后放进去的
            left = stack.pop();
            int root= quicklyDig(arr,left,right);
            if (root>left+1){//这个代表,左边至少有俩元素
                stack.push(left);//注意这里也要放做再放右
                stack.push(root-1);
            }
            if (root<right-1){//这个代表右边至少有两个元素
                stack.push(root+1);
                stack.push(right);
            }
        }
    }
}

归并排序

基本思想:先分解 后合并

常用场景:排序的数据量过大,内部排序的空间无法满足,这个时候就要使用外部排序。而归并排序就是最常用的外部排序

例子: 内存只有1G 但是需要排序的数据有100G

先把文件分成200份,每个512M,分别对512M排序,内存已经可以放下,任意排序都可以使用,进行2路归并,同时对200分有序文件做归并处理,最后结果就有序了

代码:

递归实现

public class MergeSort {
    publicvoidmergeSort(int[] arr) {
        mergeSort(arr, 0, arr.length - 1);
    }
    private void mergeSort(int[] arr, int left, int right) {
        if (right <= left) return;
        int mid= (right + left) / 2;
        mergeSort(arr, left, mid);//分解左边
        mergeSort(arr, mid + 1, right);//分解右边
        merge(arr, left, right, mid);//合并
    }
    private void merge(int[] arr, int left, int right, int mid) {
        int left1= left, right1 = mid;
        int left2= mid + 1, right2 = right;
        int[] arr2 = newint[right - left + 1];
        intarr2Index=0;//arr2的坐标两个归并段 都有数据,那边的数据小那边先放
        while (right1 >= left1 && right2 >= left2) {
            if (arr[left1] <= arr[left2])  arr2[arr2Index++] = arr[left1++];
            else                           arr2[arr2Index++] = arr[left2++];
        }
        //当走到这里的时候 说明 有个归并段 当中 没有了数据 ,拷贝另一半的全部 到tmpArr数组当中
        while (right1 >= left1)  arr2[arr2Index++] = arr[left1++];
        while (right2 >= left2)  arr2[arr2Index++] = arr[left2++];
        for (inti= left; i <= right; i++) {
            arr[i] = arr2[i - left];
        }
    }
}

非递归实现

public void mergerNoSort(int[] arr) {
    if (arr.length <= 1) return;
    //假设每个元素都是一组数据intgap=1;//表示每组元素的个数
    while (gap <= arr.length) {
        for (inti=0; i < arr.length; i += 2 * gap) {
            int left= i;
            int right= i + 2 * gap - 1;
            if (right > arr.length - 1) {  right = arr.length - 1;     }
            int mid= left + gap-1;
            if (mid>=arr.length)        {  mid = arr.length-1;         }
            merge(arr, i, right, mid);
        }
        gap *= 2;
    }
}

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

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

相关文章

决策树应用

使用Python中的sklearn中自带的决策树分类器DecisionTreeClassifier import sklearn clf sklearn.tree.DecisionTreeClassifier(criterionentropy)sklearn中只实现了ID3与CART决策树&#xff0c;所以我们暂时只能使用这两种决策树&#xff0c;在构造DecisionTreeClassifier类…

计算机视觉OpenCv学习系列:第六部分、图像操作-2

第六部分、图像操作-2第一节、图像几何形状绘制1.几何形状2.填充、绘制与着色3.代码练习与测试第二节、多边形填充与绘制1.多边形绘制函数2.绘制与填充3.代码练习与测试第三节、图像像素类型转换与归一化1.归一化方法与支持2.归一化函数3.代码练习与测试第四节、图像几何变换1.…

小智学长嵌入式入门学习路线_1 C语言基础

原课程链接&#xff1a; 嵌入式开发系统学习路线 从基础到项目 精品教程 单片机工程师必备课程 物联网开发 c语言 2022追更 前言 在学习过程中&#xff0c;老师提到了一个很重要的思想&#xff1a;主要从学习嵌入式的角度学习各项技能。比如c语言&#xff0c;语法有很多&…

【Java|golang】1807. 替换字符串中的括号内容

给你一个字符串 s &#xff0c;它包含一些括号对&#xff0c;每个括号中包含一个 非空 的键。 比方说&#xff0c;字符串 “(name)is(age)yearsold” 中&#xff0c;有 两个 括号对&#xff0c;分别包含键 “name” 和 “age” 。 你知道许多键对应的值&#xff0c;这些关系由…

Linux基本功系列之chmod命令实战

文章目录一. chmod命令介绍二. 语法格式及常用选项三. 参考案例3.1 对全部用户增加写的权限3.2 所有用户减去读的权限3.3 给文件的所有者和所有组加上读写权限3.4 设置所有用户为读写执行的权限3.5 文件拥有着为rwx&#xff0c;所属组为rw&#xff0c;其它为r3.6 去掉所有者的r…

高级Spring之ApplicationContext功能

第一步&#xff0c;我们先来看这个接口的内部结构&#xff0c;了解别人的内部&#xff0c;知己知彼&#xff0c;百战不殆&#xff1a; 这个接口的扩展功能主要体现在它继承的四个接口上&#xff1a; MessageSource&#xff1a;国际化功能 ResourcePatternResolver: 资源访问功…

第23章_Tomcat和JavaEE入门

一、JavaEE简介什么是JavaEEJavaEE&#xff08;Java Enterprise Edition&#xff09;&#xff0c;Java企业版&#xff0c;是一个用于企业级web开发平台。最早由Sun公司定制并发布&#xff0c;后由Oracle负责维护。JavaEE平台规范了在开发企业级web应用中的技术标准.在JavaEE平台…

Acwing——第 87 场周赛

题目链接 4797. 移动棋子 4798. 打怪兽 4799. 最远距离 题目描述 4797. 移动棋子 给定一个 5 行 5 列的方格矩阵&#xff0c;其中一个方格中有一个棋子。 现在&#xff0c;我们希望将棋子移动至矩阵的最中心方格中&#xff0c;即将其移动至矩阵的第 3行第 3列方格中。 每次…

8种时间序列分类方法总结

对时间序列进行分类是应用机器和深度学习模型的常见任务之一。本篇文章将涵盖 8 种类型的时间序列分类方法。这包括从简单的基于距离或间隔的方法到使用深度神经网络的方法。这篇文章旨在作为所有时间序列分类算法的参考文章。 时间序列定义 在涵盖各种类型的时间序列 (TS) 分…

分布式锁与实现(一)-为什么需要分布式锁

1 在开发中的锁是什么 在计算机科学中&#xff0c;锁是在执行多线程时用于强行限制资源访问的同步机制&#xff0c;即用于在并发控制中保证对互斥要求的满足。 在java中我们有两种资源控制方式Synchronized与AQS 1.2 基于Synchronized实现的锁控制 Synchronized是java提供的一…

JDK 8新特性之Lambda表达式

目录 一&#xff1a;使用匿名内部类存在的问题 Lambda表达式写法,代码如下&#xff1a; 二&#xff1a;Lambda的标准格式 三&#xff1a;Lambda的实现原理 四&#xff1a;Lambda省略格式 五&#xff1a;Lambda的前提条件 六&#xff1a;函数式接口 七&#xff1a;Lambd…

05回溯法

文章目录装载问题回溯算法优化算法构造最优解0-1背包问题批处理作业调度问题图的M着色问题N皇后问题最大团问题回溯算法实际上一个类似枚举的搜索尝试过程&#xff0c;主要是在搜索尝试过程中寻找问题的解&#xff0c;当发现已不满足求解条件时&#xff0c;就“回溯”返回&…

12. 字典dict类型详解

1. 基础知识 (1) 字典(dictionary)是Python中另一个非常有用的内置数据类型。 (2) 列表是有序的对象集合&#xff0c;字典是无序的对象集合。两者之间的区别在于&#xff1a;字典当中的元素是通过键来存取的&#xff0c;而不是通过偏移存取。 (3) 字典是一种映射类型&#xff…

Flowable进阶学习(三)流程、流程实例挂起与激活;启动、处理、结束流程的原理以及相关表结构与变动

文章目录流程挂起与激活流程实例挂起与激活启动、处理、结束流程的原理一、启动流程的原理启动一个流程实例时涉及到的表及表结构:ACT_RU_EXECUTION 运行时流程执行实例ACT_RU_IDENTITYLINK 运行时用户关系信息ACT_RU_TASK 运行时任务表ACT_RU_VARIABLE 运行时变量表二、处理流…

过滤器Filter总结

过滤器Filter1. 简介2. 快速入门3. 执行流程4. 使用细节4.1 拦截路径4.2 过滤器链5. 案例5.1 需求5.2 LoginFilter1. 简介 过滤器是JavaWeb三大组件之一&#xff08;Servlet、Filter&#xff0c;Listner&#xff09;&#xff1b; 作用&#xff1a; 把对资源&#xff08;servl…

Ubuntu22.04 安装 ssh

文章目录Ubuntu22.04 安装 ssh一、 环境配置二、 启动远程连接三、 开放端口四、 远程连接Ubuntu22.04 安装 ssh 一、 环境配置 安装 Ubuntu 系统后&#xff0c;我们首先需要配置管理员 root 用户&#xff1a; sudo passwd root然后&#xff0c;进行软件源的更换&#xff1a…

14 Java集合(Map集合+HashMap+泛型使用+集合面试题)

集合14.11 Map集合14.11.1 Map集合特点14.11.2 Map集合体系结构14.12 HashMap14.12.1 HashMap基本使用14.12.2 HashMap实际应用14.12.3 HashMap练习14.12.4 HashMap底层实现原理14.12.5 put的过程原码14.12.6 resize过程原码14.12.7 get的过程原码14.13 HashTable14.14 泛型高级…

5-1中央处理器-CPU的功能和基本结构

文章目录一.CPU的功能二.CPU的基本结构&#xff08;一&#xff09;运算器1.运算器的基本组成2.专用数据通路方式3.CPU内部单总线方式&#xff08;二&#xff09;控制器1.基本组成2.实现过程&#xff08;三&#xff09;寄存器一.CPU的功能 中央处理器&#xff08;CPU&#xff0…

并查集的入门与应用

目录 一、前言 二、并查集概念 1、并查集的初始化 2、并查集的合并 3、并查集的查找 4、初始化、查找、合并代码 5、复杂度 二、路径压缩 三、例题 1、蓝桥幼儿园&#xff08;lanqiaoOJ题号1135&#xff09; 2、合根植物&#xff08;2017年决赛&#xff0c;lanqiaoO…

SQL注入篇 - 布尔盲注及延时注入

数据来源 盲注 什么是盲注&#xff1a; 布尔盲注原理 布尔盲注流程 手工盲注思路&#xff08;以下的文章参考&#xff1a;DVWA-sql注入&#xff08;盲注&#xff09; - N0r4h - 博客园&#xff09; 手工盲注的过程&#xff0c;就像你与一个机器人聊天&#xff0c;这个机器人知…