目录
-
Comparable接口
- 作用
- 通常用于给对象排序
- 作用
-
简单排序
-
冒泡排序
- 概念
- 从数组的头部开始, 不断的根据相邻的两个数的大小来交换两个数的位置
- 时间复杂度: O(n^2)
- 不推荐使用
- 概念
-
选择排序
- 概念
- 从数组中选择出合适大小的数字, 放大合适的位置
- 时间复杂度: O(n^2)
- 不推荐使用
- 概念
-
插入排序
- 概念
- 简单直观切稳定
- 已有数组: 1, 3, 5, 7, 2, 6, 9, 1; 前4个已经排好序了, 现在要给第5个数字2排序, 那么排序的方法是: 2和7进行比较, 2<7, 交换2和7对位置, 然后继续比较2和5, 2<5, 交换2和5的位置, 依次进行即可
- 时间复杂度: O(n^2)
- 不推荐使用
- 概念
-
-
高级排序
-
希尔排序
-
概念
- 是插入排序的改良版
- 先对数据进行分组, 然后再对每一组数据进行插入排序
- 需要进行多次排序, 每一次排序都会使数组更加有序(部分有序)
- 最后一次增常量h=1, 这个时候就是普通的插入排序了, 但是此时的数组是部分有序的, 因此比较的次数会大大降到底
-
增常量h的确定
- 先确定初始值
-
int h = 1; while (h < a.length / 2) { h = 2 * h + 1; }
-
- 再依次减小
- 减小规则 h=h/2
- 先确定初始值
-
代码
-
package lq.sort; import org.junit.Test; import java.util.Arrays; /** * @author LQ * @create 2020-05-24 15:45 */ public class Shell { @Test public void test() { Integer[] arr1 = {8, 1, 0, 4, 1, 9, 45, 3, 7, 9, 10}; sort(arr1); System.out.println(Arrays.toString(arr1)); } public static void sort(Comparable[] a) { //先求出增常量h的初始值 int h = 1; while (h < a.length / 2) { h = 2 * h + 1; } //希尔排序 while (h >= 1) { // /* 1 根据h的值分组, h是多少, 分组的个数就是多少 2 先假定h的值是3, 即当前分了3组, 从索引值是3的位置开始都是待插入的数, 然后我们依次遍历这些数, 遍历第一个数, 这个数就是属于 第一组的, 然后遍历到第二个数, 第二个数是属于第二组的, 然后遍历到第三个数, 第三个数是属于第三组的, 然后遍历到第四个数, 此时, 第四个数是属于第一组的, 即遍历到的第四个数是属于第一组的第二个数, 继续遍历, 知道遍历到数组的最后一个数, 排序完成! */ //找出待插入的数 for (int i = h; i < a.length; i++) { for (int j = i; j >= h; j -= h) { if (greater(a[j - h], a[j])) { exchange(a, j - h, j); } else { break; } } } //减小h的值 h /= 2; } } /** * @param x * @param y * @return true: x>y */ public static boolean greater(Comparable x, Comparable y) { return x.compareTo(y) > 0; } /** * 交换数组中x和y的位置 * @param a * @param x * @param y */ public static void exchange(Comparable[] a, int x, int y) { Comparable t; t = a[x]; a[x] = a[y]; a[y] = t; } }
-
-
时间复杂度
- 直接计算时间复杂度是比较麻烦的, 所以我们直接拿测试数据进行测试
-
package lq.test; import lq.sort.Insertion; import lq.sort.Shell; import org.junit.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; /** * @author LQ * @create 2020-05-24 18:10 */ public class TestShell { @Test public void test() throws IOException { //创建数组 ArrayList<Integer> arr = new ArrayList<Integer>(); //将文件读取出来 BufferedReader reader = new BufferedReader(new InputStreamReader(TestShell.class.getClassLoader().getResourceAsStream("reverse_arr.txt"))); String line = null; while ((line = reader.readLine()) != null) { arr.add(Integer.parseInt(line)); } //调用方法 Integer[] arr2 = new Integer[arr.size()]; arr.toArray(arr2); testShell(arr2); // testInsertion(arr2); } public void testShell(Integer[] a) { //获取开始时的时间 long start = System.currentTimeMillis(); //排序 Shell.sort(a); //获取结束时的时间 long end = System.currentTimeMillis(); System.out.println("Shell: " + (end-start) + " ms"); } }
-
-
归并排序
-
递归
- 方法调用需要先把方法进入栈内存, 如果一个递归算法是一个无穷的递归时, 就会报栈内存溢出异常
-
概念
- 合并的概念图
- 合并的概念图
- 归并排序的代码
-
package lq.sort; import org.junit.Test; import java.util.Arrays; /** * @author LQ * @create 2020-05-24 15:45 */ public class Merge { private static Comparable[] assist; @Test public void test() { Integer[] arr1 = {8, 1, 0, 4, 1, 9, 45, 3, 7, 9, 10}; sort(arr1); System.out.println(Arrays.toString(arr1)); } public static void sort(Comparable[] a) { //初始化辅助数组 assist = new Integer[a.length]; //找出开始和结束的索引值 int lo = 0; int hi = a.length - 1; //调用方法 sort(a, lo, hi); } public static void sort(Comparable[] a, int lo, int hi) { //做安全检验 if (lo >= hi) { return; } //求出中间位置的索引值 int mid = (lo + hi) / 2; //递归调用 sort(a, lo, mid); sort(a, mid + 1, hi); //合并 merge(a, lo, mid, hi); } /** * 归并算法的核心 * * @param a * @param lo * @param mid * @param hi */ public static void merge(Comparable[] a, int lo, int mid, int hi) { //定义三个指针 int i = lo; int p1 = lo; int p2 = mid + 1; //遍历, 移动指针p1 p2 i while (p1 <= mid && p2 <= hi) { if (less(a[p1], a[p2])) { assist[i++] = a[p1++]; }else { assist[i++] = a[p2++]; } } //如果p1指针还没有走完, 那直接吧剩余元素放大辅助数组中去 while (p1<=mid){ assist[i++] = a[p1++]; } //如果p2指针还没有走完, 那直接吧剩余元素放大辅助数组中去 while (p2<=hi){ assist[i++] = a[p2++]; } //将辅助数组的数据拷贝到原数组 for (int j = lo; j <=hi; j++) { a[j]=assist[j]; } } /** * @param x * @param y * @return true: x>y */ public static boolean less(Comparable x, Comparable y) { return x.compareTo(y) < 0; } /** * 交换数组中x和y的位置 * * @param a * @param x * @param y */ public static void exchange(Comparable[] a, int x, int y) { Comparable t; t = a[x]; a[x] = a[y]; a[y] = t; } }
-
- 时间复杂度:O(nlogn)
- 归并排序的缺点
- 需要申请额外的数组空间, 导致空间复杂度上升, 是典型的的以空间换时间的操作
- 辅助数组的大小和原数组的大小是一样的
- 需要申请额外的数组空间, 导致空间复杂度上升, 是典型的的以空间换时间的操作
- 归并排序和希尔排序的速度是差不多的
-
-
快速排序
-
概念
- 现有数组: 6, 1, 9, 4, 7; 以6为基准, 把比6小的数放到左边, 把比6大的数字放到右边, 然后分别对6左边和右边的子数组重复上面的操作.
-
代码
-
package lq.sort; import org.junit.Test; import java.util.Arrays; /** * @author LQ * @create 2020-05-24 15:45 */ public class Quick { @Test public void test() { // Integer[] arr1 = {8, 1, 0, 4, 1, 9, 45, 3, 7, 9, 10}; Integer[] arr1 = {8, 2, 6, 0, 1, 4, 7, 9, 2, 1, 7, 78, 23, 56, 78, 12, 67, 2}; sort(arr1); System.out.println(Arrays.toString(arr1)); } public static void sort(Comparable[] a) { int lo = 0; int hi = a.length - 1; sort(a, lo, hi); } public static void sort(Comparable[] a, int lo, int hi) { //参数的合法性检验 if (lo >= hi) { return; } //将左子组和右子组排序, 然后返回中间值的索引值 int partition = partition(a, lo, hi); sort(a, lo, partition - 1); sort(a, partition + 1, hi); } public static int partition(Comparable[] a, int lo, int hi) { //定义左右指针 int left = lo; int right = hi; // while (left < right) { if (greater(a[left], a[left + 1])) { exchange(a, left, left + 1); left++; } else { exchange(a, left + 1, right); right--; } } return left; } /** * @param x * @param y * @return true: x>y */ public static boolean less(Comparable x, Comparable y) { return x.compareTo(y) < 0; } public static boolean greater(Comparable x, Comparable y) { return x.compareTo(y) > 0; } /** * 交换数组中x和y的位置 * * @param a * @param x * @param y */ public static void exchange(Comparable[] a, int x, int y) { Comparable t; t = a[x]; a[x] = a[y]; a[y] = t; } }
-
-
快速排序和归并排序的区别
- 快速排序使用的也是分治的思想
-
时间复杂度
- 最优情况
- 每一次切分都正好是在数组的正中间位置
- 时间复杂度为: O(nlogn)
- 最坏情况
- 每一次切分选择的基数都是数组中最大的或最小的
- 时间复杂度为: O(n^2)
- 平均情况
- 既不是最优或最坏, 也不是中间值, 而是: O(nlogn)
- 最优情况
-
-
-
排序的稳定性
-
概念
- 数组中如果A和B相等, A在B前面, 如果排序之后A仍然在B前面, 那么此算法是稳定的
-
稳定性的意义
- 只有对同一组数据进行多次排序稳定性才有意义
-
稳定的排序算法
- 冒泡排序
- 插入排序
- 归并排序
-
不稳定的排序算法
- 选择排序
- 每次都为当前位置选择最小的数
- 希尔排序
- 如果有两个相同的数字不在同一个组, 那么排序后就可能打乱这两个数的位置
- 快速排序
- 选择排序
-
排序算法的选择
- 如果一组数据需要多次排序就选择归并排序(稳定的排序算法 排序效率高)
-