IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 六大排序算法精讲(SelectSort、BubbleSort、InsertSort、MergeSort、QuickSort、HeapSort) -> 正文阅读

[数据结构与算法]六大排序算法精讲(SelectSort、BubbleSort、InsertSort、MergeSort、QuickSort、HeapSort)

一、前言

? ? ? ? 最近在看算法公开课,听了很多老师讲了不少很有用的东西,但是只听不练很快就忘了,所以我还是选择以博客的方式巩固自己的学习。

二、排序算法概述

? ? ? ? 今天主要讲的是比较经典的六大排序算法:选择排序、冒泡排序、插入排序、归并排序、快速排序和堆排序,除此之外还有一个比较重点的基数排序,但是因为它是不基于比较的排序,所以用法不是很广泛,所以我也只讲解它的实现原理,不多做阐述,并且,在讲解之余,我也会穿插一些其他内容。

三、选择排序

? ? ? ? 选择排序是最基础的一种排序之一,它的实现方式是:对一个无序数组,先通过遍历查找整个数组的最大值放到最后一个,然后再对未排序部分继续查找最大值,直到查找完毕为止(当然也可选择最小值)。

? ? ? ? 举个例子就是:

? ? ? ? 有一个无序数组长度N=7,我所做的第一件事就是将数组从0到N-1遍历一遍,然后找到最大值7,?然后和第N-1,也就是最后一个数交换,之后变成了:

? ? ? ? 此时,最后一个数就已经排好了,所以在接下来的排序中,我们就可以不用管这个数了。现在我们再对前N-2也就是前6个数进行相同操作,将前六个数最大值5和最后第六个数交换,如下:

? ? ? ? 然后,第六个数也排好了,可以不用管了,再对前五个数进行操作:

? ? ? ? 重复操作直到对前0个数进行排序。

? ? ? ? 代码如下:

    public static void selectsort(int[]arr){
        if(arr==null||arr.length<2)
            return;
        for(int i=0;i<arr.length-1;i++){
            int minindex=i;
            for(int j=i+1;j<arr.length;j++){
                minindex=arr[j]<arr[minindex]?j:minindex;
            }
            swap(arr,i,minindex);
        }
    }
    public static void swap(int[]arr,int i,int j){
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[i]^arr[j];
        arr[i]=arr[i]^arr[j];
    }

? ? ? ? ?这里的交换操作,我用的是异或运算,异或运算的详细解析,见这份博客:异或运算

? ? ? ? 分析:我们最后看一下选择排序的时间复杂度:假设数组长度是N,那么我们外层for循环的复杂度就是O(N),所以里面需要进行的循环次数从N到N-1,到N-2,是一个等差数组。所以一共循环的次数是N+N-1+N-2+...+1,复杂度是O(N^2)。

四、冒泡排序

? ? ? ? ·冒泡排序也是一种比较基础的排序,它的实现方法也是遍历数组,然后相邻元素一一进行比较,如果要升序排序的话,每次比较的时候都把大的那个放在后面,这样一轮循环下来,最后一个也就被排好了,然后和选择排序一样,固定排序好的那个数,对没有排序好的数继续进行操作,直到数组的每个数都被放到正确的位置。

? ? ? ? 流程如下:

?

?

?

?

?

? ? ? ? 一轮结束后,最大值已经放在最后一个,然后对前六个数继续操作。

代码:

    public static void bubblesort(int[]arr){
        if(arr==null||arr.length<2)
            return;
        for(int i=arr.length-1;i>=0;i--){
            for(int j=0;j<i;j++){
                if(arr[j]>arr[j+1])
                    swap(arr,j,j+1);
            }
        }
    }
    public static void swap(int[]arr,int i,int j){
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[i]^arr[j];
        arr[i]=arr[i]^arr[j];
    }

?分析:冒泡排序依然是要对数组进行遍历,依然是第一次遍历的长度是N,第二次是N-1,和选择一样,时间复杂度为O(N^2)。

五、插入排序

? ? ? ? 插入排序我认为和冒泡排序还是挺像的,它的实现方法是,给定一个无序数组arr,我们假设它最开始有序的长度是1,也就是arr的前1个数时有序的,这是肯定成立的。

? ? ? ? 之后从第二个数开始,我想要前2个数变的有序,我就需要将第二个数插入到前1个数都某个地方,使得前2 个数正好有序,如图:

????????

? ? ? ? 4要想和3构成的数组有序,就要选择合适的地方插入,4和3比较,4比较大,所以原地不动就可以。然后又来了个1:?

?

? ? ? ? 现在已知前2个数时有序的,那么1插入之后要想有序,必须插入到合适的位置上,1和4比较,4更大,所以1应该插入到4之前,于是1再和3比较,比3小,应该插入到3之前,但是3之前没有了,所以就把1放在第一个。

? ? ? ? ?那么前三个数也有序了,与此同理:下面的操作如下:

?

?

代码:

 public static void insertsort(int[]arr){
        if(arr==null||arr.length<2)
            return;
        for(int i=1;i<arr.length;i++){
            for(int j=i-1;j>=0;j--){
                if(arr[j]>arr[j+1])
                    swap(arr,j,j+1);
            }
        }
    }
    public static void swap(int[]arr,int i,int j){
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[i]^arr[j];
        arr[i]=arr[i]^arr[j];
    }

? ? ? ? ?分析:插入排序与前面两个排序的不同之处在于,它排序的好坏取决于数据的整齐程度,而冒泡排序和选择排序,不管原本的数组是什么样的,都要老老实实的遍历,最终结果也总是O(N^2)。但是插入排序的不同之处在于,在最优情况下,给定的数组的排好序的,也就是说每次只需要查看一下当前数是否比前一个数要大即可,复杂度是O(N),而最差情况下给定的数组的逆序给定的,那么它需要执行的次数就和选择和冒泡一样了,是N+N-1+N-1+...1,为O(N^2)的复杂度。所以插入排序平均来说还是优于前两个排序的,但是由于我们说时间复杂度都是按照最坏的情况下计算的,所以我们依然将插入排序归类为O(N^2)的排序。

六、归并排序

? ? ? ? 从这个排序开始,都是比O(N^2)快的排序的,当然也相对前面三种排序更难理解一些。

? ? ? ? 归并排序的方法是:我想要排序一个数组,那我就先将数组等分成两部分,分别排序两个部分,然后再对排序好的两个部分再进行个排序不就好了?那么就递归调用,直到被划分的小数组只有两个的时候,我们开始排序。这样一来,很容易就得到两个排好序的子数组,现在对他们进行合并:怎么合并呢?我们需要一个长度为两个数组之和的辅助数组,通过双指针解决:

????????

? ? ? ? 首先两个指针分别指向两个数组的开头,比较大小,将小的那个放入数组,然后指针右移:

?

重复操作直到有一边越界为止:

? ? ? ? 然后有一边越界之后,退出循环,对没有将没有越界的一边的剩余的数加到辅助数组里去。

?

? ? ? ? 这样两个小数组就排好序了,接下来我们只需要将辅助数组一一对应的赋值给我们真实数组即可。然后把排好序的数组和另外一个等大的数组继续进行归并,直到整个数组都有序为止。

?????????代码:

 public static void mergesort(int[]arr){
        if(arr==null||arr.length<2)
            return;
        process(arr,0,arr.length-1);
    }
    public static void process(int []arr,int l,int r){
        if(l==r)
            return;
        int mid=l+((r-l)>>1);
        process(arr,l,mid);
        process(arr,mid+1,r);
        merge(arr,l,mid,r);
    }
    public static void merge(int[]arr,int l,int m,int r){
        int[]help=new int[r-l+1];
        int p1=l,p2=m+1;
        int i=0;
        while(p1<=m&&p2<=r){
            help[i++]=arr[p1]<=arr[p2]?arr[p1++]:arr[p2++];
        }
        while(p1<=m){
            help[i++]=arr[p1++];
        }
        while(p2<=m){
            help[i++]=arr[p2++];
        }
        for(i=0;i<help.length;i++){
            arr[l+i]=help[i];
        }
    }

? ? ? ? 分析:它的时间复杂度怎么计算呢?前面说了,我们将数组依次对半分开,然后对分开的子数组计算,那么分开之后看起来其实是一个二叉树,计算的次数就是二叉树的高logN,然后进行的merge又要遍历一整个长度的数组,所以算法时间复杂度应该是O(NlogN),额外空间复杂度是辅助数组的长度O(N)。

七、快速排序

? ? ? ? 快速排序也是一个很经典的排序,它有几个不一样的版本,其实区别并不大。我们直接所3,0版本吧。3.0版本的快排就是在无序数组中任意选择一个数和最后一个数交换,然后将小于这个数的数放在数组的左边,大于这个数的数放在数组的右边,等于这个数的数放在数组的中间,递归着实现即可。所谓1.0版本就是选择最后一个值作为划分值,将小于等于它的放在左边,大于它的放在右边;所谓2.0版本就是依然选择最后一个数进行划分,小于它的放左边,大于它的放右边,等于它的放中间。

? ? ? ? 我们为了简便计算,通过2.0版本演示一下:

????????

?代码:

????????

    public static void quicksort1(int []arr){
        if(arr==null||arr.length<2)
            return;
        quicksort(arr,0,arr.length-1);
    }
 public static void quicksort(int[]arr,int l,int r){
        if(l<r){
            swap(arr,l+(int)(Math.random())*(r-l+1),r);
            int[]p=partition(arr,l,r);
            quicksort(arr,l,p[0]-1);
            quicksort(arr,p[1]+1,r);
        }
    }
    public static int[] partition(int[]arr,int l,int r){
        int less=l-1;
        int more=r;
        while(l<more){
            if(arr[l]<arr[r]){
                swap(arr,++less,l++);
            }else if(arr[l]>arr[r]){
                swap(arr,--more,l);
            }else {
                l++;
            }
        }
        swap(arr,more,r);
        return new int[]{less+1,more};
    }
    public static void swap(int[]arr,int l,int r){
        arr[l]=arr[l]^arr[r];
        arr[r]=arr[l]^arr[r];
        arr[l]=arr[l]^arr[r];
    }

? ? ? ? 复杂度依然和树结构相似,是O(NlogN)。

?八、堆排序

? ? ? ? 在介绍堆排序之前,我们先了解一下堆,堆其实就是一个二叉树,堆分为大根堆和小根堆,所谓大根堆就是指任何一颗树头结点都是最大的,小根堆同理,如下就是一个大根堆:

????????

? ? ? ? ?任何一棵树都比它子节点的值大,这就是大根堆,那么堆有哪些操作呢?

? ? ? ? 堆主要操作有两个,一个是插入一个元素后重新把大根堆转化为大根堆,一个是将堆中任何一个元素变化后,重新构建大根堆,具体方式在这就不多说了,直接上代码吧。

????????

    public static void main(String[] args) {
        int []arr={2,5,1,6,1,6,3,61,6,1,2,3};
        heapsort(arr);
        for(int a:arr){
            System.out.println(a);
        }
    }
    public static int[] heapsort(int[]arr){
        //安排成大根堆
//        for(int i=0;i< arr.length;i++){
//            heapInsert(arr,i);
//        }
        //下面过程和上面一样,稍微快了一点
        //变大根堆应该从下开始
        for(int i=arr.length-1;i>=0;i--){
            heapify(arr,i, arr.length);
        }
        int heapsize= arr.length;
        swap(arr,0, --heapsize);
        while(heapsize>0){
            heapify(arr,0,heapsize);
            swap(arr,0,--heapsize);
        }
        return arr;
    }
    public static void heapInsert(int[]arr, int index){
        //循环停止的条件是两个,一个是当前节点并不比父亲节点要打,循环停止
        //或者是到达头结点的时候,index和(index-1)/2都是0,那么依然不满足循环,退出循环

        while(arr[index]>arr[(index-1)/2]){
            swap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }
    public static void heapify(int []arr,int index,int heapsize){
        int leftson = index*2+1;
        while(leftson<heapsize){
            int max=leftson+1<heapsize&&arr[leftson+1]>arr[leftson]?leftson+1 : leftson;
            max = arr[max]>arr[index]?max:index;
            if(max==index)
                break;
            swap(arr,index,max);
            index=max;
            leftson=index*2+1;
        }
    }
    public static void swap(int[]arr,int i,int j){
        arr[i]=arr[i]^arr[j];
        arr[j]=arr[i]^arr[j];
        arr[i]=arr[i]^arr[j];

    }

?

?

?

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:27:24  更:2021-12-26 22:27:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 17:23:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码