一、简单排序

1.1Comparable接口介绍

需求:
1.定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则;
2.定义测试类Test,在测试类Test中定义测试方法Comparable getMax(Comparable c1, Comparable c2)完成测试

  1. package algorithm.sort;
  2. //1.定义一个学生类Student,具有年龄age和姓名username两个属性,并通过Comparable接口提供比较规则;
  3. public class Student implements Comparable<Student>{
  4. private String username;
  5. private int age;
  6. public String getUsername() {
  7. return username;
  8. }
  9. public void setUsername(String username) {
  10. this.username = username;
  11. }
  12. public int getAge() {
  13. return age;
  14. }
  15. public void setAge(int age) {
  16. this.age = age;
  17. }
  18. @Override
  19. public String toString() {
  20. return "Student{" +
  21. "username='" + username + '\'' +
  22. ", age=" + age +
  23. '}';
  24. }
  25. @Override
  26. public int compareTo(Student o) {
  27. return this.getAge() - o.getAge();
  28. }
  29. }
  1. package algorithm.test;
  2. import algorithm.sort.Student;
  3. //2.定义测试类Test,在测试类Test定义测试方法Comparable getMax(Comparable c1, Comparable c2)完成测试
  4. public class TestComparable {
  5. public static void main(String[] args){
  6. // 创建两个Student对象,并调用getMax方法,完成测试
  7. Student s1 = new Student();
  8. s1.setUsername("张三");
  9. s1.setAge(18);
  10. Student s2 = new Student();
  11. s2.setUsername("李四");
  12. s2.setAge(20);
  13. Comparable max = getMax(s1, s2);
  14. System.out.println(max);
  15. }
  16. public static Comparable getMax(Comparable c1, Comparable c2){
  17. int result = c1.compareTo(c2);
  18. // 如果result<0,则c1比c2小
  19. //如果result>0, 则c1比c2大;
  20. //如果result==0, 则c1和c2一样大
  21. if(result >= 0){
  22. return c1;
  23. }else{
  24. return c2;
  25. }
  26. }
  27. }

1.2 冒泡排序(时间复杂度O(n2))

排序原理:

  1. 比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。
  2. 对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大
    值。

image.png

类名 Bubble
构造方法 Bubble()
成员方法 1.private static boolean greater(Comparable v, Comparable w) 判断响铃元素大小
2.private static void exch(Comparable[] a, int i, int j)交换数组内相邻元素
3.public static void sort(Comparable[] a):对数组进行排序
  1. package algorithm.sort;
  2. public class Bubble {
  3. // 1.判断大小
  4. private static boolean greater(Comparable v, Comparable w){
  5. // result>0 则v>w
  6. return v.compareTo(w) > 0;
  7. }
  8. // 2.交换位置
  9. private static void exch(Comparable[] a, int i, int j){
  10. Comparable temp;
  11. temp = a[i];
  12. a[i] = a[j];
  13. a[j] = temp;
  14. }
  15. // 3.对数组内的元素排序
  16. // 双重for循环 1.外层循环控制数据对比轮数 2.内层循环控制每轮对比次数
  17. public static void sort(Comparable[] a){
  18. for(int i = a.length - 1; i > 0 ; i--){
  19. for(int j = 0; j < i; j++){
  20. if (greater(a[j], a[j+1])){
  21. exch(a, j, j+1);
  22. }
  23. }
  24. }
  25. }
  26. }
  1. package algorithm.test;
  2. import algorithm.sort.Bubble;
  3. import java.util.Arrays;
  4. public class TestBubble {
  5. public static void main(String[] args) {
  6. Integer[] arr = {4,3,7,9,1};
  7. Bubble.sort(arr);
  8. System.out.println(Arrays.toString(arr));
  9. }
  10. }

1.3选择排序(时间复杂度O(n2))

排序原理:(外层循环进行交换,内层循环寻找当前轮中最小值所在的索引)
1.每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定某个索引处的值为最小值,最后可以找到最小值所在的索引
2.交换第一个索引处和最小值所在的索引处的值
image.png

类名 Selection
构造方法 Selection()
成员方法 1.private static boolean greater(Comparable v, Comparable w) 判断响铃元素大小
2.private static void exch(Comparable[] a, int i, int j)交换数组内相邻元素
3.public static void sort(Comparable[] a):对数组进行排序
package algorithm.sort;

public class Selection {
//    1.比较大小
    private static boolean greater(Comparable v, Comparable w){
        return v.compareTo(w)>0;
    }
//    2.交换两个元素的位置
    private static void exch(Comparable[] a, int i, int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
//   3.排序
    public static void sort(Comparable[] a){
        for(int i=0; i<a.length - 1; i++){
            int min_index = i;
            for (int j =i+1; j<a.length; j++){
//                if (greater(a[i], a[j])){
//                    exch(a, i, j);
//                }
                if(greater(a[min_index], a[j])){
                    min_index = j;
                }
            }
//            交换最小元素所在索引
            exch(a, i, min_index);
        }
    }
}
package algorithm.test;

import algorithm.sort.Selection;

import java.util.Arrays;

public class TestSelection {
    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));
    }
}

1.4 插入排序(时间复杂度O(n2))

插入排序的工作方式非常像人们排序一手扑克牌一样。开始时,我们的左手为空并且桌子上的牌面朝下。然后,我
们每次从桌子上拿走一张牌并将它插入左手中正确的位置。为了找到一张牌的正确位置,我们从右到左将它与已在
手中的每张牌进行比较,如下图所示:
image.png
需求:
排序前:{4,3,2,10,12,1,5,6}
排序后:{1,2,3,4,5,6,10,12}
image.png
排序原理:
1.将所有元素分成两组,一组已经排序,一组未排序的
2.未排序的元素从索引1开始,一直到最末尾元素,即a.length
3.倒序索引已经排序好的元素,和未排序的元素进行比较,如果比未排序的元素大则交换,直到交换到比它小的元素,说明结束,跳出循环即可

package algorithm.sort;

public class Insertion {
//    1.判断大小
    private static boolean greater(Comparable v, Comparable w){
        return v.compareTo(w)>0;
    }
//    2.交换位置
    private static void exch(Comparable[] a, int i, int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
//    3.插入排序
    public static void sort(Comparable[] a){
//        1.会分成两组,一组已排序的,一组未排序的
//        2.找到未排序的组中的第一个元素,向已排序中的进行插入
//        3.倒序遍历已经排序的元素,依次和待插入的元素进行比较,比待插入元素大就进行交换,比待插入元素小则结束跳出循环即可
        for(int i=1; i<a.length; i++){
            for (int j=i; j>0; j--){
                if(greater(a[j-1], a[j])){
                    exch(a, j-1, j);
                }else {
                    break;
                }
            }
        }

    }
}
package algorithm.test;

import algorithm.sort.Insertion;

import java.io.PrintWriter;
import java.util.Arrays;

public class TestInsertion {
    public static void main(String[] args) {
        Integer[] a = {4,3,2,10,12,1,5,6};
        Insertion.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

二、高级排序

2.1希尔排序

希尔排序是插入排序的一种,又称“缩小增量排序”,是插入排序算法的一种更高效的改进版本。
排序原理:
1.选定一个增长量h,按照增长量h作为数据分组的依据,对数据进行分组;2.对分好组的每一组数据完成插入排序;3.减小增长量,最小减为1,重复第二步操作。
image.png

package algorithm.sort;

public class Shell {
//    1.比较大小
    private static boolean greater(Comparable v, Comparable w){
        return v.compareTo(w)>0;
    }
//    2.交换
    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数据分组的依据,对数据进行分组
//    2.对分好组的每一组数据完成插入排序
//    3.减少增长量,最小减为1,重复第二步操作
    public static void sort(Comparable[] a){
//        1.确定增长量h的值
        int h = 1;
        while (h < a.length/2){
            h = 2*h + 1;
        }
//        2.希尔排序
//        当增长量小于1,排序结束
        while(h>=1){
// 2.2 找到待插入的元素,每次分组后第一个待插入的元素都是h
            for(int i = h; i < a.length; i ++){
                for (int j = i; j>=h; j -=h){
//                    a[j]就是待插入元素,依次和a[j - h], a[j - 2h]...a[j - 3h]比较,如果a[j]小则交换,否则结束
                    if(greater(a[j-h], a[j])){
                        exch(a, j-h, j);
                    }else {
                        break;
                    }

                }

            }
//      2.1减小h的值
        h = h /2;
        }

    }
}
package algorithm.test;
import algorithm.sort.Insertion;

import java.util.Arrays;

public class TestShell {
    public static void main(String[] args) {
        Integer[] a = {9,1,2,5,7,4,8,6,3,5};
        Insertion.sort(a);
        System.out.println(Arrays.toString(a));
    }
}

2.2 归并排序(时间复杂度(O(nlog(n))))

排序原理:
1.尽可能的一组数据拆分成两个元素相等的子组,并对每个子组继续拆分,直到拆分后的每个子组的元素个数是1为止
2.将相邻的两个子组进行合并成一个有序的大组
3.不断重复步骤2,直到最终只有一个组为止
image.png

成员变量 private static Comparable[] assit:完成归并操作需要的辅助数组
成员方法 1.public static void sort(Comparable[] a):对数组内的元素进行排序
2.private static void sort(Comparable[]a, int lo, int hi):对数组a中从索引lo到索引hi之间的元素进行排序
3.private static void merge(Comparable[] a, int lo, int mid, int hi):从索引lo到索引mid为一个子组,从索引mid+1到索引hi为另一个子组,把数组a中的这两个子组的数据合并成一个有序的大组(从索引lo到索引hi)
4.private static boolean less(Comparable v, Comparable w):判断v是否小于w
5.private static void exch(Comparable[] a, int i ,int j):交换a数组中,索引i和索引j处的值

对于merge函数的执行流程:
image.png
image.png
image.png
image.png
image.png

package algorithm.sort;

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 = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
//    对数组中元素进行排序
    public static void sort(Comparable[] a){
//        1.初始化辅助数组assit
        assist = new Comparable[a.length];
//        2.定义一个lo变量,和hi变量,分别记录数组中最小的索引和最大的索引
        int lo = 0;
        int hi = a.length - 1;
//        3.调用sort重载方法完成数组
        sort(a, lo, hi);
    }
//    对数组a从lo到hi的元素进行排序
    private static void sort(Comparable[] a, int lo, int hi){
//        做安全性校验
//        递归函数的出口,hi=lo,说明已经分到了每组都只有1个数
        if(hi<=lo){
            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+1到hi为一组,对这两组数据进行归并
    private static void merge(Comparable[] a, int lo, int mid, int hi){
//        1.定义三个指针
       int i = lo;// 定义一个指针,指向assist数组归并到辅助数组assit对应的索引处
       int p1 = lo;//定义一个指针,指向第一组数组的第一个元素
       int p2 = mid + 1;//定义一个指针,指向第二组数据的第一个元素
//        2.遍历,移动p1和p2指针,比较对应索引处的值,找出小的那个,放到辅助数组的索引处
        while(p1<=mid&&p2<=hi){
            if (less(a[p1],a[p2])){
                assist[i++] = a[p1++];
            }else{
                assist[i++] = a[p2++];
            }
        }
//        3.遍历,如果p1指针没有走完,那么顺序移动p1指针,把对应的元素放到辅助数组的对应索引处
        while (p1<=mid){
            assist[i++] = a[p1++];
        }
//        3.2遍历,如果p2的指针没有走完,那么顺序移动p2指针,把对应的元素放到辅助数组的对应索引处
        while (p2<=hi){
            assist[i++] = a[p2++];
        }
//        4.把辅助数组中的元素拷贝到原数组中
        for (int index=lo; index <= hi; index++){
            a[index] = assist[index];
        }
    }
}
package algorithm.test;
import algorithm.sort.Bubble;
import algorithm.sort.Merge;

import java.util.Arrays;

public class TestMerge {
    public static void main(String[] args) {
        Integer[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
        Merge.sort(arr);
        System.out.println(Arrays.toString(arr));
    }

}

2.3快速排序

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一
部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序
过程可以递归进行,以此达到整个数据变成有序序列
排序原理:
1.首先设定一个分界值,通过该分界值将数组分成左右两部分;
2.将大于或等于分界值的数据放到到数组右边,小于分界值的数据放到数组的左边。此时左边部分中各元素都小于
或等于分界值,而右边部分中各元素都大于或等于分界值;
3.然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两
部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
4.重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当
左侧和右侧两个部分的数据排完序后,整个数组的排序也就完成了。
image.png

成员方法 1.public static void sort(Comparable[] a):对数组内的元素进行排序
2.private static void sort(Comparable[]a, int lo, int hi):对数组a中从索引lo到索引hi之间的元素进行排序
3.private static int partition(Comparable[] a, int lo, int hi):对数组a中,从索引lo到索引hi之间的元素进行分组,并返回分组界限对应的索引(从索引lo到索引hi)
4.private static boolean less(Comparable v, Comparable w):判断v是否小于w
5.private static void exch(Comparable[] a, int i ,int j):交换a数组中,索引i和索引j处的值

主要步骤:切分:
把一个数组切分成两个子数组的基本思想:
1.找一个基准值,用两个指针分别指向数组的头部和尾部;
2.先从尾部向头部开始搜索一个比基准值小的元素,搜索到即停止,并记录指针的位置;
3.再从头部向尾部开始搜索一个比基准值大的元素,搜索到即停止,并记录指针的位置;
4.交换当前左边指针位置和右边指针位置的元素;
5.重复2,3,4步骤,直到左边指针的值大于右边指针的值停止

image.png

image.png
image.png

image.png

package algorithm.sort;

public class Qucik {
    public static void sort(Comparable[] a){
        int lo = 0;
        int hi = a.length -1 ;
        sort(a, lo, hi);
    }
    private static void sort(Comparable[]a, int lo, int hi){
        if (lo>=hi){
            return;
        }
//        对a数组中从lo到hi的元素进行切分
        int partition = partition(a, lo, hi);
//        对左边分组进行排序
        sort(a, lo, partition - 1);
//        对右边分组进行排序
        sort(a, partition+1, hi);
    }
    private static int partition(Comparable[]a, int lo,int hi){
//        1.找到基准值
        Comparable key = a[lo];
//        2.定义两个指针,left指向lo,right指向hi的后面一个元素
        int left = lo;
        int right = hi + 1;
//        3.进行切分
        while(true){
//            先从右向左扫描,直到扫描到第一个比key小的元素,停止扫描
            while(less(key, a[--right])){
                if (right<=lo){
                    break;
                }
            }
//            再从左向右扫描,直到扫描到第一个比key大的元素,停止扫描
            while (less(a[++left],key)){
                if (left>=right){
                    break;
                }
            }
//            如果left>=right则停止循环,否则交换left和right的位置的值
            if(left>=right){
                break;
            }else {
                exch(a,left,right);
            }
        }
        //            交换最后right索引出和基准值所在的索引处的值
        exch(a, lo, right);
        return right;
    }
    private static boolean less(Comparable v, Comparable w){
        return v.compareTo(w)<0;
    }
    private static void exch(Comparable[] a,int i,int j){
        Comparable temp = a[i];
        a[i] = a[j];
        a[j] = temp;

    }
}

三、排序的稳定性

稳定性的定义:
数组arr中有若干元素,其中A元素和B元素相等,并且A元素在B元素前面,如果使用某种排序算法排序后,能够保
证A元素依然在B元素的前面,可以说这个该算法是稳定的。