六大排序——(插入、希尔、选择、交换、归并、计数)

news2024/11/25 4:26:19

目录

一、插入排序

二、希尔排序

 三、选择排序

1)直接选择排序:

2)堆排序

四、交换排序 

1)冒泡排序

2)快速排序

1、Hoare版

2、挖坑法

3、前后指针

快排优化

快速排序非递归来实现

快排总结

五、归并排序

递归实现

非递归实现

六、计数排序


一、插入排序

步骤:

1、从第一个元素开始,该元素可以被认你为已经被排序了

2、取下一个元素tmp,从已经排列的序列从后往前扫描

3、如果该元素大于tmp,则将它移动到下一位

4、重复步骤三,直到找到元素小于等于tmp结束

5、将tmp插入到该元素的后面,如果已排序的序列都大于tmp,则将tmp插入到下标为0位置

6、重复步骤2-5

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

样例测试:

 思路:

在待排序的序列中,假设前n-1个元素都已经有序,现将第n个元素插入到前面已经拍好的序列中,使得前n个元素有序,按照这个方法对所有元素进行插入,直到整个序列有序,但是我们不能确定待排序的序列中哪一部分是有序的,所以我们一开始认为第一个元素是有序的,依次将其后面的元素插入到这个有序的序列中来,直到整个序列有序为止。

时间复杂度:

最坏的情况是:5 4 3 2 1 每一个元素都需要移动,时间复杂度是O(n*n)

最好的情况是:1 2 3 4 5每一个元素都是有序的,时间复杂度O(n)

空间复杂度:

O(1)

所以插入排序适用于,序列接近于有序,而序列过于复杂不适合使用插入排序。

二、希尔排序

希尔排序又称缩小增量法。希尔排序的主要思想就是:先选定一个整数,把待排序的文件中的所有记录分成多个组,所有距离为记录分在同一组内,并且对每一个组进行排序,然后取,重复上述分组排序的过程,当分的组大小为1的时候,所记录在同一组内排好序.

上面的概念可能难以理解我们可以看一下图示:

其中分组完成之后就需要排序,这里我们选择插入排序对每一个组内的数据进行排序。

public void shell(int[] arr,int gap){
        for (int i = gap; i < arr.length; i++) {
            int tmp=arr[i];
            int j = i-gap;
            for (; j >= 0 ; j-=gap) {
                if(tmp<arr[j]){
                    arr[j+gap]=arr[j];
                }
                else{
                    break;
                }
            }
            arr[j+gap]=tmp;
        }
    }
    public void shellSort(int[] arr){
        int gap=arr.length;
        while(gap>1){
            gap/=2;
            shell(arr,gap);
        }
        shell(arr,1);;
    }

 希尔排序特性总结:

1、希尔排序是插入排序的优化

2、当gap>1的时候都是预排序,目的是让数组曲玉有序。当gap==1的时候,由于数组趋于有序所以排序的速度会大幅增加。

3、希尔排序时间复杂度不好计算,因为gap的取值不是固定的,在不同书中1时间复杂度都是不同的

 

所以希尔排序是不稳定的。

 三、选择排序

基本思想:

每一次从待排序的数据元素中选出最大或者最小的一个元素,存放在序列的起始位置,知道全部待排序的数据元素都已经排完。

1)直接选择排序:

1、在元素1集合array[i]——arrayy[n-1]中选择关键码最大(小)的数据元素

2、若他们不是最后一个或者第一个元素,则将它与这组元素中的最后一个(第一个)元素交换

3、在剩余的array[i]——array[n-2](array[i+1]——array[n-1])集合中,重复上述步骤,直到集合中只剩于一个元素。

选择排序的写法有很多种,并且写法也是很好理解,就是找最大致值,最小值,最小值移动到左端

最大值移动到右端

public void selectSort(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            int minIndex=i;
            for (int j = i+1; j < arr.length ; j++) {
                if(arr[j]<arr[minIndex]){
                    minIndex=j;
                }
            }
            swap(arr,i,minIndex);
        }
    }
    public void selectSort2(int[] arr){
        int left=0;
        int right=arr.length-1;
        while(left<right){
            int minIndex=left;
            int maxIndex=right;
            for (int i = left; i <= right; i++) {
                if(arr[i]<arr[minIndex]){
                    minIndex=i;
                }
                if(arr[i]>arr[maxIndex]){
                    maxIndex=i;
                }
            }
            swap(arr,minIndex,left);
            if(left==maxIndex){
                maxIndex=minIndex;
            }
            swap(arr,maxIndex,right);
            left++;
            right--;
        }
    }

直接选择排序效率比较低,时间复杂度在O(n*n)

2)堆排序

堆排序是指利用二叉堆这种数据结构所设计的一种排序算法,它是通过堆来选择数据。需要注意的是要是排升序用大根堆,降序用小根堆。

我们想要用堆排序的话,就必须要建造堆,所以我们需要根据二叉堆的堆顶元素一定大于所有元素来将最大元素与最后一个元素进行交换,接着在进行一次向下调整将次大元素找出来交换,依次来实现元素排序。

public void createHeap(int[] arr){
        for (int parent=(arr.length-1-1)/2; parent>=0 ; parent--) {
            shiftDown(parent,arr.length,arr);
        }
    }
    public void shiftDown(int parent,int len,int[] arr){
        int child=parent*2+1;
        while(child<len){
            if(child<len-1&&arr[child]<arr[child+1]){
                child=child+1;
            }
            if(arr[parent]<arr[child]){
                swap(arr,parent,child);
                parent=child;
                child=parent*2+1;
            }
            else{
                break;
            }
        }
    }
    public void heapSort(int[] arr){
        createHeap(arr);
        int end=arr.length-1;
        while(end>0){
            swap(arr,0,end);
            shiftDown(0,end,arr);
            end--;
        }
    }

堆排的时间复杂对是:O(n*logn)

四、交换排序 

1)冒泡排序

冒泡排序我们都耳熟能详了,这里就不过多的介绍了,直接上代码。

public void bubbleSort(int[] arr){
        for (int i = 0; i < arr.length-1; i++) {
            boolean flg=false;
            for (int j = 0; j < arr.length-1-i; j++) {
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                    flg=true;
                }
            }
            if(flg==false){
                break;
            }
        }
    }

这里我们用到了一些优化,如果我们1的序列已经有序列,那么我们就不需要继续排列了。

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

2)快速排序

快速排序有很多种方法,这里我们慢慢介绍:

1、Hoare版

任取待排序元素序列中的某个元素为基准值,按照该基准值将怕挨徐集合分成左右两部分,小于该基准值放在左边,大于该基准值的放在右边,接着左右序列重复该过程,直到有序。

public int parttion(int[] arr,int left,int right){
        int k=left;//记录初始下标
        int tmp = arr[left];
        while(left<right){
            while(left<right&&arr[right]>=tmp){
                right--;
            }
            while(left<right&&arr[left]<=tmp){
                left++;
            }

            swap(arr,left,right);
        }
        swap(arr,k,left);
        return left;
    }
    public void quick(int[] arr,int start,int end){
        if(start>=end){
            return;
        }
        int pivot = parttion(arr,start,end);
        quick(arr,start,pivot-1);
        quick(arr,pivot+1,end);
    }

    public void quickSort(int[] arr){
        quick(arr,0,arr.length-1);
    }

2、挖坑法

挖坑法其实和Hoare的方法大差不差都是将左面都小于这个基准值,右边都大于这个基准值,只不过Heare是双向查找,而挖坑法是找到右边比tmp小的值就将其与left进行交换,接着进行左边的查找。

public int parttion2(int arr[],int left,int right){
        int tmp=arr[left];
        while(left<right){
            while(left<right&&arr[right]>=tmp){
                right--;
            }
            arr[left]=arr[right];
            while(left<right&&arr[left]<=tmp){
                left++;
            }
            arr[right]=arr[right];
        }
        arr[left]=tmp;
        return left;
    }

3、前后指针

我们直接看代码实现更加会好理解一点:

    public int parttion3(int[] arr,int left,int right){
        int prev=left;
        int cur=prev+1;
        while(cur<=right){
            if(arr[cur]<arr[left]&&arr[++prev]!=arr[cur]){
                swap(arr,prev,cur);
            }
            cur++;
        }
        swap(arr,prev,left);
        return prev;
    }

我们可以看一下图示来理解一下这个代码:

这段代码不好理解的是arr[++prev]!=arr[cur]它的作用是将数组arr分区,可以这段图示来理解一下   

双指针还有一种写的方法,与这种方法差别不大

private static int partition(int[] array, int left, int right) {
    int d = left + 1;
    int pivot = array[left];
    for (int i = left + 1; i <= right; i++) {
        if (array[i] < pivot) {
            swap(array, i, d);
            d++;
        }
    }
    swap(array, d - 1, left);
    return d - 1;
}

快排优化

1、三数取中

当序列为1,2,3,4,5的时候,序列递归会成一条线性

所以我们需要打乱这种顺序,就需要每次取的基准值是三个数当中第二大的:

2、递归到小的子区间时可以考虑使用插入排序

快排是以类似二叉树的模型来遍历的,所以当遍历到最后一层的时候,每一个叶子节点都还需要遍历两边,所以会使时间变长,所以我们需要使用插入1排序来减小时间。

    private static int threeNum(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[right]) {
                return right;
            }else if(array[mid] > array[left]) {
                return left;
            }else {
                return mid;
            }
        }
    }
        private static void quick(int[] array,int start,int end) {
        if(start >= end) {
            return;
        }
        count++;
        //System.out.println("start: "+start);
        //System.out.println("end: "+end);
        if(end - start +1 <= 20) {
            //直接插入排序
            insertSort2(array,start,end);
            return;
        }
        //三数取中
        int mid = threeNum(array,start,end);
        //交换
        swap(array,mid,start);

        int pivot = parttion(array,start,end);

        quick(array,start,pivot-1);//左树

        quick(array,pivot+1,end);//右树
    }

快速排序非递归来实现

由于使用递归双子树的方法来实现数据的排序当数据量过于大的时候会出现栈溢出的情况所以我们应该会用非递归来实现快速排序。

void quickSortNonR(int[] a, int left, int right) {
    Stack<Integer> st = new Stack<>();
    st.push(left);
    st.push(right);
    while (!st.empty()) {
        right = st.pop();
        left = st.pop();
    if(right - left <= 1)
        continue;
    int div = PartSort1(a, left, right);
    // 以基准值为分割点,形成左右两部分:[left, div) 和 [div+1, right)
    st.push(div+1);
    st.push(right);
    st.push(left);
    st.push(div);
    }
}

快排总结

1、快速排序综合性能和使用场景都是比较好的,所以才叫快速排序。

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

3、空间复杂度:O(logn)

4、稳定性:不稳定

五、归并排序

归并排序是建立在归并操作的一种有效的排序算法,采用分治排序,分为分解、合并两个操作。

 分解:将数组分割成两个数组,在将两个数组分割成四个数组,直到不能分割为止

合并:将分割的有序数组进行排序,将其合并,一个成两个两个成四个,直到数组元素个数与原来相同即可。

递归实现

public void merge(int[] arr,int left,int mid,int right){
        int[] tmpArr = new int[right-left+1];
        int l1=left;
        int r1=mid;
        int l2=mid+1;
        int r2=right;
        int k=0;
        while(l1<=r1&&l2<=r2){
            if(arr[l1]<arr[l2]){
                tmpArr[k++]=arr[l1++];
            }
            else{
                tmpArr[k++]=arr[l2++];
            }
        }
        while(l1<=r1){
            tmpArr[k++]=arr[l1++];
        }
        while(l2<=r2){
            tmpArr[k++]=arr[l2++];
        }
        for (int i = 0; i < k; i++) {
            arr[i+left]=tmpArr[i];
        }
    }
    public void mergeSortFunc(int[] arr,int left,int right){
        if(left>=right){
            return ;
        }
        int mid  = (left+right)/2;
        mergeSortFunc(arr,left,mid);
        mergeSortFunc(arr,mid+1,right);
        merge(arr,left,mid,right);
    }
    public void mergeSort(int[] arr){
        mergeSortFunc(arr,0,arr.length-1);
    }

我们可以根据上诉思路来用递归实现分解,分解完成之后继续通过merge方法来将其合并,而merge方法我们就是同构两个有序数组合并的思路来完成的。

非递归实现

由于数组总是以一半的方式进行分割,分割的终点是数组元素只有一个,所以我们定义一个变量gap作为分割后的数组长度,遍历时一次跳过gap*2个元素,刚好是两个数组的长度,gap从1开始对两个有序数组进行排序,直到作为数组长度一半的时候结束

public void mergeSort1(int[] arr){
        int gap=1;
        while(gap<arr.length){
            for (int i = 0; i < arr.length; i+=gap*2) {
                int left=i;
                int mid=left+gap-1;
                int right=mid+gap;
                //防止mid和right越界
                if(mid >= arr.length) {
                    mid = arr.length-1;
                }
                if(right >= arr.length) {
                    right = arr.length-1;
                }
                merge(arr,left,mid,right);
            }
            gap*=2;
        }
    }

六、计数排序

计数排序是一个比较简单的排序,是通过记录每一个元素出现的次数,来进行的排序。

先假设 20 个数列为:{9, 3, 5, 4, 9, 1, 2, 7, 8,1,3, 6, 5, 3, 4, 0, 10, 9, 7, 9}。

让我们先遍历这个无序的随机数组,找出最大值为 10 和最小值为 0。这样我们对应的计数范围将是 0 ~ 10。然后每一个整数按照其值对号入座,对应数组下标的元素进行加1操作。

比如第一个整数是 9,那么数组下标为 9 的元素加 1。

 

第二个整数是 3,那么数组下标为 3 的元素加 1。

 

继续遍历数列并修改数组......。最终,数列遍历完毕。

 

数组中的每一个值,代表了数列中对应整数的出现次数。

有了这个统计结果,排序就很简单了,直接遍历数组,输出数组元素的下标值,元素的值是几,就输出几次。比如统计结果中的 1 为 2,就是数列中有 2 个 1 的意思。这样我们就得到最终排序好的结果。

0, 1, 1, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 9, 9, 9, 9, 10

代码实现:

public void countArray(int[] arr){
        //找到数组的最大值 最小值 确定数组开辟的大小
        int maxVal=arr[0];
        int minVal=arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(arr[i] < minVal) {
                minVal = arr[i];
            }
            if(arr[i] > maxVal) {
                maxVal = arr[i];
            }
        }
        int range=maxVal-minVal+1;
        int[] count=new int[range];
        for (int i = 0; i < arr.length; i++) {
            count[arr[i]-minVal]++;
        }
        int index = 0;//记录重新写会array数组的下标
        for (int i = 0; i < count.length; i++) {
            int val = count[i];
            while (val != 0) {
                arr[index] = i + minVal;
                val--;
                index++;
            }
        }
    }

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

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

相关文章

Spring整合Junit单元测试

1.Spring整合Junit单元测试 1.1 原始Junit测试Spring的问题 在测试类中&#xff0c;每个测试方法都有以下两行代码&#xff1a; ApplicationContext ac new ClassPathXmlApplicationContext("application.xml");BookDao bookDao (BookDao)ac.getBean("bookDa…

环二肽试剂:Cyclo(D-Tyr-D-Phe),对A549细胞具有抗tumor活性

英文名称&#xff1a;Cyclo(D-Tyr-D-Phe)产品结构式&#xff1a; 产品规格&#xff1a; 1.CAS号&#xff1a;N/A 2.分子式&#xff1a;C18H18N2O3 3.分子量&#xff1a;310.35 4.包装规格&#xff1a;1g、5g、10g&#xff0c;包装灵活 5.外观颜色&#xff1a;固体/粉末 6.溶解条…

【⑩MySQL】:表管理,让数据管理不再困难

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL表/数据库创建和管理的讲解✨ 目录 前言1. 基础知识2. 创建和管理数据库3.创建表4. 修改表5. 删除表6.总结 1. 基础知识 ✨1.1 表的基本概念 在MySQL数据库中&#xff0c;表是一种很重要的数据库对象&#xf…

酷炫音乐盒: 使用Python和Tkinter打造自己的音乐播放器

前言 Python的Tkinter&#xff08;Tk接口&#xff09;是一个用于创建图形用户界面&#xff08;GUI&#xff09;的标准库。它是Python的内置模块&#xff0c;无需额外安装即可使用。Tkinter提供了一组部件&#xff08;如按钮、标签、文本框等&#xff09;和布局管理器&#xff…

ROS学习——运行管理

ROS是多进程(节点)的分布式框架&#xff0c;一个完整的ROS系统实现&#xff1a; 可能包含多台主机&#xff1b; 每台主机上又有多个工作空间(workspace)&#xff1b; 每个的工作空间中又包含多个功能包(package)&#xff1b; 每个功能包又包含多个节点(Node)&#xff0c;不同的…

MyBatis—操作数据库

MyBatis &#x1f50e;前置铺垫创建数据库MyBatis 的执行流程创建对应流程 &#x1f50e;MyBatis—查询查询用户信息执行流程创建实体类创建 Interface 与 xml在 xml 中编写 SQL 语句模拟执行流程 &#x1f50e;单元测试定义优点执行单元测试引入依赖生成单元测试编写代码Asser…

优思学院|制造不良品是品质成本意识的问题吗?

制造的最大错误&#xff0c;就是制造出不良品或者不适合的物品。 谁都知道制造出这类“不良品”会使成本增加&#xff0c;但是到底会增加什么成本&#xff0c;可能就不是人人 都清楚。 若是不小心制造出不适合品&#xff0c;浪费的成本除了该物品的材料费、加工人力的费用、设…

VR电力安全警示教育:身临其境体验事故伤害

VR电力安全警示教育由广州华锐互动开发&#xff0c;是一种利用虚拟现实技术模拟电厂安全事故的应用程序。该应用程序通过模拟真实的场景和情境&#xff0c;让用户身临其境地体验VR电力安全警示教育的过程&#xff0c;从而提高用户的安全意识和应对能力。 在VR电力安全警示教育中…

Linux下获取另外一个程序的标准输出和标准错误输出的一种实现方式

问题&#xff1a;一个程序如何获取另外一个程序的标准输出和标准错误输出&#xff1f; 标准输入&#xff0c;标准输出&#xff0c;标准错误输出是一个程序的基本组成&#xff0c;在Linux下一个程序调用另外一个程序&#xff0c;如何获取其标准输出和错误输出呢&#xff1f; 分析…

专用开发工具和环境在嵌入式系统开发中的重要性

嵌入式系统是一种特殊类型的计算机系统&#xff0c;其设计和开发用于控制特定设备或执行特定任务。以下是嵌入式系统的一些特点以及与其开发相关的专用工具和环境&#xff1a; 特定目标&#xff1a;嵌入式系统通常被设计用于执行特定的任务或控制特定的设备。它们被定制以满足特…

开发一款财经直播系统需要注意的方面

数据保护与隐私&#xff1a;确保用户的个人信息和交易数据得到有效的保护&#xff0c;遵守相关的数据保护法规。采用高级加密技术来保护用户的敏感信息&#xff0c;并建立安全的存储和传输机制。 信息真实性&#xff1a;提供准确、可信的财经信息&#xff0c;确保平台上发布…

【Jenkins】Jenkins构建后端流水线

目录 一、新建任务1、输入任务名称&#xff0c;选择构建项目类型&#xff08;这里我选择的是Maven项目&#xff09;&#xff0c;任务名称一般格式为&#xff1a;项目名称-前后端2、创建成功后的结果 二、配置流水线1、进入刚创建好的任务页面中&#xff0c;点击配置2、General配…

Loki+Grafana监控docker容器日志

目标&#xff1a;最近开发人员时常需要查看各个环境项目中容器日志&#xff0c;而直接通过ssh终端使用docker logs命令查看日志不太安全&#xff0c;这会导致很多人员知道服务器的账户和密码&#xff0c;有没有一种可以收集所有容器日志的平台系统。那么这套系统就是LokiGrafan…

3.3C++输入流

C 输入概述 C输入是指程序从外部获取数据的过程。 C提供多种输入方法&#xff0c;包括从键盘输入、从文件读取、从网络获取&#xff0c;最常用的是从键盘输入。 C输入数据的函数是cin&#xff0c;它可以读取各种类型的数据&#xff0c;包括整数、浮点数、字符和字符串等。 …

7.1.8 其他Linux支持的文件系统与VFS

虽然 Linux 的标准文件系统是 ext2 &#xff0c;且还有增加了日志功能的 ext3/ext4 &#xff0c;事实上&#xff0c;Linux 还有支持很多文件系统格式的&#xff0c; 尤其是最近这几年推出了好几种速度很快的日志式文件系统&#xff0c;包括 SGI 的 XFS 文件系统&#xff0c; 可…

css文字超出元素省略,单行、多行省略

效果图: 通用CSS .box {width: 500px;border: 1px solid red;padding: 10px;line-height: 24px;}1.单行省略 .singe-line {text-overflow: ellipsis;overflow: hidden;word-break: break-all;white-space: nowrap;}<p>单行省略</p><div class"singe-lin…

如何统一接口测试的功能、自动化和性能测试用例

服务端的测试&#xff0c;大多数内容都围绕着接口展开。对于接口测试&#xff0c;无非也是功能、自动化、性能测试为主&#xff0c;偶然想到一个问题&#xff0c;如果能写一个用例&#xff0c;在功能、自动化、性能三者的测试场景中重复使用&#xff0c;肯定能省去非常多的时间…

webpack相关面试题

webpack面试题 1.webpack和vite区别2.如何优化webpack打包速度&#xff1f;3.说说webpack中常见的Plugin&#xff1f;解决了什么问题4.说说如何借助webpack来优化前端性能&#xff1f;如何优化JS代码压缩CSS代码压缩Html文件代码压缩文件大小压缩图片压缩Tree ShakingusedExpor…

中小企业的数字化热情,从未像今年618这样滚烫

一年一度的618大促告一段落&#xff0c;我们可以总结一个趋势&#xff1a;C端消费者对大促的热情在消退&#xff0c;而B端企业&#xff0c;尤其是中小企业&#xff0c;对大促的热情则以肉眼可见的速度提升。 普通消费者也好&#xff0c;广大中小企业也罢&#xff0c;参与大促的…

SSMP整合案例(11) 在界面中实现添加操作

上文 SSMP整合案例(10) vue端调整项目环境 发送请求 基本界面编写我们搭建了基本的页面结构 然后 我们来做个新增的功能 首先 新增 我们肯定是用户点击了这个新建之后 我们再来处理这个逻辑 我们之前的代码 新增是有绑定 一个事件的 但是这个 AddBook中并没有内容 首先 我们…