Java 常用的八种排序算法与代码实现
2017-09-12 00:14
459 查看
写排序算法是一个大工程,估计得好多天才可以写完。。。就慢慢写吧。未完待续。。。。
我们这里说说八大排序就是内部排序。
算法稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。
选择排序算法
两种简单排序算法分别是插入排序和选择排序,两个都是数据量小时效率高。实际中插入排序一般快于选择排序,由于更少的比较和在有差不多有序的集合表现更好的性能。
堆排序算法、
快速排序算法
希尔排序、
梳排序
这种类别的算法在实际中很少使用到,因为效率低下,但在理论教学中常常提到。
桶排序、
基数排序、
思想:
将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。
要点:
设立哨兵,作为临时存储和判断数组边界之用。
算法流程图
效率
时间复杂度:O(n^2).
做法:
代码实现:
思想
先将整个待排序的记录序列分割成为若干组,然后分别对每个组中的元素进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。(也就是将数据分组,组内嵌套插入排序)
算法流程图
效率
最好:O(n log n)
最坏:即刚好与所要的顺序相反,时间复杂度为O(n^2)
分组的依据(n/2)对复杂度的影响比较大。
做法
首先确定分的组数。
然后对组中元素进行插入排序。
然后将length/2,重复1,2步,直到length=0为止。
代码实现
当n值较小时,n和n2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n2)差别不大。
在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
思想
在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
算法流程图
效率
时间复杂度:n(n − 1) / 2 ∈ Θ(n2)
做法
按照数组顺序,记录当前数的位置 和大小,
找寻数组中当前数以后的(也就是未排序的) 最小的数和 位置,
将最小数的位置 和数值与当前数 交换
代码实现
思想
通过建立大顶堆(堆总是一棵完全二叉树。),筛选出序列中最大的元素,进行排列。
算法流程图
下图中是堆最大堆进行排序的行为。
效率
时间复杂度是O(nlogn)
做法
将序列构建成大顶堆。
将根节点与最后一个节点交换,然后断开最后一个节点。
重复第一、二步,直到所有节点断开。
代码实现
思想
将序列中所有元素两两比较,将最大的放在最后面。让较大的数往下沉,较小的往上冒
将剩余序列中所有元素两两比较,将最大的放在最后面。
算法流程图
效率
冒泡排序效率非常低,效率还不如插入排序。
做法
将序列中所有元素两两比较,将最大的放在最后面。
将剩余序列中所有元素两两比较,将最大的放在最后面。
重复第二步,直到只剩下一个数。
代码实现
对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。
做法
设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,
故在进行下一趟排序时只要扫描到pos位置即可。
代码实现
思想
1)选择一个基准元素,通常选择第一个元素或者最后一个元素,
2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
3)此时基准元素在其排好序后的正确位置
4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
算法流程图
第一趟的排序图
递归排序的全过程
效率
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
做法
选择第一个数为p,小于p的数放在左边,大于p的数放在右边。
递归的将p左边和右边的数都按照第一步进行,直到不能递归。
代码实现
算法流程图
效率
做法
代码实现
算法流程图
效率
做法
代码实现
未完待续。。。
本文主要摘引自三篇排序文章,我只是进行删减、拼接和添加一些自己的理解,并编码实现。权且认作是我原创文章。如有侵犯,联系我,我会妥善处理。
源码地址: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
内部排序和外部排序
内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。我们这里说说八大排序就是内部排序。
排序算法的稳定性?
排序算法可以根据稳定性分为两种:稳定和非稳定算法。那么怎么区分它们?如果链表中存在两个相同元素,稳定排序算法可以在排序之后保持他两原来的次序,而非稳定性的则不能保证。算法稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。
排序分类
简单排序类别:
直接插入排序选择排序算法
两种简单排序算法分别是插入排序和选择排序,两个都是数据量小时效率高。实际中插入排序一般快于选择排序,由于更少的比较和在有差不多有序的集合表现更好的性能。
有效算法:
归并排序算法、堆排序算法、
快速排序算法
冒泡排序和变体类别:
冒泡排序、希尔排序、
梳排序
这种类别的算法在实际中很少使用到,因为效率低下,但在理论教学中常常提到。
线性时间的排序:
计数排序、桶排序、
基数排序、
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
相关文章推荐
- 一遍记住Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现
- 一遍记住Java常用的八种排序算法与代码实现
- 一遍记住Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现精解
- Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现
- 常用的八种排序算法与Java代码实现
- 一遍记住Java常用的八种排序算法与代码实现
- 常用的八种排序算法与Java代码实现
- Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现
- 一遍记住Java常用的八种排序算法与代码实现
- [置顶] Java常用的八种排序算法与代码实现
- 一遍记住Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现精解
- Java常用的八种排序算法与代码实现
- Java常用的八种排序算法与代码实现