排序
排序
1.1 Comparable接口
用于自定义对象的排序规则
需求:
- 定义一个学生类,具有年龄age和username两个属性,并通过Comparable接口提供比较规则
- 定义测试类为Test,在测试类中定义方法Comparable getMax(Comparable c1,Comparable c2)完成测试
基本方法省略
package com.zebra.chapter02.ComparableTest;
public class Student implements Comparable<Student> {
private int age;
private String username;
@Override
public int compareTo(Student o) {
return this.age-o.getAge();
}
}
package com.zebra.chapter02.ComparableTest;
public class test {
public static void main(String[] args) {
Comparable c1 = new Student(10, "xiaoming");
Comparable c2 = new Student(11, "小红");
System.out.println(getMax(c1,c2));
}
public static Comparable getMax(Comparable c1, Comparable c2) {
//如果result大于0,则c1大于c2
int result = c1.compareTo(c2);
return result > 0 ? c1 : c2;
}
}
1.2BubbleSort
排序原理
- 比较相邻的元素,如果前一个元素比后一个大,交换两个元素的位置
- 对每一个相邻元素做同样的工作,从开始第一对元素到结尾最后一对元素,最终最后位置的元素就是最大值
API设计:
类名 | Bubble |
---|---|
构造方法 | Bubble():创建Bubble对象 |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素进行排序 2.private static boolean greater(Comparable v,Comparable w):比较v和w的大小 3.private static void exch(Comparable[] a,int i,int j):交换a数组中索引i和索引j处的值 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class BubbleSort {
//测试类
public static void main(String[] args) {
Integer[] arr = {3, 2, 1,43,4,56,76,3};
BubbleSort bubbleSort = new BubbleSort();
bubbleSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
public BubbleSort() {
}
public static void sort(Comparable[] comparables) {
for (int i = 0; i < comparables.length-1; i++) {
for (int i1 = 0; i1 < comparables.length - i-1; i1++) {
if (greater(comparables[i1], comparables[i1 + 1])) {
exch(comparables,i1,i1+1);
}
}
}
}
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable comparable = a[i];
a[i] = a[j];
a[j] = comparable;
}
}
分析
冒泡排序最坏情况为数组逆序,此时内层循环每次执行都需要进行交换操作,所以冒泡排序最坏情况时间复杂度为O(N^2)
1.3 SelectSort
排序原理
每一次遍历过程中,先找到最小值的索引,然后将该索引处的值放在对应位置
API设计
类名 | SelectSort |
---|---|
构造方法 | SelectSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w 3.private static boolean exch(Comparable[] a,int i,intj):交换数组a中,索引i和索引j处的值 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
Integer[] arr = {2, 3, 65, 7};
SelectSort selectSort = new SelectSort();
selectSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
public SelectSort() {
}
public static void sort(Comparable[] comparables) {
int minIndex;
for (int i = 0; i < comparables.length-1; i++) {
minIndex = i;
for (int j = i+1; j < comparables.length; j++) {
if (greater(comparables[minIndex], comparables[j])) {
minIndex = j;
}
}
if (i != minIndex) {
exch(comparables, minIndex, i);
}
}
}
greater方法和exch方法同上
}
1.4 InsertSort
排序原理
将未插入的元素在已插入的元素中找到对应的位置插入
最简单稳定的排序方法
API设计
类名 | InsertSort |
---|---|
构造方法 | InsertSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素进行排序 2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w 3.private static void exch(Comparable[]a,int i,int j):交换元素 |
private static void sort(Comparable[] comparables) {
for (int i = 1; i < comparables.length; i++) {
for (int j = i; j >0; j--) {
if (greater(comparables[j - 1], comparables[j])) {
exch(comparables, j - 1, j);
} else {
break;
}
}
}
}
分析
与冒泡排序的区别
- 两者的本质区别在于,冒泡排序是从未排序的元素中挑出最大者,放在已排序的元素中,并不会改变已排序元素的顺序,而插入排序的未排序元素在已排序元素中找自己位置时,可能会改变已排序元素的顺序
- 两个算法共同的优点在于比较稳定
1.5 ShellSort
排序原理
- 选定一个增长量,并按照增长量分组
- 对每组进行插入排序
- 减少增长量,最后减为1,重复第二步操作
h的确定
int h = 1;
while(h<arr.length/2){
h = 2*h+1;
}
//h减小的规律:
h/=2;
API设计
类名 | ShellSort |
---|---|
构造方法 | ShellSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素排序 2.判断大小 3.交换元素 |
自己的理解
public static void sort(Comparable[] comparables) {
int h = 1;
while (h < comparables.length / 2) {
h = 2*h+1;
}
while (h > 0) {
for (int i = 0; i < h; i++) {
for (int j = i + h; j < comparables.length; j += h) {
for (int k = j; k > i; k -= h) {
if (greater(comparables[k-h], comparables[k])) {
exch(comparables, k, k - h);
} else {
break;
}
}
}
}
h/=2;
}
}
注意第九行和第十行代码
标准(思想基本一致)
public static void sort(Comparable[] comparables) {
int h = 1;
while (h < comparables.length / 2) {
h = 2*h+1;
}
while (h > 0) {
for (int i = h; i < comparables.length; i++) {
for (int j = i; j >= h; j -= h) {
if (greater(comparables[j - h], comparables[j])) {
exch(comparables, j - h, j);
} else {
break;
}
}
}
h/=2;
}
}
希尔排序是插入排序的优化,性能比插入排序高很多,时间复杂度的研究比较麻烦,知道结果即可。
1.6MergeSort
排序原理
尽可能将数据拆分成两个元素相等的子组,并对每一个子组继续拆分,知道拆分后的每个子组元素个数是1为止
将相邻的两个子组合并成一个有序的大组
重复第二步,直到最终只有一个组为止
API设计
类名 | Merge |
---|---|
构造方法 | Merge() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素排序 2.private static void sort(Comparable[] a,int lo,int hi):对数组a中索引lo到索引hi之间的元素排序 3.private static void merge(Comparable[] a, int lo,int mid,int hi)从索引lo到mid为一个子组,从索引mid+1到索引hi为另一个子组,把数组a中的这两个子组的数据合并成一个有序的大组 4.private static boolean less(Comparable v,Comparable w):判断v是否小于w 5.private static void exch(Comparable[] a,int i,int j):交换a中i和j处的值 |
成员变量 | 1.private static Comparable[] assist:辅助数组 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class MergeSort {
private static Comparable[] assist;
public static void sort(Comparable[] a) {
//初始化assist数组
assist = new Comparable[a.length];
//确定最小索引和最大索引
int lo = 0;
int hi = a.length - 1;
//调用重载的方法进行排序
sort(a,lo,hi);
}
private static void sort(Comparable[] a,int lo,int hi) {
//安全校验
if (hi <= lo) {
return;
}
//分组
int mid = lo + (hi - lo) / 2;
//对每个组排序
sort(a, lo, mid);
sort(a, mid + 1, hi);
//归并merge
merge(a, lo, mid, hi);
}
private static void merge(Comparable[] a, int lo,int mid,int hi) {
int p = lo;
int index = lo;
int q = mid + 1;
while (p <= mid && q <= hi) {
if (less(a[p], a[q])) {
assist[index++] = a[q++];
} else {
assist[index++] = a[p++];
}
}
while (p <= mid) {
assist[index++] = a[p++];
}
while (q <= hi) {
assist[index++] = a[q++];
}
for (int i = lo; i <= hi; i++) {
a[i] = assist[i];
}
}
private static boolean less(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j) {
Comparable comparable = a[i];
a[i] = a[j];
a[j] = comparable;
}
public static void main(String[] args) {
Integer[] arr = {5, 4, 2, 3, 1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
算法分析
- 时间复杂度
假如数组长度为n,那么归并排序需要进行log2(n)次拆分(log2(n)设为x),即log2(n)层,自定向下第k层有2k个子数组,每个数组的长度为2(x-k),归并最多需要进行2(x-k)次比较,因此每层比较次数为2k*2(x-k)=n,那么总次数为n*log2(n),所以归并排序的时间复杂度为O(nlogn)
- 缺点
归并排序需要定义辅助数组,所以这是一个典型的空间换时间的算法
- 和希尔排序的比较
两者的时间复杂度一样,但是稳定性不一样,总体上讲,归并排序较稳定。
1.7QuickSort
排序原理
- 首先定一个分解值,将数组分为左右两部分
- 将大于等于分界值的数据放在右边,小于分解指的数据放在左边。
- 左边和右边的数据独立排序,递归
API设计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YTRs7wJg-1645491610687)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210807102105260.png)]
类名 | QuickSort |
---|---|
构造方法 | QuickSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组进行排序 2.private static void sort(Comparable[]a,int lo,int hi) 3.public static int partition(Comparable[]a,int lo,int hi):对数组a中lo到hi之间分组,并返回界限对应的索引 4,判断大小 5,交换数据 |
1.8 排序的稳定性
稳定向的定义与意义
-
数组arr中有若干元素,如果A元素和B元素相等,如果在排序之后不会改变A元素和B元素的相对位置,说明这个算法是稳定的。
-
如果数据只需要进行一次排序,则稳定性一般没有意义,如果一组数据需要进行多次排序,稳定性就是有意义的。比如一组商品对象,第一次按照价格排序,第二次按照销量排序,如果第二次排序使用稳定算法,就不会改变原有相等对象的相对位置,这样既可以保持第一次排序的原有意义,而且可以减少系统开销。
1.1 Comparable接口
用于自定义对象的排序规则
需求:
- 定义一个学生类,具有年龄age和username两个属性,并通过Comparable接口提供比较规则
- 定义测试类为Test,在测试类中定义方法Comparable getMax(Comparable c1,Comparable c2)完成测试
基本方法省略
package com.zebra.chapter02.ComparableTest;
public class Student implements Comparable<Student> {
private int age;
private String username;
@Override
public int compareTo(Student o) {
return this.age-o.getAge();
}
}
package com.zebra.chapter02.ComparableTest;
public class test {
public static void main(String[] args) {
Comparable c1 = new Student(10, "xiaoming");
Comparable c2 = new Student(11, "小红");
System.out.println(getMax(c1,c2));
}
public static Comparable getMax(Comparable c1, Comparable c2) {
//如果result大于0,则c1大于c2
int result = c1.compareTo(c2);
return result > 0 ? c1 : c2;
}
}
1.2BubbleSort
排序原理
- 比较相邻的元素,如果前一个元素比后一个大,交换两个元素的位置
- 对每一个相邻元素做同样的工作,从开始第一对元素到结尾最后一对元素,最终最后位置的元素就是最大值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lF3YpxJm-1645491587626)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210802085243746.png)]
API设计:
类名 | Bubble |
---|---|
构造方法 | Bubble():创建Bubble对象 |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素进行排序 2.private static boolean greater(Comparable v,Comparable w):比较v和w的大小 3.private static void exch(Comparable[] a,int i,int j):交换a数组中索引i和索引j处的值 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class BubbleSort {
//测试类
public static void main(String[] args) {
Integer[] arr = {3, 2, 1,43,4,56,76,3};
BubbleSort bubbleSort = new BubbleSort();
bubbleSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
public BubbleSort() {
}
public static void sort(Comparable[] comparables) {
for (int i = 0; i < comparables.length-1; i++) {
for (int i1 = 0; i1 < comparables.length - i-1; i1++) {
if (greater(comparables[i1], comparables[i1 + 1])) {
exch(comparables,i1,i1+1);
}
}
}
}
private static boolean greater(Comparable v, Comparable w) {
return v.compareTo(w) > 0;
}
private static void exch(Comparable[] a, int i, int j) {
Comparable comparable = a[i];
a[i] = a[j];
a[j] = comparable;
}
}
分析
冒泡排序最坏情况为数组逆序,此时内层循环每次执行都需要进行交换操作,所以冒泡排序最坏情况时间复杂度为O(N^2)
1.3 SelectSort
排序原理
每一次遍历过程中,先找到最小值的索引,然后将该索引处的值放在对应位置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kkul9coJ-1645491587627)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210802093647670.png)]
API设计
类名 | SelectSort |
---|---|
构造方法 | SelectSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素进行排序 2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w 3.private static boolean exch(Comparable[] a,int i,intj):交换数组a中,索引i和索引j处的值 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class SelectSort {
public static void main(String[] args) {
Integer[] arr = {2, 3, 65, 7};
SelectSort selectSort = new SelectSort();
selectSort.sort(arr);
System.out.println(Arrays.toString(arr));
}
public SelectSort() {
}
public static void sort(Comparable[] comparables) {
int minIndex;
for (int i = 0; i < comparables.length-1; i++) {
minIndex = i;
for (int j = i+1; j < comparables.length; j++) {
if (greater(comparables[minIndex], comparables[j])) {
minIndex = j;
}
}
if (i != minIndex) {
exch(comparables, minIndex, i);
}
}
}
greater方法和exch方法同上
}
1.4 InsertSort
排序原理
将未插入的元素在已插入的元素中找到对应的位置插入
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yFJhDdr2-1645491587628)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210802171636653.png)]
最简单稳定的排序方法
API设计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JlZv9bS-1645491587628)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210802171740489.png)]
类名 | InsertSort |
---|---|
构造方法 | InsertSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素进行排序 2.private static boolean greater(Comparable v,Comparable w):判断v是否大于w 3.private static void exch(Comparable[]a,int i,int j):交换元素 |
private static void sort(Comparable[] comparables) {
for (int i = 1; i < comparables.length; i++) {
for (int j = i; j >0; j--) {
if (greater(comparables[j - 1], comparables[j])) {
exch(comparables, j - 1, j);
} else {
break;
}
}
}
}
分析
与冒泡排序的区别
- 两者的本质区别在于,冒泡排序是从未排序的元素中挑出最大者,放在已排序的元素中,并不会改变已排序元素的顺序,而插入排序的未排序元素在已排序元素中找自己位置时,可能会改变已排序元素的顺序
- 两个算法共同的优点在于比较稳定
1.5 ShellSort
排序原理
- 选定一个增长量,并按照增长量分组
- 对每组进行插入排序
- 减少增长量,最后减为1,重复第二步操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-npCDIgqA-1645491587629)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210803065559447.png)]
h的确定
int h = 1;
while(h<arr.length/2){
h = 2*h+1;
}
//h减小的规律:
h/=2;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ROjaMejy-1645491587629)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210803094638541.png)]
API设计
类名 | ShellSort |
---|---|
构造方法 | ShellSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内的元素排序 2.判断大小 3.交换元素 |
自己的理解
public static void sort(Comparable[] comparables) {
int h = 1;
while (h < comparables.length / 2) {
h = 2*h+1;
}
while (h > 0) {
for (int i = 0; i < h; i++) {
for (int j = i + h; j < comparables.length; j += h) {
for (int k = j; k > i; k -= h) {
if (greater(comparables[k-h], comparables[k])) {
exch(comparables, k, k - h);
} else {
break;
}
}
}
}
h/=2;
}
}
注意第九行和第十行代码
标准(思想基本一致)
public static void sort(Comparable[] comparables) {
int h = 1;
while (h < comparables.length / 2) {
h = 2*h+1;
}
while (h > 0) {
for (int i = h; i < comparables.length; i++) {
for (int j = i; j >= h; j -= h) {
if (greater(comparables[j - h], comparables[j])) {
exch(comparables, j - h, j);
} else {
break;
}
}
}
h/=2;
}
}
希尔排序是插入排序的优化,性能比插入排序高很多,时间复杂度的研究比较麻烦,知道结果即可。
1.6MergeSort
排序原理
尽可能将数据拆分成两个元素相等的子组,并对每一个子组继续拆分,知道拆分后的每个子组元素个数是1为止
将相邻的两个子组合并成一个有序的大组
重复第二步,直到最终只有一个组为止
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G7O7ztju-1645491587629)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210807084913732.png)]
API设计
类名 | Merge |
---|---|
构造方法 | Merge() |
成员方法 | 1.public static void sort(Comparable[] a):对数组内元素排序 2.private static void sort(Comparable[] a,int lo,int hi):对数组a中索引lo到索引hi之间的元素排序 3.private static void merge(Comparable[] a, int lo,int mid,int hi)从索引lo到mid为一个子组,从索引mid+1到索引hi为另一个子组,把数组a中的这两个子组的数据合并成一个有序的大组 4.private static boolean less(Comparable v,Comparable w):判断v是否小于w 5.private static void exch(Comparable[] a,int i,int j):交换a中i和j处的值 |
成员变量 | 1.private static Comparable[] assist:辅助数组 |
package com.zebra.chapter02.Sort;
import java.util.Arrays;
public class MergeSort {
private static Comparable[] assist;
public static void sort(Comparable[] a) {
//初始化assist数组
assist = new Comparable[a.length];
//确定最小索引和最大索引
int lo = 0;
int hi = a.length - 1;
//调用重载的方法进行排序
sort(a,lo,hi);
}
private static void sort(Comparable[] a,int lo,int hi) {
//安全校验
if (hi <= lo) {
return;
}
//分组
int mid = lo + (hi - lo) / 2;
//对每个组排序
sort(a, lo, mid);
sort(a, mid + 1, hi);
//归并merge
merge(a, lo, mid, hi);
}
private static void merge(Comparable[] a, int lo,int mid,int hi) {
int p = lo;
int index = lo;
int q = mid + 1;
while (p <= mid && q <= hi) {
if (less(a[p], a[q])) {
assist[index++] = a[q++];
} else {
assist[index++] = a[p++];
}
}
while (p <= mid) {
assist[index++] = a[p++];
}
while (q <= hi) {
assist[index++] = a[q++];
}
for (int i = lo; i <= hi; i++) {
a[i] = assist[i];
}
}
private static boolean less(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
private static void exch(Comparable[] a,int i,int j) {
Comparable comparable = a[i];
a[i] = a[j];
a[j] = comparable;
}
public static void main(String[] args) {
Integer[] arr = {5, 4, 2, 3, 1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
算法分析
- 时间复杂度
假如数组长度为n,那么归并排序需要进行log2(n)次拆分(log2(n)设为x),即log2(n)层,自定向下第k层有2k个子数组,每个数组的长度为2(x-k),归并最多需要进行2(x-k)次比较,因此每层比较次数为2k*2(x-k)=n,那么总次数为n*log2(n),所以归并排序的时间复杂度为O(nlogn)
- 缺点
归并排序需要定义辅助数组,所以这是一个典型的空间换时间的算法
- 和希尔排序的比较
两者的时间复杂度一样,但是稳定性不一样,总体上讲,归并排序较稳定。
1.7QuickSort
排序原理
- 首先定一个分解值,将数组分为左右两部分
- 将大于等于分界值的数据放在右边,小于分解指的数据放在左边。
- 左边和右边的数据独立排序,递归
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vprsnf9p-1645491587630)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210807102029032.png)]
API设计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hWj8fM1U-1645491587630)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210807102105260.png)]
类名 | QuickSort |
---|---|
构造方法 | QuickSort() |
成员方法 | 1.public static void sort(Comparable[] a):对数组进行排序 2.private static void sort(Comparable[]a,int lo,int hi) 3.public static int partition(Comparable[]a,int lo,int hi):对数组a中lo到hi之间分组,并返回界限对应的索引 4,判断大小 5,交换数据 |
1.8 排序的稳定性
稳定向的定义与意义
- 数组arr中有若干元素,如果A元素和B元素相等,如果在排序之后不会改变A元素和B元素的相对位置,说明这个算法是稳定的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BD5tERkc-1645491587631)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210808074653663.png)]
-
如果数据只需要进行一次排序,则稳定性一般没有意义,如果一组数据需要进行多次排序,稳定性就是有意义的。比如一组商品对象,第一次按照价格排序,第二次按照销量排序,如果第二次排序使用稳定算法,就不会改变原有相等对象的相对位置,这样既可以保持第一次排序的原有意义,而且可以减少系统开销。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ayHKmmbx-1645491587631)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210808075246135.png)]