常见的10种排序算法

[常见的排序算法——常见的10种排序]

常见算法可以分为两大类:

  非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序。

  线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。
img

算法复杂度:

img

1、冒泡排序
思路:外层循环从1到n-1,内循环从当前外层的元素的下一个位置开始,依次和外层的元素比较,出现逆序就交换,通过与相邻元素的比较和交换来把小的数交换到最前面。

1
2
3
4
5
6
7
8
9
for(int i=0;i<arr.length-1;i++){//外层循环控制排序趟数
      for(int j=0;j<arr.length-1-i;j++){//内层循环控制每一趟排序多少次
        if(arr[j]>arr[j+1]){
          int temp=arr[j];
          arr[j]=arr[j+1];
          arr[j+1]=temp;
        }
      }
    }

img

2、选择排序
思路:冒泡排序是通过相邻的比较和交换,每次找个最小值。选择排序是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void sort(int[] array) {
int n = array.length;
for (int i = 0; i < n-1; i++) {
int min = i;
for (int j = i+1; j < n; j++) {
if (array[j] < array[min]){//寻找最小数
min = j; //将最小数的索引赋值
}
}
int temp = array[i];
array[i] = array[min];
array[min] = temp;

}
}

img

3、插入排序
思路:通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。可以理解为玩扑克牌时的理牌;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void sort(int[] array) {
int n = array.length;
/**
*从第二位数字开始,每一个数字都试图跟它的前一个比较并交换,并重复;直到前一个数字不存在或者比它小或相等时停下来
**/
for (int i = 1; i < n; i++) {//从第二个数开始
int key = array[i];
int j = i -1;
while (j >= 0 && array[j]>key) {
array[j + 1] = array[j]; //交换
j--; //下标向前移动
}
array[j+1] = key;
}
}

img

4、希尔排序
思路:希尔排序是插入排序的一种高效率的实现,也叫缩小增量排序。先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。
问题:增量的序列取法?
  关于取法,没有统一标准,但最后一步必须是1;因为不同的取法涉及时间复杂度不一样,具体了解可以参考《数据结构与算法分析》;一般以length/2为算法。(再此以gap=gap*3+1为公式)
问题2:为什么不一开始就直接采用插入排序对整个数组排一次呢?
插入排序在数据无规律时,复杂度高,先分组排序后,后面进行插入排序时,元素已经相对有序了,此时插入排序效率会高很多
相关视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void sort(int[] array) {
int n = array.length;
int h = 1;
while (h<n/3) { //动态定义间隔序列
h = 3*h +1;
}
while (h >= 1) {
for (int i = h; i < n; i++) {
for (int j = i; j >= h && (array[j] < array[j - h]); j -= h) {
int temp = array[j];
array[j] = array[j - h];
array[j-h]= temp;
}
}
h /=3;
}
}

img

5、归并排序
思路:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。它使用了递归分治的思想;相当于:左半边用尽,则取右半边元素;右半边用尽,则取左半边元素;右半边的当前元素小于左半边的当前元素,则取右半边元素;右半边的当前元素大于左半边的当前元素,则取左半边的元素。
  自顶向下:
相关视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void mergeSort(int[] array) {
int[] aux = new int[array.length];
sort(array, aux, 0, array.length - 1);
}

private static void sort(int[] array, int[] aux, int lo, int hi) {
if (hi<=lo) return;
int mid = lo + (hi - lo)/2;
sort(array, aux, lo, mid);
sort(array, aux, mid + 1, hi);
merge(array, aux, lo, mid, hi);
}

private static void merge(int[] array, int[] aux, int lo, int mid, int hi) {
System.arraycopy(array,0,aux,0,array.length);
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i>mid) array[k] = aux[j++];
else if (j > hi) array[k] = aux[i++];
else if (aux[j]<aux[i]) array[k] = aux[j++];
else array[k] = aux[i++];
}
}

  自底向上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void sort(int[] array) {
int N = a.length;
int[] aux = new int[N];
for (int n = 1; n < N; n = n+n) {
for (int i = 0; i < N-n; i += n+n) {
int lo = i;
int m = i+n-1;
int hi = Math.min(i+n+n-1, N-1);
merge(array, aux, lo, m, hi);
}
}
}

private static void merge(int[] array, int[] aux, int lo, int mid, int hi) {
for (int k = lo; k <= hi; k++) {
aux[k] = array[k];
}
// merge back to a[]
int i = lo, j = mid+1;
for (int k = lo; k <= hi; k++) {
if (i > mid) array[k] = aux[j++]; // this copying is unneccessary
else if (j > hi) array[k] = aux[i++];
else if (aux[j]<aux[i]) array[k] = aux[j++];
else array[k] = aux[i++];
}
}

缺点:因为是Out-place sort,因此相比快排,需要很多额外的空间。

为什么归并排序比快速排序慢?

  答:虽然渐近复杂度一样,但是归并排序的系数比快排大。

对于归并排序有什么改进?

  答:就是在数组长度为k时,用插入排序,因为插入排序适合对小数组排序。在算法导论思考题2-1中介绍了。复杂度为O(nk+nlg(n/k)) ,当k=O(lgn)时,复杂度为O(nlgn)

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private static int mark = 0;
/**
* 归并排序
*/
private static int[] sort(int[] array, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
mark++;
System.out.println("正在进行第" + mark + "次分隔,得到");
System.out.println("[" + low + "-" + mid + "] [" + (mid + 1) + "-" + high + "]");
// 左边数组
sort(array, low, mid);
// 右边数组
sort(array, mid + 1, high);
// 左右归并
merge(array, low, mid, high);
}
return array;
}

/**
* 对数组进行归并
*
* @param array
* @param low
* @param mid
* @param high
*/
private static void merge(int[] array, int low, int mid, int high) {
System.out.println("合并:[" + low + "-" + mid + "] 和 [" + (mid + 1) + "-" + high + "]");
int[] temp = new int[high - low + 1];
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (array[i] < array[j]) {
temp[k++] = array[i++];
} else {
temp[k++] = array[j++];
}
}
// 两个数组之一可能存在剩余的元素
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = array[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = array[j++];
}
// 把新数组中的数覆盖array数组
for (int m = 0; m < temp.length; m++) {
array[m + low] = temp[m];
}
}

/**
* 归并排序
*/
public static int[] sort(int[] array) {
return sort(array, 0, array.length - 1);
}

public static void main(String[] args) {
int[] array = { 3, 5, 2, 6, 2 };
int[] sorted = sort(array);
System.out.println("最终结果");
for (int i : sorted) {
System.out.print(i + " ");
}
}

6、快速排序
思路:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
private static void sort(int[] array) {
shuffle(array);
sort(array, 0, array.length - 1);
}
private static void sort(int[] array, int lo, int hi) {
if(hi<=lo+M) {
Insert.sort(a,lo,hi);
return;
}
int lt = lo, gt = hi;
int v = array[lo];
int i = lo;
while (i <= gt) {
if (array[i]<v) exch(array, lt++, i++);
else if (array[i]>v) exch(array, i, gt--);
else i++;
}
// a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi].
sort(array, lo, lt-1);
sort(array, gt+1, hi);
}

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

/**
*打乱数组
*/
private static void shuffle(int[] array) {
Random random = new Random(System.currentTimeMillis());
if (array == null) throw new NullPointerException("argument array is null");
int n = array.length;
for (int i = 0; i < n; i++) {
int r = i + random.nextInt(n-i); // between i and n-1
int temp = array[i];
array[i] = array[r];
array[r] = temp;
}
}
代码例子:
package test;

public class s {
public static void main(String[] args) {
int[] arr = { 5,2,4,9,7 };
sort(arr, 0, arr.length - 1);
}
public static void sort(int arr[], int low, int high) {
int l = low;
int h = high;
int k = arr[low];
while (l < h) {
// 从后往前比较
while (l < h && arr[h] >= k ){ // 如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
h--;// h=6
}
if (l < h) {
int temp = arr[h];
arr[h] = arr[l];
arr[l] = temp;
//进行过一次替换后,没必要将替换后的两值再次比较,所以i++直接下一位与k对比
l++;
}
// 从前往后比较
while (l < h && arr[l] <= k) { // 如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
l++;
}
if (l < h) {
int temp = arr[h];
arr[h] = arr[l];
arr[l] = temp;
h--;
}
// 此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用
}
print(arr);
System.out.print("l=" + (l + 1) + "h=" + (h + 1) + "k=" + k + "\n");
// 递归
if (l > low)//先判断l>low再次经行左边排序
sort(arr, low, l - 1);// 左边序列。第一个索引位置到关键值索引-1
if (h < high)//左边依次排序执行完递归后,弹栈进行右边排序
sort(arr, l + 1, high);// 右边序列。从关键值索引+1到最后一个
}
// 打印数组的方法
private static void print(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i != (arr.length - 1)) {
System.out.print(arr[i] + ",");
} else {
System.out.print(arr[i] + "]");
System.out.println();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package test;

public class s {
public static void main(String[] args) {
int[] arr = { 5,2,4,9,7 };
sort(arr, 0, arr.length - 1);
}
public static void sort(int arr[], int low, int high) {
int l = low;
int h = high;
int k = arr[low];
while (l < h) {
// 从后往前比较
while (l < h && arr[h] >= k ){ // 如果没有比关键值小的,比较下一个,直到有比关键值小的交换位置,然后又从前往后比较
h--;// h=6
}
if (l < h) {
int temp = arr[h];
arr[h] = arr[l];
arr[l] = temp;
//进行过一次替换后,没必要将替换后的两值再次比较,所以i++直接下一位与k对比
l++;
}
// 从前往后比较
while (l < h && arr[l] <= k) { // 如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
l++;
}
if (l < h) {
int temp = arr[h];
arr[h] = arr[l];
arr[l] = temp;
h--;
}
// 此时第一次循环比较结束,关键值的位置已经确定了。左边的值都比关键值小,右边的值都比关键值大,但是两边的顺序还有可能是不一样的,进行下面的递归调用
}
print(arr);
System.out.print("l=" + (l + 1) + "h=" + (h + 1) + "k=" + k + "\n");
// 递归
if (l > low)//先判断l>low再次经行左边排序
sort(arr, low, l - 1);// 左边序列。第一个索引位置到关键值索引-1
if (h < high)//左边依次排序执行完递归后,弹栈进行右边排序
sort(arr, l + 1, high);// 右边序列。从关键值索引+1到最后一个
}
// 打印数组的方法
private static void print(int[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length; i++) {
if (i != (arr.length - 1)) {
System.out.print(arr[i] + ",");
} else {
System.out.print(arr[i] + "]");
System.out.println();
}
}
}
}

7、堆排序
思路:堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public static void sort(int[] a){
int N = a.length;
int[] keys = new int[N+1];
//注意,堆的数据结构是从1开始的,0不用
for (int i = 1; i < keys.length; i++) {
keys[i] = a[i-1];
}
// //构造堆,使得堆是有序的
for(int k = N/2;k>=1;k--) sink(keys,k,N);
//排序,相当于毁掉堆
while(N>1){
exch(keys,1,N--);
sink(keys,1,N);
}
//重新写回数组
for (int i = 0; i < a.length; i++) {
a[i] = keys[i+1];
}
}

private static void sink(int[] a, int k, int N) {
// TODO Auto-generated method stub
while(2*k<=N){
int j = 2*k;
if (j < N && less(a[j], a[j+1])) j++;
if (less(a[j], a[k])) break;
exch(a, k, j);
k = j;
}
}

private static boolean less(int k, int j) {
// TODO Auto-generated method stub
return k < j;
}

private static void exch(int[] a, int i, int n) {
// TODO Auto-generated method stub
int temp = a[i];
a[i] = a[n];
a[n] = temp;
}

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package test;

public class dui {
/**
* 调整为小顶堆(排序后结果为从大到小)
*
* @param array是待调整的堆数组
* @param s是待调整的数组元素的位置
* @param length是数组的长度
*
*/
public static void heapAdjustS(int[] array, int s, int length) {
int tmp = array[s];
int child = 2 * s + 1;// 左孩子结点的位置
System.out.println("待调整结点为:array[" + s + "] = " + tmp);
while (child < length) {
// child + 1 是当前调整结点的右孩子
// 如果有右孩子且小于左孩子,使用右孩子与结点进行比较,否则使用左孩子
if (child + 1 < length && array[child] > array[child + 1]) {
child++;
}
System.out.println("将与子孩子 array[" + child + "] = " + array[child] + " 进行比较");
// 如果较小的子孩子比此结点小
if (array[s] > array[child]) {
System.out.println("子孩子比其小,交换位置");
array[s] = array[child];// 把较小的子孩子向上移动,替换当前待调整结点
s = child;// 待调整结点移动到较小子孩子原来的位置
array[child] = tmp;
child = 2 * s + 1;// 继续判断待调整结点是否需要继续调整

if (child >= length) {
System.out.println("没有子孩子了,调整结束");
} else {
System.out.println("继续与新的子孩子进行比较");
}
// continue;
} else {
System.out.println("子孩子均比其大,调整结束");
break;// 当前待调整结点小于它的左右孩子,不需调整,直接退出
}
}
}

/**
* 调整为大顶堆(排序后结果为从小到大)
*
* @param array是待调整的堆数组
* @param s是待调整的数组元素的位置
* @param length是数组的长度
*
*/
public static void heapAdjustB(int[] array, int s, int length) {
int tmp = array[s];
int child = 2 * s + 1;// 左孩子结点的位置
System.out.println("待调整结点为:array[" + s + "] = " + tmp);
while (child < length) {
// child + 1 是当前调整结点的右孩子
// 如果有右孩子且大于左孩子,使用右孩子与结点进行比较,否则使用左孩子
if (child + 1 < length && array[child] < array[child + 1]) {
child++;
}
System.out.println("将与子孩子 array[" + child + "] = " + array[child] + " 进行比较");
// 如果较大的子孩子比此结点大
if (array[s] < array[child]) {
System.out.println("子孩子比其大,交换位置");
array[s] = array[child];// 把较大的子孩子向上移动,替换当前待调整结点
s = child;// 待调整结点移动到较大子孩子原来的位置
array[child] = tmp;
child = 2 * s + 1;// 继续判断待调整结点是否需要继续调整

if (child >= length) {
System.out.println("没有子孩子了,调整结束");
} else {
System.out.println("继续与新的子孩子进行比较");
}
// continue;
} else {
System.out.println("子孩子均比其小,调整结束");
break;// 当前待调整结点大于它的左右孩子,不需调整,直接退出
}
}
}

/**
* 堆排序算法
*
* @param array
* @param inverse true 为倒序排列,false 为正序排列
*/
public static void heapSort(int[] array, boolean inverse) {
// 初始堆
// 最后一个有孩子的结点位置 i = (length - 1) / 2, 以此向上调整各结点使其符合堆
System.out.println("初始堆开始");
for (int i = (array.length - 1) / 2; i >= 0; i--) {
if (inverse) {
heapAdjustS(array, i, array.length);
} else {
heapAdjustB(array, i, array.length);
}
}
System.out.println("初始堆结束");
for (int i = array.length - 1; i > 0; i--) {
// 交换堆顶元素H[0]和堆中最后一个元素
int tmp = array[i];
array[i] = array[0];
array[0] = tmp;
// 每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
if (inverse) {
heapAdjustS(array, 0, i);
} else {
heapAdjustB(array, 0, i);
}
}
}

public static void main(String[] args) {
int[] array = { 49, 38, 65, 97, 76, 13, 27, 49 };
heapSort(array, false);
for (int i : array) {
System.out.print(i + " ");
}
}

}

img

8、计数排序
思路:将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

  找出待排序的数组中最大和最小的元素;
  统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 输入数组的元素都是介于0..k之间的
* @param data 待排序数组
* @param k 最大元素
* @return 排序结果
*/
public static int[] sort(int[] data, int k) {
// 存放临时数据的数组tmp,初始元素都是0;k为数组中最大元素
int[] tmp = new int[k + 1];

// 计算数组中每个元素i出现的次数,存入数组tmp中的第i项,即原数组中的元素值为tmp数组中的下标
for (int i = 0; i <= data.length - 1; i++) {
tmp[data[i]]++;
}
// 计算数组中小于等于每个元素的个数,即从tmp中的第一个元素开始,每一项和前一项相加
for (int j = 1; j <= k; j++) {
tmp[j] = tmp[j] + tmp[j - 1];
}
// result数组用来来存放排序结果
int[] result = new int[data.length];
for (int i = data.length - 1; i >= 0; i--) {
result[tmp[data[i]] - 1] = data[i];
tmp[data[i]]--;
}
return result;
}

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package test;

public class jishu {
public static int[] countingSort(int[] theArray) {
int[] lastArray = new int[theArray.length];
for(int i = 0; i < theArray.length; i++) {
int count = 0;
for(int j = 0; j < theArray.length; j++) {
if(theArray[i] > theArray[j]) {
count++;
}
}
lastArray[count] = theArray[i];
}
return lastArray;
}
public static void main(String[] args) {
int []theArray = {6, 4, 5, 1, 8, 7, 2, 3};
System.out.print("之前的排序:");
for(int i = 0; i < theArray.length; i++) {
System.out.print(theArray[i] + " ");
}

int []resultArray = countingSort(theArray);

System.out.print("计数排序:");
for(int i = 0; i < resultArray.length; i++) {
System.out.print(resultArray[i] + " ");
}
}

}

img

9、桶排序
思路:桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

  设置一个定量的数组当作空桶;
  遍历输入数据,并且把数据一个一个放到对应的桶里去;
  对每个不是空的桶进行排序;
  从不是空的桶里把排好序的数据拼接起来。
视频介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void bucketSort(double array[]) {
int length = array.length;
ArrayList arrList[] = new ArrayList[length];
for (int i = 0; i < length; i++) {
//0.7到0.79放在第8个桶里,编号7;第一个桶放0到0.09
int temp = (int) Math.floor(10 * array[i]);
if (null == arrList[temp])
arrList[temp] = new ArrayList();
arrList[temp].add(array[i]);
}
// 对每个桶中的数进行插入排序
for (int i = 0; i < length; i++) {
if (null != arrList[i]) {
Collections.sort(arrList[i]);
}
}
int count = 0;
for (int i = 0; i < length; i++) {
if (null != arrList[i]) {
Iterator iter = arrList[i].iterator();
while (iter.hasNext()) {
Double d = (Double) iter.next();
array[count] = d;
count++;
}
}
}
}

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 package test;

public class tong {
private int[] buckets;
private int[] array;

public tong(int range,int[] array){
this.buckets = new int[range];
this.array = array;
}

/*排序*/
public void sort(){
if(array!=null && array.length>1){
for(int i=0;i<array.length;i++){
buckets[array[i]]++;
}
}
}

/*排序输出*/
public void sortOut(){
//倒序输出数据
for (int i=buckets.length-1; i>=0; i--){
for(int j=0;j<buckets[i];j++){
System.out.print(i+"\t");
}
}
}


public static void main(String[] args) {
testBucketsSort();
}

private static void testBucketsSort(){
int[] array = {5,7,3,5,4,8,6,4,1,2};
tong bs = new tong(10, array);
bs.sort();
bs.sortOut();//输出打印排序
}

}

img

10、基数排序
思路:基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

  取得数组中的最大数,并取得位数;
  arr为原始数组,从最低位开始取每个位组成radix数组;
  对radix进行计数排序(利用计数排序适用于小范围数的特点);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void radixSort(int[] array,int radix, int distance) {
int length = array.length;
int[] temp = new int[length];
int[] count = new int[radix];
int divide = 1;

for (int i = 0; i < distance; i++) {

System.arraycopy(array, 0,temp, 0, length);
Arrays.fill(count, 0);

for (int j = 0; j < length; j++) {
int tempKey = (temp[j]/divide)%radix;
count[tempKey]++;
}

for (int j = 1; j < radix; j++) {
count [j] = count[j] + count[j-1];
}
for (int j = length - 1; j >= 0; j--) {
int tempKey = (temp[j]/divide)%radix;
count[tempKey]--;
array[count[tempKey]] = temp[j];
}
divide = divide * radix;
}
}

代码例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  package test;
package test;

/**
* 基数排序
* 平均O(d(n+r)),最好O(d(n+r)),最坏O(d(n+r));空间复杂度O(n+r);稳定;较复杂
* d为位数,r为分配后链表的个数
*
*
*/
public class ji_shu {
//pos=1表示个位,pos=2表示十位
public static int getNumInPos(int num, int pos) {
int tmp = 1;
for (int i = 0; i < pos - 1; i++) {
tmp *= 10;
}
return (num / tmp) % 10;
}
//求得最大位数d
public static int getMaxWeishu(int[] a) {
int max = a[0];
for (int i = 0; i < a.length; i++) {
if (a[i] > max)
max = a[i];
}
int tmp = 1, d = 1;
while (true) {
tmp *= 10;
if (max / tmp != 0) {
d++;
} else
break;
}
return d;
}
public static void radixSort(int[] a, int d) {
int[][] array = new int[10][a.length + 1];
for (int i = 0; i < 10; i++) {
array[i][0] = 0;
// array[i][0]记录第i行数据的个数
}
for (int pos = 1; pos <= d; pos++) {
for (int i = 0; i < a.length; i++) {
// 分配过程
int row = getNumInPos(a[i], pos);
int col = ++array[row][0];
array[row][col] = a[i];
}
for (int row = 0, i = 0; row < 10; row++) {
// 收集过程
for (int col = 1; col <= array[row][0]; col++) {
a[i++] = array[row][col];
}
array[row][0] = 0;
// 复位,下一个pos时还需使用
}
}
}
public static void main(String[] args) {
int[] a = { 49, 38, 65, 197, 76, 213, 27, 50 };
radixSort(a, getMaxWeishu(a));
for (int i : a)
System.out.print(i + " ");
}
}

img
小结
  排序算法要么简单有效,要么是利用简单排序的特点加以改进,要么是以空间换取时间在特定情况下的高效排序。但是这些排序方法都不是固定不变的,需要结合具体的需求和场景来选择甚至组合使用。才能达到高效稳定的目的。没有最好的排序,只有最适合的排序。
  下面就总结一下排序算法的各自的使用场景和适用场合。
img

  1. 从平均时间来看,快速排序是效率最高的,但快速排序在最坏情况下的时间性能不如堆排序和归并排序。而后者相比较的结果是,在n较大时归并排序使用时间较少,但使用辅助空间较多。

  2. 上面说的简单排序包括除希尔排序之外的所有冒泡排序、插入排序、简单选择排序。其中直接插入排序最简单,但序列基本有序或者n较小时,直接插入排序是好的方法,因此常将它和其他的排序方法,如快速排序、归并排序等结合在一起使用。

  3. 基数排序的时间复杂度也可以写成O(d*n)。因此它最使用于n值很大而关键字较小的的序列。若关键字也很大,而序列中大多数记录的最高关键字均不同,则亦可先按最高关键字不同,将序列分成若干小的子序列,而后进行直接插入排序。

  4. 从方法的稳定性来比较,基数排序是稳定的内排方法,所有时间复杂度为O(n^2)的简单排序也是稳定的。但是快速排序、堆排序、希尔排序等时间性能较好的排序方法都是不稳定的。稳定性需要根据具体需求选择。

  5. 上面的算法实现大多数是使用线性存储结构,像插入排序这种算法用链表实现更好,省去了移动元素的时间。具体的存储结构在具体的实现版本中也是不同的。

-------------本文结束 感谢阅读-------------