冒泡排序
冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成
#include <stdio.h>
void bubbleSort(int arr[], int n) {
int i, j, temp;
for (i = 0; i < n - 1; i++) {
// 标志位,用于优化,如果在一趟排序中没有发生交换,说明序列已经有序,无需再进行排序
int swapped = 0;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1; // 标记发生了交换
}
}
// 如果在内层循环中没有发生交换,则序列已经有序,无需再进行排序
if (!swapped) {
break;
}
}
}
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
在上面的代码中,bubbleSort
函数实现了冒泡排序算法。外层循环控制遍历次数,内层循环控制每一轮的比较和交换操作。如果在一个完整的遍历过程中没有发生任何交换,那么序列已经是有序的,可以提前结束排序。
printArray
函数用于打印排序后的数组。
main
函数中定义了待排序的数组,并计算了数组的大小(元素个数)。然后调用 bubbleSort
函数进行排序,并使用 printArray
函数打印排序后的结果。
请注意,冒泡排序的时间复杂度在最坏和平均情况下是 O(n^2),其中 n 是待排序序列的大小。它对于小数据集来说是简单和有效的,但对于大数据集来说可能不是最高效的排序算法。
选择排序
选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
#include <stdio.h>
void selectionSort(int arr[], int n) {
int i, j, min_idx, temp;
for (i = 0; i < n - 1; i++) {
// 假设当前位置是最小的
min_idx = i;
// 寻找从i到n-1中的最小元素
for (j = i + 1; j < n; j++) {
if (arr[j] < arr[min_idx]) {
// 如果找到更小的元素,更新最小元素的索引
min_idx = j;
}
}
// 将找到的最小元素与当前位置i的元素交换
temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
在这个实现中,selectionSort
函数通过两个嵌套的循环来执行选择排序。外层循环控制需要进行比较的次数,内层循环用来找到当前未排序部分中的最小元素,并与未排序部分的第一个元素交换。
printArray
函数与之前的冒泡排序示例中的相同,用于打印排序后的数组。
main
函数中定义了待排序的数组,并调用了 selectionSort
函数进行排序。排序完成后,使用 printArray
函数打印排序后的结果。
选择排序的时间复杂度在最坏和平均情况下是 O(n^2),其中 n 是待排序序列的大小。与冒泡排序相比,选择排序在每一轮中通过减少比较次数来略微优化性能,但它仍然是一种效率较低的排序算法,不适用于大型数据集
插入排序
插入排序(Insertion Sort)是一种简单直观的排序算法,它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
#include <stdio.h>
void insertionSort(int arr[], int n) {
int i, key, j;
for (i = 1; i < n; i++) {
key = arr[i];
j = i - 1;
/* 将大于 key 的元素向后移动 */
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {12, 11, 13, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
在上面的代码中,insertionSort
函数实现了插入排序算法。它使用两个循环:外部循环遍历整个数组,从第二个元素开始(索引为1),因为第一个元素默认被视为已排序。内部循环负责将当前元素(key
)与已排序部分的元素进行比较,并移动大于key
的元素,从而为key
找到正确的插入位置。
printArray
函数用于打印排序后的数组,与之前的示例相同。
main
函数中定义了待排序的数组,并计算了数组的大小。然后调用 insertionSort
函数进行排序,并使用 printArray
函数打印排序后的结果。
插入排序的时间复杂度在最坏和平均情况下是 O(n^2),其中 n 是待排序序列的大小。它对于小型和部分已排序的数据集来说表现良好,但对于大型随机数据集来说效率较低。
快速排序
快速排序(Quick Sort)是一种高效的排序算法。它的基本思想是采用分而治之(Divide and Conquer)的策略,通过选择一个基准元素(pivot),将待排序的数组分为两个子数组,一个包含小于基准的元素,另一个包含大于或等于基准的元素,然后对这两个子数组递归地进行快速排序。
#include <stdio.h>
void swap(int *xp, int *yp) {
int temp = *xp;
*xp = *yp;
*yp = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high]; // 选择最后一个元素作为基准
int i = (low - 1); // 索引i指向小于基准的元素
for (int j = low; j <= high - 1; j++) {
// 如果当前元素小于或等于基准
if (arr[j] < pivot) {
i++; // 增加i
swap(&arr[i], &arr[j]); // 交换arr[i]和arr[j]
}
}
swap(&arr[i + 1], &arr[high]); // 将基准放到正确的位置
return (i + 1);
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
/* pi 是分区索引,arr[p] 现在在正确的位置 */
int pi = partition(arr, low, high);
// 分别对基准左右两边的子数组进行递归排序
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {10, 7, 8, 9, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
quickSort(arr, 0, n - 1);
printf("Sorted array: \n");
printArray(arr, n);
return 0;
}
在这个实现中,swap
函数用于交换两个整数的值。partition
函数用于将数组分为两部分,并返回基准元素的最终位置。quickSort
函数是递归的排序函数,它调用 partition
来对数组进行分区,并对基准的左右两边的子数组进行递归排序。
printArray
函数用于打印排序后的数组。
main
函数中定义了一个待排序的数组,并调用 quickSort
函数进行排序。排序完成后,使用 printArray
函数打印排序后的结果。
快速排序的平均时间复杂度为 O(n log n),在最坏情况下为 O(n^2),但这通常只在输入数组已经排序或接近排序的情况下发生。通过随机选择基准元素或使用三数取中等策略,可以降低最坏情况发生的概率。
堆排序
堆排序(Heap Sort)是一种利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序就是利用堆数据结构的设计思想,通过堆化数组,达到排序的目的。
#include <stdio.h>
// 调整堆
void heapify(int arr[], int n, int i) {
int largest = i; // 初始化largest为根
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) {
int swap = arr[i];
arr[i] = arr[largest];
arr[largest] = swap;
// 递归地调整受影响的子堆
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--) {
// 将当前最大的元素arr[0]和arr[i]交换
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 调整剩下的元素,使其满足堆的性质
heapify(arr, i, 0);
}
}
// 打印数组
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
heapSort(arr, n);
printf("Sorted array is \n");
printArray(arr, n);
return 0;
}
在这个实现中,heapify
函数负责将数组中的某个子树调整为最大堆。heapSort
函数首先构建整个数组的最大堆,然后交换堆顶元素(最大值)和最后一个元素,之后重新调整剩余元素为最大堆,如此反复,直到所有元素都排序完毕。
printArray
函数用于打印排序后的数组。
main
函数中定义了一个待排序的数组,并计算了数组的大小。然后调用 heapSort
函数进行排序,并使用 printArray
函数打印排序后的结果。
堆排序的时间复杂度是 O(n log n),其中 n 是待排序数组的元素个数。这是因为堆排序主要由构建初始堆和调整堆两部分组成,每部分的时间复杂度都是 O(n log n)。堆排序是一种原地排序算法,空间复杂度是 O(1)。
并归排序
归并排序(Merge Sort)是一种基于比较的排序算法,采用分而治之(Divide and Conquer)的策略。它将一个序列分为两个等长(几乎等长)的子序列,然后对子序列进行排序,最后将排序结果合并起来得到完全有序的序列。
#include <stdio.h>
#include <stdlib.h>
// 合并两个已排序的数组
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];
// 拷贝数据到临时数组 L[] 和 R[]
for (i = 0; i < n1; i++)
L[i] = arr[l + i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
// 合并临时数组回 arr[l..r]
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++;
}
// 拷贝 L[] 的剩余元素
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
// 拷贝 R[] 的剩余元素
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);
}
}
// 打印数组
void printArray(int arr[], int size) {
int i;
for (i = 0; i < size; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int arr_size = sizeof(arr) / sizeof(arr[0]);
printf("Given array is \n");
printArray(arr, arr_size);
mergeSort(arr, 0, arr_size - 1);
printf("\nSorted array is \n");
printArray(arr, arr_size);
return 0;
}
在这个实现中,merge
函数负责将两个已排序的子数组合并成一个有序数组。mergeSort
函数是递归函数,它将数组不断对半分割,直到只剩下一个元素,然后调用 merge
函数将子数组合并成有序数组。
printArray
函数用于打印数组。
main
函数中定义了一个待排序的数组,并计算了数组的大小。然后调用 mergeSort
函数进行排序,并使用 printArray
函数打印排序后的结果。
归并排序的时间复杂度是 O(n log n),其中 n 是待排序数组的元素个数。它的空间复杂度也是 O(n),因为需要额外的空间来存储临时数组。
希尔排序
希尔排序(Shell Sort)是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法的基本思想是:先将整个待排序的记录序列分割成为若干子序列(由相隔某个“增量”的记录组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的记录"基本有序"时,再对全体记录进行依次直接插入排序。
#include <stdio.h>
// 希尔排序函数
void shellSort(int arr[], int n) {
int gap, i, j, temp;
for (gap = n / 2; gap > 0; gap /= 2) {
// 对每个子序列进行插入排序
for (i = gap; i < n; i++) {
temp = arr[i];
// 将arr[i]插入到前面的已排序序列中
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
// 打印数组
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {9, 8, 3, 7, 5, 6, 4, 1};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array:\n");
printArray(arr, n);
shellSort(arr, n);
printf("Sorted array:\n");
printArray(arr, n);
return 0;
}
在这个实现中,shellSort
函数接受一个整数数组 arr
和它的长度 n
作为参数。gap
是增量,开始时设置为数组长度的一半,并在每次迭代中减半,直到 gap
为 1。对于每个 gap
值,算法都会对数组进行一次“部分”插入排序,这意味着它只将元素与其 gap
距离内的元素进行比较和交换。
printArray
函数用于打印数组的元素。
main
函数中定义了一个待排序的数组,并调用了 shellSort
函数进行排序。排序完成后,使用 printArray
函数打印排序后的数组。
希尔排序的时间复杂度依赖于增量序列的选取,最好的情况下时间复杂度为 O(n log n),最坏的情况下为 O(n^2)。不过,在实际应用中,希尔排序通常比简单的插入排序要快,特别是在大数据集上。
桶排序
桶排序(Bucket Sort)是一种分配式排序算法,它将待排序的数据分到有限数量的桶里,然后对每个桶里的数据进行排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果,其工作的原理是假设输入数据服从均匀分布,平均情况下它的运行时间最快,为线性时间O(n)。
#include <stdio.h>
#include <stdlib.h>
// 桶排序函数
void bucketSort(int arr[], int n) {
// 1. 找出最大值和最小值
int minVal = arr[0], maxVal = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < minVal) minVal = arr[i];
if (arr[i] > maxVal) maxVal = arr[i];
}
// 2. 计算桶的数量
int bucketSize = (maxVal - minVal) / (n - 1);
if ((maxVal - minVal) % (n - 1) != 0) bucketSize++;
// 3. 初始化桶
int bucketCount = (maxVal - minVal) / bucketSize + 1;
int *buckets = (int *)malloc(bucketCount * sizeof(int));
for (int i = 0; i < bucketCount; i++) {
buckets[i] = INT_MIN;
}
// 4. 将元素放入桶中
for (int i = 0; i < n; i++) {
int index = (arr[i] - minVal) / bucketSize;
if (buckets[index] < arr[i]) {
buckets[index] = arr[i];
}
}
// 5. 对每个桶中的元素进行排序
for (int i = 0; i < bucketCount; i++) {
int j = i + 1;
while (buckets[j] != INT_MIN) {
int temp = buckets[j];
int k;
for (k = j; k > i && temp < buckets[k - 1]; k--) {
buckets[k] = buckets[k - 1];
}
buckets[k] = temp;
j++;
}
}
// 6. 将排序后的元素放回原数组
int index = 0;
for (int i = 0; i < bucketCount; i++) {
if (buckets[i] != INT_MIN) {
while (buckets[i] != INT_MIN) {
arr[index++] = buckets[i];
buckets[i] = INT_MIN;
}
}
}
free(buckets);
}
// 打印数组
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {0.9, 0.1, 0.8, 0.3, 0.5, 0.7, 0.2, 0.4, 0.6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array:\n");
printArray(arr, n);
bucketSort(arr, n);
printf("Sorted array:\n");
printArray(arr, n);
return 0;
}
在这个实现中,bucketSort
函数首先找出数组中的最大值和最小值,然后计算桶的大小和数量。接着,它初始化桶并将元素放入桶中。然后,对每个桶中的元素进行排序(在这个例子中,我们使用了简单的插入排序)。最后,将排序后的元素放回原数组。
请注意,桶排序通常用于处理整数或浮点数,并且假设输入数据是均匀分布的。如果输入数据的分布不是均匀的,那么桶排序可能不如其他排序算法有效。此外,这个实现中假设了桶内的排序使用插入排序,但也可以使用其他排序算法。
基数排序
基数排序(Radix Sort)是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表示成字符串(如名字或日期),基数排序并不是只能用于整数。
基数排序按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的排序结果就是高优先级高的在前,高优先级相同的低优先级高的在前。
#include <stdio.h>
#include <stdlib.h>
// 获取数字的第d位数
int getDigit(int num, int d) {
return (num / (int)pow(10, d - 1)) % 10;
}
// 基数排序函数
void radixSort(int arr[], int n) {
// 找到数组中最大数的位数
int max_num = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max_num) {
max_num = arr[i];
}
}
int max_digit = 0;
while (max_num > 0) {
max_num /= 10;
max_digit++;
}
// 从最低位开始,对每位数字进行计数排序
for (int d = 1; d <= max_digit; d++) {
// 初始化桶
int bucket[10] = {0};
// 将元素放入对应的桶中
for (int i = 0; i < n; i++) {
int digit = getDigit(arr[i], d);
bucket[digit]++;
}
// 将桶中的元素放回原数组
int index = 0;
for (int i = 0; i < 10; i++) {
while (bucket[i] > 0) {
arr[index++] = i;
bucket[i]--;
}
}
}
}
// 打印数组
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {170, 45, 75, 90, 802, 24, 2, 66};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array:\n");
printArray(arr, n);
radixSort(arr, n);
printf("Sorted array:\n");
printArray(arr, n);
return 0;
}
在这个实现中,我们首先找到数组中最大数的位数,然后从最低位开始,对每一位数字使用计数排序的思想进行排序。具体地,我们创建10个桶来对应0到9这10个数字,然后遍历数组,将每个元素的当前位数字对应的桶的计数器加一。接着,我们按照桶的顺序将元素放回原数组,这样就完成了对当前位的排序。然后,我们继续对下一位进行同样的操作,直到所有位都排序完成。
基数排序的时间复杂度是O(d(n+k)),其中d是最大数的位数,n是待排序元素的个数,k是桶的数量。在最好的情况下,当n个元素都是d位数且各个位的数字分布均匀时,基数排序的时间复杂度接近O(n)。然而,在最坏的情况下,当n个元素都是同一个数时,基数排序的时间复杂度会退化为O(dn)。