您的位置:首页 > 编程语言 > Java开发

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

2017-09-12 00:14 459 查看
写排序算法是一个大工程,估计得好多天才可以写完。。。就慢慢写吧。未完待续。。。。

内部排序和外部排序

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

我们这里说说八大排序就是内部排序。

排序算法的稳定性?

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



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

排序分类

简单排序类别:

直接插入排序

选择排序算法

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

有效算法:

归并排序算法、

堆排序算法、

快速排序算法

冒泡排序和变体类别:

冒泡排序、

希尔排序、

梳排序

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

线性时间的排序:

计数排序、

桶排序、

基数排序、

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: