【数据结构】基于比较的排序算法 - 交换排序

一,前言

排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减地排列起来的操作。而对于一个排序算法 而言,我们通常会从以下三个方面分析其性能:

  • 稳定性:假设待排序序列中存在多个重复值,若是经过排序后,重复值之间的相对位置不变,则称这个排序算法是稳定的,否则就是不稳定。
  • 时间复杂度:代码执行次数随n的增长趋势,通常使用大O的渐进法表示
  • 空间复杂度:额外存储随n的增长趋势,其中递归算法空间=递归栈的最大深度,通常使用大O渐进表示法

常见的基于比较的排序算法 有七种,对于这七种排序算法,又因其实现方式不同划分为以下四类 :

  • 插入排序:直接插入排序、希尔排序
  • 选择排序:选择排序、堆排序
  • 交换排序:冒泡排序、快速排序
  • 归并排序:归并排序

本节内容主要讲解交换排序,并且都是以排升序为例进行讲解。

二,冒泡排序

1,算法思想

反复比较相邻两个元素,如果顺序不对则交换,让较大的元素像泡泡一样冒到待排序序列末尾。每一趟结束后,当前未排序区间里的最大值会被放到最终位置,下一趟的待排序范围就缩小一个元素。

2,算法特性

适合教学与小规模的数据排序,实际工程中一般不会使用。

时间复杂度:最坏/平均情况下为O(N^2),最好情况下为O(N)

空间复杂度:O(1)

稳定性:稳定

3,排序过程演示

4,算法实现

java 复制代码
    /**
     * 冒泡排序
     * @param array 待排序序列
     */
    public static void bubbleSort(int[] array) {
        if(array == null) return;
        int n = array.length;
        // 外层循环表示排序趟数,n个元素最多排序n-1趟
        for(int i = 0; i < n - 1; i ++) {
            boolean sorted = true;
            // 内部循环表示待排序范围
            for(int j = 0; j < n - 1 - i; j ++) {
                if(array[j] > array[j + 1]) {
                    swap(array, j, j + 1);
                    sorted = false;
                }
            }
            // 小优化:若是本轮排序没有交换则已经有序,无需再排序
            if(sorted) break;
        }
    }

    /**
     * 交换数组中两个下标的元素
     * @param array 待操作序列
     * @param i 下标1
     * @param j 下标2
     */
    private static void swap(int[] array, int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

三,快速排序

1,算法思想

快速排序采用分治思想。任取待排序序列中的一个元素作为基准值,按照关键字将当前区间划分为两个子序列:左子序列中的元素均不大于基准值,右子序列中的元素均不小于基准值,并将基准值放到其最终有序位置。然后对左右两个子序列分别重复上述划分过程,直到子序列长度为 0 或 1 时自然有序。

其分治过程可概括为:

  1. 选择基准值:从当前区间中选取一个元素作为基准值;

  2. 划分区间:将当前区间划分为左、右两个子序列,使左子序列元素不大于基准值,右子序列元素不小于基准值,基准值到达最终位置;

  3. 递归处理:对基准值左右两个子序列分别递归执行快速排序,直到子序列长度不超过 1。

2,算法特性

平均性能好、常数小、缓存友好、实践中通常很快。

时间复杂度:最好/平均情况下为O(N * logN),最坏情况下为O(N^2)

空间复杂度:平均情况下为O(logN),最坏情况下为O(N)

稳定性:不稳定

3,排序过程演示

快速排序有多种划分方式和优化策略。常见划分方式有二路划分和三路划分:二路划分将区间分为"小于基准"和"不小于基准"两部分;三路划分进一步将"等于基准"的元素集中处理,适合大量重复元素的场景。

常见优化包括:

  • 基准值优化:如随机基准、三数取中,主要目的为降低最坏情况概率
  • 小区间优化:子区间长度较小时改用直接插入排序,减少递归调用
  • 递归优化:尾递归优化或较小区间递归、较大区间循环处理,控制递归栈深度

为了便于表达最基本的排序逻辑,以下实现采用基础二路划分,固定选择端点作为基准,未做上述优化,递归划分过程如下图所示:

注:

上图仅展示递推与划分的过程,而具体每一次递归时划分数组的逻辑略,下文有详解。

划分区间也有多种方式,如Hoare法、挖坑法、前后指针法等等,在基础教学示例中可任选一种,但不同划分对常数和重复元素性能有影响。此处我使用的是前后指针法,划分区间过程如下图所示:

4,算法实现

java 复制代码
    /**
     * 快速排序-递归实现
     * @param array 待排序数组
     * @param left 左边界
     * @param right 右边界
     */
    public static void quickSort(int[] array, int left, int right) {
        // 递归出口
        if(left >= right) {
            return;
        }
        // 划分数组
        int pivot = partition(array, left, right);
        // 递归左右区间
        quickSort(array, left, pivot - 1);
        quickSort(array, pivot + 1, right);
    }

    /**
     * 按照基准值划分数组
     *  -使用当前区间最左侧元素作为基准值
     *  -本实现未优化,实际使用建议三数取中/随机基准/小区间插入排序
     * @param array 待操作数组
     * @param left 左边界
     * @param right 右边界
     * @return int
     */
    private static int partition(int[] array, int left, int right) {
        // 记录基准值
        int pivotValue = array[left];
        int pivotIndex = left;
        // 划分数组
        while(left < right) {
            // 基准值在最左侧,必须先移动右指针
            while(left < right && array[right] >= pivotValue) {
                right --;
            }
            while(left < right && array[left] <= pivotValue) {
                left ++;
            }
            swap(array, left, right);
        }
        // 相遇点为基准值有序位置
        swap(array, pivotIndex, left);
        return left;
    }

    /**
     * 交换数组中两下标元素
     * @param array 待操作数组
     * @param i 下标1
     * @param j 下标2
     */
    private static void swap(int[] array ,int i, int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
相关推荐
H.莓飛1 小时前
【C++】【STL】vector_OJ题
linux·服务器·开发语言·数据结构·c++·visual studio
Rabitebla1 小时前
【Linux操作系统】进程篇(十一):进程程序替换——从底层原理到 exec 函数族
linux·服务器·c++·笔记·学习·算法
tellmewhoisi1 小时前
深度学习0:数学知识:正态分布1
人工智能·深度学习·算法
-梅1 小时前
算法(1) 动态规划
算法·动态规划
青少儿编程课堂2 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:Bellman-Ford与SPFA含负权最短路及负环检测全解析
c++·python·算法·bfs·信息学竞赛
octopus_c3 小时前
STL 之 string:auto 与范围 for、常用接口、模拟实现与深浅拷贝
开发语言·c++·算法
老师我太想进步了202618 小时前
【数据结构-4】算法美学之顺序表
数据结构·算法
风影同志7832418 小时前
编码器是激活函数吗?—— 一个看起来很像、其实不是的类比
算法
UIU11418 小时前
P1481 魔族密码
c++·算法·动态规划