常见的排序算法

排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作;

排序的方式可分为内部排序和外部排序:

  • 内部排序:数据元素全部放在内存中的排序;
  • 外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序;

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持 不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的,否则称为不稳定的;例如:

以下排序都是按照由小到大的顺序进行排序 

一、直接插入排序

1. 基本思想

把待排序的记录按其值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列

2. 示例描述

  1. 第一个待插入元素 8,此时已排序的序列为空,直接将 8 插入,此时该序列变为:8,2,5,7,6,9,1,4
  2. 此时待插入元素 2,此时已排序的有序序列为 [8],从待插入元素位置之前的一个位置开始,向前遍历,找到 待插入元素应该在的位置,并将其插入,即 将 2 插入到 8 前面,此时有序序列为 [2,8],此时该序列变为:2,8,5,7,6,9,1,4
  3. 此时待插入元素为 5,有序序列为 [2,8],同样重复上述逻辑,找到 5 应该存在的位置并将其插入,此时该序列变为:2,5,8,7,6,9,1,4
  4. 对每个待排序序列执行此步骤,直到排序完毕,最终该序列有序:1,2,4,5,6,7,8,9

3. 代码实现

    public static void insertSort(int[] arr){
        for (int i = 1; i < arr.length; i++) {
            int tmp = arr[i], j;
            for (j = i-1; j >= 0; j--) {
                if (arr[j] > tmp){ // 此时若为 arr[j] >= tmp 则是不稳定的排序
                    arr[j+1] = arr[j];
                } else {
                    break;
                }
            }
            arr[j+1] = tmp;
        }
    }

4. 特性总结

插入排序当元素越接近有序的情况下,该算法的效率越高;

时间复杂度:最坏: O(N^2),最好:O(N),一般情况下:O(N^2),空间复杂度为 O(1),是稳定的排序;

二、希尔排序

1. 基本思想

希尔排序法又称缩小增量法的插入排序;希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成多个组,所有距离为该整数的记录分在同一组内,并对每一组内的记录进行排序,然后,复上述分组和排序的工作;当分组数 = 1 时,所有记录在统一组内排好序;

2. 示例描述

3. 代码实现

    public static void shellSort(int[] arr){
        int gap = arr.length;
        while(gap >= 1){
            for (int i = gap; i < arr.length; i++) {
                int tmp = arr[i], j;
                for (j = i - gap; j >= 0; j -= gap) {
                    if(arr[j] > tmp){
                        arr[j + gap] = arr[j];
                    } else {
                        break;
                    }
                }
                arr[j + gap] = tmp;
            }
            gap /= 2;
        }
    }

4. 特性总结

希尔排序是对直接插入排序的优化,充分利用了直接插入排序越接近有序效率越高的特性,当 gap > 1时都是预排序,目的是让数组更接近于有序,当gap == 1时,数组已经接近有序的了,这样效率就很高,整体而言,就达到优化的效果;

时间复杂度与 gap 的取值有关,在 O(n^1.25 ~ 1.6 * n^1.25) 之间,空间复杂度为 O(1),是不稳定的排序;

三、选择排序

1. 基本思想

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排序完;

2. 示例描述

重复上述过程直到待排序序列不存在,即排序完毕;

3. 代码实现

    public static void selectSort(int[] arr){
        for (int left = 0; left < arr.length; left++) {
            int right = left; //假设待排序序列的第一个元素是最小的,则 right 应指向第一个元素的下标
            for (int i = left + 1; i < arr.length; i++) {
                // 找到当前待排序序列中的最小元素
                if(arr[i] < arr[right]){
                    right = i;
                }
            }
            // 交换
            int tmp = arr[left];
            arr[left] = arr[right];
            arr[right] = tmp;
        }
    }

4. 特性总结

选择排序每一轮都会有一个最小的元素处在最终的位置上,该排序算法易于理解,但是效率很低,实际中很少使用;

时间复杂度(最坏最坏一般) O(N^2),空间复杂度 O(1),是不稳定的排序;

四、堆排序 *

1. 基本思想

堆是一种数据结构,也称为优先级队列,基于完全二叉树实现,堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,注意的是排升序要建大堆,排降序建小堆;

2. 示例描述

3. 代码实现

// 创建大根堆
    public static void createMaxHeap(int[] arr) {
        int parent = arr.length / 2 - 1;
        for (; parent >= 0 ; parent--) {
            shiftDown(arr, parent, arr.length);
        }
    }

// 向下调整
    public static void shiftDown(int[] arr, int parent, int end) {
        int maxChild = 2 * parent + 1;
        while(maxChild < arr.length) {
            if (maxChild + 1 < arr.length && arr[maxChild] < arr[maxChild + 1]) {
                maxChild++; // 1. 比较左右子节点,选择较大的一个
            }
            if (arr[parent] < arr[maxChild]) { // 2. 比较父节点和较大的子节点
                int tmp = arr[parent];
                arr[parent] = arr[maxChild];
                arr[maxChild] = tmp;

                // 更新 tmpParent 和 leftChild,这里需要继续对新子树进行堆化
                parent = maxChild;
                maxChild = 2 * parent + 1;
            } else {
                break; // 如果父节点大于等于子节点,不需要再继续堆化,跳出循环
            }
        }
    }

// 堆排序
    public static void heapSort(int[] arr) {
        // 先创建大根堆
        createMaxHeap(arr);
        int cur = arr.length - 1;
        while (cur > 0) {
            int tmp = arr[0];
            arr[0] = arr[cur];
            arr[cur] = tmp;
            shiftDown(arr, 0, cur--);
        }
    }

 4. 特性总结

堆排序使用堆来选数,效率就高了很多,每一轮之后,都会有一个最大的元素处在其最终的位置上;

时间复杂度(最坏最坏一般) O(N^logN),空间复杂度 O(1),是不稳定的排序;

五、冒泡排序

1. 基本思想

冒泡排序属于交换排序,根据序列中的两个键值来比较交换这两个记录的位置,会将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前面移动;

2. 示例描述

3. 代码实现

    public static void bubbleSort(int[] arr) {
        for (int i = 0; i < arr.length - 1; i++) {
            for (int j = 0; j < arr.length - i - 1; j++) {
                if (arr[j] > arr[j+1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                }
            }
        }
    }

4. 特性总结

冒泡排序每一轮都会将当前待排序序列的最大值交换到其最终的位置上,该算法简单易懂,容易理解;

时间复杂度:最好:O(N),最坏:O(N^2),一般:O(N^2),空间复杂度 O(1),是稳定的排序;

六、快速排序 *

1. 基本思想

快速排序也属于交换排序,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止;

2. 示例描述

将区间按照基准值划分为左右两半部分的常见方式有:hoare 法,挖坑法,随机数法等,此时以随机数法为例

在排序区间内容随机选取一个元素作为基准

int tmp = nums[new Random().nextInt(r - l + 1) + l];

3. 代码实现

    public static void quickSort(int[] arr) {
        quickSort1(arr, 0, arr.length - 1);
    }

    // 随机数法 + 前后指针
    public static void quickSort1(int[] arr, int start, int end) {
        if (start >= end) {
            return;
        }
        int random = arr[new Random().nextInt(end - start + 1) + start];
        int left = start - 1, right = end + 1, i = start;
        while (i < right) {
            if (arr[i] < random) {
                swap(arr, ++left, i++);
            } else if (arr[i] == random) {
                i++;
            } else {
                swap(arr, --right, i);
            }
        }
        quickSort1(arr, start, left);
        quickSort1(arr, right, end);
    }


    // hoare法
    private static void quickSort2(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }
        int i = left;
        int j = right;
        int pivot = array[left];
        while (i < j) {
            while (i < j && array[j] >= pivot) {
                j--;
            }
            while (i < j && array[i] <= pivot) {
                i++;
            }
            swap(array, i, j);
        }
        swap(array, i, left);
        quickSort2(array, left, i - 1);
        quickSort2(array, i + 1, right);
    }

    // 挖坑法
    private static void quickSort3(int[] array, int left, int right) {
        if(left >= right){
            return;
        }
        int i = left;
        int j = right;
        int pivot = array[left];
        while (i < j) {
            while (i < j && array[j] >= pivot) {
                j--;
            }
            array[i] = array[j];
            while (i < j && array[i] <= pivot) {
                i++;
            }
            array[j] = array[i];
        }
        array[i] = pivot;
        quickSort3(array, left, i-1);
        quickSort3(array, i+1, right);
    }

    public static void swap(int[] arr, int tmp1, int tmp2) {
        int tmp = arr[tmp1];
        arr[tmp1] = arr[tmp2];
        arr[tmp2] = tmp;
    }

4. 特性总结

快速排序每一趟排序过后,选择的基准位置一定处于最终的位置,他的整体的综合性能和使用场景都是比较好的;

时间复杂度:最好:O(N*logN),最坏:O(N^2),一般:O(N*logN),空间复杂度 O(logN),是不稳定的排序;

七、归并排序 *

1. 基本思想

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用;将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序;

2. 示例描述

归并排序的核心步骤为分解和合并

3. 代码实现

    public static void mergeSort(int[] arr, int start, int end) {
        if (start >= end) {
            return;
        }
        int mid = start + (end - start) / 2;
        mergeSort(arr, start, mid);
        mergeSort(arr, mid + 1, end);
        int[] tmp = new int[end - start + 1];
        int cur1 = start, cur2 = mid + 1, i = 0;
        while (cur1 <= mid && cur2 <= end){
            if (arr[cur1] <= arr[cur2]) {  // 此处若是 arr[cur1] < arr[cur2] 则是不稳定的
                tmp[i++] = arr[cur1++];
            } else {
                tmp[i++] = arr[cur2++];
            }
        }
        while (cur1 <= mid) {
            tmp[i++] = arr[cur1++];
        }
        while (cur2 <= end) {
            tmp[i++] = arr[cur2++];
        }
        for (int j = start; j <= end; j++) {
            arr[j] = tmp[j - start];
        }
    }

4. 特性总结

归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题;

时间复杂度:最好,最坏,一般:O(N*logN),空间复杂度 O(N),是稳定的排序;

八、海量数据排序问题

假设内存只有 1G,需要排序的数据有 100G,因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M;
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以;
  3. 进行 2 路归并,同时对 200 份有序文件做归并过程,最终结果就有序了;

九、排序算法复杂度及稳定性总结

 

  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Rcnhtin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值