C 语言十大排序算法
物协算法培训 1 排序算法
1. 冒泡排序 (Bubble Sort)
-
思想:通过相邻元素之间的比较和交换,每次将最大(或最小)的元素“冒泡”到数组的末尾。
-
实现:使用嵌套循环,外层控制循环次数,内层进行相邻元素的比较和交换。
-
时间复杂度:O(n^2)。即使是最优情况下,也需要进行完整的一轮比较。
#include <stdio.h> void bubbleSort(int arr[], int n) { int i, j, temp; for (i = 0; i < n-1; i++) { // 每次循环都将最大的元素移到末尾 for (j = 0; j < n-i-1; j++) { if (arr[j] > arr[j+1]) { // 交换元素 temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } } 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"); for (int i=0; i < n; i++) printf("%d ", arr[i]); printf("\n"); return 0; }
2. 选择排序 (Selection Sort)
-
思想:每次选择未排序部分的最小元素,然后将其放置在已排序部分的末尾。
-
实现:通过扫描未排序的部分找到最小的元素,并与未排序部分的第一个元素交换。
-
时间复杂度:O(n^2)。在任何情况下,都需要进行 n(n-1)/2 次比较。
#include <stdio.h> // 选择排序函数 void selectionSort(int arr[], int n) { int i, j, minIndex, temp; // 声明循环和临时变量 // 外层循环用于控制未排序部分的起始位置 for (i = 0; i < n - 1; i++) { // 初始化未排序部分的最小元素索引为当前位置 minIndex = i; // 内层循环用于寻找未排序部分的最小元素 for (j = i + 1; j < n; j++) { if (arr[j] < arr[minIndex]) { minIndex = j; // 更新最小元素的索引 } } // 将未排序部分的最小元素交换到已排序部分的末尾 temp = arr[minIndex]; arr[minIndex] = arr[i]; arr[i] = temp; } } int main() { int arr[] = {64, 25, 12, 22, 11}; // 给定的数组 int n = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小 selectionSort(arr, n); // 调用选择排序函数对数组进行排序 printf("Sorted array: \n"); for (int i = 0; i < n; i++) printf("%d ", arr[i]); // 打印排序后的数组 printf("\n"); return 0; }
3. 插入排序 (Insertion Sort)
-
思想:将数组视为已排序和未排序两部分,每次将未排序部分的元素插入到已排序部分的适当位置。
-
实现:逐个将未排序部分的元素插入已排序部分的正确位置。
-
时间复杂度:O(n^2)。最坏情况下,需要移动大量元素。
#include <stdio.h> // 插入排序函数 void insertionSort(int arr[], int n) { int i, key, j; // 声明循环和临时变量 // 从数组的第二个元素开始,逐个将元素插入到已排序部分的正确位置 for (i = 1; i < n; i++) { key = arr[i]; // 将当前元素存储在临时变量 key 中 j = i - 1; // j 用于指向已排序部分的最后一个元素 // 将比 key 大的元素向后移动,为 key 腾出插入的位置 while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; // 向后移动元素 j = j - 1; // 更新 j 的值,继续向前比较 } arr[j + 1] = key; // 将 key 插入到正确的位置 } } int main() { int arr[] = {12, 11, 13, 5, 6}; // 给定的数组 int n = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小 insertionSort(arr, n); // 调用插入排序函数对数组进行排序 printf("Sorted array: \n"); for (int i = 0; i < n; i++) printf("%d ", arr[i]); // 打印排序后的数组 printf("\n"); return 0; }
4. 希尔排序 (Shell Sort)
-
思想:是插入排序的一种改进版本,通过比较相隔一定间隔的元素,实现部分排序,最后再进行插入排序。
-
实现:逐渐减小间隔,对每个间隔进行插入排序。
-
时间复杂度:取决于间隔序列的选择。通常介于 O(n log n) 和 O(n^2) 之间。
#include <stdio.h> // 希尔排序函数 void shellSort(int arr[], int n) { int i, j, temp, gap; // 声明循环和临时变量 // 初始时,将间隔(步长)设置为数组长度的一半,然后逐步缩小间隔 for (gap = n / 2; gap > 0; gap /= 2) { // 对每个间隔进行插入排序 for (i = gap; i < n; i++) { temp = arr[i]; // 将当前元素存储在临时变量中 // 对当前间隔内的元素进行插入排序 for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { arr[j] = arr[j - gap]; // 向后移动元素 } arr[j] = temp; // 将临时变量中存储的值放入正确的位置 } } } int main() { int arr[] = {12, 34, 54, 2, 3}; // 给定的数组 int n = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小 shellSort(arr, n); // 调用希尔排序函数对数组进行排序 printf("Sorted array: \n"); for (int i = 0; i < n; i++) printf("%d ", arr[i]); // 打印排序后的数组 printf("\n"); return 0; }
5. 归并排序 (Merge Sort)
-
思想:采用分治法,将数组分为两半,分别排序,然后合并已排序的部分。
-
实现:递归地将数组分成更小的数组,然后两两合并,直到整个数组有序。
-
时间复杂度:始终为 O(n log n)。递归拆分和合并的复杂度均为 O(n log n)。
#include <stdio.h> #include <stdlib.h> // 合并两个已排序的子数组 void merge(int arr[], int left, int mid, int right) { int i, j, k; int n1 = mid - left + 1; // 左子数组的大小 int n2 = right - mid; // 右子数组的大小 // 创建临时数组 int L[n1], R[n2]; // 将数据复制到临时数组 L 和 R for (i = 0; i < n1; i++) L[i] = arr[left + i]; for (j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; // 合并临时数组到 arr i = 0; j = 0; k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } // 将剩余元素复制到 arr while (i < n1) { arr[k] = L[i]; i++; k++; } while (j < n2) { arr[k] = R[j]; j++; k++; } } // 归并排序 void mergeSort(int arr[], int left, int right) { if (left < right) { // 找到中间点 int mid = left + (right - left) / 2; // 对左右两部分递归进行归并排序 mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); // 合并已排序的两部分 merge(arr, left, mid, right); } } // 输出数组 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = {38, 27, 43, 3, 9, 82, 10}; // 给定的数组 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; }
6. 快速排序 (Quick Sort)
-
思想:同样采用分治法,通过选定一个基准值,将数组分为比基准值小和比基准值大的两部分,然后递归地对这两部分进行排序。
-
实现:选取基准值,分割数组,递归排序子数组,最后合并。
-
时间复杂度:平均时间复杂度为 O(n log n),最坏情况为 O(n^2)。
#include <stdio.h> // 交换元素 void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; } // 划分数组并返回划分点的索引 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选择最后一个元素作为基准值 int i = (low - 1); // 初始化较小元素的索引 for (int j = low; j <= high - 1; j++) { // 如果当前元素小于或等于基准值 if (arr[j] <= pivot) { i++; // 较小元素的索引加1 swap(&arr[i], &arr[j]); // 交换 arr[i] 和 arr[j] } } swap(&arr[i + 1], &arr[high]); // 交换 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); } } // 输出数组元素 void printArray(int arr[], int size) { for (int 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; }
7. 堆排序 (Heap Sort)
-
思想:利用堆这种数据结构,将数组看作是完全二叉树,并且满足堆的性质(大顶堆或小顶堆)。
-
实现:构建最大堆或最小堆,然后不断将堆顶元素与末尾元素交换,再重新调整堆。
-
时间复杂度:O(n log n)。
#include <stdio.h> // 堆调整函数,将以 root 为根的子树调整为最大堆 void heapify(int arr[], int n, int root) { int largest = root; // 将根节点设置为最大值 int left = 2 * root + 1; // 左子节点索引 int right = 2 * root + 2; // 右子节点索引 // 如果左子节点大于根节点 if (left < n && arr[left] > arr[largest]) largest = left; // 如果右子节点大于根节点 if (right < n && arr[right] > arr[largest]) largest = right; // 如果最大值不是根节点,交换根节点和最大值,并递归调整 if (largest != root) { int temp = arr[root]; arr[root] = arr[largest]; arr[largest] = temp; // 递归调整 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--) { // 将堆顶元素(最大值)与当前未排序部分的最后一个元素交换 int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; // 重新构建堆 heapify(arr, i, 0); } } // 输出数组元素 void printArray(int arr[], int size) { for (int 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: \n"); printArray(arr, n); return 0; }
8. 计数排序 (Counting Sort)
-
思想:适用于已知数据范围的整数排序,通过统计每个元素的出现次数,然后进行排序。
-
实现:创建一个辅助数组来统计每个元素出现的次数,然后根据统计信息,将元素放置到正确的位置。
-
时间复杂度:O(n + k),其中 k 是数据范围。
#include <stdio.h> #include <stdlib.h> void countingSort(int arr[], int n, int range) { int output[n]; // 用于存储排序后的数组 int count[range + 1]; // 计数数组,存储每个元素的出现次数 // 将计数数组的所有元素初始化为 0 for (int i = 0; i <= range; ++i) count[i] = 0; // 统计每个元素出现的次数 for (int i = 0; i < n; ++i) ++count[arr[i]]; // 修改计数数组,使其存储元素的最终位置 for (int i = 1; i <= range; ++i) count[i] += count[i - 1]; // 将元素放置到 output 数组中 for (int i = n - 1; i >= 0; --i) { output[count[arr[i]] - 1] = arr[i]; --count[arr[i]]; } // 将 output 数组复制到原数组中,完成排序 for (int i = 0; i < n; ++i) arr[i] = output[i]; } // 输出数组元素 void printArray(int arr[], int size) { for (int i = 0; i < size; ++i) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = {4, 2, 2, 8, 3, 3, 1}; int n = sizeof(arr) / sizeof(arr[0]); int range = 8; // 范围为 0 到 8 的整数 printf("Original array: \n"); printArray(arr, n); countingSort(arr, n, range); printf("Sorted array: \n"); printArray(arr, n); return 0; }
9. 桶排序 (Bucket Sort)
-
思想:将数组划分为若干个桶,每个桶内部再进行排序,然后将各个桶中的元素合并得到最终的排序结果。
-
实现:确定桶的数量和范围,将元素分配到对应的桶中,然后对每个桶内的元素进行排序。
-
时间复杂度:取决于桶的数量和内部排序算法。
#include <stdio.h> #include <stdlib.h> // 桶排序中的桶数据结构 struct Node { int data; struct Node* next; }; // 插入排序,用于桶内部排序 void insertionSort(struct Node* bucket) { struct Node* i, *j; for (i = bucket->next; i != NULL; i = i->next) { int temp = i->data; for (j = i->next; j != NULL && j->data < temp; j = j->next) j->next->data = j->data; j->data = temp; } } // 桶排序 void bucketSort(int arr[], int n, int max_value) { int i, j; // 创建桶数组,其中每个桶用链表来实现 struct Node* buckets[max_value]; // 初始化桶数组 for (i = 0; i < max_value; i++) buckets[i] = NULL; // 将数组元素分配到对应的桶中 for (i = 0; i < n; i++) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = arr[i]; newNode->next = NULL; int index = arr[i] / max_value; if (buckets[index] == NULL) buckets[index] = newNode; else { struct Node* temp = buckets[index]; while (temp->next != NULL) temp = temp->next; temp->next = newNode; } } // 对每个桶进行插入排序 for (i = 0; i < max_value; i++) { if (buckets[i] != NULL) insertionSort(buckets[i]); } // 将排序后的元素放回原数组 int index = 0; for (i = 0; i < max_value; i++) { struct Node* temp = buckets[i]; while (temp != NULL) { arr[index++] = temp->data; temp = temp->next; } } // 释放动态分配的内存 for (i = 0; i < max_value; i++) { struct Node* temp = buckets[i]; while (temp != NULL) { struct Node* prev = temp; temp = temp->next; free(prev); } } } // 输出数组元素 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) printf("%d ", arr[i]); printf("\n"); } int main() { int arr[] = {29, 25, 3, 49, 9, 37, 21, 43}; // 给定的数组 int n = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小 int max_value = 50; // 最大值范围 printf("Original array: \n"); printArray(arr, n); // 打印原始数组 bucketSort(arr, n, max_value); // 调用桶排序函数对数组进行排序 printf("Sorted array: \n"); printArray(arr, n); // 打印排序后的数组 return 0; }
10. 基数排序 (Radix Sort)
-
思想:按照位数进行排序,从最低有效位(个位)到最高有效位(最高位),每个位上进行稳定排序。
-
实现:根据位数进行排序,可以借助计数排序等稳定排序算法。
-
时间复杂度:O(d * (n + k)),其中 d 是数字的最大位数,k 是基数(常数)。
#include <stdio.h> // 获取数组中的最大值 int getMax(int arr[], int n) { int max = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] > max) max = arr[i]; } return max; } // 对数组按照指定位数进行计数排序(基数排序中的一步) void countSort(int arr[], int n, int exp) { int output[n]; int count[10] = {0}; // 统计每个数字出现的次数 for (int i = 0; i < n; i++) count[(arr[i] / exp) % 10]++; // 修改 count[i],使其包含小于等于 i 的元素个数 for (int i = 1; i < 10; i++) count[i] += count[i - 1]; // 从原数组中构建输出数组 for (int i = n - 1; i >= 0; i--) { output[count[(arr[i] / exp) % 10] - 1] = arr[i]; count[(arr[i] / exp) % 10]--; } // 将输出数组复制到原数组中 for (int i = 0; i < n; i++) arr[i] = output[i]; } // 基数排序 void radixSort(int arr[], int n) { int max = getMax(arr, n); // 获取数组中的最大值 // 对每一位进行计数排序,exp 表示位数,从个位到最高位 for (int exp = 1; max / exp > 0; exp *= 10) countSort(arr, n, exp); } // 输出数组元素 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; }