简单排序
1、Comparable接口
java中提供的含比较规则的接口
在这里我们以学生类为例:
定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则;
实现Comparable接口,重写compareTo()方法
public class Student implements Comparable<Student>{
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [username=" + username + ", age=" + age + "]";
}
@Override
public int compareTo(Student arg0) {
// TODO Auto-generated method stub
return this.getAge()-arg0.getAge();
}
}
2、冒泡排序
排序原理:
- 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置
- 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值
- 对最后位置最大元素之前的元素继续进行1.2.两步
排序前:[4, 5, 6, 3, 2, 1]
排序后:[1, 2, 3, 4, 5, 6]
时间复杂度:O(N^2)
public class Bubble {
/*
* 对数组a中的元素进行排序(注意:数组a的类型Comparable,所以参数数组必须是引用数据类型,并实现Comparable接口
*/
public static void sort(Comparable[] a) {
for(int i=a.length-1;i>0;i--) {
for(int j=0;j<i;j++) {
//比较索引j和索引j+1处的值
if(greater(a[j], a[j+1])) {
exch(a,j,j+1);
}
}
}
}
/*
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
3、选择排序
排序原理
- 每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引处的值为最小值,最后可以找到最小值所在的索引
- 交换第一个索引处和最小值所在的索引处的值
排序前:[4,6,8,7,9,2,10,1]
排序后:[1,2,4,6,7,8,9,10]
时间复杂度:O(N^2)
public class Selection {
/*
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for(int i=0;i<a.length-1;i++) {
//定义一个变量,记录最小元素所在的索引,默认为参与选择排序的第一个元素所在位置
int minIndex=i;
for(int j=i+1;j<a.length;j++) {
//需要比较最小索引minIndex处的值和j索引处的值
if(greater(a[minIndex],a[j])) {
minIndex=j;
}
}
//交换最小元素所在索引minIndex处的值和i索引处的值
exch(a,i,minIndex);
}
}
/*
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
4、插入排序
排序原理:
- 把所有元素分成两组,已排序的和未排序的
- 找到未排序的组中的第一个元素,向已排序的组中进行插入
- 倒序遍历已排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素# 高级排序
- 把待插入的元素放到这个位置的下1位,并将其余元素向后移1位
排序前:[4,3,2,10,12,1,5,6]
排序后:[1,2,3,4,5,6,10,12]
时间复杂度:O(N^2)
public class Insertion {
/*
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
for(int i=1;i<a.length;i++) {
for(int j=i;j>0;j--) {
//比较索引j处的值和索引j-1处的值,如果索引j-1处的值比索引j处的值大,则交换数据,如果不大,说明找到合适的位置,退出内层循环
if(greater(a[j-1], a[j])) {
exch(a, j-1, j);
}else {
break;
}
}
}
}
/*
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
高级排序
1、希尔排序又称“缩小增量排序”
排序原理:
- 选定一个增长量h,按照增长量h作为分组的依据,对数据进行分组
- 对分好组的每一组数据完成插入排序
- 减少缩进量,最小减为1,重复2
排序前:[9,1,2,5,7,4,8,6,3,5]
排序后:[1,2,3,4,5,5,6,7,8,9]
public class Shell {
public static void sort(Comparable[] a) {
//1.根据数组a的长度,确定增量h的初始值
int h=1;
while(h<a.length/2) {
h=h*2+1;
}
//2.希尔排序
while(h>=1) {
//排序
//2.1找到待插入的元素
for(int i=h;i<a.length;i++) {
//2.2把待插入的元素插入到有序数列中
for(int j=i;j>=h;j=j-h) {
//2.2.1待插入元素是a[j],比较a[j]和a[j-h]
if(greater(a[j-h], a[j])) {
//交换元素
exch(a, j-h, j);
}else {
//待插入元素已经找到了合适的位置,结束循环
break;
}
}
}
//减小h的值
h=h/2;
}
}
/*
* 比较v元素是否大于w元素
*/
private static boolean greater(Comparable v,Comparable w) {
return v.compareTo(w)>0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
2、归并排序
在谈归并之前,我们先要知道递归和分治法是什么:
递归: 定义方法时,在方法内部调用方法本身.
注意: 不能无限的调用自己,必须要有边界条件,能够让递归结束.因为每一次递归调用都会在栈内存中开辟新的空间,重新执行方法,如果递归的层级太深,很容易造成内存溢出,会报错:StackOverflowError(栈内存溢出错误)
public void show( ){
System.out.println("aaaa");
show( );
}
分治法:
将已有序的子序列合并,得到完全有序的序列,
即先使每个子序列有序,再使子序列段间有序,
若将两个有序表合并成一个有序表,称为二路归并
排序原理: 采用分治法
- 尽可能的将一组数据拆分成两个元素相等的子组,并对每一个子组继续拆分,直到拆分后的每个子组的元素个数是1为止
- 将相邻的两个子组进行合并成一个有序的大组
- 不断重复步骤2,直到最终只有一个组为止
排序前:[8,4,5,7,1,3,6,2]
排序后:[1,2,3,4,5,6,7,8]
时间复杂度:O(nlogn)
缺点: 需要申请额外的数组空间,导致空间复杂度提升,是典型的以空间换时间的操作
public class Merge {
private static Comparable[] assist;
/*
* 比较v元素是否大于w元素
*/
private static boolean less(Comparable v,Comparable w) {
return v.compareTo(w)<0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
/*
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
//1.初始化辅助数组assist
assist=new Comparable[a.length];
//2.定义一个lo变量和hi变量,分别记录数组中最小的索引和最大的索引
int lo=0;
int hi=a.length-1;
//3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a,lo,hi);
}
/*
* 对数组a中从lo到hi的元素进行排序
*/
private static void sort(Comparable[] a,int lo,int hi) {
//做安全性校验
if(lo>=hi) {
return;
}
//对lo到hi之间的数据进行分组(两组)
int mid=lo+(hi-lo)/2;
//分别对每组数据进行排序
sort(a,lo,mid);
sort(a,mid+1,hi);
//再把两个组中的数据进行归并
merge(a,lo,mid,hi);
}
/*
* 对数组中,从lo到mid为一组,从mid到hi为一组,对两组数据进行归并
*/
private static void merge(Comparable[] a,int lo,int mid,int hi) {
//定义3个指针
int i=lo;
int p1=lo;
int p2=mid+1;
//遍历,移动p1指针和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的对应索引处
while(p1<=mid&&p2<=hi) {
//比较对应索引处的值
if(less(a[p1], a[p2])) {
assist[i++]=a[p1++];
}else {
assist[i++]=a[p2++];
}
}
//遍历,如果p1指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
while(p1<=mid) {
assist[i++]=a[p1++];
}
//遍历,如果p2指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
while(p2<=hi) {
assist[i++]=a[p2++];
}
//把辅助数组中的元素拷贝到原数组中
System.arraycopy(assist, lo, a, lo, hi-lo+1);
}
}
3、快速排序
快速排序算法中,需要对数组先进性切分。其切分原理:
把一个数组切分成两个数组的基本思想:
- 找一个基准值,用两个指针分别指向数组的头部和尾部;
- 先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针位置;
- 再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针位置;
- 交换当前左边指针和右边指针位置的元素;
- 重复2,3,4步,直到左边指针的值大于右边指针的值(相遇)停止。
排序原理:
1.首先设定一个分界值,通过该分界值将数组分成左右两部分
2. 将大于或等于分界值的数据放到右边数组,小于的放到左边数组
3. 左右两边数组单独进行排序。重复步骤2.
4. 重复上述过程,即递归定义。通过递归将左右两侧排好序,再递归至一个数组
排序前:[6,1,2,7,9,3,4,5,8]
排序后:[1,2,3,4,5,6,7,8,9]
时间复杂度:最优(平均情况:O(nlogn) 最坏:O(n^2)
public class Quick {
private static Comparable[] assist;
/*
* 比较v元素是否大于w元素
*/
private static boolean less(Comparable v,Comparable w) {
return v.compareTo(w)<0;
}
/*
* 数组元素i和j交换位置
*/
private static void exch(Comparable[] a,int i,int j) {
Comparable temp;
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
/*
* 对数组a中的元素进行排序
*/
public static void sort(Comparable[] a) {
//1.初始化辅助数组assist
assist=new Comparable[a.length];
//2.定义一个lo变量和hi变量,分别记录数组中最小的索引和最大的索引
int lo=0;
int hi=a.length-1;
//3.调用sort重载方法完成数组a中,从索引lo到索引hi的元素的排序
sort(a,lo,hi);
}
/*
* 对数组a中从lo到hi的元素进行排序
*/
private static void sort(Comparable[] a,int lo,int hi) {
//做安全性校验
if(lo>=hi) {
return;
}
//需要对数组中的lo索引到hi索引处的元素进行分组
int partition=partition(a, lo, hi);//返回是分组的分界值所在的索引,分界值位置变换后的索引
//让左子组有序
sort(a,lo,partition-1);
//让右子组有序
sort(a,partition+1,hi);
}
//对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引
public static int partition(Comparable[] a,int lo,int hi) {
//确定分界值
Comparable key=a[lo];
//定义两个指针,分别指向待切分元素的最小索引处和最大索引处的下一个位置
int left=lo;
int right=hi+1;
//切分
while(true) {
//先从右往左扫描,移动指针right,找到一个比分界值小的元素,停止
while(less(key, a[--right])) {
if(right==lo) {
break;
}
}
//再从左往右扫描,移动指针left,找到一个比分界值大的元素,停止
while(less(a[++left], key)) {
if(left==hi) {
break;
}
}
//判断left>=right,如果是,则证明元素扫描完毕,结束循环,如果不是,则交换元素
if(left>=right) {
break;
}else {
exch(a, left, right);
}
}
//交换分界值
exch(a, right, lo);
return right;
}
}