内排序

  排序使程序开发中一种常见的操作。将一组杂乱无章的数据按一定的规则将其排列好。使数据得以快速检索到,这就是排序的目的。
对于一个排序算法,一般可以从下面三个方面去衡量算法的优劣:

1) 时间复杂度:主要缝隙关键字的比较次数和移动字数;
2) 空间复杂度:缝隙排序算法中需要多少辅助内存;
3) 稳定性:当两个关键字相同,如果排序完成后两者的顺序保持不变,则是该排序算法是稳定。
 排序算法大致可以分为内部算法和外部算法。如果整个排序过程不需要借助于外部储存器(磁盘),所有的操作都在内存中完成,则称之为内部算法。如果参与排序树数据元素非常多,数据量非常大,则计算机必须借助外部储存器(如磁盘),这种排序称为外部排序。

内部排序的分类

一般我们所说排序指的就是内部排序,常见的内部排序又可以分为6大类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package sort;

// 定义一个数据包装类
class DataWrap implements Comparable<DataWrap>
{
int data;
String flag;
public DataWrap(int data, String flag)
{
this.data = data;
this.flag = flag;
}
public String toString()
{
return data + flag;
}
// 根据data实例变量来决定两个DataWrap的大小
public int compareTo(DataWrap dw)
{
return this.data > dw.data ? 1
: (this.data == dw.data ? 0 : -1);
}
}

1.1 选择排序

选择排序方法有两种,直接选择排序和堆排序。直接选择排序简单直观,但是性能较差;堆排序较为高效,但是实现起来较为复杂。

1.1.1 直接选择排序

直接选择排序思想:程序遍历n个数,然后比较出最大的一个与最后一个数进行交换,最大值确定好了;接下来遍历n-1,找个这n-1个数中的最大值并且交换位置,直至n=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
43
44
45
46
47
48
49
50
51
52
package sort;

import java.util.*;

public class SelectSort
{
public static void selectSort(DataWrap[] data)
{
System.out.println("开始排序");
int arrayLength = data.length;
// 依次进行n-1趟比较, 第i趟比较将第i大的值选出放在i位置上。
for (int i = 0; i < arrayLength - 1 ; i++ )
{
// minIndex永远保留本趟比较中最小值的索引
int minIndex = i ;
// 第i个数据只需和它后面的数据比较
for (int j = i + 1 ; j < arrayLength ; j++ )
{
// 如果第minIndex位置的数据 > j位置的数据
if (data[minIndex].compareTo(data[j]) > 0)
{
// 将j的值赋给minIndex
minIndex = j;
}
}
// 每趟比较最多交换一次
if (minIndex != i)
{
DataWrap tmp = data[i];
data[i] = data[minIndex];
data[minIndex] = tmp;
}
System.out.println(java.util.Arrays.toString(data));
}
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(21 , ""),
new DataWrap(30 , ""),
new DataWrap(49 , ""),
new DataWrap(30 , "*"),
new DataWrap(16 , ""),
new DataWrap(9 , "")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
selectSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.1.2 堆排序

堆排序的思路:将数据建立成最大(小)堆,将根节点与最后一个节点交换;重复上面的过程。
1)堆树是一颗完全二叉树;
2)堆树中某个节点的值总是不大于或不小于其孩子节点的值;
3)堆树中每个节点的子树都是堆树。
当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆。如下图所示,左边为最大堆,右边为最小堆。
引用:https://www.cnblogs.com/zf-blog/p/9010977.html
堆排序是选择算法的改进,可以减少选择排序中的比较次数,进而减少排序时间。对于堆排序来说,有n个数据,需要进行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
55
56
57
58
59
60
61
62
63
64
65
66
67
package sort;



public class HeapSort {
public static void heapSort(DataWrap[] data) {
System.out.println("开始排序");
int arrayLength=data.length;
//循环建堆
for(int i=0;i<arrayLength-1;i++) {
//建堆
buildMaxdHeap(data,arrayLength-1-i);
//交换堆与最后一个的数据
swap(data,0,arrayLength-1-i);
System.out.println(java.util.Arrays.toString(data));
}
}

private static void swap(DataWrap[] data, int i, int j) {
// TODO Auto-generated method stub
DataWrap tmp=data[i];
data[i]=data[j];
data[j]=tmp;


}

private static void buildMaxdHeap(DataWrap[] data, int lastIndex) {
//从lastIndex出节点(最后一个)的父节点开始
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;
//判断是否存在右子节点
if(biggerIndex<lastIndex) {
biggerIndex++;
}
if((data[k].compareTo(data[biggerIndex])<0)) {
swap(data,k,biggerIndex);
k=biggerIndex;
}else {
break;
}
}
}
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(21 , ""),
new DataWrap(30 , ""),
new DataWrap(49 , ""),
new DataWrap(30 , "*"),
new DataWrap(21 , "*"),
new DataWrap(16 , ""),
new DataWrap(9 , "")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
heapSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.2 交换排序

交换排序的主体操作是对数组中的数据不断的进行交换操作。交换排序主要有冒泡排序和快速排序。

1.2.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
package sort;

public class BubbleSort {
public static void bubbleSort(DataWrap[] data) {
System.out.println("开始排序");
int arrayLength=data.length;
for(int i=0;i<arrayLength-1;i++) {
for(int j=0;j<arrayLength-1-i;j++) {
if(data[j].compareTo(data[j+1])>0) {
DataWrap tmp=data[j+1];
data[j+1]=data[j];
data[j]=tmp;
}
}
System.out.println(java.util.Arrays.toString(data));
}
}

public static void main(String[] args) {
DataWrap[] data = { new DataWrap(9, ""), new DataWrap(16, ""), new DataWrap(21, "*"), new DataWrap(23, ""),
new DataWrap(30, ""), new DataWrap(49, ""), new DataWrap(21, ""), new DataWrap(30, "*") };
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
bubbleSort(data);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}

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


public class QuickSort
{
// 将指定数组的i和j索引处的元素交换
private static void swap(DataWrap[] data, int i, int j)
{
DataWrap tmp;
tmp = data[i];
data[i] = data[j];
data[j] = tmp;
}
// 对data数组中从start~end索引范围的子序列进行处理
// 使之满足所有小于分界值的放在左边,所有大于分界值的放在右边
private static void subSort(DataWrap[] data
, int start , int end)
{
// 需要排序
if (start < end)
{
// 以第一个元素作为分界值
DataWrap base = data[start];
// i从左边搜索,搜索大于分界值的元素的索引
int i = start;
// j从右边开始搜索,搜索小于分界值的元素的索引
int j = end + 1;
while(true)
{
// 找到大于分界值的元素的索引,或i已经到了end处
while(i < end && data[++i].compareTo(base) <= 0);
// 找到小于分界值的元素的索引,或j已经到了start处
while(j > start && data[--j].compareTo(base) >= 0);
if (i < j)
{
swap(data , i , j);
}
else
{
break;
}
}
swap(data , start , j);
// 递归左子序列
subSort(data , start , j - 1);
// 递归右边子序列
subSort(data , j + 1, end);
}
}
public static void quickSort(DataWrap[] data)
{
subSort(data , 0 , data.length - 1);
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(9 , ""),
new DataWrap(-16 , ""),
new DataWrap(21 , "*"),
new DataWrap(23 , ""),
new DataWrap(-30 , ""),
new DataWrap(-49 , ""),
new DataWrap(21 , ""),
new DataWrap(30 , "*"),
new DataWrap(13 , "*")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
quickSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.3 插入排序

插入排序主要分为直接插入排序、Shell排序和折半排序。

1.3.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
package sort;

public class insertSort {
public static void insertSort(DataWrap[] data) {
System.out.println("开始排序");
int arrayLenght=data.length;
for(int i=1;i<arrayLenght;i++) {
DataWrap tmp=data[i];

if(data[i].compareTo(data[i-1])<0){
int j=i-1;
for(;j>=0&&data[j].compareTo(tmp)>0;j--) {
data[j+1]=data[j];
}

data[j+1]=tmp;
}
System.out.println(java.util.Arrays.toString(data));
}

}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(9 , ""),
new DataWrap(-16 , ""),
new DataWrap(21 , "*"),
new DataWrap(23 , ""),
new DataWrap(-30 , ""),
new DataWrap(-49 , ""),
new DataWrap(21 , ""),
new DataWrap(30 , "*"),
new DataWrap(13 , "*")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
insertSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.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
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
package sort;

public class BinaryInsertSort
{
public static void binaryInsertSort(DataWrap[] data)
{
System.out.println("开始排序:\n");
int arrayLength = data.length;
for(int i = 1 ; i < arrayLength ; i++)
{
// 当整体后移时,保证data[i]的值不会丢失
DataWrap tmp = data[i];
int low = 0;
int high = i - 1;
while(low <= high)
{
// 找出low、high中间的索引
int mid = (low + high) / 2;
// 如果tmp值大于low、high中间元素的值
if(tmp.compareTo(data[mid]) > 0)
{
// 限制在索引大于mid的那一半中搜索
low = mid + 1;
}
else
{
// 限制在索引小于mid的那一半中搜索
high = mid - 1;
}
}
// 将low到i处的所有元素向后整体移一位
for(int j = i ; j > low ; j--)
{
data[j] = data[j - 1];
}
// 最后将tmp的值插入合适位置
data[low] = tmp;
System.out.println(java.util.Arrays.toString(data));
}
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(9 , ""),
new DataWrap(-16 , ""),
new DataWrap(21 , "*"),
new DataWrap(23 , ""),
new DataWrap(-30 , ""),
new DataWrap(-49 , ""),
new DataWrap(21 , ""),
new DataWrap(30 , "*"),
new DataWrap(30 , "")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
binaryInsertSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.3.3 希尔排序

Shell排序方法思路:以一个较为合适的步长h,让数据中相差为h的步数进行排序;让后将步长改为h/2,在进行排序;递归下去,直至h=1。(当h=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
55
56
57
58
public class ShellSort
{
public static void shellSort(DataWrap[] data)
{
System.out.println("开始排序:");
int arrayLength = data.length;
// h变量保存可变增量
int h = 1;
// 按h * 3 + 1得到增量序列的最大值
while(h <= arrayLength / 3)
{
h = h * 3 + 1;
}
while(h > 0)
{
System.out.println("===h的值:" + h + "===");
for (int i = h ; i < arrayLength ; i++ )
{
// 当整体后移时,保证data[i]的值不会丢失
DataWrap tmp = data[i];
// i索引处的值已经比前面所有值都大,表明已经有序,无需插入
// (i-1索引之前的数据已经有序的,i-1索引处元素的值就是最大值)
if (data[i].compareTo(data[i - h]) < 0)
{
int j = i - h;
// 整体后移h格
for ( ; j >= 0 && data[j].compareTo(tmp) > 0 ; j-=h)
{
data[j + h] = data[j];
}
// 最后将tmp的值插入合适位置
data[j + h] = tmp;
}
System.out.println(java.util.Arrays.toString(data));
}
h = (h - 1) / 3;
}
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(9 , ""),
new DataWrap(-16 , ""),
new DataWrap(21 , "*"),
new DataWrap(23 , ""),
new DataWrap(-30 , ""),
new DataWrap(-49 , ""),
new DataWrap(21 , ""),
new DataWrap(30 , "*"),
new DataWrap(30 , ""),
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
shellSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

1.4 归并排序

归并排序基本思想:就是将两个有序的序列合成一个新的有序序列。其时间效率为,需要一个与原始序列同样大的辅助序列,因此空间效率较差。算法是稳定的。

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
class DataWrap implements Comparable<DataWrap>
{
int data;
String flag;
public DataWrap(int data, String flag)
{
this.data = data;
this.flag = flag;
}
public String toString()
{
return data + flag;
}
// 根据data实例变量来决定两个DataWrap的大小
public int compareTo(DataWrap dw)
{
return this.data > dw.data ? 1
: (this.data == dw.data ? 0 : -1);
}
}
public class MergeSort
{
// 利用归并排序算法对数组data进行排序
public static void mergeSort(DataWrap[] data)
{
// 归并排序
sort(data , 0 , data.length - 1);
}
/**
* 将索引从left到right范围的数组元素进行归并排序
* @param data 待排序的数组
* @param left 待排序的数组的第一个元素索引
* @param right 待排序的数组的最后一个元素的索引
*/
private static void sort(DataWrap[] data
, int left, int right)
{
if (left < right)
{
// 找出中间索引
int center = (left + right) / 2;
// 对左边数组进行递归
sort(data, left, center);
// 对右边数组进行递归
sort(data, center + 1, right);
// 合并
merge(data, left, center, right);
}
}
/**
* 将两个数组进行归并,归并前两个数组已经有序,归并后依然有序。
* @param data 数组对象
* @param left 左数组的第一个元素的索引
* @param center 左数组的最后一个元素的索引,center+1是右数组第一个元素的索引
* @param right 右数组的最后一个元素的索引
*/
private static void merge(DataWrap[] data
, int left, int center, int right)
{
// 定义一个与待排序序列长度相同的临时数组
DataWrap[] tmpArr = new DataWrap[data.length];
int mid = center + 1;
// third记录中间数组的索引
int third = left;
int tmp = left;
while (left <= center && mid <= right)
{
// 从两个数组中取出小的放入中间数组
if (data[left].compareTo(data[mid]) <= 0)
{
tmpArr[third++] = data[left++];
}
else
{
tmpArr[third++] = data[mid++];
}
}
// 剩余部分依次放入中间数组
while (mid <= right)
{
tmpArr[third++] = data[mid++];
}
while (left <= center)
{
tmpArr[third++] = data[left++];
}
// 将中间数组中的内容复制拷回原数组
// (原left~right范围的内容复制回原数组)
while (tmp <= right)
{
data[tmp] = tmpArr[tmp++];
}
}
public static void main(String[] args)
{
DataWrap[] data = {
new DataWrap(9 , ""),
new DataWrap(-16 , ""),
new DataWrap(21 , "*"),
new DataWrap(23 , ""),
new DataWrap(-30 , ""),
new DataWrap(-49 , ""),
new DataWrap(21 , ""),
new DataWrap(30 , "*"),
new DataWrap(30 , "")
};
System.out.println("排序之前:\n"
+ java.util.Arrays.toString(data));
mergeSort(data);
System.out.println("排序之后:\n"
+ java.util.Arrays.toString(data));
}
}

https://www.cnblogs.com/tianliang94/p/10458888.html