目录
1.Java比较器
(1)Comparable
(2)Comparator
(3)Comparable和Comparator使用对比
2.后面所有排序算法类继承的父类
3.冒泡排序
3.1 基本思想
3.2 图解原理:
3.3 Java代码实现
3.4 冒泡排序的优化
(1)优化切入点发现
(2)优化实现
3.5 性能分析
Java中的对象只能进行比较操作==或!=,不能使用>或<的,但是在开发场景中我们需要对多个对象进行排序,言外之意,就需要比较对象的大小,Java中使用Comparable或Comparator接口中任何一个
java.lang.Comparable
使用方法:
像String、包装类等实现了Comparable接口,重写了compareTo(obj)方法,给出了比较两个对象的方式
String示例:
使用String的该接口实现自然排序:
import java.util.Arrays;
public class ComparableDemo {
public static void main(String[] args) {
String[] arr = new String[]{"AA","CC","GG","BB","MM","DD"};
//可以发现此方法对原数组中元素的顺序进行了改变
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
可以发现最终还是使用Comparable接口的compareTo方法在进行比较
自定义类实现Comparable接口实现自然排序:
public class Goods implements Comparable{
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明比较商品大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序
@Override
public int compareTo(Object o) {
if(o instanceof Goods){
Goods goods = (Goods)o;
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else {
//return 0;
return this.name.compareTo(goods.name);
}
}
throw new RuntimeException("传入的数据类型不一致!");
}
}
import java.util.Arrays;
public class ComparableDemo {
public static void main(String[] args) {
Goods[] arr = new Goods[4];
arr[0] = new Goods("lenovoMouse",43);
arr[1] = new Goods("huaweiMouse",65);
arr[2] = new Goods("xiaomiMouse",25);
arr[3] = new Goods("dellMouse",43);
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}
}
java.util.Comparator
示例代码1:
import java.util.Arrays;
import java.util.Comparator;
public class ComparableDemo {
public static void main(String[] args) {
String[] arr = new String[]{"AA","CC","GG","BB","MM","DD"};
//String本身实现Comparable接口从小到大进行排序,
// 我们想要从大到小进行排序,所以我们需要实现Comparator接口的compare方法来实现这种定制
Arrays.sort(arr, new Comparator() {
//按照字符串从大到小进行排列
@Override
public int compare(String o1, String o2) {
return -s1.compareTo(s2);
}
});
System.out.println(Arrays.toString(arr));
}
}
示例代码2:
public class Goods implements Comparable{
private String name;
private double price;
public Goods(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
//指明比较商品大小的方式:按照价格从低到高排序,再按照产品名称从低到高排序
@Override
public int compareTo(Object o) {
if(o instanceof Goods){
Goods goods = (Goods)o;
if(this.price > goods.price){
return 1;
}else if(this.price < goods.price){
return -1;
}else {
//return 0;
return this.name.compareTo(goods.name);
}
}
throw new RuntimeException("传入的数据类型不一致!");
}
}
import java.util.Arrays;
import java.util.Comparator;
public class ComparableDemo {
public static void main(String[] args) {
Goods[] arr = new Goods[4];
arr[0] = new Goods("lenovoMouse",43);
arr[1] = new Goods("huaweiMouse",65);
arr[2] = new Goods("xiaomiMouse",25);
arr[3] = new Goods("dellMouse",43);
Arrays.sort(arr, new Comparator() {
//定义新的比较商品大小的方式:按照价格从低到高排序,再按照产品名称从高到低排序
@Override
public int compare(Goods o1, Goods o2) {
if(o1.getPrice() == o2.getPrice()){
return -o1.getName().compareTo(o2.getName());
}else {
return Double.compare(o1.getPrice(),o2.getPrice());
}
}
});
System.out.println(Arrays.toString(arr));
}
}
写如下父类的定义是为了在父类中定义比较和交换方法
public abstract class Sort {
/**
* 排序方法
*
* 由各个具体的排序类自己去实现
*/
public abstract void sort(Comparable[] arr);
/**
* 交换数组指定两个位置的元素
*/
protected void swap(Comparable[] arr,int i,int j){
Comparable temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 比较数组中位置i处的元素和位置j处的元素
*
* 返回值为正整数:arr[i] > arr[j]
* 返回值为负整数:arr[i] < arr[j]
* 返回值为零:arr[i] = arr[j]
*/
protected int compare(Comparable[] arr,int i,int j){
return arr[i].compareTo(arr[j]);
}
}
小结:
import java.util.Arrays;
public class BubbleSort extends Sort{
@Override
public void sort(Comparable[] arr) {
//遍历n-1趟
for(int i = 0;i < arr.length-1;i++){
//内部循环为每一趟遍历arr.length-i个位置
//错误写法
//遍历到最后一个位置会让数组访问越界
// for(int j = 0;j < arr.length-i;j++){
// //每个位置和它后面的一个元素做比较
// if(compare(arr,j,j+1) > 0){
// //前一个元素大于后面的元素,它们就交换
// swap(arr,j,j+1);
// }
// }
//分析上述越界原因:因为我们总是和后面的一个元素做比较,上述的写法每一趟总是会对比多一个元素
// 所以我们的当前元素只需要遍历到n-1位置的元素即可,这样后面一个元素就是n位置的元素
// 这样整体上会把每次未排序的序列刚好遍历完
//正确写法
//遍历比较未排序序列的相邻连个值
for(int j = 0;j < arr.length-1-i;j++){
//每个位置和它后面的一个元素做比较
if(compare(arr,j,j+1) > 0){
//前一个元素大于后面的元素,它们就交换
swap(arr,j,j+1);
}
}
//以下两步不是必须,此处打印只是为了结果能清楚的观察
System.out.println("第"+(i+1)+"趟排序后的数组为:");
System.out.println(Arrays.toString(arr));
}
}
//写法2:
/*
@Override
public void Sort(Comparable[] arr) {
//外部为每次要排好序的位置,即每趟末尾都是最大值
//而该i就相当于一个到此结束的标志位置
for (int i = arr.length-1; i > 0; i--)
{
//每次要排好序的是i,让j循环到i-1,然后在比较的时候由于是j和j+1相比较和交换,所以正好会对比到i位置
for (int j = 0; j < i; j++)
{
//每个位置进行比较,大于就交换
if (compare(arr,j,j+1) > 0)
swap(arr,j,j+1);
//否则就继续迭代下一个位置
}
}
}
*/
}
import java.util.Arrays;
public class SortTest {
public static void main(String[] args) {
Integer[] arr = new Integer[]{3, 9, -1, 10, -2};
System.out.println("未排序的数组为:");
System.out.println(Arrays.toString(arr));
BubbleSort bubbleSort = new BubbleSort();
bubbleSort.sort(arr);
}
}
上述代码我们把测试用例改成
运行结果为:
可以发现上面的后3趟并没有发生任何交换,做的是无用的比较
将测试用例修改为
运行结果为:
可以发现上面的排好序的数组仍然要进行n-1趟,所以上述这种原始的冒泡排序的写法与输入无关,不管输入的数组是否有序,都会遍历n-1趟,比较次数为次,交换次数为倒置的数量
不过通过如上可以发现,排好序,就不会再发生交换,我们思考当不发生交换的时候,是否能表明已经排好序,答案是肯定的,此处不做证明
优化思路:
代码实现:
import java.util.Arrays;
public class BubbleSortImprove extends Sort{
private boolean isSwap = false; //标识变量,表示是否进行过交换
@Override
public void sort(Comparable[] arr) {
//遍历n-1趟
for(int i = 0;i < arr.length-1;i++){
//内部循环为每一趟遍历arr.length-i个位置
//遍历比较未排序序列的相邻连个值
for(int j = 0;j < arr.length-1-i;j++){
//每个位置和它后面的一个元素做比较
if(compare(arr,j,j+1) > 0){
isSwap = true;
//前一个元素大于后面的元素,它们就交换
swap(arr,j,j+1);
}
}
System.out.println("第"+(i+1)+"趟排序后的数组为:");
System.out.println(Arrays.toString(arr));
if(!isSwap){ //在一趟中未发生任何交换
break;
}else { //发生了交换,重置标识变量,用于下次判断是否发生过交换
isSwap = false;
}
}
}
}
public class SortTest {
public static void main(String[] args) {
Integer[] arr = new Integer[]{3, 9, -1, 10, 20};
BubbleSortImprove bubbleSortImprove = new BubbleSortImprove();
bubbleSortImprove.sort(arr);
}
}
将测试用例修改为
运行结果为:
优化前的冒泡排序:
优化后的冒泡排序:
说明:关于性能测试的代码,我把排序算法实现类中的打印语句进行了注释,此时打印并不是我们关心的,还影响运行结果
优化前后性能测试:(输入是随机数据)
public class SortPerformanceTest {
public static void main(String[] args) {
//创建100000个随机数据
Double[] arr1 = new Double[100000];
for (int i = 0; i < arr1.length; i++) {
arr1[i] = (Double) (Math.random() * 10000000); //这里使用10000000是为了让数据更分散
}
//赋值上述创建的数组arr1的值到数组arr2
Double[] arr2 = new Double[100000];
for (int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
//创建两种排序类的对象
BubbleSort bubbleSort = new BubbleSort();
BubbleSortImprove bubbleSortImprove = new BubbleSortImprove();
//使用优化前的冒泡排序对arr1进行排序
long bubbleSort_start = System.currentTimeMillis();
bubbleSort.sort(arr1);
long bubbleSort_end = System.currentTimeMillis();
System.out.println("优化前的冒泡排序所用的时间为:"+(bubbleSort_end - bubbleSort_start)+"ms");
//使用优化后的冒泡排序对arr2进行排序
long bubbleSortImprove_start = System.currentTimeMillis();
bubbleSortImprove.sort(arr2);
long bubbleSortImprove_end = System.currentTimeMillis();
System.out.println("优化后的冒泡排序所用的时间为:"+(bubbleSortImprove_end - bubbleSortImprove_start)+"ms");
}
}
可以发现,对于随机输入,优化后的冒泡排序与优化前相比性能有改善但不是特别明显,甚至有时候因为我们多了对每次遍历对flag判断的代码,反而优化后比优化前所花费的时间还要长