选择排序
先把最小的拿出来,剩下的,再把最小的拿出来
剩下的,再把最小的拿出来
每次选择还没处理的元素里的最小的元素
下面是动画演示过程
假设有一个数组,我们依次选择最小的数据出来
可否进行原地排序?
但是这里有一个问题,就是我们其实是开辟了两个存储空间,那么有没有可以优化的地步呢?
arr[i…n)未排序
arr[i…n)中的最小值
答案是有的,我们可以观看如下演示,利用i j两个指针
代码演示(使用带约束的泛型)
public class SelectionSort{
private SelecttionSort(){}
public static<E extends Comparable<E>> void sort(E[] arr)
{
//arr[0...i)是有序的,arr[i...n)是无序的
for(int i = 0 ; i < arr.length;i++){
//选择arr[i..n)中的最小值的索引
int minIndex = i;
for(int j=i;j<arr.length;j++){
if(arr[j].compareTo(arr[minIndex])<0)
minIndex=j;
}
swap(arr,i,minIndex);
}
}
private static<E> void swap(E[] arr,int i, int j)
{
E t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
public static void main(String[] args)
{
Integer[] arr={1,4,2,3,6,5};
SelectionSort.sort(arr);
for(int e:arr)
System.out.print(e+" ");
System.out.println();
Student[] students = {new Student("Alice",98),
new Student("Bobo",100),
new Student("Charles",66)};
SelectionSort.sort(students);
for(Student student:students)
System.out.print(student+" ");
System.out.println();
//两轮数据规模不一样
int[] dataSiez={10000,100000};
for(int n: dataSize){
Integer[] arr = ArrayGenerator.generateRandomArray(n,n);
//验证排序结果
Sorting.sortTest("SelecitonSort",arr);
}
}
}
使用compareTo接口
自定义一个学生类
public class Student implements Comparable<Student>{
private String name;//姓名
private int score;//得分
public Student(String name, int score){
this.name = name;
this.score = score;
}
@Override
public boolean equals(Object student){
if(this == student)return true;
if(student==null) return false;
if(this.getClass()!= student.getClass())return false;
Student another = (Student)student;
return this.name.equals(another.name);
}
@Override
public int compareTo(Student another){
if(this.score<another.score)
return -1;
else if(this.score == another.score)
return 0;
return 1;
//简单写法,更加优雅的写法,这是分数从小到大的排序方法
return this.score-anther.score;
}
@Override
public String toString(){
return String.format("Student(name: %s, score: %d)",name,score);
}
}
选择排序法的复杂度分析
为了实现对于算法复杂度的分析,很明显,我们需要生成一个很大的数组来进行测试
public class ArrayGenerator{
private ArrayGenerator(){}
public static Integer[] generateOrderedArray(int n){
Integer[] arr = new Integer[n];
for(int i = 0 ; i < n ; i++)
arr[i]=i;
return arr;
}
//生成一个长度为n的随机数组,每个数字的范围是[0,bound)
public static Integer[] generateRandomArray(int n,int bound){
Integer[] arr = new Integer[n];
Random rnd = new Random();
for(int i = 0; i < n ;i++)
arr[i] = rnd.nextInt(bound);
return arr
}
}
辅助验证排序结果
public class SortingHelper{
private SortingHelper(){}
public static<E extends Comparable> boolean isSorted(E[] arr){
for(int i = 1 ; i < arr.length; i++)
if(arr[i-1].compareTo(arr[i]) > 0 )return false;
return true;
}
//测试排序算法
public static<E extends Comparable<E>> void sortTest(String sortname,E[] arr){
//验证排序结果
long startTime = System.nanoTime();
if(sortname.equlas("SelectionSort"))
SelectionSort.sort(arr);
long endTime=System.nanoTime();
double time = (endTime-startTime)/100000000.0;
//检验算法正确性,如果不正确就要抛出异常
if(!SortingHelper.isSorted(arr))
throw new RuntimeException(sortname+" falied!");
System.out.println(String.format("%s , n = %d : %f s"),sortname,arr.length.time);
}
}
插入排序
联想生活中的打牌知识,大多数人在拿到牌之后会采用插入的方式进行排列
每次处理一张牌,把这张牌插入到前面已经排好序的牌中
计算机逻辑实现整体动画演示
arr[0,i)已经排好序arr[i…n)没有排好序==(循环不变量)==,把arr[i]放到合适的位置
这里的插入和选择的区别到底是什么?
选择是选择最小的元素,因此i之前都是最小的,就是数组排序完成之后应该存放的位置
但插入排序法不是这样的,每次只处理当前的元素,把当前的元素放在合适的位置。他不会动i还未遍历到的元素
代码实现
public class InsertionSort{
private InsertionSort(){}
public static<E extends Comparable<E>> void sort(E[] arr){
for(int i = 0 ; i < arr.length; i++){
//将arr[i]插入到合适的位置
for(int j = i ;j-1>=0;j--)
if(arr[j].compareTo(arr[j-1])<0)
swap(arr,j,j-1);
else break;
}
}
}
插入排序法的小优化
每次交换其实是三次操作,先暂存一个元素,然后找到索引应该插入的位置,之后的元素我们可以进行平移优化操作
我们的操作用了赋值,只用了一次。
复杂度并未改变
public static<E extends Comparable<E>> void sort2(E[] arr){
for(int i = 0 ; i < arr.length; i++){
//将arr[i]插入到合适的位置
E t = arr[i];
int j;
for(j=i;j-1>=0&&t.compareTo(arr[j-1])<0;j--){
arr[j]=arr[j-1];
}
arr[j]=t;
}
}
插入排序的特性
极端情况
对于有序数组,插入排序的复杂度是o(n)
整体,插入排序的复杂度依然是o(n^2)
一个银行业务处理的数据,基本按照时间的顺序一个一个排序出来,只有个别业务排序时间较长。对于近乎有效的数据使用插入排序法是更好的选择,对于有效的数据时间复杂度O(n)
选择排序不论是有序还是无序,时间复杂度都是一样的
而对于插入排序,有序的数据更快速