我用GPT-3.5写了一个关于C++排序的博客,大家看看和我手写的哪一个好
手写版:你还不懂排序?那是你没看到这篇文章…_我爱OJ的博客-CSDN博客
以下内容来自GPT-3.5大模型:
目录
C是一门常用于编写高效的系统级程序和应用程序的编程语言,它支持多种排序算法。在这篇文章中,我们将深入讨论C中的各种排序算法。我们将首先介绍排序算法的基本概念,然后讨论比较排序和非比较排序,接着逐一介绍各种排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序和基数排序。最后我们还将讨论如何选择最适合的排序算法。本文将深入讨论排序算法,旨在为C++程序员提供一份有用的参考。
一、排序算法的基本概念
排序指将一组数据按照特定的方式重新排列的过程。在实际应用中,我们需要选择最适合的排序算法。常见的排序算法分为两大类:比较排序和非比较排序。
比较排序:比较排序指通过比较数组元素之间的大小关系进行排序的算法。常见的比较排序算法包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序等。
非比较排序:非比较排序是不需要比较数组元素之间的大小关系,而是根据其他的性质对元素进行排序。常见的非比较排序算法包括计数排序和基数排序。
在实际应用中,我们需要考虑排序算法的时间复杂度、空间复杂度、稳定性、在不同数据量及数据之间相关性下的表现,以及算法本身的实现难度等因素。不同排序算法有不同的优缺点,根据实际情况选择最适合的算法是至关重要的。
二、比较排序算法
1、冒泡排序
冒泡排序是一种简单的比较排序算法,它通过比较相邻的元素交换位置实现排序。冒泡排序的基本思想是,从数组的第一个元素开始,依次比较相邻的两个元素的大小,如果前一个元素比后一个元素大,则交换它们的位置,这样一次遍历后,最大的元素就被移动到了数组的尾部。再对未排序部分重复上述操作,直到整个数组排序完成。
冒泡排序是一种时间复杂度为O(n^2)的算法,在处理大量数据时效率较低。鉴于其实现简单,仍然有一定的应用场景。
C++代码实现
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
swap(arr[j], arr[j+1]);
}
}
}
}
2、选择排序
选择排序是一种简单的比较排序算法,类似于冒泡排序,只是每次遍历只进行一次交换。选择排序的基本思想是,从数组的第一个元素开始,依次找到数组中最小的元素,将其与数组的第一个元素交换位置。再从数组的第二个元素开始,找到数组中第二小的元素,将其与数组的第二个元素交换位置,以此类推。
选择排序的时间复杂度也是O(n^2),与冒泡排序相同。虽然选择排序的时间复杂度与冒泡排序相同,但由于每次遍历只进行一次交换,因此在实际应用中有一定的优势。
C++代码实现
void selectionSort(int arr[], int n) {
int minIndex;
for (int i = 0; i < n-1; i++) {
minIndex = i;
for (int j = i+1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
swap(arr[i], arr[minIndex]);
}
}
3、插入排序
插入排序是一种简单的比较排序算法,基本思想是将待排序的元素插入到已经排好序的元素中。插入排序的过程,就像我们将扑克牌重新排列的过程一样。插入排序的时间复杂度也是O(n^2)。它比冒泡排序和选择排序要快一些,因为在实际应用中,数组的有序程度越高,插入排序的表现会越好。
C++代码实现
void insertionSort(int arr[], int 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;
}
}
4、希尔排序
希尔排序是一种改进的插入排序算法,基本思想是将待排序的元素分组进行插入排序,再逐步减小分组的规模。希尔排序的时间复杂度为O(nlogn),它比插入排序等O(n^2)的算法有更好的表现。而且,希尔排序的实现比较简单,是一个常用的排序算法之一。
C++代码实现
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;
for (j = i; j >= gap && arr[j-gap] > temp; j -= gap) {
arr[j] = arr[j-gap];
}
arr[j] = temp;
}
}
}
5、归并排序
归并排序是一种分治法的排序算法。它的基本思想是,将数组分成两部分,每一部分继续分成两部分,直到分成单元素数组。然后将各个单元素数组进行合并,合并时按大小顺序进行排序,合并完成后就得到了一个有序的数组。归并排序的时间复杂度为O(nlogn),它是一种稳定的排序算法。
C++代码实现
void merge(int arr[], int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
int L[n1], R[n2];
for (i = 0; i < n1; i++) {
L[i] = arr[l+i];
}
for (j = 0; j < n2; j++) {
R[j] = arr[m+1+j];
}
i = 0;
j = 0;
k = l;
while (i < n1 && j < n2) {
if(L[i] <= R[j]) {
arr[k] = L[i];
i++;
}
else {
arr[k] = R[j];
j++;
}
k++;
}
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(int arr[], int l, int r) {
if (l < r) {
int m = l + (r-l)/2;
mergeSort(arr, l, m);
mergeSort(arr, m+1, r);
merge(arr, l, m, r);
}
}
6、快速排序
快速排序是一种分治法的排序算法。它的基本思想是,通过选择一个基准点,将待排序的数组不断划分成两个子数组,直到所有子数组的长度都为1或0。划分过程中,对于左子数组,所有元素都比基准点小,而对于右子数组,所有元素都比基准点大。然后将左、右子数组递归地进行快速排序,最后将左、右子数组合并起来即可得到一个有序的数组。快速排序的时间复杂度为O(nlogn),受数据分布的影响,最坏情况下时间复杂度可达到O(n^2),但实际应用时,快速排序通常是速度最快的。
C++代码实现
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 pi = partition(arr, low, high);
quickSort(arr, low, pi-1);
quickSort(arr, pi+1, high);
}
}
7、堆排序
堆排序是一种基于二叉堆的选择排序算法。堆排序的基本思想是,将待排序的数组转化为一个大顶堆或小顶堆,然后逐一将堆顶元素与最后一个元素交换,直到所有元素都被排序。堆排序的时间复杂度为O(nlogn),它是一种稳定的排序算法。
C++代码实现
void heapify(int arr[], int n, int i) {
int largest = i;
int left = 2*i+1;
int right = 2*i+2;
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
for (int i = n/2-1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n-1; i > 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
三、非比较排序算法
1、计数排序
计数排序是一种非比较排序算法,它用于排序的数的范围较小,例如在0到100之间。计数排序的基本思想是,统计数组中每个元素出现的次数,然后根据出现次数将元素排序。
计数排序的时间复杂度为O(n+k),其中n是数组的长度,k是数的范围。由于需要创建一个频率数组来存储元素出现的次数,因此需要额外的内存空间,空间复杂度为O(k)。
C++代码实现
void countingSort(int arr[], int n) {
int maxVal = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
int count[maxVal+1], output[n];
memset(count, 0, sizeof(count));
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
for (int i = 1; i <= maxVal; i++) {
count[i] += count[i-1];
}
for (int i = n-1; i >= 0; i--) {
output[count[arr[i]]-1] = arr[i];
count[arr[i]]--;
}
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
}
2、基数排序
基数排序是一种非比较排序算法。它的基本思想是,将待排序的数组分解成若干个数字位(例如个位、十位、百位等等),对每一位进行排序,最后组合起来得到有序的序列。
基数排序的时间复杂度为O(d(n+k)),其中d是数字的位数,n是数组的长度,k是数字范围。由于需要额外的存储空间来存储排序结果,因此空间复杂度为O(n+k)。
C++代码实现
int maxDigit(int arr[], int n) {
int maxVal = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
int digit = 0;
while (maxVal != 0) {
maxVal /= 10;
digit++;
}
return digit;
}
void radixSort(int arr[], int n) {
int digit = maxDigit(arr, n);
int count[10], output[n];
int exp = 1;
for (int i = 0; i < digit; i++) {
memset(count, 0, sizeof(count));
for (int j = 0; j < n; j++) {
count[(arr[j]/exp)%10]++;
}
for (int j = 1; j < 10; j++) {
count[j] += count[j-1];
}
for (int j = n-1; j >= 0; j–) {
output[count[(arr[j]/exp)%10]-1] = arr[j];
count[(arr[j]/exp)%10]–;
}
for (int j = 0; j < n; j++) {
arr[j] = output[j];
}
exp *= 10;
}
}
快速排序(Quick Sort)
快速排序是一种分治算法,其核心思想是取一个元素作为基准,将比基准小的元素都放在基准的左侧,将比基准大的元素都放在基准的右侧,然后对左右两侧的子数组分别进行快速排序,最终得到排序后的数组。
快速排序的时间复杂度为O(nlogn)。虽然在最坏情况下快速排序会退化成O(n^2)的时间复杂度,但是由于基准元素的选取和划分策略的选择可以在很大程度上避免这种情况的发生,因此快速排序在实际应用中表现良好。
C++代码实现
void merge(int arr[], int left, int mid, int right) {
int len = right - left + 1;
int temp[len];
int i = left, j = mid+1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) {
temp[k++] = arr[i++];
}
while (j <= right) {
temp[k++] = arr[j++];
}
for (int p = 0; p < len; p++) {
arr[left+p] = temp[p];
}
}
void mergeSort(int arr[], int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid+1, right);
merge(arr, left, mid, right);
}
归并排序(Merge Sort)
归并排序是一种稳定的排序算法,其核心思想是将待排序的数组从中间分成左右两个子数组,对左右两个子数组分别进行排序,最终将两个有序的子数组合并成一个有序数组。
归并排序的时间复杂度为O(nlogn),但是由于归并排序需要使用额外的空间来存储临时数据,因此其空间复杂度较高。
C++代码实现
void heapify(int arr[], int n, int i) {
int largest = i;
int l = 2*i+1;
int r = 2*i+2;
if (l < n && arr[l] > arr[largest]) {
largest = l;
}
if (r < n && arr[r] > arr[largest]) {
largest = r;
}
if (largest != i) {
swap(arr[i], arr[largest]);
heapify(arr, n, largest);
}
}
void heapSort(int arr[], int n) {
for (int i = n/2-1; i >= 0; i--) {
heapify(arr, n, i);
}
for (int i = n-1; i >= 0; i--) {
swap(arr[0], arr[i]);
heapify(arr, i, 0);
}
}
堆排序(Heap Sort)
堆排序是一种基于堆数据结构的排序算法。其核心思想是利用堆数据结构来维护待排序的元素,并依次将堆顶元素取出放入已排序的数组中。堆排序分为两个步骤:构建堆和取出元素。
堆排序的时间复杂度为O(nlogn),但是由于堆排序需要使用额外的空间来存储堆数据结构,因此其空间复杂度较高。
C++代码实现
void countingSort(int arr[], int n, int k) {
int count[k], output[n];
for (int i = 0; i < k; i++) {
count[i] = 0;
}
for (int i = 0; i < n; i++) {
count[arr[i]]++;
}
for (int i = 1; i < k; i++) {
count[i] += count[i-1];
}
for (int i = n-1; i >= 0; i--) {
output[count[arr[i]]-1] = arr[i];
count[arr[i]]--;
}
for (int i = 0; i < n; i++) {
arr[i] = output[i];
}
}
计数排序(Counting Sort)
计数排序是一种稳定的排序算法,其核心思想是统计待排序数组中每个元素出现的次数,然后依次取出已排序的元素。计数排序只适用于数据量较小、值域范围较小的情况。
计数排序的时间复杂度为O(n+k),其中k为数据的值域,因此当k较大时,计数排序的时间复杂度也会比较大。
C++代码实现
int maxDigit(int arr[], int n) {
int maxVal = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
int digit = 0;
while (maxVal != 0) {
maxVal /= 10;
digit++;
}
return digit;
}
void radixSort(int arr[], int n) {
int digit = maxDigit(arr, n);
int count[10], output[n];
int exp = 1;
for (int i = 0; i < digit; i++) {
memset(count, 0, sizeof(count));
for (int j = 0; j < n; j++) {
count[(arr[j]/exp)%10]++;
}
for (int j = 1; j < 10; j++) {
count[j] += count[j-1];
}
for (int j = n-1; j >= 0; j--) {
output[count[(arr[j]/exp)%10]-1] = arr[j];
count[(arr[j]/exp)%10]--;
}
for (int j = 0; j < n; j++) {
arr[j] = output[j];
}
exp *= 10;
}
}
基数排序(Radix Sort)
基数排序的核心思想是将待排序的元素从低位到高位依次进行排序,每次排序时,根据位数的不同,使用计数排序(Counting Sort)作为子程序来进行元素排序。因此,实现基数排序需要一个计数排序算法作为子程序。
基数排序的时间复杂度为O(d(n+k)),其中d为最大数的位数,k为基数。因此,当k比较小而d比较大时,基数排序算法的时间复杂度也会比较小。
总结
本文介绍了C++中常用的六种排序算法:快速排序、归并排序、堆排序、计数排序、基数排序和选择排序。这些排序算法具有不同的时间复杂度和空间复杂度,因此在实际应用中选择合适的算法对于提高程序的运行效率非常重要。