常用排序算法思想
我们都知道 java.util 包中 Arrays.sort() 方法,对 int[ ] 数组类型的排序会根据数组的长度及有序度等因素,去选择最优的算法策略对其进行排序。
本篇主要针对下面几种常用的排序算法思路做简单介绍及代码实现 (本文代码均以升序演示)。
- 桶排序 :声明一个新数组,每个下标就是一个桶,遍历要排序的数组,依次把元素往对应的桶里扔时,就把那个桶的值加1即可。
- 冒泡排序:元素两两比较,小的数往上冒。一直重复这样的操作,直到没有要交换的元素为止。
- 快速排序:找一个元素作为基准数,把数组中小于基准数的元素移动到左边,大于的移到右边,遍历一次后就可以找到基准数在数组中该待的位置,然后依次对左右两区进行相同操作即可。
- 插入排序:往一个已经排好序的数列中插入一个数,使得插入后数列仍然有序。
- 选择排序:在序列中依次选择最小,第二小,第三小…的数放到数列中。
1.桶排序
1.1思想
int[ ] a = new int[5] ;

桶排序非常有趣,是所有排序算法中最快最简单的排序算法。这些桶桶说白了就是数组的每个下标。遍历要排序的元素时,把元素扔进对应的桶中并将数组指定位置的值加1。最终收集数据时,数组每个位置的值为几就输出几次下标,这样就实现桶排序了。
1.2代码实现
public static int[] bucketSort(int[] array) {
//创建10个桶桶
int[] ints = new int[10];
//遍历要排序的数组
for (int i = 0; i < array.length; i++) {
//给元素对应桶桶的值+1
ints[array[i]]++;
}
return ints;
}
以上几行代码就已经完成排序了,只是为了便于数据的收集,我们可以对桶桶进行遍历,取那些值不为0的桶桶即可。下标的值是几就说明元素出现了几次。这里给出收集数组排序后的代码。
public static int[] printt(int[] bucket,int[] array){
int index = 0;
//遍历桶桶
for (int i = 0; i < bucket.length; i++) {
//不等于0的桶桶原数组才有对应的值
if(bucket[i] != 0){
for (int j = 0; j < bucket[i]; j++) {
array[index++] = i;
}
}
}
return array;
}
1.3总结
桶排序实际上只需遍历一次所有的待排序元素,然后依次放入指定的位置,所以它是最快的排序算法。但是要知道,待排序数组的范围是多大就需要多少个桶桶,当元素跨度范围越大时空间浪费就越大,这是一种典型的空间换时间的做法,虽然这种排序不常用,但多一种思维也是有用武之地的。
2.冒泡排序

2.1思想
从尾部开始比较相邻的两个元素,如果后元素比前元素小,就交换位置。遍历一次后,就可以把最小的元素冒出来。一直重复这样的操作,冒第二小、第三小…直到冒完所有元素即可。
2.2代码实现
public static int[] bubbleSort(int[] array) {
//声明一个变量待会交换的时候用
int min;
//遍历要排序的数组
for (int i = 0; i < array.length - 1; i++) {
//从最后一个元素开始,向前遍历
for (int j = array.length - 1; j > i; j--) {
//两两比较,如果位置不对,就交换位置
if (array[j] < array[j - 1]) {
min = array[j];
array[j] = array[j - 1];
array[j - 1] = min;
}
}
}
return array;
}
2.3总结
冒泡排序用到的额外存储空间只有一个,即用于交换位置的临时变量,就是前面代码的 min 。对于同样大小的元素时并不移动。
其实冒泡排序可以对其进行改,进使它性能更好。比如一次冒两个元素,进行正向和反向两次冒泡比较,即可冒出最大最小两个元素,这里不提供代码实现,感兴趣的动手实现吧。
3.快速排序

3.1思想
进行一次遍历时,为了方便,我们一般选择区间第一个元素作为基准数。把小于基准数的元素扔到左边,大于基准数的元素扔到右边,这次遍历结束后,基准数应该待的下标位置就找到了。然后分别对左右两边区间进行相同操作,分别重新选择基准数,对区间遍历,找这基准数该待的位置。直到各分区只有一个数为止。
3.2代码实现
public static int[] quickSort(int[] array, int start, int end) {
if (start < end) {
//拿第一个数作为基数,并记录下来防止后面丢了
int temp = array[start];
int i = start;
int j = end;
while (i < j) {
//从右边找比基数小的下标位置 j
while (array[j] >= temp && i < j) {
j--;
}
if (i < j) {
//把小的数丢到 i 位置
array[i] = array[j];
}
//从左边找比基数大的下标位置 i
while (array[i] <= temp && i < j) {
i++;
}
if (i < j) {
//把大的数丢到 j 位置
array[j] = array[i];
}
}
//当i与j相遇就结束一次遍历,i或j的下标就是基数该待的位置
array[i] = temp;
//此时 数组被分为两部分,基数的左边和右边 递归这两部分即可。
quickSort(array, start, i - 1);
quickSort(array, i + 1, end);
}
return array;
}
3.3总结
快速排序是跳跃式的交换,交换距离很大。这种算法实际上是一种分治法思想,也就是把问题分为一个个小部分,然后分别解决,最后把结果组合起来。大多数情况下,我们认为快速排序的平均时间复杂度为O(nlogn),空间复杂度为常量。快速排序基本上被认为是所有排序算法中平均性能最好的。
4.插入排序

4.1思想
插入排序实际上把待排序数组分成了两部分。一部分已排序,另一部分未排序,只要一步步把未排序部分的元素插入到已排序部分,使其仍然有序即可。这里的重点就很明确了,需要找到插入点的下标。
4.2代码实现
public static int[] insertSort(int[] array) {
//第一个元素不需要排序,认为在已排列部分中,从第二个元素开始找其插入点
for (int i = 1; i < array.length; i++) {
//记录当次要排序的元素
int temp = array[i];
int j = i;
//一旦找到比它小的数,这个小的数的后一位就是要插入的位置
for (; j > 0 && temp < array[j - 1]; j--) {
//这段元素都向后挪个位给它
array[j] = array[j - 1];
}
//最后把这个元素放入插入点
array[j] = temp;
}
return array;
}
4.3总结
很明确,插入排序在数列近似有序时,效率才比较高,因为这样会减少移动次数。总的来说性能不是很好。但它的好处就是占用空间很少,只需要一个额外空间来存储临时变量即可。对于插入算法的优化,只需要围绕如何快速找到插入点即可,比如二分插入,还有一个希尔排序。了解即可,这里不做代码演示。
5.选择排序

5.1思想
选择排序非常简单,只需要依次选择最小,放到第一位;选择第二小,放到第二位…直到选完所有元素即可。
5.2代码实现
public static int[] selectSort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
//记录最小数的值
int temp = array[i];
//记录最小数的位置
int minIndex = i;
for (int j = i + 1; j < array.length; j++) {
if (array[j] < array[minIndex]) {
minIndex = j;
}
}
//找到当次最小值的位置后,交换位置
array[i] = array[minIndex];
array[minIndex] = temp;
}
return array;
}
5.3总结
选择排序的重要步骤就是在待排序的数列中寻找最小(或最大)的元素,可以从这个出发点去找优化方案。另外,选择排序不需要一个个挪动元素,而是直接交换。相比于冒泡排序来说,它性能会好一些。
各排序算法性能对比
最后附上各排序算法的平均时间复杂度、空间复杂度及稳定性的对比表。
| 排序算法 | 平均时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|
| 桶排序 | O(n+m) | O(m) | 取决于桶内排序的算法 |
| 冒泡排序 | O(n2) | O(1) | 稳定 |
| 快速排序 | O(nlogn) | O(logn) | 不稳定 |
| 插入排序 | O(n2) | O(1) | 稳定 |
| 选择排序 | O(n2) | O(1) | 不稳定 |
动态图来源于网络

372

被折叠的 条评论
为什么被折叠?



