喵星之旅-成长的雏鹰-java语言基础-7-数组排序、Arrays工具类使用

排序是什么

所谓排序就是按照其中的某个或某些关键字的大小,按照升序或者降序的方式把他们排列起来的操作。
在我们的生活中有很多关于排序的例子:一个班级的学生的成绩排序;在买东西时,这些商品可以按照销量、价格进行排序……
在介绍排序之前,我们要介绍排序中一个比较重要的概念-稳定性。何为稳定性:稳定性就是在待排序的记录序列中,存在相同的记录,这些相同记录有着一定的顺序,在我们使用某个排序算法对序列进行排序后,这些相同的序列仍然按照之前的顺序存在在序列中,这种排序算法我们称它是稳定的,否则就是不稳定的。
java中的排序主要是基于比较的、原地的、内存的、升序的。

冒泡排序

冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 /*
* 冒泡排序
* 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
* 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
* 针对所有的元素重复以上的步骤,除了最后一个。
* 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
* @param numbers 需要排序的整型数组
*/
public static void bubbleSort(int[] numbers)
{
int temp = 0;
int size = numbers.length;
for(int i = 0 ; i < size-1; i ++) {
for(int j = 0 ;j < size-1-i ; j++) {
//交换两数位置
if(numbers[j] > numbers[j+1]) {
temp = numbers[j];
numbers[j] = numbers[j+1];
numbers[j+1] = temp;
}
}
}
}

选择排序

先定义一个记录最小元素的下标,然后循环一次后面的,找到最小的元素,最后将他放到前面排序好的序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

public static void main(String[] args) {
int a[]=new int[]{3,1,4,1,5,9,2,6,5,3,5,8,9};
for (int i = 0; i < a.length-1; i++) {
int index=i;//标记第一个为待比较的数
for (int j = i+1; j < a.length; j++) { //然后从后面遍历与第一个数比较
if (a[j]<a[index]) { //如果小,就交换最小值
index=j;//保存最小元素的下标
}
}
//找到最小值后,将最小的值放到第一的位置,进行下一遍循环
int temp=a[index];
a[index]=a[i];
a[i]=temp;
}
}

插入排序

定义一个待插入的数,再定义一个待插入数的前一个数的下标,然后拿待插入数与前面的数组一一比较,最后交换。

1
2
3
4
5
6
7
8
9
10
11
12
  int a[]=new int[]{3,1,4,1,5,9,2,6,5,3,5,8,9};
for (int i = 1; i < a.length; i++) { //长度不减1,是因为要留多一个位置方便插入数
//定义待插入的数
int insertValue=a[i];
//找到待插入数的前一个数的下标
int insertIndex=i-1;
while (insertIndex>=0 && insertValue <a[insertIndex]) {//拿a[i]与a[i-1]的前面数组比较
a[insertIndex+1]=a[insertIndex];
insertIndex--;
}
a[insertIndex+1]=insertValue;
}

希尔排序

如果我们要插入一个元素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
public class ShellSort {
public static void main(String[] args) {
int[] arr =new int[]{3,1,4,1,5,9,2,6,5,3,5,8,9};
shellSort(arr);
System.out.println(Arrays.toString(arr));

}

//希尔排序
public static void shellSort(int[] arr){

//挨个遍历步长,缩短步长,直到步长为零
for(int gap =arr.length/2;d>0;gap=gap/2){
//遍历所有的元素
for(int i=gap;i<arr.length;i++){
//遍历本组中所有的元素,从第一个元素开始
for(int j=i-gap;j>=0;j=j-gap){//本组前面的那个元素
//如果当前元素大于加上步长之后的那个元素,(前面的比后面的大了)交换
if(arr[j]>arr[j+gap]){
int temp = arr[j];
arr[j]=arr[j+gap];
arr[j+gap]=temp;
}
}
}
}
}
}

快速排序

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
排序原理:

1
2
3
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
public class Quick {

/*
比较v元素是否大于等于w元素
*/
private static boolean greater(Comparable v, Comparable w){
return v.compareTo(w) >= 0;
}


/*
数组元素i和j交换位置
*/
private static void exch(Comparable[] args, int i,int j){
Comparable temp;
temp = args[i];
args[i] = args[j];
args[j] = temp;
}

// 对数组内的元素进行排序
public static void quickSort(Comparable[] args) {
quickSort(args, 0, args.length - 1);
}

public static void quickSort(Comparable[] args, int left, int right) {
// 安全性校验
if (right < left){
return;
}
// 取第一个数为基准值
Comparable key = args[left];
int i = left, j = right;
while (i < j){
// 先从右往左扫描,找到一个比基准值小的元素
while (greater(args[j], key) && j > i)
j --;
// 再从左往右扫描,找一个比基准值大的元素
while (greater(key, args[i]) && i < j)
i ++;
if (i < j)
exch(args, i, j);
}
// 交换基准值与i、j相遇位置的元素
exch(args, left, i);
// 对分成的左边部分进行排序
quickSort(args, left, i - 1);
// 对分成的右边部分进行排序
quickSort(args, i + 1, right);
}
}

归并排序

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
排序原理

1
2
3
尽可能的一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是 1为止;
将相邻的两个子组进行合并成一个有序的大组;
不断的重复步骤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
public class Merge {
// 归并所需要的辅助数组
private static Comparable[] assistTemp;

/*
对数组 args 中的元素进行排序
*/
public static void mergeSort(Comparable[] args) {
// 初始化辅助数组 assistTemp
assistTemp = new Comparable[args.length];
mergeSort(args, 0, args.length - 1, assistTemp);
}

public static void mergeSort(Comparable[] args, int left, int right, Comparable[] assistTemp) {
// 分解
if (left < right) {
int mid = (left+right) / 2;
// 向左递归进行分解
mergeSort(args, left, mid, assistTemp);
// 向右递归进行分解
mergeSort(args, mid + 1, right, assistTemp);
// 每分解一次便合并一次
merge(args, left, right, mid, assistTemp);
}
}

/*
合并
*/
private static void merge(Comparable[] args, int left, int right, int mid, Comparable[] assistTemp) {
// 用于合并数组的下标,即操作 assistTemp 下标
int tempIndex = 0;
int i = left, j = mid + 1;
while(i <= mid && j <= right){
// 当 args[i] <= args[j] 时,将 args[i] 加入 assistTemp
if(greater(args[j], args[i])){
assistTemp[tempIndex ++] = args[i ++];
}
else{
assistTemp[tempIndex ++] = args[j ++];
}
}
// 将未遍历完的数组直接放入 assistTemp
while(i <= mid){
assistTemp[tempIndex ++] = args[i ++];
}
while(j <= right){
assistTemp[tempIndex ++] = args[j ++];
}
// 将 assistTemp 的元素赋值给 args
for(i = right; i >= left; --i){
args[i] = assistTemp[--tempIndex];
}
}

/*
比较 v元素 是否大于 w元素
*/
private static boolean greater(Comparable v, Comparable w){
return v.compareTo(w) > 0;
}
}

快速排序和归并排序的区别:
快速排序是一种分治的排序算法,它将一个数组分成两个子数组,将两部分独立的排序。快速排序和归并排序是互补的:归并排序将数组分成两个子数组分别排序,并将有序的子数组归并从而将整个数组排序,而快速排序的方式则是当两个数组都有序时,整个数组自然就有序了。在归并排序中,一个数组被等分为两半,归并调用发生在处理整个数组之前,在快速排序中,切分数组的位置取决于数组的内容,递归调用发生在处理整个数组之后。

计数排序

计数排序是一个基于非比较的排序算法,元素从未排序状态变成已排序状态的过程,是由额外空间的计数和元素本身的值决定的。

排序步骤:

1
2
3
4
找出待排序的数组array中最大的元素max
统计数组中每个值为 i 的元素出现的次数,存入数组 count 的第 i 项
对所有的计数累加(从 count中的第一个元素开始,每一项和前一项相加)
反向填充目标数组:将每个元素 i 放在新数组的第 count [i] 项,每放一个元素就将 count [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
27
28
29
30
31
32
33
34
35
36
37
38
public class Count {

public static void countSort(Comparable[] args) {
// 获取最大值,以便确定 countArray 大小
int countLen =(int) findMaxElement(args) + 1;
int[] countArray = new int[countLen];
// 开始计数
for (Comparable t: args)
countArray[(int) t] ++;
int idx = 0;
for (int i = 0; i < countLen; ++i){
while (countArray[i] > 0){
args[idx ++] = i;
countArray[i] --;
}
}
}

/*
比较v元素是否大于w元素
*/
private static boolean greater(Comparable v, Comparable w){
return v.compareTo(w) > 0;
}

/*
返回数组最大值
*/
private static Comparable findMaxElement(Comparable[] args) {
if (args.length == 0) return null;
Comparable max = args[0];
for (Comparable t: args){
if (greater(t, max))
max = t;
}
return max;
}
}

Arrays常用方法

Arrays.toString()方法

方法作用:快速输出数组内容。

1
2
int[] a = {1,2,3,4,5};
System.out.println(Arrays.toString(a));

Arrays.sort()方法

方法运用:给数组排序,默认升序

1
2
3
int[] a = new int[5]{5,4,3,2,1};
Arrays.sort(a); // 1 2 3 4 5

Arrays.equals()方法

Arrays.equals()方法

1
2
3
int[] a = {1,2,3};
int[] b = {1,2,3};
boolean isSame = Arrays.equals(a,b);

Arrays.equals()是比较数组内容,而a.equals(b) 这样的方法是比较地址值

Arrays.copyOf()

方法作用:拷贝数组
第一个参数是原数组,第二个参数是拷贝长度(可以小于原数组长度,也可以大于,相等也可以),返回值是将原数组拷贝一份返回。
返回值是一个新数组,会改变接收这个新数组的引用的一些属性。

1
2
int[] arr1 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] arr2 = Arrays.copyOf(arr1, 7);
文章目录
  1. 排序是什么
  2. 冒泡排序
  3. 选择排序
  4. 插入排序
  5. 希尔排序
  6. 快速排序
  7. 归并排序
  8. 计数排序
  9. Arrays常用方法
    1. Arrays.toString()方法
    2. Arrays.sort()方法
    3. Arrays.equals()方法
    4. Arrays.copyOf()
|