算法(三)排序

排序算法原理与优缺点对比

排序就是让一组无序数据变为有序
衡量排序算法的优劣从下面三个角度解析

1、时间复杂度
最好时间复杂度、最坏时间复杂度、平均时间复杂度

2、空间复杂度
若空间复杂度是1,也叫做原地排序

3、稳定性
相等的数据对象,在排序之后,顺序是否保证不变


排序算法一、冒泡排序

原理

从第一个数据开始,一次比较相邻元素的大小。
若前者大于后者,则进行交换操作,将大的元素往后交换。
经过多次迭代,直到没有交换操作

像是在一个水池中处理数据一样,每次把最大的数据传递到最后

性能

最好时间复杂度:O(n)
当输入数组刚好是顺序的时候,只需挨个比较一遍即可,无需交换操作,所以时间复杂度为O(n)

最坏时间复杂度O(n^2)
当数组完全逆序,每轮排序都需要挨个比较n次,并且重复n次,所以时间复杂度为O(n^2)

平均时间复杂度O(n^2)
数组乱序的时候的时间复杂度

空间复杂度O(1)
冒泡排序无需额外空间,冒泡排序过程中,当元素相同时不做交换,所以冒泡排序是最稳定的排序算法

代码

/**
 * @author hym
 * @date 2021/11/26
 * @description 冒泡排序
 */
public class Bubble {
    public static void main(String[] args) {
        int[] arr = new int[]{7, 8, 2, 6, 5, 1, 9, 0, 3, 4};
        System.out.println("未排序前" + Arrays.toString(arr));
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    int swap = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = swap;
                }
            }
        }
        System.out.println("排序后:" + Arrays.toString(arr));
    }
}

在这里插入图片描述

插入排序

原理

选取未排序元素,插入到已排序区间的合适位置,直到未排序区间为空

从左到右维护一个有序的序列,直到所有待排序数据均已完成插入操作

性能

最好时间复杂度O(n)
数组完全有序,比较一次即可找到正确位置
此过程重复n次,即可完成排序

最坏时间复杂度O(n^2)
数组完全逆序,每次都需要比较n次才能找到位置,此过程重复n次,清空未排序空间

平均时间复杂度O(n^2)
往数组中插入一个元素的平均时间复杂度为O(n)
插入排序可以理解为重复n次的数组插入操作,所以平均时间复杂度为O(n^2)

空间复杂度O(1)
无需开辟额外空间,是最稳定的排序算法

代码

/**
 * @author hym
 * @date 2021/11/26
 * @description 插入排序
 */
public class Insert {
    public static void main(String[] args) {
        int[] arr = new int[]{7, 8, 2, 6, 5, 1, 9, 0, 3, 4};
        System.out.println("原始数据 " + Arrays.toString(arr));
        for (int i = 1; i < arr.length; i++) {
            int swap = arr[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (arr[j] > swap) {
                    arr[j + 1] = arr[j];
                } else {
                    break;
                }
            }
            arr[j + 1] = swap;
        }
        System.out.println("排序后 " + Arrays.toString(arr));
    }
}

在这里插入图片描述

插入排序跟冒泡排序算法的异同

相同
平均时间复杂度都是O(n^2),且都是稳定的排序算法,都属于原地排序

差异
冒泡排序每轮的交换操作是动态的,需要三个赋值操作
插入排序每轮的交换动作会固定待插入的数据,只需要一步赋值操作

归并排序

原理

用到了分治思想,将数组不断地二分,直到最后每个部分只包含一个数据。然后对每部分分别进行排序,最后将排序好的相邻两部分合并,这个数组就有序了

代码

/**
 * @author hym
 * @date 2021/11/26
 * @description 归并排序
 */
public class Merge {
    public static void main(String[] args) {
        int[] arr = new int[]{7, 8, 2, 6, 5, 1, 9, 0, 3, 4};
        int[] temp = new int[arr.length];
        System.out.println("原始数据 " + Arrays.toString(arr));
        mergeSort(arr, temp, 0, arr.length - 1);
        System.out.println("排序后 " + Arrays.toString(arr));
    }

    public static void mergeSort(int[] arr, int[] temp, int start, int end) {
        if (start < end) {
            int mid = (start + end) / 2;
            // 对左侧元素进行递归排序
            mergeSort(arr, temp, start, mid);
            // 对右侧元素进行递归排序
            mergeSort(arr, temp, mid + 1, end);
            // 合并
            combineMerge(arr, temp, start, mid, end);
        }
    }

    public static void combineMerge(int[] arr, int[] temp, int left, int mid, int right) {
        int p1 = left, p2 = mid + 1, k = left;
        while (p1 <= mid && p2 <= right) {
            if (arr[p1] <= arr[p2]) {
                temp[k++] = arr[p1++];
            } else {
                temp[k++] = arr[p2++];
            }
        }
        while (p1 <= mid) {
            temp[k++] = arr[p1++];
        }
        while (p2 <= right) {
            temp[k++] = arr[p2++];
        }
        // 复制回原数组
        for (int i = left; i <= right; i++) {
            arr[i] = temp[i];
        }
    }
}

在这里插入图片描述

性能

时间复杂度

采用二分迭代,复杂度O(logn)

每次的迭代,需要对两个有序数组进行合并,时间复杂度为O(n)

归并排序的复杂度使二者的乘积O(nlogn)

它的执行频次与输入序列无关,归并排序最好、最坏、平均时间复杂度均为O(nlogn)

空间复杂度

由于每次合并的操作都需要开辟基于数组的临时内存空间,空间复杂度是O(n),归并排序合并时,相同元素的顺序不会改变,是稳定的排序算法

快速排序

原理

分治法,每轮迭代选取数组中任一元素作为分区点,将与之相比较小的元素放在左侧,较大的元素放在右侧。

再利用分治思想,继续分别对左右两侧进行同样操作,直到区间缩小为1,即完成排序

代码

/**
 * @author hym
 * @date 2021/11/26
 * @description
 */
public class Quick {
    public static void main(String[] args) {
        int[] arr = new int[]{6, 1, 2, 4, 9, 7, 5, 6, 3, 7};
        System.out.println("原始数据 " + Arrays.toString(arr));
        quickSort(arr, 0, arr.length - 1);
        System.out.println("排序后 " + Arrays.toString(arr));
    }

    public static void quickSort(int[] arr, int low, int high) {
        int i, j, temp, t;
        if (low >= high) {
            return;
        }
        i = low;
        j = high;
        temp = arr[low];
        while (i < j) {
            // 先看右边,依次往左递减
            while (temp <= arr[j] && i < j) {
                j--;
            }
            // 再看左边,依次往右递增
            while (temp >= arr[i] && i < j) {
                i++;
            }
            t = arr[j];
            arr[j] = arr[i];
            arr[i] = t;
        }
        arr[low] = arr[i];
        arr[i] = temp;
        // 递归调用左半数组
        quickSort(arr, low, j - 1);
        // 递归调用右半数组
        quickSort(arr, j + 1, high);
    }
}

在这里插入图片描述

性能

时间复杂度

最好时间复杂度 O(nlogn)
每次选取分区点,都能选到中位数,将数组一分为二。则时间复杂度跟归并一样

最坏时间复杂度 O(n^2)
每次分区都选中了最小值或最大值,得到不均等的两组,需要n次分区操作,每次分区平均扫描 n/2个元素。此时时间复杂度退化为 O(n^2)

平均时间复杂度 O(nlogn)
极端情况很少见,平均时间复杂度为O(nlogn)

空间时间复杂度 O(1)
使用交换法
很显然,快速排序的分区过程涉及交换操作,是不稳定的排序算法

排序算法性能分析

排序性能下限:冒泡和插入排序 时间复杂度O(n^2)

尝试分治法,利用归并排序可将时间复杂度降低到O(nlogn)。但是归并排序需要额外开辟临时空间(为了保证稳定性。另外,在归并时,在数组中插入元素导致数据挪移)

为了规避时间损耗,采用快速排序。通过交换操作,解决插入元素导致的数据挪移,并且降低了不必要的空间开销。但由于其动态二分的交换数据,导致排序结果不稳定

如何选择

1、数据规模比较小,可选择O(n^2)的算法,容易想出来,且没有性能上的实际差距

2、数据规模比较大,需要选择O(nlogn)的算法

归并排序的空间复杂度为 O(n),意味着需要2倍于排序的数据的空间,对空间的消耗较大

快速排序的平均时间复杂度 O(nlogn),不具备稳定性,需要考虑是否有稳定性要求

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值