十大排序算法详解

本文主要讲十大排序算法:

        1、冒泡排序

        2、选择排序

        3、插入排序

        4、归并排序

        5、快速排序

        6、堆排序

        7、希尔排序

        8、计数排序

        9、桶/箱排序

        10、基数排序

在正文开始前,先说几个概念:

        稳定性:指的是排序后,相邻相等元素的位置不会被交换。(当然你也可以在算法实现中让他不稳定,比如:条件判断时,a=b 时你交换a和b的位置,但是这么做没有必要)【下面判断算法是否稳定都是以遇到a=b时不交换位置的情况】。

比如:排序前:[3 ,2 ,2 ,1],排序后为:[1 ,2 ,2 ,3],即说明是稳定的,若为: [1 ,2 ,2 ,3],就是不稳定。

         时间复杂度:分为最佳时间复杂度、平均时间复杂度、最差时间复杂度。即完成排序所需要的时间。

        空间复杂度:即算法需要用到的额外空间,这个不是所有算法都需要用到额外空间。

图表汇总: 

算法名称稳定性最好时间复杂度平均时间复杂度最差时间复杂度空间复杂度
冒泡排序稳定O(n)O(n^2)O(n^2)O(1)
选择排序不稳定O(n^2)O(n^2)O(n^2)O(1)
插入排序稳定O(n)O(n^2)O(n^2)O(1)
归并排序稳定O(nlogn)O(nlogn)O(nlogn)O(n)
快速排序不稳定O(nlogn)O(nlogn)O(n^2)O(1)/O(logn)
堆排序不稳定O(nlogn)O(nlogn)O(nlogn)O(1)
希尔排序不稳定O(nlogn)O(nlogn)O(n^2)O(1)
计数排序稳定O(n+k)O(n+k)O(n+k)O(k)
桶排序皆可O(n+k)O(n+k)O(n^2)O(n+k)
基数排序稳定O(n)O(d*(n+k))O(d*(n+k))O(n+k)

一:冒泡排序

冒泡排序的思想:当前位置(i位置)的元素与当前位置后一位(i+1位置)比较大小,根据从大/从小排序的规则判断是否交换 i 位置和 i+1 位置元素。每一轮比较后,该轮的最后一个元素一定是最小/最大的。然后开始下一轮排序,直到排序完成。

特点:

  • 稳定性:稳定
  • 最好时间复杂度:基本有序的情况下时间复杂度最好,为O(n)。
  • 平均时间复杂度:O(n^2)
  • 最坏时间复杂度:完全逆序,为O(n^2)。如需要从小到大排,但是数组初始为从大到小有序。
  • 空间复杂度:可不使用额外数组,为O(1),也可使用一个额外数组为O(n)。
  • 适用情况:适用于小规模。

算法实现C++: 

 for (int i=0; i<ans.size()-1; i++)
    {
        for (int j=0; j<ans.size()-i-1; j++)
        {
            if (ans[j] > ans[j + 1])
            {
                int temp = ans[j];
                ans[j] = ans[j+1];
                ans[j + 1] = temp;
            }
        }
    }

优化: 设置一个标志,当前一轮数据已经有序时,没必要进行下一轮比较。

 vector<int> ans = {4,5,6,7,0,1,2,3};
    bool flag = false; // 设置一个标志
    for (int i=0; i<ans.size()-1; i++)
    {
        flag = false;  // 每轮开始设置为false
        for (int j=0; j<ans.size()-i-1; j++)
        {
            if (ans[j] > ans[j + 1])
            {
                int temp = ans[j];
                ans[j] = ans[j+1];
                ans[j + 1] = temp;
                flag = true;  // 说明交换过
            }
        }
        if (flag == false) 
        {
            break;
        }
    }

二:选择排序

选择排序思想:在未排序的序列中找到最小(大)元素放到序列的起始位置处,再从剩下的元素中找最小(大)元素,放到前一轮排序元素的后面。以此类推直到排序完成。

特点:

  • 稳定性:不稳定
  • 最好时间复杂度:O(n^2)
  • 平均时间复杂度:O(n^2)
  • 最坏时间复杂度:O(n^2)
  • 空间复杂度:不需要使用额外空间。O(1)
  • 适用情况:适用于小规模,数据基本上有序时性能会较好。

算法实现C++: 直接找每轮最小/最大的数字,然后放到前一轮排序的后边。比如[0]是第一轮最小的数字,[i]是第二轮找到的最小数字,我们直接交换[1]和[i]位置的数字就可以。

void selectionSort(int arr[], int n) { // n表示数组长度
    for (int i = 0; i < n-1; i++) {
        int minIndex = i; // 记录最小元素的索引

        // 在未排序部分找到最小元素的索引
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        
        // 将最小元素与未排序部分的第一个元素交换
        swap(arr[minIndex], arr[i]);
    }
}

三:插入排序

插入排序思想:将一个数组分为已排序和未排序两部分,初始时已排序部分只包含一个元素(即第一个元素),然后不断地将未排序部分的元素插入到已排序部分的正确位置(需要遍历已排序的找到最合适位置),直到所有元素都被插入到已排序部分,从而完成排序。插入排序改良版为希尔排序。

特点:

  • 稳定性:稳定
  • 最好时间复杂度:基本有序时最好,O(n)
  • 平均时间复杂度:O(n^2)
  • 最差时间复杂度:完全逆序下为,O(n^2)
  • 空间复杂度:可以不使用额外空间,为O(1)
  • 适用情况:优势在于实现简单,代码量少。小规模情况下表现良好,不适应大规模。

算法实现C++:假设目前遍历到[i]位置,则[i]以前的为有序数组,我们要从[i-1]开始,即从有序数组的尾部开始逐个与[i]比较大小,找到[i]最合适的位置插入。这一点和选择排序不一样,注意二者区别。

void insertionSort(int arr[], int n) { //n为数组长度
    for (int i = 1; i < n; i++) {
        int key = arr[i]; // 选择未排序部分的第一个元素作为当前要插入的元素
        int j = i - 1;
        
        // 将比当前元素大的元素向后移动
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        
        arr[j + 1] = key; // 将当前元素插入到正确的位置
    }
}

四:归并排序

归并排序思想:是一种分治思想的排序算法,将待排序数组不断地划分为更小的子数组,直到划分为单个元素的数组。然后再将这些单个元素的数组按照顺序合并,直到最终得到一个完全有序的数组。

特点:

  • 稳定性:稳定
  • 最好时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 空间复杂度:O(n)
  • 适用情况:不受输入数据的影响,适用大规模数据。

算法实现C++:归并排序使用递归也可以使用迭代法的方式进行划分和合并操作。首先将待排序数组从中间位置划分为两个子数组,然后递归地对这两个子数组进行划分操作,直到划分为单个元素的数组。然后通过合并操作,将两个有序的子数组合并为一个有序的数组。重复这个过程,直到最终得到完全有序的数组。

在合并操作中,归并排序创建一个临时数组,用于存储合并过程中的结果。通过比较两个子数组的元素大小,按照顺序将元素放入临时数组中,直到一个子数组的所有元素都被放入临时数组。然后将未放入临时数组的子数组的剩余元素依次放入临时数组中,最后将临时数组中的元素复制回原始数组的对应位置,完成合并操作。

 递归实现归并算法:

// 合并两个有序子数组
void merge(int arr[], int left, int middle, int right) {
    int n1 = middle - left + 1; // 左子数组的长度
    int n2 = right - middle; // 右子数组的长度

    // 创建临时数组来存储合并后的结果
    int temp1[n1];
    int temp2[n2];

    // 将数据复制到临时数组
    for (int i = 0; i < n1; i++) {
        temp1[i] = arr[left + i];
    }
    for (int j = 0; j < n2; j++) {
        temp2[j] = arr[middle + 1 + j];
    }

    // 合并临时数组中的元素到原始数组
    int i = 0;
    int j = 0;
    int k = left;

    while (i < n1 && j < n2) {
        if (temp1[i] <= temp2[j]) {
            arr[k] = temp1[i];
            i++;
        } else {
            arr[k] = temp2[j];
            j++;
        }
        k++;
    }

    // 将剩余元素复制到原始数组
    while (i < n1) {
        arr[k] = temp1[i];
        i++;
        k++;
    }

    while (j < n2) {
        arr[k] = temp2[j];
        j++;
        k++;
    }
}

// 归并排序递归函数
void mergeSort(int arr[], int left, int right) { //初始值,left=0, right=arr.size()-1
    if (left < right) {
        int middle = left + (right - left) / 2;

        // 递归地对左右子数组进行排序
        mergeSort(arr, left, middle);
        mergeSort(arr, middle + 1, right);

        // 合并两个有序子数组
        merge(arr, left, middle, right);
    }
}

 迭代实现归并排序: 

// 合并两个有序子数组
void merge(int arr[], int left, int middle, int right) {
    int n1 = middle - left + 1; // 左子数组的长度
    int n2 = right - middle; // 右子数组的长度

    // 创建临时数组来存储合并后的结果
    int temp1[n1];
    int temp2[n2];

    // 将数据复制到临时数组
    for (int i = 0; i < n1; i++) {
        temp1[i] = arr[left + i];
    }
    for (int j = 0; j < n2; j++) {
        temp2[j] = arr[middle + 1 + j];
    }

    // 合并临时数组中的元素到原始数组
    int i = 0;
    int j = 0;
    int k = left;

    while (i < n1 && j < n2) {
        if (temp1[i] <= temp2[j]) {
            arr[k] = temp1[i];
            i++;
        } else {
            arr[k] = temp2[j];
            j++;
        }
        k++;
    }

    // 将剩余元素复制到原始数组
    while (i < n1) {
        arr[k] = temp1[i];
        i++;
        k++;
    }

    while (j < n2) {
        arr[k] = temp2[j];
        j++;
        k++;
    }
}

// 归并排序迭代函数
void mergeSort(int arr[], int n) {
    int currentSize;
    int leftStart;

    // 不断地划分子数组并进行合并操作
    for (currentSize = 1; currentSize < n; currentSize = 2 * currentSize) {
        // 划分子数组并进行合并
        for (leftStart = 0; leftStart < n - 1; leftStart += 2 * currentSize) {
            int mid = min(leftStart + currentSize - 1, n - 1);
            int rightEnd = min(leftStart + 2 * currentSize - 1, n - 1);

            merge(arr, leftStart, mid, rightEnd);
        }
    }
}

五:快速排序

快速排序思想:选取一个基准元素,通过一轮划分操作将数组分成左右两个部分,左边部分的元素都小于等于基准元素,右边部分的元素都大于等于基准元素,然后再分别对左右两个部分进行递归排序,最终完成整个数组的排序。

特点:

  • 稳定性:不稳定
  • 最好时间复杂度:每次选取的基准元素刚好将素组元素平分,这样快速排序的递归树的高度为logn,因此时间复杂度为O(nlogn)。
  • 平均时间复杂度:O(nlogn)
  • 最差时间复杂度:每次选择的基准元素都是当前子数组中的最大或最小元素时最差,为O(n^2)
  • 空间复杂度:原地排序,不需要额外空间。O(1)。
  • 适用情况:适用于大规模且数据大小很随机的情况,如果数据基本有序时间复杂度会变为O(n^2)。

算法实现C++:快速排序的划分操作使用双指针的方式进行,通过一个索引i从左到右扫描数组,找到第一个大于基准元素的元素,在此之前的元素都是小于等于基准元素的,然后再通过一个索引j从右到左扫描数组,找到第一个小于基准元素的元素,在此之后的元素都是大于等于基准元素的,然后交换这两个元素。重复这个过程,直到i和j相遇,将基准元素交换到相遇点的位置,然后再分别对基准元素的左右两部分进行递归排序。

// 交换数组中两个元素的位置
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 划分操作,将数组划分成左右两个部分
int partition(int arr[], int low, int high) {
    int pivot = arr[high]; // 选择最后一个元素作为基准元素
    int i = low - 1; // 记录小于等于基准元素的索引位置

    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }

    swap(arr[i + 1], arr[high]); // 将基准元素放到正确的位置
    return i + 1;
}

// 快速排序递归函数
void quickSort(int arr[], int low, int high) {
    if (low < high) {
        int pivotIndex = partition(arr, low, high); // 找到划分点

        // 对划分点左右两个部分进行递归排序
        quickSort(arr, low, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, high);
    }
}

六:堆排序

堆排序思想:堆排序是选择排序的一种。使用堆的数据结构进行排序的算法。它的主要思想是将待排序的数组构建成一个最大堆或最小堆,然后通过不断从堆顶取出元素,将剩下的调整为最大或者最小堆,再取堆顶直到排序完成。

特点:

  • 稳定性:稳定性取决于在向下/向上调整的过程中是否存在相等元素交换操作。一般不稳定。
  • 最好时间复杂度:O(nlogn)
  • 平均时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 空间复杂度:O(1)
  • 适用情况:大规模数据,代码量大,需要进行大规模交换操作,在解决”求前几大的数“时,几乎为首选算法。

算法实现C++:堆排序首先要进行一个构建堆的过程,将待排序的数组调整为一个完全二叉树,并满足堆的性质。对于最大堆,即任意节点的值都大于其子节点的值;对于最小堆,任意节点的值都小于其子节点的值。构建堆的过程可以通过两种方式实现:自顶向下的调整和自底向上的调整。

#include <iostream>
using namespace std;

// 交换数组中两个元素的位置
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 自底向上的堆排序
void heapSortBottomUp(int arr[], int n) {
    // 建立最大堆
    for (int i = n / 2 - 1; i >= 0; i--) {
        // 对每个非叶子节点进行向下调整
        int root = i;
        int maxChild;

        while (root * 2 + 1 < n) {
            maxChild = root * 2 + 1; // 左子节点
            if (maxChild + 1 < n && arr[maxChild] < arr[maxChild + 1]) {
                // 如果右子节点存在且比左子节点大,则将最大的节点设为右子节点
                maxChild++;
            }
            if (arr[root] < arr[maxChild]) {
                // 如果根节点比子节点小,则交换它们的位置
                swap(arr[root], arr[maxChild]);
                root = maxChild;
            } else {
                break;
            }
        }
    }

    // 交换根节点和最后一个节点,并对剩余的部分进行调整
    for (int i = n - 1; i > 0; i--) {
        swap(arr[0], arr[i]);

        // 调整剩余部分为最大堆
        int root = 0;
        int maxChild;

        while (root * 2 + 1 < i) {
            maxChild = root * 2 + 1; // 左子节点
            if (maxChild + 1 < i && arr[maxChild] < arr[maxChild + 1]) {
                // 如果右子节点存在且比左子节点大,则将最大的节点设为右子节点
                maxChild++;
            }
            if (arr[root] < arr[maxChild]) {
                // 如果根节点比子节点小,则交换它们的位置
                swap(arr[root], arr[maxChild]);
                root = maxChild;
            } else {
                break;
            }
        }
    }
}

// 自顶向下的堆排序
void heapSortTopDown(int arr[], int n) {
    // 建立最大堆
    for (int i = 1; i < n; i++) {
        // 自底向上调整堆
        int child = i;
        int parent = (child - 1) / 2;

        while (child > 0 && arr[child] > arr[parent]) {
            // 子节点比父节点大时,交换它们的位置
            swap(arr[child], arr[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
    }

    // 交换根节点和最后一个节点,并对剩余的部分进行调整
    for (int i = n - 1; i > 0; i--) {
        swap(arr[0], arr[i]);

        // 调整剩余部分为最大堆
        int parent = 0;
        int maxChild;

        while (true) {
            maxChild = parent * 2 + 1; // 左子节点
            if (maxChild + 1 < i && arr[maxChild] < arr[maxChild + 1]) {
                // 如果右子节点存在且比左子节点大,则将最大的节点设为右子节点
                maxChild++;
            }
            if (arr[parent] >= arr[maxChild]) {
                // 如果父节点比子节点大或者没有子节点,则结束循环
                break;
            } else {
                // 否则,交换父节点和子节点的位置,并继续向下调整
                swap(arr[parent], arr[maxChild]);
                parent = maxChild;
            }
        }
    }
}

int main() {
    int arr[] = {5, 2, 6, 1, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    heapSortBottomUp(arr, n);

    cout << "自底向上的堆排序结果:";
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }

    cout << endl;

    heapSortTopDown(arr, n);

    cout << "自顶向下的堆排序结果:";
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }

    return 0;
}

七:希尔排序(shell排序)

希尔排序思想:希尔排序是对插入排序的升级优化,对于插入排序每次只能将数据移动一位。而希尔排序就是希望同时移动多位。通过较大间隔的插入排序将数组分隔为多个子序列,然后逐渐缩小间隔,最终使得整个数组变为一个有序序列。

特点:时间复杂度是与间隔序列的选择有关

  • 稳定性:不稳定
  • 最好时间复杂度:最好的间隔下为,O(nlogn)
  • 平均时间复杂度:O(nlogn)
  • 最差时间复杂度:O(n^2),间隔为1时退化为插入排序,即为最差。
  • 空间复杂度:O(1)
  • 适用情况:中小型规模,对稳定性没有要求。

算法实现C++:希尔排序首先选择一个间隔序列,通常为n/2、n/4、n/8...直到间隔为1。然后,对于每个间隔,将数组分为多个子序列,分别对每个子序列进行插入排序。在插入排序过程中,不再是相邻元素比较和交换,而是通过较大间隔的比较和交换。最后,当间隔缩小为1时,再进行一次完整的插入排序,完成整个数组的排序。

#include <iostream>
using namespace std;
// 希尔排序
void shellSort(int arr[], int n) {
    // 使用希尔增量序列
    for (int gap = n / 2; gap > 0; gap /= 2) {
        // 对每个子序列进行插入排序
        for (int i = gap; i < n; i++) {
            int temp = arr[i];
            int j = i;

            // 插入排序
            while (j >= gap && arr[j - gap] > temp) {
                arr[j] = arr[j - gap];
                j -= gap;
            }
            arr[j] = temp;
        }
    }
}

int main() {
    int arr[] = {5, 2, 6, 1, 3};
    int n = sizeof(arr) / sizeof(arr[0]);

    shellSort(arr, n);

    cout << "希尔排序结果:";
    for (int i = 0; i < n; i++) {
        cout << arr[i] << " ";
    }

    return 0;
}

八:计数排序

计数排序思想:是一种线性时间的非比较排序算法,它的思想是统计序列中每个元素出现的次数,然后根据这些次数将元素放回序列中,从而达到排序的目的。

特点:

  • 稳定性:稳定
  • 最好时间复杂度:O(n+k),(n是数组的长度,k是计数数组的大小,即数据最小值到最大的范围)。最好为待排序序列的所有元素都相同时,k=1,O(n+1)
  • 平均时间复杂度:O(n+k)。
  • 最差时间复杂度:O(n+k)。最差情况为待排序序列的元素范围很大,且每个元素都出现一次的情况下。
  • 空间复杂度:O(k),需要使用一个计数数组来存储每个元素的出现次数。
  • 适用情况:数据值比较集中时会更好,需要占用大量空间。计数排序仅适用于非负整数的排序,如果需要对负数或其他类型数据进行排序,需要进行适当的转换或调整。

算法实现C++:计数排序首先需要确定待排序序列的最大值和最小值,然后创建一个计数数组,该数组的长度等于最大值和最小值之差加1。然后遍历待排序序列,统计每个元素出现的次数,并将其存储到计数数组中。接着,按照计数数组的顺序依次将元素放回原始序列中,从而得到一个有序序列。

// 计数排序
void countingSort(int arr[], int n) {
    // 找到数组中的最大值和最小值
    int minVal = arr[0];
    int maxVal = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] < minVal) {
            minVal = arr[i];
        }
        if (arr[i] > maxVal) {
            maxVal = arr[i];
        }
    }

    // 创建计数数组,用于统计每个元素出现的次数
    int countArray[maxVal - minVal + 1] = {0};
    for (int i = 0; i < n; i++) {
        countArray[arr[i] - minVal]++;
    }

    // 根据计数数组的值,按顺序将元素放回原始数组中
    int index = 0;
    for (int i = 0; i < maxVal - minVal + 1; i++) {
        while (countArray[i] > 0) {
            arr[index] = i + minVal;
            index++;
            countArray[i]--;
        }
    }
}

九:桶/箱排序

桶/箱排序思想:桶排序可以看成计数排序的升级版,它的基本思想是将待排序的元素映射到一个有限数量的桶中,然后对每个桶中的元素进行排序,最后将所有桶中的元素按照顺序取出,即可得到有序的结果。

特点:

  • 稳定性:桶排序是否稳定需要看每一个桶使用的排序算法是否稳定。
  • 最好时间复杂度:O(n+k),k表示桶的个数。待排序的元素被均匀地分布在各个桶中,并且每个桶中的元素都是有序的。这种情况下,每个桶内的排序时间复杂度为O(1),取出桶中的元素的时间复杂度也为O(1)。
  • 平均时间复杂度:O(n+k)
  • 最差时间复杂度:O(n^2)。待排序的元素都分配到了同一个桶中,导致这个桶内的元素数量很大。
  • 空间复杂度:O(n+k)
  • 适用情况:适用于元素分布比较均匀的情况下,最大值和最小值相差大也可使用,如果元素分布不均匀,则某一个桶内的元素可能会比较多,导致桶内的排序时间复杂度增加。桶排序需要额外的空间来存储桶,如果元素数量较大,可能会导致空间复杂度较高

算法实现C++:

void bucketSort(vector<int>& arr) {
    int n = arr.size();

    // 创建桶,每个桶存储一个区间范围内的元素
    vector<vector<int>> buckets(n);

    // 确定数组中的最大值和最小值
    int max_val = *max_element(arr.begin(), arr.end());
    int min_val = *min_element(arr.begin(), arr.end());

    // 计算每个元素应该分到哪个桶中
    int range = (max_val - min_val) / (n - 1);
    for (int i = 0; i < n; i++) {
        int idx = (arr[i] - min_val) / range;
        buckets[idx].push_back(arr[i]);
    }

    // 对每个桶中的元素进行排序
    for (int i = 0; i < n; i++) {
        sort(buckets[i].begin(), buckets[i].end());
    }

    // 将排序后的桶中元素合并为最终的排序结果
    int idx = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < buckets[i].size(); j++) {
            arr[idx++] = buckets[i][j];
        }
    }
}

十:基数排序

基数排序思想:基数排序可以做为桶排序的扩展,按照数字的位数进行排序。它适用于位数较小的数字集合。基数排序的优点在于,它可以适用于字符串的排序。在对字符串进行基数排序时,可以将每个字符映射为对应的ASCII码或Unicode码,然后按照字符的ASCII码或Unicode码进行排序。

特点:

  • 稳定性:稳定
  • 最好时间复杂度:O(n)。如果数据的位数d较小且每个桶中的数据量相对均衡,则基数排序的最好时间复杂度接近于O(n)。
  • 平均时间复杂度:O(d*(n+k))。由于d是固定的常数,因此可以简化为O(n+k)。
  • 最差时间复杂度:O(d*(n+k))。其中d为数字或字符的位数,n为输入的数据量,k为每个桶的大小。最差情况发生在每个数字或字符的位数都需要进行排序的情况下。
  • 空间复杂度:O(n+k)
  • 适用情况:适用于位数较小的数字或字符集合的排序,整数长度在10w~100w之间效率较好。

算法实现C++:

基数排序排—整数—的具体步骤如下:

1. 创建10个桶,表示数字的0-9个位数。

2. 从个位数开始,按照每个数字的个位数将数字放入对应的桶中。

3. 将所有桶中的数字按照顺序取出,形成一个新的数组。

4. 重复上述两个步骤,但是按照十位数、百位数等依次对数字进行排序。

5. 当最高位的数字排完后,最终的数组即为有序数组。

#include <iostream>
#include <vector>
using namespace std;

void countingSort(vector<int>& arr, int exp) {
    const int radix = 10;
    int n = arr.size();
    vector<int> count(radix, 0);
    vector<int> output(n);

    // 统计每个桶中的元素个数
    for (int i = 0; i < n; i++) {
        count[(arr[i] / exp) % radix]++;
    }

    // 计算每个桶中元素的结束位置
    for (int i = 1; i < radix; i++) {
        count[i] += count[i - 1];
    }

    // 将元素放入对应的桶中
    for (int i = n - 1; i >= 0; i--) {
        output[count[(arr[i] / exp) % radix] - 1] = arr[i];
        count[(arr[i] / exp) % radix]--;
    }

    // 将排序结果赋值给原始数组
    for (int i = 0; i < n; i++) {
        arr[i] = output[i];
    }
}

void radixSort(vector<int>& arr) {
    int max_val = *max_element(arr.begin(), arr.end());

    for (int exp = 1; max_val / exp > 0; exp *= 10) {
        countingSort(arr, exp);
    }
}

int main() {
    vector<int> arr = {170, 45, 75, 90, 802, 24, 2, 66};
    radixSort(arr);

    cout << "排序后的数组:";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;
  
    return 0;
}

 基数排序排—字符串—具体步骤如下:

1. 创建256个桶,表示ASCII码或Unicode码的取值范围。

2. 从字符串的最后一个字符开始,按照每个字符的ASCII码或Unicode码将字符串放入对应的桶中。

3. 将所有桶中的字符串按照顺序取出,形成一个新的字符串数组。

4. 重复上述两个步骤,但是按照倒数第二个字符、倒数第三个字符等依次对字符串进行排序。

5. 当最前面的字符排完后,最终的字符串数组即为有序字符串数组。

#include <iostream>
#include <vector>
using namespace std;

void countingSort(vector<string>& arr, int exp) {
    const int radix = 256;
    int n = arr.size();
    vector<int> count(radix, 0);
    vector<string> output(n);

    // 统计每个桶中的元素个数
    for (int i = 0; i < n; i++) {
        count[arr[i][exp]]++;
    }

    // 计算每个桶中元素的结束位置
    for (int i = 1; i < radix; i++) {
        count[i] += count[i - 1];
    }

    // 将元素放入对应的桶中
    for (int i = n - 1; i >= 0; i--) {
        output[count[arr[i][exp]] - 1] = arr[i];
        count[arr[i][exp]]--;
    }

    // 将排序结果赋值给原始数组
    for (int i = 0; i < n; i++) {
        arr[i] = output[i];
    }
}

void radixSort(vector<string>& arr) {
    int max_len = 0;
    for (string str : arr) {
        max_len = max(max_len, (int)str.length());
    }

    for (int exp = max_len - 1; exp >= 0; exp--) {
        countingSort(arr, exp);
    }
}

int main() {
    vector<string> arr = {"apple", "banana", "cat", "dog", "elephant", "fire", "goat"};
    radixSort(arr);

    cout << "排序后的字符串数组:";
    for (string str : arr) {
        cout << str << " ";
    }
    cout << endl;

    return 0;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值