排序算法总结

排序算法总结

概述

  • 排序问题是算法领域一个十分重要的问题,如果一组数组是有序的,我们可以在此基础上解决很多问题。

  • 对于排序问题,总的来说 内排序外排序

    • 内排序:指待排序列完全存放在内存中所进行的排序过程,适合数据量不太大的元素序列。
    • 外排序:指排序过程中还需访问外存储器,足够大的元素序列,因不能完全放入内存,只能使用外排序。

    这里只介绍内排序。

  • 另外还要明确一个概念:排序算法的稳定性

    若对任意的数据元素序列,使用某个排序方法,对它按关键字进行排序:若相同关键字元素间的位置关系,排序前与排序后保持一致,称此排序方法是稳定的;而不能保持一致的排序方法则称为不稳定的。

    比如两个关键字都为2,排序前和排序后这两个2的相对顺序没有发生改变,则该排序算法是稳定的。

  • 排序算法概览

在这里插入图片描述

排序方法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性说明
简单插入排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定
希尔排序 O ( n 1.3 ) O(n^{1.3}) O(n1.3) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)不稳定2 2 1
简单选择排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( 1 ) O(1) O(1)不稳定2 2 1
堆排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( 1 ) O(1) O(1)不稳定2 1 1
冒泡排序 O ( n 2 ) O(n^2) O(n2) O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n) O ( 1 ) O(1) O(1)稳定
快速排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n 2 ) O(n^2) O(n2) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n ) O(n) O(n)不稳定2 1 1
二路归并排序 O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n l o g 2 n ) O(nlog_2 n) O(nlog2n) O ( n ) O(n) O(n)稳定
计数排序 O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k) O ( n + k ) O(n+k) O(n+k)稳定数据范围0~k
桶排序 O ( n ∗ l o g 2 ( n / m ) + n ) O(n*log_2(n/m)+n) O(nlog2(n/m)+n) O ( n ∗ l o g 2 ( n / m ) + n ) O(n*log_2(n/m)+n) O(nlog2(n/m)+n) O ( n ∗ l o g 2 ( n / m ) + n ) O(n*log_2(n/m)+n) O(nlog2(n/m)+n) O ( n ) O(n) O(n)稳定桶个数为m
基数排序 O ( n ∗ d ) O(n*d) O(nd) O ( n ∗ d ) O(n*d) O(nd) O ( n ∗ d ) O(n*d) O(nd) O ( n ) O(n) O(n)稳定数据最长长度为d
  • 可以直接复制代码到如下网站进行测试:AcWing,如下的各种排序算法代码都可以在这个测试测试。

问题描述:给出一组数据,请将这组数据升序排列。

一. 插入排序

1.1 简单插入排序

分析

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 11:47
#include <iostream>

using namespace std;

const int N = 1010;  // 输入数据个数的的上限

int n;  // 实际元素个数
int q[N];

void sort() {
    for (int i = 1; i < n; i++) {
        int t = q[i], j = i;
        for (; j > 0 && q[j - 1] > t; j--) q[j] = q[j - 1];
        q[j] = t;
    }
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    sort();
    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    printf("");
    return 0;
}
  • Java
import java.util.Scanner;

/**
 * Created by WXX on 2021/3/1 11:24
 */
public class Main {

    public static final int N = 1010;  // 输入数据个数的的上限

    static int n;  // 实际元素个数
    static int[] q = new int[N];

    private static void sort() {
        for (int i = 1; i < n; i++) {
            int t = q[i], j = i;
            for (; j > 0 && q[j - 1] > t; j--) q[j] = q[j - 1];
            q[j] = t;
        }
    }

    public static void main(String[] args) {

        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        for (int i = 0; i < n; i++) q[i] = sn.nextInt();

        sort();
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

1.2 希尔排序

分析

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 12:00
#include <iostream>

using namespace std;

const int N = 1010;

int n;
int q[N];

void shell_sort(int d) {
    for (int i = d; i < n; i++) {
        int t = q[i], j = i;
        for (; j >= d && q[j - d] > t; j -= d) q[j] = q[j - d];
        q[j] = t;
    }
}

void sort() {
    int d[] = {5, 3, 1};
    for (int step : d) shell_sort(step);
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    sort();
    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    printf("");
    return 0;
}
  • Java
import java.util.Scanner;

/**
 * Created by WXX on 2021/3/1 11:50
 */
public class Main {

    public static final int N = 1010;

    static int n;
    static int[] q = new int[N];

    private static void shellSort(int d) {
        for (int i = d; i < n; i++) {
            int t = q[i], j = i;
            for (; j >= d && q[j - d] > t; j -= d) q[j] = q[j - d];
            q[j] = t;
        }
    }

    private static void sort() {
        int[] d = {5, 3, 1};
        for (int step : d) shellSort(step);
    }

    public static void main(String[] args) {

        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        for (int i = 0; i < n; i++) q[i] = sn.nextInt();

        sort();
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

二. 选择排序

2.1 简单选择排序

分析

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 14:28
#include <iostream>

using namespace std;

const int N = 1010;

int n;
int q[N];

void sort() {
    for (int i = 0; i < n - 1; i++) {
        int k = i;  // 未排序的数据中最小值对应的下标
        for (int j = i + 1; j < n; j++)
            if (q[j] < q[k])
                k = j;
        swap(q[i], q[k]);
    }
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    sort();
    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    printf("");
    return 0;
}
  • Java
import java.util.Scanner;

/**
 * Created by WXX on 2021/3/1 14:34
 */
public class Main {

    public static final int N = 1010;

    static int n;
    static int[] q = new int[N];

    private static void sort() {
        for (int i = 0; i < n - 1; i++) {
            int k = i;  // 未排序的数据中最小值对应的下标
            for (int j = i + 1; j < n; j++)
                if (q[j] < q[k])
                    k = j;
            int t = q[i]; q[i] = q[k]; q[k] = t;  // 交换q[i]和q[k]的值
        }
    }

    public static void main(String[] args) {

        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        for (int i = 0; i < n; i++) q[i] = sn.nextInt();

        sort();
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

2.2 堆排序

分析

  • 首先用筛选法创建大顶堆,然后每次将堆顶元素换到最后再shiftDown,如此循环即可。

代码

  • C++
// Created by WXX on 2021/3/1 14:59
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N];

void down(int u) {
    int k = u;
    if (2 * u <= n && q[2 * u] > q[k]) k = 2 * u;
    if (2 * u + 1 <= n && q[2 * u + 1] > q[k]) k = 2 * u + 1;
    if (k != u) {
        swap(q[k], q[u]);
        down(k);
    }
}

void sort() {
    // 筛选法建堆,时间复杂度O(n),大根堆
    for (int i = n / 2; i >= 1; i--) down(i);

    int size = n;
    while (n) {
        swap(q[n], q[1]);
        n--;
        down(1);
    }
    n = size;
}

int main() {

    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &q[i]);

    sort();
    for (int i = 1; i <= n; i++) printf("%d ", q[i]);
    printf("");
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by WXX on 2021/3/1 14:40
 * 注意:为了方便这里存储数据从q[1]开始,q[0]未使用
 */
public class Main {

    public static final int N = 100010;

    static int n;
    static int[] q = new int[N];

    private static void down(int u) {
        int k = u;
        if (2 * u <= n && q[2 * u] > q[k]) k = 2 * u;
        if (2 * u + 1 <= n && q[2 * u + 1] > q[k]) k = 2 * u + 1;
        if (k != u) {
            int t = q[k]; q[k] = q[u]; q[u] = t;  // 交换
            down(k);
        }
    }

    private static void sort() {
        // 筛选法建堆,时间复杂度O(n),大根堆
        for (int i = n / 2; i >= 1; i--) down(i);

        int size = n;
        while (n != 0) {
            int t = q[n]; q[n] = q[1]; q[1] = t;  // 交换
            n--;
            down(1);
        }
        n = size;
    }

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        for (int i = 1; i <= n; i++) q[i] = Integer.parseInt(t[i - 1]);

        sort();
        for (int i = 1; i <= n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

三. 交换排序

3.1 冒泡排序

分析

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 15:07
#include <iostream>

using namespace std;

const int N = 1010;

int n;
int q[N];

void sort() {
    bool flag = true;  // true代表在本轮中交换了某些数据
    for (int i = n - 1; i > 0; i--) {
        if (!flag) break;
        flag = false;
        for (int j = 0; j < i; j++)
            if (q[j] > q[j + 1]) {
                flag = true;
                swap(q[j], q[j + 1]);
            }
    }
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    sort();
    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    printf("");
    return 0;
}
  • Java
import java.util.Scanner;

/**
 * Created by WXX on 2021/3/1 15:16
 */
public class Main {

    public static final int N = 1010;  // 输入数据个数的的上限

    static int n;  // 实际元素个数
    static int[] q = new int[N];

    private static void sort() {
        boolean flag = true;  // true代表在本轮中交换了某些数据
        for (int i = n - 1; i > 0; i--) {
            if (!flag) break;
            flag = false;
            for (int j = 0; j < i; j++)
                if (q[j] > q[j + 1]) {
                    flag = true;
                    int t = q[j]; q[j] = q[j + 1]; q[j + 1] = t;
                }
        }
    }

    public static void main(String[] args) {

        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        for (int i = 0; i < n; i++) q[i] = sn.nextInt();

        sort();
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

3.2 快速排序

分析

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 15:51
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N];

void quick_sort(int l, int r) {

    if (l >= r) return;
    int x = q[(l + r) / 2], i = l - 1, j = r + 1;
    while(i < j) {
        while(q[++i] < x);
        while(q[--j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    quick_sort(l, j);
    quick_sort(j + 1, r);
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    quick_sort(0, n - 1);

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by WXX on 2021/3/1 16:06
 */
public class Main {

    public static final int N = 100010;

    static int n;
    static int[] q = new int[N];

    private static void sort(int l, int r) {

        if (l >= r) return;
        int x = q[(l + r) / 2], i = l - 1, j = r + 1;
        while (i < j) {
            while (q[++i] < x) ;
            while (q[--j] > x) ;
            if (i < j) {
                int t = q[i]; q[i] = q[j]; q[j] = t;
            }
        }
        sort(l, j);
        sort(j + 1, r);
    }

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        for (int i = 0; i < n; i++) q[i] = Integer.parseInt(t[i]);

        sort(0, n - 1);
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

四. 归并排序

4.1 二路归并排序

分析

  • 自顶向下归并排序

在这里插入图片描述

  • 自底向上进行归并

在这里插入图片描述

代码

  • C++
// Created by WXX on 2021/3/1 16:27
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N], tmp[N];

// 自顶向下归并排序
void merge_sort(int l, int r) {

    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];

    for(i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    merge_sort(0, n - 1);

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
// Created by WXX on 2021/3/1 16:57
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int q[N], tmp[N];

// 自底向上归并排序
void merge_sort() {

    for (int sz = 1; sz < n; sz += sz)  // 每次合并的两个数组的长度
        for (int l = 0; l + sz < n; l += sz + sz) {
            int mid = l + sz - 1, r = min(l + sz + sz - 1, n - 1);
            int k = 0, i = l, j = mid + 1;
            while (i <= mid && j <= r)
                if (q[i] <= q[j]) tmp[k++] = q[i++];
                else tmp[k++] = q[j++];
            while (i <= mid) tmp[k++] = q[i++];
            while (j <= r) tmp[k++] = q[j++];
            for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
        }
}

int main() {

    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    merge_sort();

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by WXX on 2021/3/1 16:30
 */
public class Main {

    public static final int N = 100010;

    static int n;
    static int[] q = new int[N], tmp = new int[N];

    // 自顶向下归并排序
    private static void sort(int l, int r) {

        if (l >= r) return;

        int mid = l + r >> 1;
        sort(l, mid); sort(mid + 1, r);

        int k = 0, i = l, j = mid + 1;
        while (i <= mid && j <= r)
            if (q[i] <= q[j]) tmp[k++] = q[i++];
            else tmp[k++] = q[j++];
        while (i <= mid) tmp[k++] = q[i++];
        while (j <= r) tmp[k++] = q[j++];

        for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
    }

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        for (int i = 0; i < n; i++) q[i] = Integer.parseInt(t[i]);

        sort(0, n - 1);
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by WXX on 2021/3/1 16:30
 */
public class Main {

    public static final int N = 100010;

    static int n;
    static int[] q = new int[N], tmp = new int[N];

    // 自底向上归并排序
    private static void sort() {

        for (int sz = 1; sz < n; sz += sz)  // 每次合并的两个数组的长度
            for (int l = 0; l + sz < n; l += sz + sz) {
                int mid = l + sz - 1, r = Math.min(l + sz + sz - 1, n - 1);
                int k = 0, i = l, j = mid + 1;
                while (i <= mid && j <= r)
                    if (q[i] <= q[j]) tmp[k++] = q[i++];
                    else tmp[k++] = q[j++];
                while (i <= mid) tmp[k++] = q[i++];
                while (j <= r) tmp[k++] = q[j++];
                for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
            }
    }

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        for (int i = 0; i < n; i++) q[i] = Integer.parseInt(t[i]);

        sort();
        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

Leetcode中自底向上归并排序的应用

C++

/**
 * 执行用时:24 ms, 在所有 C++ 提交中击败了93.76%的用户
 * 内存消耗:13.1 MB, 在所有 C++ 提交中击败了70.38%的用户
 */
class Solution {
public:
    ListNode *merge(ListNode *l1, ListNode *l2) {
        if (l1 == NULL) return l2;
        if (l2 == NULL) return l1;
        if (l1->val < l2->val) {
            l1->next = merge(l1->next, l2);
            return l1;
        } else {
            l2->next = merge(l1, l2->next);
            return l2;
        }
    }

    ListNode *mergeKLists(vector<ListNode *> &lists) {

        if (lists.empty()) return NULL;
        for (int step = 1; step < lists.size(); step += step)
            for (int i = 0; i + step < lists.size(); i += step + step)
                lists[i] = merge(lists[i], lists[i + step]);
        return lists[0];
    }
};

Java

/**
 * 执行用时:3 ms, 在所有 Java 提交中击败了76.22%的用户
 * 内存消耗:40.7 MB, 在所有 Java 提交中击败了12.53%的用户
 */
public class Solution {
    // 返回l1和l2合并后链表的头结点
    private ListNode mergeTwoList(ListNode l1, ListNode l2) {

        if (l1 == null || l2 == null)
            return l1 == null ? l2 : l1;
        else if (l1.val < l2.val) {
            l1.next = mergeTwoList(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoList(l1, l2.next);
            return l2;
        }
    }

    public ListNode mergeKLists(ListNode[] lists) {

        if (lists == null || lists.length == 0) return null;

        for (int step = 1; step < lists.length; step += step)  // 每次合并的两个链表的索引差值
            for (int i = 0; i + step < lists.length; i += (step + step))  // i 代表两个待合并链表中的第一个
                lists[i] = mergeTwoList(lists[i], lists[i + step]);  // list[i] 和 list[i+step] 进行合并
        return lists[0];
    }
}
  • 当然这个题目还有其他解法,比如优先队列实现,这里就不展示代码了

五. 非比较排序

5.1 计数排序

问题描述

  • 如果存在一组数据,这些数据的值都位于0~k之间,请给这组数据排序。

分析

  • 因为数据都在0~k之间,因此我们可以新开一个数组c,记录各个数据出现的次数。
  • 之前对数组c求前缀和,结果仍然存入c中,则此时c[k]代表的含义是小于等于 k 的数据的个数为c[k]个,因此最终 数据i 应该被存放在 索引c[i]-1 处。
  • 按照上述描述将结果存入到另一个数组中即可。
  • 参考网址

代码

  • C++
// Created by WXX on 2021/3/1 20:02
#include <iostream>
#include <cstring>

using namespace std;

const int N = 100010;

int n, k;  // 数据个数n,数据范围0~k
int q[N];

void sort() {
    // 将数组q拷贝一份到tmp中,目的是让最终排序结果存储在q中
    int tmp[N];
    memcpy(tmp, q, n * sizeof(int));
    // 统计各数据出现次数,然后计算每个数据应该存放的下标
    int c[k + 1] = {0};
    for (int i = 0; i < n; i++) c[tmp[i]]++;
    for (int i = 1; i <= k; i++) c[i] += c[i - 1];
    // 将数据放到应该存放的位置, 下面这样的写法是不稳定的排序算法
    for (int i = 0; i < n; i++) q[--c[tmp[i]]] = tmp[i];
    // // 稳定的计数排序应该从后向前枚举每个数
    // for (int i = n - 1; i >= 0; i--) q[--c[tmp[i]]] = tmp[i];
}

int main() {

    /*
     * 测试数据
     * 12 4
     * 0 3 2 2 2 1 3 0 1 2 0 0
     */
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++) scanf("%d", &q[i]);

    sort();

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * Created by WXX on 2021/3/1 20:17
 */
public class Main {

    public static final int N = 100010;

    static int n, k;  // 数据个数n,数据范围0~k
    static int[] q = new int[N];

    private static void sort() {
        // 将数组q拷贝一份到tmp中,目的是让最终排序结果存储在q中
        int[] tmp = new int[N];
        System.arraycopy(q, 0, tmp, 0, n);
        // 统计各数据出现次数,然后计算每个数据应该存放的下标
        int[] c = new int[k + 1];
        for (int i = 0; i < n; i++) c[tmp[i]]++;
        for (int i = 1; i <= k; i++) c[i] += c[i - 1];
        // 将数据放到应该存放的位置
        for (int i = 0; i < n; i++) q[--c[tmp[i]]] = tmp[i];
    }

    public static void main(String[] args) throws Exception {

        /*
         * 测试数据
         * 12 4
         * 0 3 2 2 2 1 3 0 1 2 0 0
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] t = br.readLine().split(" ");
        n = Integer.parseInt(t[0]); k = Integer.parseInt(t[1]);
        t = br.readLine().split(" ");
        for (int i = 0; i < n; i++) q[i] = Integer.parseInt(t[i]);

        sort();

        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

5.2 桶排序

问题描述

  • 给你一组数据分布范围较大,但数据个数相对较少的数据,对这组数据进行排序。

分析

  • 桶排序步骤:

    (1)确定桶的个数M,将待排序集合中处于同一个值域的元素存入同一个桶中;

    (2)桶内使用各种现有的算法进行排序;

    (3)从小到大依次收集每一个桶,最终得到结果。

  • 参考网址

  • 桶排序和快排的关系: 快排相当于用两个桶收集数据,但快排没有开辟额外空间。

  • 桶排序和计数排序的关系: 计数排序是桶排序的一种特殊情况,当桶的大小和数据范围相同时,每个桶中只会存放一个数据的个数,此时桶排序演化成了计数排序。桶排序可以看成是对时间和空间开销的一种折中方案。

代码

  • C++
// Created by WXX on 2021/3/1 20:50
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
int q[N];
// 读取数据的过程中记录最大值、最小值。初始设为负无穷、正无穷
int maxv = -2e9, minv = 2e9;

void bucket_sort() {
    const int K = 10;  // 每个桶中数据的个数,可以认为数超参数
    int M = (maxv - minv) / K + 1;  // 桶的个数
    vector<vector<int>> bucket(M);  // M个桶
    // 收集
    for (int i = 0; i < n; i++) bucket[(q[i] - minv) / K].push_back(q[i]);
    // 对每个桶中的数据进行排序
    for (int i = 0; i < M; i++) sort(bucket[i].begin(), bucket[i].end());
    // 收集
    for (int i = 0, j = 0, k = 0; i < n;) {  // 当前收集到第j个桶的第k个数据
        if (k < bucket[j].size()) q[i++] = bucket[j][k++];
        else j++, k = 0;
    }
}

int main() {

    /*
     * 测试数据
     * 20
     * -7 51 3 121 -3 32 21 43 4 25 56 77 16 22 87 56 -10 68 99 70
     */
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &q[i]);
        maxv = max(maxv, q[i]);
        minv = min(minv, q[i]);
    }

    bucket_sort();

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Created by WXX on 2021/3/1 21:18
 */
public class Main {

    public static final int N = 100010;

    static int n;
    static int[] q = new int[N];
    // 读取数据的过程中记录最大值、最小值。初始设为负无穷、正无穷
    static int maxv = Integer.MIN_VALUE, minv = Integer.MAX_VALUE;

    private static void bucketSort() {
        final int K = 10;  // 每个桶中数据的个数,可以认为数超参数
        int M = (maxv - minv) / K + 1;  // 桶的个数
        List<List<Integer>> bucket = new ArrayList<>(M);  // M个桶
        for (int i = 0; i < M; i++) bucket.add(new ArrayList<>());
        // 收集
        for (int i = 0; i < n; i++) bucket.get((q[i] - minv) / K).add(q[i]);
        // 对每个桶中的数据进行排序
        for (int i = 0; i < M; i++) Collections.sort(bucket.get(i));
        // 收集
        for (int i = 0, j = 0, k = 0; i < n; ) {  // 当前收集到第j个桶的第k个数据
            if (k < bucket.get(j).size()) q[i++] = bucket.get(j).get(k++);
            else {
                j++; k = 0;
            }
        }
    }

    public static void main(String[] args) throws Exception {

        /*
         * 测试数据
         * 20
         * -7 51 3 121 -3 32 21 43 4 25 56 77 16 22 87 56 -10 68 99 70
         */
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        for (int i = 0; i < n; i++) {
            q[i] = Integer.parseInt(t[i]);
            maxv = Math.max(maxv, q[i]);
            minv = Math.min(minv, q[i]);
        }

        bucketSort();

        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}

5.3 基数排序

问题描述

  • 给你一组非负整数(0~INT_MAX),请对这些数据进行排序。

分析

  • 基数排序也可以称为多关键字排序,也是一种非比较性质的排序算法。

  • 基数排序在桶排序的基础上做了优化,桶排序需要选择适当的映射规则,来完成集合中元素到多个桶的映射,也可以称之为值域划分。但是当集合中元素跨度很大时,映射规则的设计比较困难,若规则设计的宽泛一些,则桶的个数较少,随便避免了许多空桶的情况,但是可能会存在元素分布不均,桶排序则演变为普通的比较性质排序;若规则设计的较为精确,则桶的个数较多,可能会存在大部分桶都是空桶的情况,存在较大空间浪费。

  • 桶排序之所以存在上述问题,原因在于算法中对待排序元素的属性选择所致。排序过程选择使用了元素本身的 “大小” 属性,所以算法处理的元素集合就是这个 “大小” 空间。例如,若待排序元素为整型,而整型数字在 “大小” 方面可以是无限大或者无限小的;若待排序元素为字符串,而字符串在 “长度” 方面是无限大的。而桶排序又是一种对元素总容量敏感的排序算法,所以存在使用限制。

  • 算法过程(采用LSD,即最低位优先法)

    (1)根据待排序元素的类型申请桶空间,并从待排序集合中计算出元素的最大位数;

    (2)从右向左,根据元素当前位数的值,将所有元素移动到对应的桶中;

    (3)将所有桶中元素移动回原始集合中;

    (4)重复步骤 2, 3,直到遍历完所有位数。

  • 参考网址

  • 关于 最高位优先MSD法最低位优先LSD法

    n 个记录的序列 { R 1 , R 2 , . . . , R n R_1,R_2,...,R_n R1,R2,...,Rn},对关键字 ( K i d − 1 , K i d − 2 , . . . , K i 0 K_i^{d-1},K_i^{d-2},...,K_i^0 Kid1,Kid2,...,Ki0) 有序是指(d表示数据的最多位数):对于序列中任意两个记录 R i R_i Ri R j R_j Rj (1≤i<j≤n) 都满足下列(词典)有序关系: ( K i d − 1 , K i d − 2 , . . . , K i 0 K_i^{d-1},K_i^{d-2},...,K_i^0 Kid1,Kid2,...,Ki0) < ( K j d − 1 , K j d − 2 , . . . , K j 0 K_j^{d-1},K_j^{d-2},...,K_j^0 Kjd1,Kjd2,...,Kj0) ,则 K d − 1 K^{d-1} Kd1 被称为 最主关键字 K 0 K^0 K0 被称为 最次关键字

    最高位优先LSD法:先对 K d − 1 K^{d-1} Kd1 进行桶排序排序,再对 K d − 2 K^{d-2} Kd2 进行桶排序,……, 依次类推,直至最后对最低位关键字桶排序完成为止。

    最低位优先MSD法:先对 K 0 K^0 K0 进行桶排序排序,再对 K 1 K^1 K1 进行桶排序,……, 依次类推,直至最后对最高位关键字桶排序完成为止。

代码

  • C++
// Created by WXX on 2021/3/1 22:25
#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

const int N = 100010, RADIX = 10;  // 十进制数据

int n;
int q[N];
int d;  // 输入的最大数据的位数
int base[10];  // base[i] = 10 ^ i

void sort() {
    vector<vector<int>> f(RADIX);  // 负责收集数据的数组
    for (int i = 0; i < d; i++) {
        // 分配
        for (int j = 0; j < n; j++) f[(q[j] / base[i]) % RADIX].push_back(q[j]);
        // 收集
        for (int ii = 0, j = 0, k = 0; ii < n;) {  // 当前收集到第j个桶的第k个数据
            if (k < f[j].size()) q[ii++] = f[j][k++];
            else j++, k = 0;
        }
        for (auto &v : f) v.clear();  // &不能省略
    }
}

int main() {

    /*
     * 测试数据
     * 10
     * 1086 187 30 76 0 1359 36 777 9 2
     */
    // 输入要求:必须大于等于0
    int maxv = -2e9;  // 用于计算数据的位数
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &q[i]);
        maxv = max(maxv, q[i]);
    }

    d = ceil(log10(maxv + 1));
    base[0] = 1;
    for (int i = 1; i < 10; i++) base[i] = base[i - 1] * 10;

    sort();

    for (int i = 0; i < n; i++) printf("%d ", q[i]);
    return 0;
}
/**
d = ceil(log10(maxv + 1));  // 必须加1,否则下面的测试数据无法正确排序
5
3 1 10 4 5
*/
  • Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by WXX on 2021/3/1 22:54
 */
public class Main {

    public static final int N = 100010, RADIX = 10;  // 十进制数据

    static int n;
    static int[] q = new int[N];
    static int d;  // 输入的最大数据的位数
    static int[] base = new int[10];  // base[i] = 10 ^ i

    private static void sort() {
        List<List<Integer>> f = new ArrayList<>();
        for (int i = 0; i < RADIX; i++) f.add(new ArrayList<>());
        for (int i = 0; i < d; i++) {
            // 分配
            for (int j = 0; j < n; j++) f.get((q[j] / base[i]) % RADIX).add(q[j]);
            // 收集
            for (int ii = 0, j = 0, k = 0; ii < n; ) {  // 当前收集到第j个桶的第k个数据
                if (k < f.get(j).size()) q[ii++] = f.get(j).get(k++);
                else {
                    j++; k = 0;
                }
            }
            for (List<Integer> v : f) v.clear();
        }
    }

    public static void main(String[] args) throws Exception {

        /*
         * 测试数据
         * 10
         * 1086 187 30 76 0 1359 36 777 9 2
         */
        // 输入要求:数组q中的数据必须大于等于0
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(br.readLine());
        String[] t = br.readLine().split(" ");
        int maxv = Integer.MIN_VALUE;  // 用于计算数据的位数
        for (int i = 0; i < n; i++) {
            q[i] = Integer.parseInt(t[i]);
            maxv = Math.max(maxv, q[i]);
        }

        d = (int) Math.ceil(Math.log10(maxv + 1));
        base[0] = 1;
        for (int i = 1; i < 10; i++) base[i] = base[i - 1] * 10;

        sort();

        for (int i = 0; i < n; i++) System.out.print(q[i] + " ");
        System.out.println();
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值