排序算法:冒泡排序,插入排序,选择排序,归并排序,快速排序

涉及排序算法列表

排序算法:冒泡排序,插入排序,选择排序,归并排序,快速排序

算法分析评价涉及层面

  • 1.最好情况、最坏情况、平均情况时间复杂度分析
  • 2.原地排序(Sorted in place)。原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
  • 3.稳定性。这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

时间复杂度O(n2)算法:冒泡排序,插入排序,选择排序

冒泡排序,插入排序,选择排序代码

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;

/**
 * 排序算法的执行效率分析
 * <p>
 * 1.最好情况、最坏情况、平均情况时间复杂度
 * 2.原地排序(Sorted in place)。原地排序算法,就是特指空间复杂度是 O(1) 的排序算法。
 * 3.稳定性。这个概念是说,如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。
 *
 * @author gaoge
 * @since 2022/11/29 17:07
 */
public class Sort {

    /**
     * 冒泡排序
     * <p>
     * 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。
     * 如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
     * 冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法。
     * 最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 O(n)。
     * 而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 O(n2)。
     * 平均情况下的时间复杂度就是 O(n2)
     *
     * @param a 数组
     * @param n 数组大小
     */
    public static void bubbleSort(int[] a, int n) {
        for (int i = 0; i < n; i++) {
            boolean flag = false;
            for (int j = 0; j < n - i - 1; j++) {
                if (a[j] > a[j + 1]) {
                    int temp = a[j];
                    a[j] = a[j + 1];
                    a[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag) {
                break;
            }
        }
    }

    /**
     * 插入排序
     * <p>
     * 首先,我们将数组中的数据分为两个区间,已排序区间和未排序区间。
     * 初始已排序区间只有一个元素,就是数组的第一个元素。插入算法的核心思想是取未排序区间中的元素,
     * 在已排序区间中找到合适的插入位置将其插入,
     * 并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
     * 插入排序算法的运行并不需要额外的存储空间,所以空间复杂度是 O(1),也就是说,这是一个原地排序算法。
     * 在插入排序中,对于值相同的元素,我们可以选择将后面出现的元素,插入到前面出现元素的后面,
     * 这样就可以保持原有的前后顺序不变,所以插入排序是稳定的排序算法。
     * 最好是时间复杂度为 O(n),最坏情况时间复杂度为 O(n2),平均时间复杂度为 O(n2)。
     *
     * @param a 数组
     * @param n 数组大小
     */
    public static void insertionSort(int[] a, int n) {
        if (n <= 1) {
            return;
        }
        for (int i = 1; i < n; ++i) {
            int value = a[i];
            int j = i - 1;
            // 查找插入的位置
            for (; j >= 0; --j) {
                if (a[j] > value) {
                    // 数据移动
                    a[j + 1] = a[j];
                } else {
                    break;
                }
            }
            // 插入数据
            a[j + 1] = value;
        }
    }

    /**
     * 选择排序
     * <p>
     * 选择排序算法的实现思路有点类似插入排序,也分已排序区间和未排序区间。
     * 但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
     * 选择排序是一种不稳定的排序算法。比如 5,8,5,2,9 这样一组数据,使用选择排序算法来排序的话,
     * 第一次找到最小元素 2,与第一个 5 交换位置,那第一个 5 和中间的 5 顺序就变了,所以就不稳定了。
     * 正是因此,相对于冒泡排序和插入排序,选择排序就稍微逊色了。
     * 首先,选择排序空间复杂度为 O(1),是一种原地排序算法。
     * 选择排序的最好情况时间复杂度、最坏情况和平均情况时间复杂度都为 O(n2)。
     *
     * @param a 数组
     * @param n 数组大小
     */
    public static void selectionSort(int[] a, int n) {
        for (int i = 0; i < n; i++) {
            int value = a[i];
            int min = value;
            int num = i;
            for (int j = i; j < n; j++) {
                if (a[j] < min) {
                    min = a[j];
                    num = j;
                }
            }
            a[i] = min;
            a[num] = value;
        }
    }

    /**
     * 生成20个随机数组
     *
     * @return 随机数组
     * @author gaoge
     * @since 2022/11/29 17:07
     */
    public static int[] randomArray() throws NoSuchAlgorithmException {
        int[] arr = new int[10];
        Random rand = SecureRandom.getInstanceStrong();
        for (int i = 0; i < arr.length; i++) {
            //生成随机数100以内;
            arr[i] = rand.nextInt(100);
        }
        return arr;
    }

    /**
     * 测试排序算法
     *
     * @param args
     * @throws NoSuchAlgorithmException
     */
    public static void main(String[] args) throws NoSuchAlgorithmException {
        int[] ints;
        String start = "原始数组:";
        String end = "排序数组:";


        //冒泡排序
        System.out.println("冒泡排序");
        ints = randomArray();
        System.out.println(start + Arrays.toString(ints));
        bubbleSort(ints, ints.length);
        System.out.println(end + Arrays.toString(ints));

        //插入排序
        System.out.println("插入排序");
        ints = randomArray();
        System.out.println(start + Arrays.toString(ints));
        insertionSort(ints, ints.length);
        System.out.println(end + Arrays.toString(ints));

        //选择排序
        System.out.println("选择排序");
        ints = randomArray();
        System.out.println(start + Arrays.toString(ints));
        selectionSort(ints, ints.length);
        System.out.println(end + Arrays.toString(ints));
    }

}

时间复杂度O(nlogn)算法:归并排序、快速排序

归并排序代码

import java.util.Arrays;

/**
 * 归并排序
 * <p>
 * 归并排序的核心思想:如果要排序一个数组,我们先把数组从中间分成前后两部分,
 * 然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了
 * 我们申请一个临时数组 tmp,大小与 A[p...r]相同。我们用两个游标 i 和 j,
 * 分别指向 A[p...q]和 A[q+1...r]的第一个元素。
 * 比较这两个元素 A[i]和 A[j],如果 A[i]<=A[j],我们就把 A[i]放入到临时数组 tmp,
 * 并且 i 后移一位,否则将 A[j]放入到数组 tmp,j 后移一位。
 * 继续上述比较过程,直到其中一个子数组中的所有数据都放入临时数组中,
 * 再把另一个数组中的数据依次加入到临时数组的末尾,这个时候,
 * 临时数组中存储的就是两个子数组合并之后的结果了。
 * 最后再把临时数组 tmp 中的数据拷贝到原数组 A[p...r]中。
 * <p>
 * 在合并的过程中,如果 A[p...q]和 A[q+1...r]之间有值相同的元素,
 * 先把 A[p...q]中的元素放入 tmp 数组。这样就保证了值相同的元素,
 * 在合并前后的先后顺序不变。所以,归并排序是一个稳定的排序算法。
 * <p>
 * 不管是最好情况、最坏情况,还是平均情况,时间复杂度都是 O(nlogn)
 * <p>
 * 临时内存空间最大也不会超过 n 个数据的大小,所以空间复杂度是 O(n)。
 * <p>
 * 归并排序不是原地排序算法。
 * 这是因为归并排序的合并函数,在合并两个有序数组为一个有序数组时,需要借助额外的存储空间。
 *
 * @author gaoge
 * @since 2022/11/29 17:07
 */
public class MergeSort {

    /**
     * 归并排序算法
     *
     * @param a 是数组
     * @param n 表示数组大小
     */
    public static void mergeSort(int[] a, int n) {
        mergeSortInternally(a, 0, n - 1);
    }

    /**
     * 递归调用函数
     *
     * @param a 数组
     * @param p 开始索引
     * @param r 结束索引
     */
    private static void mergeSortInternally(int[] a, int p, int r) {
        // 递归终止条件
        if (p >= r) {
            return;
        }
        // 取p到r之间的中间位置q,防止(p+r)的和超过int类型最大值
        int q = p + (r - p) / 2;
        // 分治递归,光看第一层
        mergeSortInternally(a, p, q);
        mergeSortInternally(a, q + 1, r);

        // 将A[p...q]和A[q+1...r]合并为A[p...r]
        merge(a, p, q, r);
    }

    private static void merge(int[] a, int p, int q, int r) {
        int i = p;
        int j = q + 1;
        int k = 0;
        // 申请一个大小跟a[p...r]一样的临时数组
        int[] tmp = new int[r - p + 1];
        while (i <= q && j <= r) {
            if (a[i] <= a[j]) {
                tmp[k++] = a[i++];
            } else {
                tmp[k++] = a[j++];
            }
        }

        // 判断哪个子数组中有剩余的数据
        int start = i;
        int end = q;
        if (j <= r) {
            start = j;
            end = r;
        }

        // 将剩余的数据拷贝到临时数组tmp
        while (start <= end) {
            tmp[k++] = a[start++];
        }

        // 将tmp中的数组拷贝回a[p...r]
        for (i = 0; i <= r - p; ++i) {
            a[p + i] = tmp[i];
        }
    }

    /**
     * 合并(哨兵)
     *
     * @param arr 数组
     * @param p   开始索引
     * @param q   中间索引
     * @param r   结束所有
     */
    private static void mergeBySentry(int[] arr, int p, int q, int r) {
        int[] leftArr = new int[q - p + 2];
        int[] rightArr = new int[r - q + 1];

        for (int i = 0; i <= q - p; i++) {
            leftArr[i] = arr[p + i];
        }
        // 第一个数组添加哨兵(最大值)
        leftArr[q - p + 1] = Integer.MAX_VALUE;

        for (int i = 0; i < r - q; i++) {
            rightArr[i] = arr[q + 1 + i];
        }
        // 第二个数组添加哨兵(最大值)
        rightArr[r - q] = Integer.MAX_VALUE;

        int i = 0;
        int j = 0;
        int k = p;
        while (k <= r) {
            // 当左边数组到达哨兵值时,i不再增加,直到右边数组读取完剩余值,同理右边数组也一样
            if (leftArr[i] <= rightArr[j]) {
                arr[k++] = leftArr[i++];
            } else {
                arr[k++] = rightArr[j++];
            }
        }
    }

    public static void main(String[] args) {
        int[] ints = new int[]{9, 8, 4, 6, 7, 7, 5, 4, 0, 3, 34, 12};
        System.out.println(Arrays.toString(ints));
        mergeSort(ints, ints.length);
        System.out.println(Arrays.toString(ints));
    }
}

快速排序代码

import java.util.Arrays;

/**
 * 快速排序
 * <p>
 * 快排的思想是这样的:如果要排序数组中下标从 p 到 r 之间的一组数据,
 * 我们选择 p 到 r 之间的任意一个数据作为 pivot(分区点)。
 * 我们遍历 p 到 r 之间的数据,将小于 pivot 的放到左边,
 * 将大于 pivot 的放到右边,将 pivot 放到中间。经过这一步骤之后,
 * 数组 p 到 r 之间的数据就被分成了三个部分,
 * 前面 p 到 q-1 之间都是小于 pivot 的,中间是 pivot,
 * 后面的 q+1 到 r 之间是大于 pivot 的
 * 根据分治、递归的处理思想,
 * 我们可以用递归排序下标从 p 到 q-1 之间的数据和下标从 q+1 到 r 之间的数据,
 * 直到区间缩小为 1,就说明所有的数据都有序了。
 * <p>
 * 如果每次分区操作,都能正好把数组分成大小接近相等的两个小区间,
 * 那快排的时间复杂度递推求解公式跟归并是相同的。所以,快排的时间复杂度也是 O(nlogn)。
 * 但是,公式成立的前提是每次分区操作,我们选择的 pivot 都很合适,
 * 正好能将大区间对等地一分为二。但实际上这种情况是很难实现的。
 * 我举一个比较极端的例子。如果数组中的数据原来已经是有序的了,比如 1,3,5,6,8。如果我们每次选择最后一个元素作为 pivot,
 * 那每次分区得到的两个区间都是不均等的。我们需要进行大约 n 次分区操作,
 * 才能完成快排的整个过程。每次分区我们平均要扫描大约 n/2 个元素,
 * 这种情况下,快排的时间复杂度就从 O(nlogn) 退化成了 O(n2)。
 * T(n) 在大部分情况下的时间复杂度都可以做到 O(nlogn),只有在极端情况下,才会退化到 O(n2)。
 * 而且,我们也有很多方法将这个概率降到很低,
 * <p>
 * 快速排序通过设计巧妙的原地分区函数,可以实现原地排序,
 * 解决了归并排序占用太多内存的问题。
 * <p>
 * 因为分区的过程涉及交换操作,如果数组中有两个相同的元素,比如序列 6,8,7,6,3,5,9,4,
 * 在经过第一次分区操作之后,两个 6 的相对先后顺序就会改变。所以,快速排序并不是一个稳定的排序算法。
 *
 * @author gaoge
 * @since 2022/11/29 17:07
 */
public class QuickSort {


    /**
     * 快速排序算法
     *
     * @param a 是数组
     * @param n 表示数组大小
     */
    public static void quickSort(int[] a, int n) {
        quickSortInternally(a, 0, n - 1);
    }

    /**
     * 递归调用函数
     *
     * @param a 数组
     * @param p 开始索引
     * @param r 结束索引
     */
    private static void quickSortInternally(int[] a, int p, int r) {
        if (p >= r) {
            return;
        }
        // 获取分区点
        int q = partition(a, p, r);
        quickSortInternally(a, p, q - 1);
        quickSortInternally(a, q + 1, r);
    }

    private static int partition(int[] a, int p, int r) {
        int pivot = a[r];
        int i = p;
        for (int j = p; j < r; ++j) {
            if (a[j] < pivot) {
                if (i == j) {
                    ++i;
                } else {
                    int tmp = a[i];
                    a[i++] = a[j];
                    a[j] = tmp;
                }
            }
        }

        int tmp = a[i];
        a[i] = a[r];
        a[r] = tmp;
        return i;
    }

    public static void main(String[] args) {
        int[] ints = new int[]{9, 8, 4, 6, 7, 7, 5, 4, 0, 3, 34, 12};
        System.out.println(Arrays.toString(ints));
        quickSort(ints, ints.length);
        System.out.println(Arrays.toString(ints));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卑微的Coder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值