【数据结构与算法】排序

一、Comparable接口

所有可以排序对象所对应的类,都实现了 Comparable 接口(Comparable 中的泛型为要比较的对象类型,即该类的类型),并重写了compareTo()方法。

public int compareTo(Object obj)

  • 返回值 = 0:this == o
  • 返回值 >0:this > obj
  • 返回值 < 0:this < obj

(1)定义 Student 类

public class Student implements Comparable<Student>{
    private String name;
    private int age;
	
	// 省略 构造器、set、get方法等
	
    @Override
    public int compareTo(Student stu) {
        // 根据年龄比较
        return this.age - stu.age;
    }
}

(2)测试

public class Test {
    public static Comparable getMax(Comparable c1, Comparable c2){
        int result = c1.compareTo(c2);

        if(result >= 0){
            return c1;  // c1大
        }else {
            return c2;
        }
    }

    public static void main(String[] args) {
        Student stu1 = new Student("张三",20);
        Student stu2 = new Student("李四",30);

        Comparable max = getMax(stu1, stu2);
        System.out.println(max); // Student{name='李四', age=30}
    }
}

二、冒泡排序 Bubble

其思想是:相邻的元素两两比较,较大的数下沉,较小的数冒起来。整个过程如同气泡冒起,因此被称作冒泡排序。

  1. 比较相邻的两个元素,如果前一个元素大,就交换位置。
  2. 依次比较每一对,每遍历一次,就会选出一个相对最大的元素(之前已经选出的元素除外)。遍历的总对数是 length - 1

平均时间复杂度为O(n^2),适用于元素个数较少的排序。

在这里插入图片描述

public class Bubble {
    /**
     * 对数组 a 排序
     *
     * @param a 数组
     */
    public static void sort(Comparable[] a) {
        // 因为最后一个元素没有下一个,所以第一次遍历的总对数为 length-1,以后每
        // 每次遍历的对数,都要减去已经选出的相对最大值个数
        for (int i = a.length - 1; i > 0; i--) {
            //
            for (int j = 0; j < i; j++) {
                if (isGreater(a[j], a[j + 1])) {
                    swap(a, j, j + 1); // a[j] 大,交换
                }
            }
        }
    }

    /**
     * 判断 v 元素 是否大于 w 元素
     *
     * @param v 元素v
     * @param w 元素w
     * @return 较大元素
     */
    private static boolean isGreater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a 数组
     * @param i 索引i
     * @param j 索引j
     */
    private static void swap(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        Integer[] ints = {4, 5, 6, 3, 2, 1};
        Bubble.sort(ints);
        System.out.println(Arrays.toString(ints));
    }
}
public class BubbleSort {
    public static void sort(int[] arr){
        int temp = 0; // 在交换的时候使用

        // 第一轮比较的次数 i 是 length-1, 以后每次都少 1 次
        for (int i = arr.length - 1; i > 0; i--) {
            // 比较次数为 i
            for (int j = 0; j < i; j++) {
                if(arr[j] > arr[j+1]){
                    temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {5,6,8,2,1,5,6,8};
        BubbleSort.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

三、选择排序 Selection

其思想是:选出相对最小的元素,放在相对最前面(交换)。

  1. 每一次遍历,都假定第一个就是最小元素。在依次与其他元素比较,最终找到最小元素所在的索引。
  2. 最小元素与最前面元素交换。

平均时间复杂度为O(n^2),适用于元素个数较少的排序。
在这里插入图片描述

public class Selection {
    /**
     * 对数组 a 排序
     *
     * @param a
     */
    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; // a[minIndex] 大,更换索引
                }
            }
            // 交换最小元素所在索引处的值
            swap(a,i,minIndex);
        }
    }

    /**
     * 判断 v 元素 是否大于 w 元素
     *
     * @param v
     * @param w
     * @return
     */
    private static boolean isGreater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a
     * @param i
     * @param j
     */
    private static void swap(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }


    public static void main(String[] args) {
        Integer[] ints = {4, 6, 8, 7, 9, 2 ,10, 1};
        Selection.sort(ints);
        System.out.println(Arrays.toString(ints));
    }
}

四、插入排序 Insertion

其思想是:将初始数据分为有序部分和无序部分,依次将无序部分的元素插入有序部分中。

  1. 把原始数据分为两组:有序、无序。
  2. 在无序部分中选出一个元素作为待插入元素。
  3. 倒叙遍历有序部分元素,依次和待插入元素比较,直到找到一个元素小于待插入元素,将其放入这个元素的后面(交换),后面元素后移。(利用冒泡思想实现比较并插入)

平均时间复杂度为O(n^2),适用于元素个数较少的排序。

在这里插入图片描述


public class Insertion {
    /**
     * 对数组 a 排序
     *
     * @param a
     */
    public static void sort(Comparable[] a) {
        // 默认索引为0的元素为有序部分,无序部分从索引1出开始
        for (int i = 1; i < a.length; i++) {
            for (int j = i; j > 0; j--) {
                if (isGreater(a[j-1], a[j])) {
                    swap(a,j,j-1); // a[j-1] 大,交换
                }else {
                    break; // a[j-1] 小,说明已经插入好
                }
            }
        }
    }

    /**
     * 判断 v 元素 是否大于 w 元素
     *
     * @param v
     * @param w
     * @return
     */
    private static boolean isGreater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a
     * @param i
     * @param j
     */
    private static void swap(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

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

五、希尔排序 Shell

希尔排序又称为缩小增量排序,是对插入排序的一种优化版本。优化的地方在于:不用每次插入一个元素时,就和有序部分中的所有元素进行比较。

  1. 取一个整数 h 作为间隔,所有距离为 h 的元素为一组。
  2. 每组元素分别排序。
  3. 缩小间隔 h,直至 h = 1。

在这里插入图片描述

间隔 h 的取值

// 初始值
int h = 1;
while(h < 数组长度 / 2){
	h = 2h + 1;
}

// 减小规则
h = h /2;
public class Shell {
    /**
     * 对数组 a 排序
     *
     * @param a
     */
    public static void sort(Comparable[] a) {

        // 确定间隔初始值
        int h = 1;
        while (h < a.length) {
            h = 2 * h + 1;
        }

        while (h >= 1) {
            // 从第一组中的第二个元素,开始往后依次比较
            for (int i = h; i < a.length; i++) {
                for (int j = i; j >= h; j -= h) {
                    if (isGreater(a[j - h], a[j])) {
                        swap(a, j, j - h); // a[j-h] 大于 插入元素 a[j]
                    }else {
                        break;
                    }
                }
            }
            h = h / 2;
        }
    }

    /**
     * 判断 v 元素 是否大于 w 元素
     *
     * @param v
     * @param w
     * @return
     */
    private static boolean isGreater(Comparable v, Comparable w) {
        return v.compareTo(w) > 0;
    }

    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a
     * @param i
     * @param j
     */
    private static void swap(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }


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

六、归并排序 Merge

其思想是:将元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的。

  1. 尽可能将数据分为两个元素个数相等的子组,并对每一个子组继续拆分,直至每组元素个数为1。
  2. 将相邻的两个子组合并,合并过程中进行排序。

平均时间复杂度为O(nlogn) < O(n^2)

在这里插入图片描述

在这里插入图片描述

public class Merge {

    // 归并所需要的辅助数组
    private static Comparable[] assist;

    /**
     * 对数组 a 排序
     *
     * @param a
     */
    public static void sort(Comparable[] a) {
        // 初始化辅助数组
        assist = new Comparable[a.length];

        // 开始索引和结束索引
        int start = 0;
        int end = a.length - 1;

        // 排序
        sort(a, start, end);

    }


    /**
     * 对数组 a 中部分元素排序
     *
     * @param a
     * @param start
     * @param end
     */
    private static void sort(Comparable[] a, int start, int end) {
        // 安全性校验
        if (end <= start) {
            return;
        }

        // 分组
        int end1 = start + (end - start) / 2;

        // 对子组排序
        sort(a, start, end1);
        sort(a, end1 + 1, end); // start2 = end1 + 1

        // 归并
        merge(a, start, end1, end);
    }


    /**
     * 把两个子组合并成为一个大组,且排序
     * 两个子组:a[start1.end1], a[start2,end2]
     *
     * @param a
     * @param start1
     * @param end1
     * @param end2
     */
    private static void merge(Comparable[] a, int start1, int end1, int end2) {
        // 定义三个指针
        int i = start1;
        int p1 = start1;
        int p2 = end1 + 1;

        // 遍历,移动 p1 p2指针,找出较小的放到 辅助数组对应索引处
        while (p1 <= end1 && p2 <= end2) {
            if (isLess(a[p1], a[p2])) {
                assist[i++] = a[p1++];  // a[p1]小
            } else {
                assist[i++] = a[p2++];  // a[p2]小
            }
        }

        // 如果 p1 指针没遍历完
        while (p1 <= end1) {
            assist[i++] = a[p1++];
        }

        // 如果 p2 指针没遍历完
        while (p2 <= end2) {
            assist[i++] = a[p2++];
        }

        // 辅助数组 复制 到原数组
        for (int index = start1; index <= end2; index++) {
            a[index] = assist[index];
        }
    }

    /**
     * 判断 v 元素 是否小于 w 元素
     *
     * @param v
     * @param w
     * @return
     */
    private static boolean isLess(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

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

七、快速排序 Quick

快速排序是对冒泡排序的一种改进。

  1. 设定一个分界值(数组第一个元素),将数组分为两部分。
  2. 将小于分界值的元素放到左边,大于或等于分界值的元素放到右边。左边部分总比右边部分小。
  3. 分别继续细分,直到整个数组变成有序序列。

平均时间复杂度为O(nlogn) < O(n^2)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Quick {

    /**
     * 对数组 a 排序
     *
     * @param a
     */
    public static void sort(Comparable[] a) {

        // 开始索引和借宿索引
        int start = 0;
        int end = a.length - 1;

        // 排序
        sort(a, start, end);
    }

    /**
     * 对数组 a 中部分元素排序
     *
     * @param a
     * @param start
     * @param end
     */
    private static void sort(Comparable[] a, int start, int end) {
        // 安全性校验
        if (end <= start) {
            return;
        }

        // 分组,得到分界值所在的索引
        int partition = partition(a, start, end);

        // 对子组排序,不需要对分界值排序
        sort(a, start, partition - 1);
        sort(a, partition + 1, end);
    }

    /**
     * 将 a[start,edn] 进行分组
     *
     * @param a
     * @param start
     * @param end
     * @return 分组分界值所在的索引
     */
    private static int partition(Comparable[] a, int start, int end) {
        // 确定分解值
        Comparable key = a[start];
        // 定义两个指针
        int left = start;
        int right = end + 1;

        // 切分
        while (true) {
            // 从右往左,移动right,找到一个比分界值小的元素
            while (isLess(key, a[--right])) {
                if (right == start) {
                    break;
                }
            }

            // 从左往右,移动left,找到一个比分界值大的元素
            while (isLess(a[++left], key)) {
                if (left == end) {
                    break;
                }
            }

            if (left >= right) {
                break; // 扫描完毕
            } else {
                swap(a, left, right); // 交换,继续扫描
            }
        }
        // 交换分界值
        swap(a, start, right);
        return right;
    }

    /**
     * 判断 v 元素 是否小于 w 元素
     *
     * @param v
     * @param w
     * @return
     */
    private static boolean isLess(Comparable v, Comparable w) {
        return v.compareTo(w) < 0;
    }

    /**
     * 数组元素 i 和 j 交换位置
     *
     * @param a
     * @param i
     * @param j
     */
    private static void swap(Comparable[] a, int i, int j) {
        Comparable temp;
        temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }

    public static void main(String[] args) {
        Integer[] ints = {6, 1, 2, 7, 9, 3, 4, 5, 8};
        Quick.sort(ints);
        System.out.println(Arrays.toString(ints));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值