目录
  1. 1. 一、选择排序Select Sort
  2. 2. 二、插入排序InsertionSort
  3. 3. 三、 冒泡排序BubbleSort
  4. 4. 四、希尔排序 没看懂
  5. 5. 五、归并排序
  6. 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
public class SortTestHelper {

// SortTestHelper不允许产生任何实例
private SortTestHelper(){}

// 生成有n个元素的随机数组,每个元素的随机范围为[rangeL, rangeR]
//这里返回值使用包装类是因为后面用到包装类的强转方法?
public static Integer[] generateRandomArray(int n, int rangeL, int rangeR) {

assert rangeL <= rangeR;

Integer[] arr = new Integer[n];

for (int i = 0; i < n; i++)
//(Math.random() * (rangeR - rangeL + 1) + rangeL)是生成rangeL, int rangeR之
//间的随机数
arr[i] = new Integer((int)(Math.random() * (rangeR - rangeL + 1) + rangeL));
return arr;
}

// 打印arr数组的所有内容
public static void printArray(Object arr[]) {

for (int i = 0; i < arr.length; i++){
System.out.print( arr[i] );
System.out.print( ' ' );
}
System.out.println();

return;
}
// 判断arr数组是否有序
public static boolean isSorted(Comparable[] arr){

for( int i = 0 ; i < arr.length - 1 ; i ++ )
if( arr[i].compareTo(arr[i+1]) > 0 )
return false;
return true;
}

// 测试sortClassName所对应的排序算法排序arr数组所得到结果的正确性和算法运行时间
public static void testSort(String sortClassName, Comparable[] arr){

// 通过Java的反射机制,通过排序的类名,运行排序函数
// * 依然是,使用反射机制并不是这个课程的重点, 大家也完全可以使用自己的方式书写代码, 最终只要能够测试出自己书写的算法的效率即可
// * 推荐大家阅读我在问答区向大家分享的一个学习心得: 【学习心得分享】请大家抓大放小,不要纠结于C++语言的语法细节
// * 链接: http://coding.imooc.com/learn/questiondetail/4100.html
try{
// 通过sortClassName获得排序函数的Class对象
Class sortClass = Class.forName(sortClassName);
// 通过排序函数的Class对象获得排序方法
Method sortMethod = sortClass.getMethod("sort",new Class[]{Comparable[].class});
// 排序参数只有一个,是可比较数组arr
Object[] params = new Object[]{arr};

long startTime = System.currentTimeMillis();
// 调用排序函数
sortMethod.invoke(null,params);
long endTime = System.currentTimeMillis();

assert isSorted( arr );

System.out.println( sortClass.getSimpleName()+ " : " + (endTime-startTime) + "ms" );
}
catch(Exception e){
e.printStackTrace();
}
}
}
}

排序时间复杂度最优的是nlogn,首先接触on^2^的算法,编码简单,易于实现,是一些简单情景的首选.在一些特殊情况下,简单的排序算法更有效

一、选择排序Select Sort

第一次遍历1-n个元素,选出最小元素的和第一位元素交换(这个交换可能进行了多次),第二次遍历第2-n个元素,选出最小的和第二位元素交换

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
public class SelectionSort {

// 我们的算法类不允许产生任何实例
private SelectionSort(){}
//Comparable[]是实现了comporable接口的数组,具体没看懂待解决
public static void sort(Comparable[] arr){

int n = arr.length;
for( int i = 0 ; i < n ; i ++ ){
// 寻找[i, n)区间里的最小值的索引
int minIndex = i;
for( int j = i + 1 ; j < n ; j ++ )
// 使用compareTo方法比较两个Comparable对象的大小
if( arr[j].compareTo( arr[minIndex] ) < 0 )
minIndex = j;

swap( arr , i , minIndex);
}
}

private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}

二、插入排序InsertionSort

  • 除了第一个元素之外,从第二个元素开始,若是比前一个元素大就交换,反复进行,直到比前一个元素小的时候停止
  • 插入排序第一段代码由于交换操作非常多会影响算法的速度
  • 改进后,先将目标位置的元素值保存,再将目标位置的元素与前面的元素比较,若是比前一个元素大就将前一个元素值赋给它,再将原来的值与更往前的一个元素比较,并持续将后一个元素的值赋给前一个,直到比某一个元素小的时候停止,并将之前保存的值插入这个位置
  • 插入排序在本来排列就较为有序的数组排序效率很高,因为第二层循环j > 0 && arr[j].compareTo(arr[j-1]) < 0 可能会提前结束
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 class InsertionSort{

// 我们的算法类不允许产生任何实例
private InsertionSort(){}

public static void sort(Comparable[] arr){

int n = arr.length;
for (int i = 0; i < n; i++) {

// 寻找元素arr[i]合适的插入位置

// 写法1
// for( int j = i ; j > 0 ; j -- )
// if( arr[j].compareTo( arr[j-1] ) < 0 )
// swap( arr, j , j-1 );
// else
// break;

// 写法2
for( int j = i; j > 0 && arr[j].compareTo(arr[j-1]) < 0 ; j--)
swap(arr, j, j-1);

}
}

private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}

// 测试InsertionSort
public static void main(String[] args) {

int N = 20000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
SortTestHelper.testSort("bobo.algo.InsertionSort", arr);

return;
}
}
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
import java.util.*;

public class InsertionSort{

// 我们的算法类不允许产生任何实例
private InsertionSort(){}
//Comparable[]是实现了comparable接口的数组,这里面向接口思想,意思只要是实现了Comparable 接口的数组都可以,如果是自定义类型的数组,必须重写Compareto这个函数
public static void sort(Comparable[] arr){

int n = arr.length;
for (int i = 0; i < n; i++) {
// 插入排序好于选择排序,因为循环可能会提前结束,但是又由于插入排序交
//换次数较多导致性能下降,所以后续继续改进了算法采用赋值代替交换
Comparable e = arr[i];
int j = i;//j用来保存e用来插入的位置
for( ; j > 0 && arr[j-1].compareTo(e) > 0 ; j--)
arr[j] = arr[j-1];
arr[j] = e;
}
}

private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}

三、 冒泡排序BubbleSort

将相邻两个元素不断交换,将最大值冒泡到顶部(第n位),再重复这一过程,将次大的值,冒泡到n-1位,直到只剩一个元素。

时间复杂度 On^2^

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
import java.util.Arrays;

public class BubbleSort {
// 我们的算法类不允许产生任何实例
private BubbleSort(){}
public static void sort(Comparable[] arr){
int n = arr.length;
boolean swapped = false;
do{
swapped = false;
for( int i = 1 ; i < n ; i ++ )
if( arr[i-1].compareTo(arr[i]) > 0 ){
swap( arr , i-1 , i );
swapped = true;
}
// 优化, 每一趟Bubble Sort都将最大的元素放在了最后的位置
// 所以下一次排序, 最后的元素可以不再考虑
n --;
}while(swapped);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}

四、希尔排序 没看懂

是插入排序的延伸

复杂度n^3/2^

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
public class ShellSort {

// 我们的算法类不允许产生任何实例
private ShellSort(){}

public static void sort(Comparable[] arr){

int n = arr.length;

// 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
int h = 1;
while (h < n/3) h = 3*h + 1;

while (h >= 1) {

// h-sort the array
for (int i = h; i < n; i++) {

// 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序
Comparable e = arr[i];
int j = i;
for ( ; j >= h && e.compareTo(arr[j-h]) < 0 ; j -= h)
arr[j] = arr[j-h];
arr[j] = e;
}

h /= 3;
}
}
}

五、归并排序

  • 时间复杂度 O nlogn logn是因为将n个数分成logn层 n是因为每层的排序

  • 将元素分成2段进行排序,不断递归

image-20200305212617827
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
import java.util.*;

public class MergeSort{

// 我们的算法类不允许产生任何实例
private MergeSort(){}

// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Comparable[] arr, int l, int mid, int r) {

Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l, j = mid+1;
for( int k = l ; k <= r; k ++ ){

if( i > mid ){ // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j-l]; j ++;
}
else if( j > r ){ // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i-l]; i ++;
}
else if( aux[i-l].compareTo(aux[j-l]) < 0 ){ // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i-l]; i ++;
}
else{ // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j-l]; j ++;
}
}
}

// 递归使用归并排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r) {

// if (l >= r)
// return;

// int mid = (l+r)/2;
// sort(arr, l, mid);
// sort(arr, mid + 1, r);
// merge(arr, l, mid, r);
// 优化2: 对于缩小到一定规模的小规模数组, 使用插入排序
if( r - l <= 15 ){
InsertionSort.sort(arr, l, r);
return;
}

int mid = (l+r)/2;
sort(arr, l, mid);
sort(arr, mid + 1, r);

// 优化1: 对于arr[mid] <= arr[mid+1]的情况,不进行merge
// 对于近乎有序的数组非常有效,但是对于一般情况,有一定的性能损失
if( arr[mid].compareTo(arr[mid+1]) > 0 )
merge(arr, l, mid, r);
}

public static void sort(Comparable[] arr){

int n = arr.length;
sort(arr, 0, n-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
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
import java.util.*;

public class MergeSortBU{

// 我们的算法类不允许产生任何实例
private MergeSortBU(){}

// 将arr[l...mid]和arr[mid+1...r]两部分进行归并
private static void merge(Comparable[] arr, int l, int mid, int r) {

Comparable[] aux = Arrays.copyOfRange(arr, l, r+1);

// 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1
int i = l, j = mid+1;
for( int k = l ; k <= r; k ++ ){

if( i > mid ){ // 如果左半部分元素已经全部处理完毕
arr[k] = aux[j-l]; j ++;
}
else if( j > r ){ // 如果右半部分元素已经全部处理完毕
arr[k] = aux[i-l]; i ++;
}
else if( aux[i-l].compareTo(aux[j-l]) < 0 ){ // 左半部分所指元素 < 右半部分所指元素
arr[k] = aux[i-l]; i ++;
}
else{ // 左半部分所指元素 >= 右半部分所指元素
arr[k] = aux[j-l]; j ++;
}
}
}

public static void sort(Comparable[] arr){

int n = arr.length;

// Merge Sort Bottom Up 无优化版本
// for (int sz = 1; sz < n; sz *= 2)
// for (int i = 0; i < n - sz; i += sz+sz)
// // 对 arr[i...i+sz-1] 和 arr[i+sz...i+2*sz-1] 进行归并
// merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1));

// Merge Sort Bottom Up 优化
// 对于小数组, 使用插入排序优化
for( int i = 0 ; i < n ; i += 16 )
InsertionSort.sort(arr, i, Math.min(i+15, n-1) );

for( int sz = 16; sz < n ; sz += sz )
for( int i = 0 ; i < n - sz ; i += sz+sz )
// 对于arr[mid] <= arr[mid+1]的情况,不进行merge
if( arr[i+sz-1].compareTo(arr[i+sz]) > 0 )
merge(arr, i, i+sz-1, Math.min(i+sz+sz-1,n-1) );

}
}

六、快速排序

通过partition过程,找到第一个元素应该放入的位置,在这个位置前的元素都小于它,在他之后的元素都大于他,并不断递归这个过程

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
import java.util.*;

public class QuickSort {

// 我们的算法类不允许产生任何实例
private QuickSort(){}

// 对arr[l...r]部分进行partition操作
// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]
private static int partition(Comparable[] arr, int l, int r){

Comparable v = arr[l];

int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v
for( int i = l + 1 ; i <= r ; i ++ )
if( arr[i].compareTo(v) < 0 ){
j ++;
swap(arr, j, i);
}

swap(arr, l, j);

return j;
}

// 递归使用快速排序,对arr[l...r]的范围进行排序
private static void sort(Comparable[] arr, int l, int r){

if( l >= r )
return;

int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}

public static void sort(Comparable[] arr){

int n = arr.length;
sort(arr, 0, n-1);
}

private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
文章作者: liuDH
文章链接: http://yoursite.com/2020/03/02/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 毛毛裤裤的世界
打赏
  • 微信
  • 支付寶