排序

常见的七大排序
在这里插入图片描述
一.插入排序:
基于线性表插入元素的方式而演化出来的
1.实现方法:
(1) 将整个区间分为[0, bound]已排序区间和[bound, size]待排序区间
(2) 把bound位置的元素往前面已排序区间的合适位置进行插入,保证插入完毕后整体数组仍然有序

public static void insertSort(int[] array) {
    for (int bound = 1; bound < array.length; bound++) {
        //处理bound位置的元素如何插入
        int tmp = array[bound];
        int cur = bound - 1;
        for (; cur >= 0; cur--) {
            if (array[cur] > tmp) {
                //tmp元素还需要往前查找,同时就需要把cur位置的元素往后搬运
                array[cur + 1] = array[cur];
            } else {
                break;
            }
        }
        array[cur + 1] = tmp;
    }
}

2.性能分析:
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定排序
3.重要特性:
(1) 如果一个数组本身已经比较小了,此时使用插入排序效率就特别高
(2) 如果一个数组本身已经接近有序,此时使用插入排序效率也很高

二.希尔排序
1.实现方法:
先把整个数组分成若干组,分成的组数称为gap。针对每组数据分别进行插排(此时数组没有彻底完成最终排序,但有序性整体来说提高了不少),再针对gap = gap - 1重复进行分组插排,最后再针对gap = 1进行插排。

public static void shellSort(int[] array) {
    int gap = array.length / 2;
    while (gap > 1) {
        insertSortGap(array, gap);
        gap = gap / 2;
    }
    insertSortGap(array, 1);
}

private static void insertSortGap(int[] array, int gap) {
    for (int bound = gap; bound < array.length; bound++) {
        int tmp = array[bound];
        int cur = bound - gap;
        for (; cur >= 0; cur -= gap) {
            if (array[cur] > tmp) {
                array[cur + gap] = array[cur];
            } else {
                break;
            }
        }
        array[cur + gap] = tmp;
    }
}

2.性能分析:
时间复杂度:O(N^2) 理论上最快能达到O(N^1.3)
空间复杂度:O(1)
稳定性:不稳定

三.选择排序
1.实现方法:
(1) 创建一个bound变量,作为边界,划分为两个区间 [0, bound]已排序区间和[bound, size]待排序区间
(2) 每次从当前待排序区间取出一个最小值(打擂台的方式取出),放到bound位置上,同时bound++

public static void selectSort(int[] array) {
    for (int bound = 0; bound < array.length; bound++) {
        for (int cur = bound; cur < array.length; cur++) {
            if (array[cur] < array[bound]) {
                // 以 bound 位置的元素作为擂台.
                // 拿当前元素和擂台上的元素进行 pk.
                // pk 赢了就进行交换. 当前是升序排序, 谁小, 谁就赢了
                swap(array, cur, bound);
            }
        }
    }
}

2.性能分析:
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:不稳定

四.堆排序
1.实现方法(以升序为例) :
(1) 建立一个大堆(根节点一定是最大值):从最后一个非叶子节点出发从后往前遍历,针对当前位置进行向下调整
(2) 拿堆顶元素和最后一个元素进行交换,此时再从根节点开始进行向下调整

public static void heapSort(int[] array) {
    //1.先建立堆
    creatHeap(array);
    int heapSize = array.length;
    for (int i = 0; i < array.length - 1; i++) {
        // 2. 交换堆顶元素和堆中的最后一个元素
        swap(array, 0, heapSize - 1);
        // 3. 把最后一个元素从堆中删除掉
        heapSize--;
        // 4. 针对当前的堆从 根节点 开始进行向下调整
        shiftDown(array, heapSize, 0);
    }
}
private static void shiftDown(int[] array, int size, int index) {
    int parent = index;
    int child = 2 * parent + 1;
    while (child < size) {
        if (child + 1 < size && array[child + 1] > array[child]) {
            child = child + 1;
        }
        if (array[child] > array[parent]) {
            swap(array, child, parent);
        } else {
            break;
        }
        parent = child;
        child = 2 * parent + 1;
    }
}
private static void creatHeap(int[] array) {
   for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
       shiftDown(array, array.length, i);
   }
}

2.性能分析:
时间复杂度:O(NlogN)
空间复杂度:O(1)
稳定性:不稳定

五.冒泡排序
1.实现方法(以升序为例) :
循环比较相邻的值,最终就能找出在当前待排序区间中的最大值或最小值。
从前往后遍历,每次找最大,放到最后。
从后往前遍历,每次找最小,放到最前面。

public static void bubbleSort(int[] array) {
    // 从后往前遍历, 每次找最小元素放前面
    // [0, bound), 已排序区间
    // [bound, size), 待排序区间
    for (int bound = 0; bound < array.length; bound++) {
        // 接下来就需要在待排序区间中找到当前的最小值.
        // 具体的找法就是, 比较相邻元素, 看是否符合升序要求, 不符合就交换元素
        for (int cur = array.length - 1; cur > bound; cur--) {
            if (array[cur - 1] > array[cur]) {
                swap(array, cur - 1, cur);
            }
        }
    }
}

2.性能分析:
时间复杂度:O(N^2)
空间复杂度:O(1)
稳定性:稳定排序

六.快速排序
1.实现方法:
核心操作:partition,针对整个数组进行整排,选定一个基准值(可以选最后/第一个元素),整理之后的数字,基准值左侧的元素都小于基准值,右侧的元素都大于基准值。
partition具体操作:选定一个基准值(此时选最后一个元素为基准值为例),让left从左往右找到一个比基准值大的,让right从右往左找到一个比基准值小的元素,交换left和right位置的元素,重复刚才的过程,直到left和right重合。当left和right重合的值,此时当前元素一定是大于基准值,就把重合位置和基准值进行交换,再针对两个区间,递归进行刚才的过程。

如果选择第一个元素为基准值,那么必须要先从右往左,再从左往右。
如果选择最后一个为基准值,必须先要从左往右,再从右往左。

public static void quickSort(int[] array) {
    quickSortHelper(array, 0, array.length - 1);
}
// [left, right] 前闭后闭区间. 针对当前范围进行快速排序
private static void quickSortHelper(int[] array, int left, int right) {
    if (left >= right) {
        return;
    }
    int index = partition(array, left, right);
    quickSortHelper(array, left, index - 1);
    quickSortHelper(array, index + 1, right);
}

private static int partition(int[] array, int left, int right) {
    int baseValue = array[right];
    int i = left;
    int j = right;
    while (i < j) {
        // 1. 先从左往右找到一个大于基准值的元素
        while (i < j && array[i] <= baseValue) {
            i++;
        }
        // 此时 i 指向的位置要么和 j 重合, 要么就是一个比基准值大的元素
        // 2. 再从右往左找到一个小于基准值的元素
        while (i < j && array[j] >= baseValue) {
            j--;
        }
        // 此时 j 指向的元素要么和 i 重合, 要么就是比基准值小的元素
        // 3. 交换 i 和 j 的值
        if (i < j) {
            swap(array, i, j);
        }
    }
    // 当整个循环结束, i 和 j 就重合了. 接下来就把 基准值 位置的元素交换到 i j 重合位置上.
    // 此时 i 和 j 重合位置的元素一定是大于基准值的元素.
    swap(array, i, right);
    return i;
}

2.性能分析;
时间复杂度:最坏情况O(N^2) 平均情况O(NlogN)
空间复杂度:最坏O(N) 平均O(logN)
稳定性:不稳定

七.归并排序
把两个有序数组归并成一个数组
1.实现方法:
借助一个临时的空间,先把数组对等的分为两组,如果当前这两个数组都是有序的话直接进行归并,就能得到一个最终有序的数组。不是有序的,继续往下分组,当组成只有一个元素时,这个组一定是有序的,就可以把两个相邻的组进行归并了。

public static void mergeSort(int[] array) {
    mergeSortHelper(array, 0, array.length);
}

private static void mergeSortHelper(int[] array, int left, int right) {
    if (right - left <= 1) {
        return;
    }
    // 针对 [left, right) 区间, 分成对等的两个区间
    int mid = (left + right) / 2;
    // [left, mid)
    // [mid, right)
    mergeSortHelper(array, left, mid);
    mergeSortHelper(array, mid, right);
    // 通过上面的递归, 认为这两个区间都被排好序了. 接下来就可以进行合并了
    merge(array, left, mid, right);
}

private static void merge(int[] array, int left, int mid, int right) {
    int cur1 = left;
    int cur2 = mid;
    // 临时空间需要能容纳下 两个数组合并后的结果
    int[] output = new int[right - left];
    int outputIndex = 0;
    while (cur1 < mid && cur2 < right) {
        if (array[cur1] < array[cur2]) {
            // 把 cur1 位置的元素插入到 output 中
            output[outputIndex] = array[cur1];
            cur1++;
            outputIndex++;
        } else {
            output[outputIndex] = array[cur2];
            cur2++;
            outputIndex++;
        }
    }
    while (cur1 < mid) {
        output[outputIndex] = array[cur1];
        cur1++;
        outputIndex++;
    }
    while (cur2 < right) {
        output[outputIndex] = array[cur2];
        cur2++;
        outputIndex++;
    }
    // 最后一步要把数据从临时空间中拷贝回原来的数组中.
    for (int i = 0; i < right - left; i++) {
        array[left + i] = output[i];
    }
}

2.性能分析:
时间复杂度:O(NlogN)
空间复杂度:O(n)
稳定性:稳定排序
3.重要特性:
(1) 归并排序能高效的针对链表进行排序,数组能够随机访问,链表只能取next,堆排序,希尔排序是无法高效的应用在链表上的。
(2) 归并排序是一种“外部排序”(数据量非常大,无法全部加载到内存中)的主要实现方式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值