Java数据结构—排序
一、简单排序
1.Comparable接口
Java提供了一个接口Comparable就是用来定义排序顺序规则的。
**需求:**通过Comparable接口提供比较规则,来判断哪个学生年龄大(Student(name,age))
Student类
public class Student implements Comparable<Student>{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.getAge()-o.getAge();
}
}
测试类
ublic class TestComparable {
private static Comparable getMax(Comparable c1,Comparable c2){
return c1.compareTo(c2)>=0?c1:c2;
}
public static void main(String[] args) {
Student student1 = new Student();
student1.setName("学生1");
student1.setAge(18);
Student student2 = new Student();
student2.setName("学生2");
student2.setAge(20);
System.out.println(getMax(student1,student2));
}
}
输出:
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" "-javaagent:C:\软件\IntelliJ IDEA
Student{name='学生2', age=20}
Process finished with exit code 0
2.冒泡排序
需求:排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
1.比较相邻的元素,如果一个元素比后一个元素大,就交换这两个元素位置
2.从每对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素,最终最后位置的元素就是最大值。
冒泡排序的时间复杂度为:O(N^2)
BubbleSort类:
public class BubbleSort {
public void sort(Comparable[] a){
for (int i=0;i<a.length-1;i++){
for (int j=0;j<a.length-1-i;j++){
if (isGreater(a[j],a[j+1])){
exchange(a,j,j+1);
}
}
}
}
private static boolean isGreater(Comparable c1,Comparable c2){
return c1.compareTo(c2)>0;
}
private static void exchange(Comparable[] a,int i, int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
测试类
public class BubbleSortTest {
public static void main(String[] args) {
Integer[] a = {6,5,4,1,2,3};
BubbleSort bubbleSort = new BubbleSort();
bubbleSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 2, 3, 4, 5, 6]
Process finished with exit code 0
3.选择排序
需求:排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依此进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他索引的值为最小值,最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值
SelectSort类:
public class SelectSort {
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++){
if (isGreater(a[minIndex],a[j])){
minIndex = j;
}
}
exchange(a,i,minIndex);
}
}
public static boolean isGreater(Comparable c1, Comparable c2){
return c1.compareTo(c2)>0;
}
public static void exchange(Comparable[] a,int i,int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
测试类:
public class SelectSortTest {
public static void main(String[] args) {
Integer[] a = {4,5,63,2,1};
SelectSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出:
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 2, 4, 5, 63]
Process finished with exit code 0
选择排序的时间复杂度为:O(N^2)
4.插入排序
插入排序是一种简单直观且稳定的排序算法
需求:排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
- 将所有的元素分为两组,已经排序的和未排序的
- 找到未排序的组中第一个元素,向已经排序的组中进行插入
- 倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插元素,那么就把待插元素放到这个位置,其他的元素向后移动一位
InsertSort类:
public class InsertSort {
public static void sort(Comparable[] a){
for (int i=1;i<a.length;i++){
//当前元素为a[i],依次和i之前的元素比较,找到一个小于等于a[i]的元素
for (int j=i;j>0;j--){
if (isGreater(a[j-1],a[j])){
exchange(a,j-1,j);
}else {
break;
}
}
}
}
public static boolean isGreater(Comparable c1, Comparable c2){
return c1.compareTo(c2)>0;
}
public static void exchange(Comparable[] a,int i,int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
测试类:
public class InsertSortTest {
public static void main(String[] args) {
Integer[] a = {4,5,63,2,1};
InsertSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 2, 4, 5, 63]
Process finished with exit code 0
排序的时间复杂度为:O(N^2)
二、高级排序
1.希尔排序
需求:排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
- 选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组
- 对分好的每一组数据完成插入排序
- 减少增长量,最小减为1,重复第二步操作
增长量h的确定规则
int h = 1;
while(h<数组长度/2){
h = 2h+1;
}
//循环结束后我们就可以确定h的最大值
//h的减少规则为:
h = h/2;
ShellSort类
public class ShellSort {
/**
* 希尔排序
* @param a
*/
public void sort(Comparable[] a){
//根据数组a的长度,确定增长量h的初始值
int h = 1;
while(h<a.length/2){
h = 2*h+1;
}
//希尔排序
while (h>=1) {
//找到待插入的元素
for (int i = h; i < a.length; i++) {
//把待插入的元素插入到有序数列中
for (int j = i; j >= h; j -= h) {
//待插入元素是a[j],比较a[j]和a[j-h]
if (isGreater(a[j - h], a[j])) {
exchange(a, j - h, j);
} else { //待插入元素已找到合适位置
break;
}
}
}
//减少h的值
h = h/2;
}
}
private static boolean isGreater(Comparable c1,Comparable c2){
return c1.compareTo(c2)>0;
}
private static void exchange(Comparable[] a,int i, int j){
Comparable temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
测试类
public class ShellSortTest {
public static void main(String[] args) {
Integer[] a = {1,6,5,7,8,5};
ShellSort shellSort = new ShellSort();
shellSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出结果
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 5, 5, 6, 7, 8]
Process finished with exit code 0
2.归并排序
**递归:**定义方法的时候,在方法内部调用方法本身,称为递归。
public void show(){
System.out.println("11232");
show();
}
**作用:**把大型复杂的问题,层层转换为一个与原问题相似的,规模较小的问题来求解,递归策略只需要少量程序就可以描述解决过程中所需要的多次重复计算,大大地减少了程序的代码量。
**注意事项:**在递归中,不能无限制地调用自己,必须要有边界条件,否则递归层级太深,会造成栈内存溢出。
**归并排序:**归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法,将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序,将两个有序表合并成一个有序表,称为二路归并。
**需求:**排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
- 尽可能的一组拆分成两个元素相等的子组,并对每个子组继续拆分,直到拆分后的每个子组的元素个数是1为止
- 将相邻的两个子组进行合并成一个有序的大组
- 不断重复步骤2,直到最终有一个组为止
MergeSort类:
public class MergeSort {
//归并所需要的辅助数组
private static Comparable[] assist;
/**
* 判断c1是否比c2小
* @param c1
* @param c2
* @return
*/
private static boolean isLess(Comparable c1,Comparable c2){
return c1.compareTo(c2)<0;
}
/**
* 交换 a[i] 和 a[j]
* @param a
* @param i
* @param j
*/
private static void exchange(Comparable[] a,int i, int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* 对a排序
* @param a
*/
public static void sort(Comparable[] a){
//初始化数组assist
assist = new Comparable[a.length];
//定义lo,hi来记录数组中最小索引和最大索引
int lo = 0;
int hi = a.length-1;
//调用sort重载方法完成数组a中,从索引lo到索引hi的元素排序
sort(a,lo,hi);
}
/**
* 对a[lo]到a[hi]的元素进行排序
* @param a
* @param lo
* @param hi
*/
private static void sort(Comparable[] a,int lo, int hi){
//安全性校验
if (lo>=hi){
return;
}
//对lo到hi之间的数据分为两组
int mid = (lo+hi)/2;
//分别对每组数据进行排序
sort(a,lo,mid);
sort(a,mid+1,hi);
//再把两组数据进行归并
merge(a,lo,mid,hi);
}
/**
* a[lo]到a[mid]为一组,a[mid+1]到a[hi]为一组,对这两组数据继续归并
* @param a
* @param lo
* @param mid
* @param hi
*/
private static void merge(Comparable[] a,int lo,int mid, int hi){
//定义三个指针
int i = lo;
int p1 = lo;
int p2 = mid+1;
//遍历,移动p1指针和p2指针,比较对应索引的值,找到小的那一个,放到辅助数组对应的索引处
while (p1<=mid && p2<=hi){
//比较索引处的值
if (isLess(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++];
}
//把辅助数组中的元素拷贝到原数组中
for (int index=lo;index<=hi;index++){
a[index] = assist[index];
}
}
}
测试类:
public class MergeSortTest {
public static void main(String[] args) {
Integer[] a = {4,5,63,2,1} ;
MergeSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 2, 4, 5, 63]
Process finished with exit code 0
归并排序时间复杂度:O(nlogn)
缺点:需要额外分配辅助数组内存空间
3.快速排序
快速排序是对冒泡排序的一种改进,它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据要小,然后再按此方法对两数据分别进行快速排序,这个排序过程可以递归进行,依此达到整个数据变成有序序列
**需求:**排序前:{4,5,63,2,1} 排序后:{1,2,3,4,5,6}
排序原理:
- 首先设定一个分界值,通过该分界值将数组分为左右两部分
- 将大于或大于分界值的放到数组右边,小于分界值的放到数组左边
- 左右边可以独立排序,左边可以取一个分界值,将左边分为两部分,同2;右边也可以做类似处理
- 重复上述过程。当左右两部分的数据排好之后,整个数组也就完成了。
QuickSort类:
public class QuickSort {
/**
* 判断c1是否比c2小
* @param c1
* @param c2
* @return
*/
private static boolean isLess(Comparable c1,Comparable c2){
return c1.compareTo(c2)<0;
}
/**
* 交换 a[i] 和 a[j]
* @param a
* @param i
* @param j
*/
private static void exchange(Comparable[] a,int i, int j){
Comparable temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* 对a排序
* @param a
*/
public static void sort(Comparable[] a){
int lo = 0;
int hi = a.length-1;
sort(a,lo,hi);
}
/**
* 对a[lo]到a[hi]的元素进行排序
* @param a
* @param lo
* @param hi
*/
private static void sort(Comparable[] a,int lo, int hi){
//安全性校验
if (lo>=hi){
return;
}
//需要对数组中lo索引到hi索引处的元素进行分组(左子组,右子组)
//返回的是分组的分界值所在的索引,分界值位置变换后的索引
int partition = getPartition(a,lo,hi);
//让左子组有序
sort(a,lo,partition-1);
//让右子组有序
sort(a,partition+1,hi);
}
/**
* 对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引
* @param a
* @param lo
* @param hi
*/
private static int getPartition(Comparable[] a,int lo,int hi){
//分界值
Comparable key = a[lo];
//定义两个指针,分别指向待切分数组的最小索引和最大索引
int left = lo;
int right = hi+1;
//切分
while(true){
//找到左边比分界值大的数
while (isLess(a[++left],key)){
if(left==hi){
break;
}
}
//找到右边边比分界值小的数
while (isLess(key,a[--right])){
if(right==lo){
break;
}
}
if (left >= right){
break;
}else {
exchange(a,left,right);
}
}
//交换分界值
exchange(a,lo,right);
return right;
}
}
测试类:
public class QuickSortTest {
public static void main(String[] args) {
Integer[] a = {4,5,63,2,1};
QuickSort.sort(a);
System.out.println(Arrays.toString(a));
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_201\bin\java.exe"
[1, 2, 4, 5, 63]
Process finished with exit code 0
快速排序时间复杂度:O(nlogn)