(1)Comparable接口介绍
Java提供了一个接口Comparable就是用来定义排序 规则的,在这里我们以案例的形式对Comparable接口做一个简单的回顾。
需求:
1.定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则;
2.定义测试类Test,在测试类Test中定义测试方法Comparable getMax(Comparable c1,Comparable c2)完成测试
学生类:
package cn.itcast.algorithm.sort; /** * 1.定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则; * @author caizhengjie */ 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 o) { return this.getAge() - o.getAge(); } }
测试类:
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Student; /** * 2.定义测试类Test,在测试类Test中定义测试方法Comparable getMax(Comparable c1,Comparable c2)完成测试 */ public class TestComparable { public static void main(String[] args) { Student s1 = new Student(); s1.setUsername("alex"); s1.setAge(10); Student s2 = new Student(); s2.setUsername("jack"); s2.setAge(20); Comparable<Student> max = getMax(s1,s2); System.out.println(max); } public static Comparable<Student> getMax(Comparable<Student> c1,Comparable<Student> c2){ int result = c1.compareTo((Student) c2); // 如果result<0,c1<c2 // 如果result>0,c1>c2 // 如果result=0,c1=c2 if (result >= 0){ return c1; } else { return c2; } } }
运行结果:
Student{username='jack', age=20}
(2)冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
需求:
排序前:{4,5,6,3,2,1}
排序后:{1,2,3,4,5,6}
排序原理:
比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值。冒泡排序API设计:
冒泡排序的代码实现:
package cn.itcast.algorithm.sort; /** * @author :caizhengjie * @description:冒泡排序 * @date :2021/1/25 10:32 下午 */ public class Bubble { /** * 对数组arr中的元素进行排序 * @param arr */ public static void sort(Comparable<Integer>[] arr){ for (int i = arr.length - 1;i > 0;i--){ for (int j = 0;j < i;j++){ if (greater(arr[j],arr[j+1])){ exch(arr,j,j+1); } } } } /** * 比较v元素是否大于w元素 * @param v * @param w * @return */ public static boolean greater(Comparable<Integer> v,Comparable<Integer> w){ return v.compareTo((Integer) w)>0; } /** * 数组元素i和j交换位置 * @param arr * @param i * @param j */ public static void exch(Comparable<Integer>[] arr,int i,int j){ Comparable<Integer> comparable = arr[i]; arr[i] = arr[j]; arr[j] = comparable; } }
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Bubble; import java.util.Arrays; /** * @author :caizhengjie * @description:TODO * @date :2021/1/25 10:57 下午 */ public class BubbleTest { public static void main(String[] args) { Integer[] arr = {4,5,6,3,2,1}; Bubble.sort(arr); System.out.println(Arrays.toString(arr)); } }
冒泡排序的时间复杂度分析 :
冒泡排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以, 我们分析冒泡排序的时间复杂度,主要分析一下内层循环体的执行次数即可。
在最坏情况下,也就是假如要排序的元素为{6,5,4,3,2,1}逆序,那么:
元素比较的次数为:
(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
元素交换的次数为:
(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
总执行次数为:
(N ^ 2/2-N/2)+(N ^ 2/2-N/2)=N^2-N;
按照大O推导法则,保留函数中的最高阶项那么最终冒泡排序的时间复杂度为O(N^2).
(3)选择排序
选择排序是一种更加简单直观的排序方法。
需求:
排序前:{4,6,8,7,9,2,10,1}
排序后:{1,2,4,5,7,8,9,10}
排序原理:
每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处 的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引
交换第一个索引处和最小值所在的索引处的值
选择排序API设计:
选择排序的代码实现:
package cn.itcast.algorithm.sort; /** * @author :caizhengjie * @description:选择排序 * @date :2021/1/26 10:14 下午 */ public class Selection { /** * 对数组arr中的元素进行排序 * @param arr */ public static void sort(Comparable<Integer>[] arr){ for (int i=0;i <= arr.length-2;i++){ // 假定本次遍历,最小值所在的索引是i int minIndex = i; for (int j=i+1;j<arr.length;j++){ if (greater(arr[minIndex],arr[j])){ // 跟最小值换索引 minIndex = j; } } // 交换i索引处和minTndex索引处的值 exch(arr,i,minIndex); } } /** * 比较v元素是否大于w元素 * @param v * @param w * @return */ public static boolean greater(Comparable<Integer> v,Comparable<Integer> w){ return v.compareTo((Integer) w)>0; } /** * 数组元素i和j交换位置 * @param arr * @param i * @param j */ public static void exch(Comparable<Integer>[] arr,int i,int j){ Comparable<Integer> comparable = arr[i]; arr[i] = arr[j]; arr[j] = comparable; } }
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Selection; import java.util.Arrays; /** * @author :caizhengjie * @description:TODO * @date :2021/1/26 10:35 下午 */ public class SelectionTest { public static void main(String[] args) { Integer[] arr = {4,6,8,7,9,2,10,1}; Selection.sort(arr); System.out.println(Arrays.toString(arr)); } }
选择排序的时间复杂度分析:
选择排序使用了双层for循环,其中外层循环完成了数据交换,内层循环完成了数据比较,所以我们分别统计数据 交换次数和数据比较次数:
数据比较次数:
(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
数据交换次数: N-1
时间复杂度:N ^ 2/2-N/2+(N-1)=N ^ 2/2+N/2-1; 根据大O推导法则,保留最高阶项,去除常数因子,时间复杂度为O(N^2);
(4)插入排序
插入排序(Insertion sort)是一种简单直观且稳定的排序算法。
需求:
排序前:{4,3,2,10,12,1,5,6}
排序后:{1,2,3,4,5,6,10,12}
排序原理:
把所有的元素分为两组,已经排序的和未排序的;
找到未排序的组中的第一个元素,向已经排序的组中进行插入;
倒叙遍历已经排序的元素,依次和待插入的元素进行比较,直到找到一个元素小于等于待插入元素,那么就把待 插入元素放到这个位置,其他的元素向后移动一位;
插入排序API设计:
插入排序代码实现:
package cn.itcast.algorithm.sort; /** * @author :caizhengjie * @description:插入排序 * @date :2021/1/26 10:45 下午 */ public class Insertion { /** * 对数组arr中的元素进行排序 * @param arr */ public static void sort(Comparable<Integer>[] arr){ for (int i=1;i< arr.length;i++){ // 当前元素为a[i],依次和i前面的元素比较,找到一个小于等于a[i]的元素 for (int j=i;j>0;j--){ if (greater(arr[j-1],arr[j])){ // 交换元素 exch(arr,j-1,j); } else { // 找到了该元素,结束 break; } } } } /** * 比较v元素是否大于w元素 * @param v * @param w * @return */ public static boolean greater(Comparable<Integer> v,Comparable<Integer> w){ return v.compareTo((Integer) w)>0; } /** * 数组元素i和j交换位置 * @param arr * @param i * @param j */ public static void exch(Comparable<Integer>[] arr,int i,int j){ Comparable<Integer> comparable = arr[i]; arr[i] = arr[j]; arr[j] = comparable; } }
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Insertion; import java.util.Arrays; /** * @author :caizhengjie * @description:TODO * @date :2021/1/26 10:52 下午 */ public class InsertionTest { public static void main(String[] args) { Integer[] arr = {4,3,2,10,12,1,5,6}; Insertion.sort(arr); System.out.println(Arrays.toString(arr)); } }
插入排序的时间复杂度分析:
插入排序使用了双层for循环,其中内层循环的循环体是真正完成排序的代码,所以,我们分析插入排序的时间复 杂度,主要分析一下内层循环体的执行次数即可。
最坏情况,也就是待排序的数组元素为{12,10,6,5,4,3,2,1},那么:
比较的次数为:
(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
交换的次数为:
(N-1)+(N-2)+(N-3)+…+2+1=((N-1)+1)*(N-1)/2=N^2/2-N/2;
总执行次数为:
(N ^ 2/2-N/2)+(N ^ 2/2-N/2)=N^2-N;
按照大O推导法则,保留函数中的最高阶项那么最终插入排序的时间复杂度为O(N^2).
(5)希尔排序
希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。
需求:
排序前:{9,1,2,5,7,4,8,6,3,5}
排序后:{1,2,3,4,5,5,6,7,8,9}
排序原理:
选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;
对分好组的每一组数据完成插入排序;
减小增长量,最小减为1,重复第二步操作。
增长量h的确定:增长量h的值每一固定的规则,我们这里采用以下规则:
int h=1 while(h<5){ h=2h+1;//3,7 } // 循环结束后我们就可以确定h的最大值; // h的减小规则为: h=h/2
希尔排序的API设计:
希尔排序的代码实现:
package cn.itcast.algorithm.sort; /** * @author :caizhengjie * @description:希尔排序 * @date :2021/1/30 4:28 下午 */ public class Shell { /** * 对数组arr中的元素进行排序 * @param arr */ public static void sort(Comparable<Integer>[] arr){ // 1.根据数组arr的长度,确定增长量h的初始值 int h = 1; while (h < arr.length/2){ h = 2*h + 1; } // 2.希尔排序 while (h >= 1){ // 找到待插入的元素 for (int i = h;i < arr.length;i++){ // arr[i]就是待插入的元素,把arr[i]插入到arr[i-h],arr[i-2h],arr[i-3h]...序列中 for (int j = i;j >= h;j-=h){ //arr[j]就是待插入元素,依次和arr[j-h],arr[j-2h],arr[j-3h]进行比较, // 如果arr[j]小,那么 交换位置,如果不小于,arr[j]大,则插入完成。 if (greater(arr[j - h],arr[j])){ // 交换元素 exch(arr,j-h,j); } else { // 带插入元素已经找到合适的位置,结束循环 break; } } } // 减少h的值 h = h/2; } } /** * 比较v元素是否大于w元素 * @param v * @param w * @return */ public static boolean greater(Comparable<Integer> v,Comparable<Integer> w){ return v.compareTo((Integer) w)>0; } /** * 数组元素i和j交换位置 * @param arr * @param i * @param j */ public static void exch(Comparable<Integer>[] arr,int i,int j){ Comparable<Integer> comparable = arr[i]; arr[i] = arr[j]; arr[j] = comparable; } }
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Shell; import java.util.Arrays; /** * @author :caizhengjie * @description:TODO * @date :2021/1/30 4:46 下午 */ public class ShellTest { public static void main(String[] args) { Integer[] arr = {9,1,2,5,7,4,8,6,3,5}; Shell.sort(arr); System.out.println(Arrays.toString(arr)); } }
希尔排序的时间复杂度分析:
我们可以使用事后分析法对希尔排序和插入排序做性能比较。
在资料的测试数据文件夹下有一个reverse_shell_insertion.txt文件,里面存放的是从100000到1的逆向数据,我们 可以根据这个批量数据完成测试。测试的思想:在执行排序前前记录一个时间,在排序完成后记录一个时间,两个 时间的时间差就是排序的耗时。
希尔排序和插入排序性能比较测试代码:
package cn.itcast.algorithm.test; import cn.itcast.algorithm.sort.Insertion; import cn.itcast.algorithm.sort.Shell; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.ArrayList; /** * @author :caizhengjie * @description:TODO * @date :2021/1/30 5:07 下午 */ public class SortCompare { public static void main(String[] args) throws Exception { // 创建一个ArrayList集合,保存读取出来的整数 ArrayList<Integer> list = new ArrayList<>(); // 读取reverse_arr.txt文件并保存到ArrayList中 BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(new FileInputStream("resources/reverse_arr.txt"))); while (bufferedReader.readLine() != null){ // line是字符串,把line换成Integer,存储到集合中 int i = Integer.parseInt(bufferedReader.readLine()); list.add(i); } bufferedReader.close(); // 把集合转换成数组 Integer[] arr = new Integer[list.size()]; list.toArray(arr); // 调用测试代码完成测试 // 希尔排序:18毫秒 // testShell(arr); // 插入排序:5057毫秒 testInsertion(arr); } /** * 测试希尔排序 * @param arr */ public static void testShell(Integer[] arr){ // 获取执行前的时间 long start = System.currentTimeMillis(); // 执行算法代码 Shell.sort(arr); // 获取执行之后的时间 long end = System.currentTimeMillis(); // 算出程序执行的时间并输出 System.out.println("希尔排序的执行时间为:"+(end - start)+"毫秒"); } /** * 测试插入排序 * @param arr */ public static void testInsertion(Integer[] arr){ // 获取执行前的时间 long start = System.currentTimeMillis(); // 执行算法代码 Insertion.sort(arr); // 获取执行之后的时间 long end = System.currentTimeMillis(); // 算出程序执行的时间并输出 System.out.println("插入排序的执行时间为:"+(end - start)+"毫秒"); } }
通过测试发现,在处理大批量数据时,希尔排序的性能确实高于插入排序。