八大排序方法详解(附java代码)

来源:互联网 发布:《linux就该这么学 编辑:程序博客网 时间:2024/06/05 00:33

八大排序方法详解(附java代码)

这是我花了一天多时间整理和编写的,实用性是比较强的,建议收藏起来,不理解的可以多次翻看。

java8大排序有:5大基本类,分为8种,分别是:插入排序(直接插入排序,希尔排序),选择排序(简单选择排序,堆排序),交换排序(冒泡排序,快速排序),归并排序,基数排序
图解:
1
其中,最容易让我们理解的是冒泡排序和选择排序,并且代码的体现也是很简单,但是其他一些排序也是我们程序员要了解和掌握的,因为数据的排序关系到一个算法复杂度(就是排序所需要的次数,时间)。如果是简单的几百、几千个数据,我们用什么排序方法都是差不都的,但是如果是几十万、几千万的数据排序,不同的排序方法可能要相差几十秒的时间,这个就不能马虎了。
下面是每个排序方法的详解:

一.直接插入排序

1.基本思想

在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。

2.图解

2

3.理解

根据上面的图,可以看到插入排序先是假设第一个数据是排列好的,第二个数据插入到第一个数据前面或后面(根据大小规则),后面数据插入时,判断插入到排列好的数组中的哪一个位置,插入后就是一个排列好的数组,每次插入一个数据,以此类推,直到最后,所有的数据插入完毕,就可以得到一个排列好的数组数据。这就是插入排序。

二.希尔排序

1.基本思想

算法先将要排序的一组数按某个增量d(n/2,n为要排序数的个数)分成若干组,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。当增量减到1时,进行直接插入排序后,排序完成。

2.图解

3

3.理解:

希尔排序算是八大排序中比较难理解之一了。
但是也是可以想得通的,关键是理解“插入”。
希尔排序是插入排序的另一种,只不过比直接插入排序高效一点,但是代码复杂不少。
希尔排序提到“增量”(也有人说成步数,其实就是相隔几个的意思)。

比如这里十个数,第一次定5个增量

这里把序号1、6,2、7,3、8,4、9,5、10两两按按要求插入排序,也就是决定每组的两个数字那个在前哪个在后

第二次定义三个增量

这里把序号1、4、7、10,2、5、8,3,6,9按要求排序,对这三个数进行排序

第三次把增量变为1

十个数一起插入排列,但是前面很多数据已经做好排序了,这时在插入遍历的次数就少很多了

希尔排序的精髓

希尔排序就是减少了排序的次数
比如要六个数进行排序,简单插入排序就是把后面5个数一次一次按顺序插入,需要操作5次;而希尔排序是第一次把数字两两插入到正确的顺序,第二次把这三组数据插入。
你可以这样想,希尔排序:三个排好序列的数组,插入到三个排好序列的数组,这样的排序其实也是节省了很多时间;因为直接排序想起来是很简单的,但是实际操作的时候还是要不断对前面的每一个数据做比较!当然是一组排列好的数据插入到另一组排列好的数据种高效一点。
如果前面的数组数据是11个呢,增量是几个?其实也是5个,这里只要把1、6、11做插入排序就可以了,两个或十个都可以插入排序为什么三个不可以呢??当然增量也可以是6个,有一个数据单独做一组也是可以。
其实希尔排序的增量不一定要按规定,你可以自己定,但是传统都是按一半一半的样子。
关于插入排序,你也可以有其他想法,比如把10个数据分成两堆,前面5个和后面5个同时进行插入排序,第二次,后面5个依次插入到前面5个数据中,最终也能得到一个排列好的数组;或者你把数据分成3堆,4堆,这些都是可以的。。。

三.简单选择排序

1.基本思想

在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

2.图解

4

3.理解

上面也说过选择排序相对来说是所有排序中最简单的一种了。
选择排序,你可以选择所有数据中最大那个或最小那个,放到第一个位置,然后从剩下的数据中选择第二个合适的数据,以此类推,一直到结束,就完成了选择排序。
选择排序理解起来是不难,而且代码编写也是比较容易。
但是,有些人,很容易把冒泡排序和选择排序弄混。
选择排序是从一堆数据中选出一个最小的数据。而冒泡是从两个选出最小的那个。
你也可以这样想,两个数据有什么好选的,多个才有必要选嘛,所有一个数据和多个数据比较的那种排序就是选择排序;而且冒泡是那种不断的,两两对比才会不断冒泡,所以两两对比的排序叫做冒泡排序。

四.堆排序

基本思想

堆排序是一种树形选择排序,是对直接选择排序的有效改进。
这里的树是二叉树。

图解

初始序列:46,79,56,38,40,84

1.建堆

5

2.交换

6
交换2
7
上面图解其实也是简略了很多步骤,比如建堆前面还有好几步才建成二叉树的堆,交换后也是,要重新设计二叉树,其中一些步骤也是需要自己想。

3.理解

堆排序也是我认为最最难理解的一个中排序了。因为涉及到二叉树,代码写起来也是麻烦得要死!
但是想通了的话,光是理解其实不是很难。
说白了堆排序就是选择排序的一种,就是不断选出最大值,最终完成排序。
但是这里涉及到一个二叉树,二叉树是什么呢?二叉树就是一个根有两个节点或一个节点,并且根节点的值一定比支点的值大,就是那么简单。
你也不需要去专门看二叉树的定义,上面那句话就够了,根本不需要理解“满二叉树”或“半二叉树”等概念。
如果只是想理解这个排序的思想,你甚至可以不去考虑这个二叉树是怎么建立的,他的过程是怎么样子的?不考虑!
你只需要知道二叉树的根节点的值最大,不断获取根节点的值,就是不断获取剩下数据中的最大值。
其实过程中最重要的是,剔除、重建的不断循环过程!就是当你剔除一个根节点后,重建二叉树,然后不断循环,直到最后一个数据!
到这里堆排序的思想也是想通了吧!
这个二叉树感觉可以单独写一个博客文章,把一个步骤拆分成几个步骤就会很清楚了。

五.冒泡排序

1.基本思想

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

2.图解

8

3.理解

其实冒泡一轮后,数据的第一个数是最小的,第二轮冒泡剩下的数,得到第二个数是最小的,依次类推,排列所有的数据。
这个冒泡排序方法和选择排序方法有点相似都是在一轮后第一个位置都是最小值,第二轮后都是第二个位置是剩下数据的最小值。所以有点容易弄混乱。
记住冒泡是两个两个不断冒泡比较就不会弄乱了!

六.快速排序

1.基本思想

选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分。

图解

9

3.理解

快速排序并不是很难理解但是代码也不简单。
你要知道快速排序其实还是冒泡排序的另一个特殊情况而已。
从上面图解可以看到,快速排序的思想是选择一个数(可以是第一个或最后一个或其他的),然后不断冒泡,一轮后,选中的数就到了他合适的位置,并且把原本一堆的数据分成了前面和后面两堆,这时,并不断对前面和后面的数据重复图解的步骤,最终的数据合起来,就是一个排列好的数据。

七.归并排序

1.基本思路

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

2.图解

10

3.理解

其实归并排序和上面说到的希尔排序是有异曲同工的设计思想,都是先把一部分数据排列好,然后让一部分数据插入一部分数据,不断融合,直到完全融合。这种排序比一个个排序高效一点。

八.基数排序

1.基本思路

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成一个有序序列。

2.图解

11

3.理解

基数排序想通了也是不难的,这是一个取巧的方法,不过一般人却想不到的!
这里你要理解百位,千位可以为零的思想。
比如比较33,9,121,44,1001
这里9的十位和百位都是零,肯定比十位百位不为零的数据小。
同样没有百位的33和44 也是肯定比有百位的121和1001小,但是33的十位比44的十位小,所以33肯定比44小,同样也可以比较出121和1001,最终得到正确的排列。
这里基数排序不一定是要从个位开始比较,也是可以从所有数据中最高位的那个位数开始比较,同样能达到效果。
这里的基数,不要理解成一个基准数或对比数,而是一个位数的理解!

上面就是一些理解,下面是代码。有些东西你不一定要写,因为你有写一辈子不会去写,但是你懂了其中的思想和道理,你就已经可以装逼了!
不过思想通了,代码不一定那么容易就能写得出来,有些还是要费很大脑筋的,实在不行你先看看我的代码,结合上面的理解,就能理清代码的逻辑了。再不行就直接套这些代码了!这些代码也不是一定要这样写,你完全理解后,代码的写法都是多样的。

所有排序的代码

import java.util.ArrayList;import java.util.List;public class Main {    public static void  main(String[] args) {        Main sort=new Main();        System.out.println("各种排序的代码");        System.out.print("没排序前的数据   ");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        //这里不想让所有的方法都是静态的,所以才用对象去调用        sort.printData(array);        sort.insertSort();//直接插入排序        sort.shellSort();//希尔排序        sort.selectSort();//选择排序        sort.heapSort();//堆排序        sort.bubbleSort();//冒泡排序        sort.quickSort();//快速排序        sort.mergingSort();//归并排序        sort.radixSort();//基数排序    }    /**     * 打印数据     */    private  void printData(int[] array) {        //输出排列好的数组        for (int i = 0; i < array.length; i++) {            System.out.print(array[i] + "  ");        }    }    /**     * 直接插入排序代码     * 直接插入排序需要依次每次选出一个数据,插入到之前排序的数组(不管是一个还是N个)的合适位置     * 判断到合适的位置后,插入前,要把该位置后面的数据都要后移一个位置     * 这时在把数据放到合适的位置,不断循环直到完全插入数据,就完成了排序     */    public  void insertSort() {        System.out.print("\n1.简单插入排序 :");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        int temp = 0;        for (int i = 1; i < array.length; i++) {            temp = array[i];//选中要插入的数据,每次往下推一个            int j = i - 1; //这里j,后面需要用到,所以抽出来            for (; j >= 0 && temp < array[j]; j--) {//第一个条件也是可以写,也可以不写;这个写法我也是比较难想到,但是如果你把条件抽到括号就不对了                array[j + 1] = array[j];     //将大于temp的值整体后移一个单位            }            array[j + 1] = temp;//这里的已经不是i-1,而是看它对比通过了几次后的值        }        printData(array);    }    /**     * 希尔排序,插入排序的另一种表现形式而已     * 希尔排序就是减少了排序的次数     * 希尔排序的思想就是一组排序好的数据插入另一组排序好的数据,比一个个插入更高效     */    public  void shellSort() {        System.out.print("\n2.     希尔排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        double d1 = array.length;        int temp = 0;        while (true) {            d1 = Math.ceil(d1 / 2);//四舍五入取值            int d = (int) d1;//double类型的数据强转成int类型,这是“增量”            for (int x = 0; x < d; x++) {//数据分组                for (int i = x + d; i < array.length; i += d) {//各组内的数据进行排序                    int j = i - d;                    temp = array[i];                    for (; j >= 0 && temp < array[j]; j -= d) {//插入数据                        array[j + d] = array[j];                    }                    array[j + d] = temp;                }            }            if (d == 1) //增量为1,并表明所以数据一起排了序,排序完成                break;        }        //输出排列好的数组        printData(array);    }    /**     * 简单选择排序     * 这个思想和代码都是比较简单的啦     * 选择排序就是(从小到大排序)第一次选择所有数据中最小的数据,和第一个数据交换,然后从剩下的所有数据选择最小的放在第二个位置,以此类推,就可以得到排列好的数据。     */    public  void selectSort() {        System.out.print("\n3.简单选择排序: ");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        int position = 0;        for (int i = 0; i < array.length; i++) {//遍历所有的数据            position = i;//要遍历的游标值            int temp = array[i];//该游标值对应的数值            for (int j = i + 1; j < array.length; j++) {//遍历剩下的数据中的最小值                if (array[j] < temp) {//如果temp比剩下的任何一个数据大,就交换,保证temp是最小的                    temp = array[j];                    position = j;                }            }            //上面一个for、if保证了temp是剩下数中最小的数,position是最终交换的游标值            //下面两步是把所有的数据的最小值和剩下数组数据的第一个数组数据数做交换            array[position] = array[i];            array[i] = temp;        }        //输出排列好的数组        printData(array);    }    /**     * 堆排序     * 堆排序不仅思想上是所有排序中最麻烦的,而且代码也是!     * 说白了堆排序就是选择排序的一种,就是不断选出最大值,最终完成排序。     * 但是这里涉及到一个二叉树,二叉树是什么呢?二叉树就是一个根有两个节点或一个节点,并且根节点的值一定比支点的值大,就是那么简单。     * 堆排序就是不断剔除根节点并不断重新建堆的过程,直到只剩下一个节点,就完成所有的排序了     */    public  void heapSort() {        System.out.print("\n4.       堆排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        int arrayLength = array.length;        //循环建堆        for (int i = 0; i < arrayLength - 1; i++) {            //建堆            buildMaxHeap(array, arrayLength - 1 - i);            //交换堆顶和最后一个元素            swap(array, 0, arrayLength - 1 - i);//            System.out.println(Arrays.toString(array));//建堆并交换后的数据        }        //输出排列好的数组        printData(array);    }    private  void swap(int[] data, int i, int j) {        int tmp = data[i];        data[i] = data[j];        data[j] = tmp;    }    //对data数组从0到lastIndex建大顶堆    private  void buildMaxHeap(int[] data, int lastIndex) {        //从lastIndex处节点(最后一个节点)的父节点开始        for (int i = (lastIndex - 1) / 2; i >= 0; i--) {            //k保存正在判断的节点            int k = i;            //如果当前k节点的子节点存在            while (k * 2 + 1 <= lastIndex) {                //k节点的左子节点的索引                int biggerIndex = 2 * k + 1;                //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在                if (biggerIndex < lastIndex) {                    //若果右子节点的值较大                    if (data[biggerIndex] < data[biggerIndex + 1]) {                        //biggerIndex总是记录较大子节点的索引                        biggerIndex++;                    }                }                //如果k节点的值小于其较大的子节点的值                if (data[k] < data[biggerIndex]) {                    //交换他们                    swap(data, k, biggerIndex);                    //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值                    k = biggerIndex;                } else {                    break;                }            }        }    }    /**     * 冒泡排序     * 冒泡排序也是比较简单的一种,两两比较,这个在逻辑上和代码上都不难想到把     * 一轮冒泡后,得到最小的值放在第一个位置,第二轮对剩下的数据进行冒泡,再把剩下数据中的最小的放在第二个位置,一次类推     * 冒泡排序和选择排序在一轮后显示的数据相似,但是过程却是很不一样的。冒泡就是不断的比较,而选择是从所有的选出其中最小的。     */    public  void bubbleSort() {        System.out.print("\n5.     冒泡排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        int temp = 0;        for (int i = 0; i < array.length - 1; i++) {            for (int j = 0; j < array.length - 1 - i; j++) {                if (array[j] > array[j + 1]) {                    temp = array[j];                    array[j] = array[j + 1];                    array[j + 1] = temp;                }            }        }        //输出排列好的数组        printData(array);    }    /**     * 快速排序     * 快速排序也是冒泡排序中的一种,但是不一定选中最大或最小值,选中的是任意一个数值,并把它放到适当的游标值的位置,不断对各组数据冒泡,最终得到排列好的数据     */    public  void quickSort() {        System.out.print("\n6.     快速排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        quick(array);        //输出排列好的数组        printData(array);    }    private  int getMiddle(int[] list, int low, int high) {        int tmp = list[low];    //数组的第一个作为中轴        while (low < high) {            while (low < high && list[high] >= tmp) {                high--;            }            list[low] = list[high];   //比中轴小的记录移到低端            while (low < high && list[low] <= tmp) {                low++;            }            list[high] = list[low];   //比中轴大的记录移到高端        }        list[low] = tmp;              //中轴记录到尾        return low;                   //返回中轴的位置    }    private  void _quickSort(int[] list, int low, int high) {        if (low < high) {            int middle = getMiddle(list, low, high);  //将list数组进行一分为二            _quickSort(list, low, middle - 1);        //对低字表进行递归排序            _quickSort(list, middle + 1, high);       //对高字表进行递归排序        }    }    private  void quick(int[] a2) {        if (a2.length > 0) {    //查看数组是否为空            _quickSort(a2, 0, a2.length - 1);//里面涉及到不断循环遍历        }    }    /**     * 归并排序     * 其实归并排序和上面说到的希尔排序是有异曲同工的设计思想,都是先把一部分数据排列好,然后让一部分数据插入一部分数据,不断融合,直到完全融合。     */    public  void mergingSort() {        System.out.print("\n7.     归并排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        sort(array, 0, array.length - 1);        //输出排列好的数组        printData(array);    }    private  void sort(int[] data, int left, int right) {        if (left < right) {            //找出中间索引            int center = (left + right) / 2;            //对左边数组进行递归            sort(data, left, center);            //对右边数组进行递归            sort(data, center + 1, right);            //合并            merge(data, left, center, right);        }    }    private  void merge(int[] data, int left, int center, int right) {        int[] tmpArr = new int[data.length];        int mid = center + 1;        //third记录中间数组的索引        int third = left;        int tmp = left;        while (left <= center && mid <= right) {            //从两个数组中取出最小的放入中间数组            if (data[left] <= data[mid]) {                tmpArr[third++] = data[left++];            } else {                tmpArr[third++] = data[mid++];            }        }        //剩余部分依次放入中间数组        while (mid <= right) {            tmpArr[third++] = data[mid++];        }        while (left <= center) {            tmpArr[third++] = data[left++];        }        //将中间数组中的内容复制回原数组        while (tmp <= right) {            data[tmp] = tmpArr[tmp++];        }//        System.out.println(Arrays.toString(data));//过程    }    /**     * 基数排序     * 基数排序,比较的是各个数值的位数上的大小     * 相对来说速度也是不错的,比如第一次就可以比较出只有个位数的数据的大小排列顺序,第二次就可以比较出只有十位和各位的数据,以此类推,一直比较,就会比较完所有的数。     */    public  void radixSort() {        System.out.print("\n8.     基数排序:");        int array[] = {34, 18, 54, 5, 4, 69, 99, 98, 54, 56};        sort(array);        //输出排列好的数组        printData(array);    }    private  void sort(int[] array) {        //首先确定排序的趟数;        int max = array[0];        for (int i = 1; i < array.length; i++) {            if (array[i] > max) {                max = array[i];            }        }        int time = 0;        //判断位数;        while (max > 0) {            max /= 10;            time++;        }        //建立10个队列;        List<ArrayList> queue = new ArrayList<ArrayList>();        for (int i = 0; i < 10; i++) {            ArrayList<Integer> queue1 = new ArrayList<Integer>();            queue.add(queue1);        }        //进行time次分配和收集;        for (int i = 0; i < time; i++) {            //分配数组元素;            for (int j = 0; j < array.length; j++) {                //得到数字的第time+1位数;                int x = array[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);                ArrayList<Integer> queue2 = queue.get(x);                queue2.add(array[j]);                queue.set(x, queue2);            }            int count = 0;//元素计数器;            //收集队列元素;            for (int k = 0; k < 10; k++) {                while (queue.get(k).size() > 0) {                    ArrayList<Integer> queue3 = queue.get(k);                    array[count] = queue3.get(0);                    queue3.remove(0);                    count++;                }            }        }    }}

上面的java代码运行后的结果:
12

到这里八大排序已经说完了,但是还是会有些人不完全理解,比如堆排序和希尔排序。
对于不理解可以参考一下其他人的说法,或者你看网上的视频,视频讲解了详细的过程。
网上也会有其他名称的排序,其实都是和上面的五大类的分支,比如插入排序还有好几种呢,但是有些是很冷门的,没有必要完全研究。
有些不清楚的,也是可以留言交流。

共勉:比别人做得更好,你才能有更出彩的机会。