Java 常用的八种排序算法与代码实现

原创 2017年09月12日 00:14:42

写排序算法是一个大工程,估计得好多天才可以写完。。。就慢慢写吧。未完待续。。。。

内部排序和外部排序

内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。
我们这里说说八大排序就是内部排序。

排序算法的稳定性?

排序算法可以根据稳定性分为两种:稳定和非稳定算法。那么怎么区分它们?如果链表中存在两个相同元素,稳定排序算法可以在排序之后保持他两原来的次序,而非稳定性的则不能保证。
这里写图片描述

算法稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。

排序分类

简单排序类别:

  • 直接插入排序
  • 选择排序算法

两种简单排序算法分别是插入排序和选择排序,两个都是数据量小时效率高。实际中插入排序一般快于选择排序,由于更少的比较和在有差不多有序的集合表现更好的性能。

有效算法:

  • 归并排序算法、
  • 堆排序算法、
  • 快速排序算法

冒泡排序和变体类别:

  • 冒泡排序、
  • 希尔排序、
  • 梳排序
    这种类别的算法在实际中很少使用到,因为效率低下,但在理论教学中常常提到。

线性时间的排序:

  • 计数排序、
  • 桶排序、
  • 基数排序、

1. 直接插入排序

插入排序是稳定的

  • 思想:
    将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

  • 要点:
    设立哨兵,作为临时存储和判断数组边界之用。

  • 算法流程图
    这里写图片描述

  • 效率
    时间复杂度:O(n^2).

  • 做法:
首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。
设定插入数和得到已经排好序列的最后一个数的位数。insertNum和j=i-1。
从最后一个数开始向前循环,如果插入数小于当前数,就将当前数向后移动一位。
将当前数放置到空着的位置,即j+1
  • 代码实现:
private static int[] insertionSort(int[] arrayToSort) {
        int length = arrayToSort.length;
        int insertNum; //要插入的数
        for (int i = 1; i < length; i++) { // 排序多少次,第一个数不用排序
            insertNum = arrayToSort[i];
            int j = i - 1; //已经排序好的序列元素个数
            while (j >= 0 && arrayToSort[j] > insertNum) {
                arrayToSort[j + 1] = arrayToSort[j]; //j 位元素大于insertNum, j 以后元素都往后移动一格
                j--;
            }
            arrayToSort[j + 1] = insertNum;//比较到第j 位时 小于 insertNum ,所以insertNum 应该放在 j+1 位
        }
        return arrayToSort;
    }

2. 希尔排序

希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序。希尔排序方法是一个不稳定的排序方法。

  • 思想
    先将整个待排序的记录序列分割成为若干组,然后分别对每个组中的元素进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。(也就是将数据分组,组内嵌套插入排序)

  • 算法流程图
    这里写图片描述

  • 效率
    最好:O(n log n)
    最坏:即刚好与所要的顺序相反,时间复杂度为O(n^2)
    分组的依据(n/2)对复杂度的影响比较大。
  • 做法
    首先确定分的组数。
    然后对组中元素进行插入排序。
    然后将length/2,重复1,2步,直到length=0为止。
  • 代码实现
  private static int[] shellSort(int[] arrayToSort) {
        int length = arrayToSort.length;
        while (length != 0) {
            length = length / 2;
            for (int j = 0; j < length; j++) { //分的组数 ,length 为组的步长
                for (int i = j + length; i < arrayToSort.length; i += length) {  //遍历每组中的元素,从第二个数开始 第一个元素是 j
                    //里面实际上是嵌套了一个 插入排序

                    int x = i - length;//j为当前组有序序列最后一位的位数
                    int temp = arrayToSort[i];//当前要插入的元素
                    while (x >= 0 && arrayToSort[x] > temp) { //从后往前遍历。
                        arrayToSort[x + length] = arrayToSort[x];//向后移动length位
                        x -= length;
                    }
                    arrayToSort[x + length] = temp;

                }
            }
        }
        return arrayToSort;
    }

希尔排序的时间性能优于直接插入排序的原因:

  • 当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  • 当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
  • 在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。

3. 简单选择排序

选择排序类似于插入排序,只是是有选择的插入

  • 思想
    在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。

  • 算法流程图
    这里写图片描述

  • 效率
    时间复杂度:n(n − 1) / 2 ∈ Θ(n2)
  • 做法
    按照数组顺序,记录当前数的位置 和大小,
    找寻数组中当前数以后的(也就是未排序的) 最小的数和 位置,
    将最小数的位置 和数值与当前数 交换
  • 代码实现
 private static int[] simpleSelectSort(int[] arrayToSort) {
        int length = arrayToSort.length;
        for (int i = 0; i < length; i++) {
            int key = arrayToSort[i];
            int position = i; // 最小数据的位置
            for (int j = i + 1; j < length; j++) { //遍历后面的数据比较最小
                if (arrayToSort[j] < key) { //如果当前数据不是最小的,则交换
                    //记录最小的
                    key = arrayToSort[j];
                    position = j;
                }
            }
            //交换
            arrayToSort[position] = arrayToSort[i]; //将 最小的 位置放如 i 的值
            arrayToSort[i] = key; //将最小的值放入 i
        }
        return arrayToSort;
    }

4. 堆排序

堆排序是选择排序种类的一部分 不是稳定的排序。堆排序是一种树形选择排序,是对直接选择排序的有效改进。

  • 思想
    通过建立大顶堆(堆总是一棵完全二叉树。),筛选出序列中最大的元素,进行排列。
  • 算法流程图
    这里写图片描述
    下图中是堆最大堆进行排序的行为。
    这里写图片描述
  • 效率
    时间复杂度是O(nlogn)
  • 做法
    将序列构建成大顶堆。
    将根节点与最后一个节点交换,然后断开最后一个节点。
    重复第一、二步,直到所有节点断开。
  • 代码实现
private static int[] heapSort(int[] arrayToSort) {
        int arrayLength = arrayToSort.length;
        //循环建堆
        for (int i = 0; i < arrayLength - 1; i++) {
            //建大顶堆
            buildMaxHeap(arrayToSort, arrayLength - 1 - i);
            //交换堆顶和最后一个元素
            swap(arrayToSort, 0, arrayLength - 1 - i);
        }

        return arrayToSort;
    }

    private static void swap(int[] data, int i, int j) {
        int tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

    /**
     * 对data数组从0到lastIndex建大顶堆
     *
     * @param data
     * @param lastIndex
     */
    private static void buildMaxHeap(int[] data, int lastIndex) {
        // 从lastIndex处节点(最后一个节点)的父节点开始
        // (lastIndex - 1) / 2 为最后的一个根节点的索引
        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+1 此时 则biggerIndex 实际为右节点的索引,所以biggerIndex总是记录较大子节点的索引
                        biggerIndex++;
                    }
                }
                //如果k节点(k为根节点)的值小于其较大的子节点的值
                if (data[k] < data[biggerIndex]) {
                    //交换他们
                    swap(data, k, biggerIndex);
                    //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
                } else {
                    break;
                }
            }
        }
    }

5. 冒泡排序

这种类别的算法在实际中很少使用到,因为效率低下,但在理论教学中常常提到。

  • 思想
    将序列中所有元素两两比较,将最大的放在最后面。让较大的数往下沉,较小的往上冒
    将剩余序列中所有元素两两比较,将最大的放在最后面。
  • 算法流程图
    这里写图片描述
  • 效率
    冒泡排序效率非常低,效率还不如插入排序。
  • 做法
    将序列中所有元素两两比较,将最大的放在最后面。
    将剩余序列中所有元素两两比较,将最大的放在最后面。
    重复第二步,直到只剩下一个数。
  • 代码实现
 private static int[] bubbleSort(int[] arrayToSort) {
        int arrayLength = arrayToSort.length;
        for (int i = 0; i < arrayLength; i++) {//i为拍好序的元素个数
            for (int j = 0; j < arrayLength - i - 1; j++) { //j 为未排序的元素个数
                if (arrayToSort[j + 1] < arrayToSort[j]) {
                    int tmp = arrayToSort[j + 1];
                    arrayToSort[j + 1] = arrayToSort[j];
                    arrayToSort[j] = tmp;
                }
            }
            System.out.println();
            print(arrayToSort);
        }
        return arrayToSort;
    }

冒泡排序优化

  • 思想
    对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。
  • 做法
    设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,
    故在进行下一趟排序时只要扫描到pos位置即可。
  • 代码实现
 private static int[] bubbleSort2(int[] arrayToSort) {
        int arrayLength = arrayToSort.length;

        for (int i = 0; i < arrayLength; i++) {//i为拍好序的元素个数
            int pos = 0;
            for (int j = 0; j < arrayLength - i - 1; j++) { //j 为未排序的元素个数
                if (arrayToSort[j + 1] < arrayToSort[j]) {
                    int tmp = arrayToSort[j + 1];
                    arrayToSort[j + 1] = arrayToSort[j];
                    arrayToSort[j] = tmp;
                    pos = 1;
                }
            }
            if (pos == 0) {// pos 等于 0 时,说明已经排序好了,就不需要再做比较了
                break;
            }
        }
        return arrayToSort;
    }

6. 快速排序

快速排序(类似于归并算法)是一种分而治之算法。首先它将列表分为两个更小的子列表:一个大一个小。然后递归排序这些子列表。下面就用分而治之的方法来排序子数组。快速排序是一个不稳定的排序方法。

  • 思想
    1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
    2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
    3)此时基准元素在其排好序后的正确位置
    4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
  • 算法流程图
    第一趟的排序图
    这里写图片描述
    递归排序的全过程
    这里写图片描述
  • 效率
    快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
  • 做法
    选择第一个数为p,小于p的数放在左边,大于p的数放在右边。
    递归的将p左边和右边的数都按照第一步进行,直到不能递归。
  • 代码实现
 private static int[] quickSort(int[] arrayToSort, int start, int end) {
        if (start < end) {
            int base = arrayToSort[start]; // 选定的基准值(第一个数值作为基准值)
            int temp; // 记录临时中间值
            int i = start, j = end;
            do {
                while (arrayToSort[i] < base && i < end)// 左边 i < end 数组不能越界
                    i++;
                while (arrayToSort[j] > base && j > start)// 右边 j > start 数组不能越界
                    j--;
                if (i <= j) {//得到上边两个while的不满足条件,比如 下标 i 的值大于 base 和 下标 j 的值小于 base 交换位置
                    temp = arrayToSort[i];
                    arrayToSort[i] = arrayToSort[j];
                    arrayToSort[j] = temp;
                    i++;
                    j--;
                }
            } while (i <= j);// i <= j 说明第一趟还没有比较完。
            // 由于第一趟的两个 while  i++和 j-- 操作,i 和j之间的元素都是排序好的,但是i和j 之间相差的元素个数不确定。
            if (start < j) {
                quickSort(arrayToSort, start, j); //递归比较第一趟的左边部分,第一趟循环完毕,下标 j 是小于 base 的 所以 j 之前的就是 左边部分
            }
            if (end > i) {
                quickSort(arrayToSort, i, end);//递归比较第一趟的右边边部分,下标 i 是大于 base 的 所以 j 之前的就是 右边部分
            }
        }
        return arrayToSort;
    }

7. 归并排序

  • 思想
  • 算法流程图
  • 效率
  • 做法
  • 代码实现

8. 基数排序

  • 思想
  • 算法流程图
  • 效率
  • 做法
  • 代码实现

未完待续。。。

本文主要摘引自三篇排序文章,我只是进行删减、拼接和添加一些自己的理解,并编码实现。权且认作是我原创文章。如有侵犯,联系我,我会妥善处理。
源码地址:https://github.com/527515025/JavaTest/tree/master/src/main/java/com/us/acm
参考资料:
https://zhuanlan.zhihu.com/p/27005757
http://blog.csdn.net/hguisu/article/details/7776068/
http://blog.csdn.net/langbinhui/article/details/23614331

版权声明:本文为博主编写文章,未经博主允许转载,转载请注明出处。

相关文章推荐

《读大学,究竟读什么?》 笔记

很偶然的看到了这本书,由,于25岁年纪就当任为公司董事长的覃彪喜先生所著。 看完颇有感触,做小札于此,聊表心意。开篇,作者以一系列的问题引出了文章的核心问题:读大学,究竟读什么? 最后作者的答案是:...

Java Web架构知识整理——记一次阿里面试经历

“你学习一门技术的最佳时机是三年前,其次是现在。”这话从来很灵验。经过这次面试,觉得需要整理下Java Web相关的资料,以便自己提高或者更快适应可能面临的新的工作。   首先谈谈Java Web需...

Java常用的八种排序算法与代码实现

Java常用的八种排序算法与代码实现 (原文地址:http://www.jianshu.com/p/5e171281a387) 1.直接插入排序 经常碰到这样一类排序问题:把新的数据插入到已经...

一遍记住Java常用的八种排序算法与代码实现

1.直接插入排序 经常碰到这样一类排序问题:把新的数据插入到已经排好的数据列中。 将第一个数和第二个数排序,然后构成一个有序序列将第三个数插入进去,构成一个新的有序序列。对第四个数、第...

Java常用的八种排序算法与代码实现

原文链接:http://www.jianshu.com/p/5e171281a387 1.直接插入排序 经常碰到这样一类排序问题:把新的数据插入到已经排好的数据列中。将第一个数和第二个数排序,然后...
  • et54h
  • et54h
  • 2017年05月16日 11:02
  • 126

一遍记住Java常用的八种排序算法与代码实现

1.直接插入排序 经常碰到这样一类排序问题:把新的数据插入到已经排好的数据列中。 将第一个数和第二个数排序,然后构成一个有序序列将第三个数插入进去,构成一个新的有序序列。对第四个数、第五个数…...

[Java]常用的八种排序算法与代码实现

1.直接插入排序 2.希尔排序 3.简单选择排序 4.堆排序 5.冒泡排序 6.快速排序 7.归并排序 8.基数排序 1.直接插入排序 经常碰到这样一类排序问题:把新的数据...

常用的八种排序算法与Java代码实现

直接插入排序 希尔排序 简单选择排序 堆排序 冒泡排序 快速排序 归并排序 基数排序...
  • lyankj
  • lyankj
  • 2016年09月21日 10:03
  • 327

常用排序算法总结与代码实现(C语言)

一、直接插入排序 InsertSort(A) 1. for i←2 to n do 2. key ← A[i] 3. j ←i-1 4. while(j...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java 常用的八种排序算法与代码实现
举报原因:
原因补充:

(最多只允许输入30个字)