深入解析C语言排序算法
引言:
C语言是一种泛应用于系统程和算法实现的高级编程语言。在计算机科学中,算法是解决问题的步骤和规则的有限序。本文将深入解析C语言中常见的算法,并提供示例代码和详细解释,帮助读者更好地理解和应用这些算法。
1.冒泡排序
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的列表,比较相邻的两个元素,并按大小顺序交换们,直到整个列表排序完成。冒泡排序的基本思想通过不断交换相邻元素将最大(或最小的元素逐渐“浮”到列表的末尾。
算法原理:
冒泡排序基本思想是通过不断地交换相邻的元素将最大(或最小)的元素逐渐“浮”到数列的端。具体步骤如下:
1.从第一个元素开始,比较相邻的两个元素。
2.如果顺序不正确,则交换这个元素的位置。
3.继向后遍历,重复步骤1
和步骤2
,直到遍历到最后一个元素。
4.重复述步骤,每次遍都会将当前未排序部分的最大(或最小)元素放到已排序部分的末尾。
5.重复执行n-1
次(n
为待排序元素的个数),到所有元素排序完成。
示例代码:
#include <stdio.h>
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]) {
// 交换相邻元素的位置
int 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]);
printf("原始数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
bubbleSort(arr, n);
printf("\n排序后的数组:");
for ( i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
输出结果:
原始数组:64 34 25 12 22 11 90
排序后的数组:11 12 22 25 34 64 90
注意事项:
1.冒泡排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会发生改变。
2.冒泡排序的时间复杂度为O(n^2)
,其中n为待排序元素的个数。在最坏情况,需要进行n*(n-1)/2
次比较和交换操作。
3.冒泡排序适用于小规模数据或者基本有序的数据集,对大规模乱序的数据集效率较低。
4.在实际应用中,可以考虑使用其他高效的排序算法,如快速排序、归并排序等,以提高排序率。
2.插入排序
插入排序(Insertion Sort)是一种简单直观的排序算法,它的本思想是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录增加1
的序表。
算法原理:
插入排序的基本思想是将待排序的元素按照大小顺序逐个插入到已经排序好的序列中。具体步骤如下:
1.从第一个元素开始,该元素可以认为已经被排序。
2.取出下一个元素,在已经排序的素序列中从后向前扫描。
3.如果该元素(已排序)大于新元素,将该元素移到下一位置。
4.重复步骤3
,直到找到排序的元素小于者等于新元素的位置。
5.将新元素插入该位置后。
6.重复步2~5
,直到所有元素都排序完成。
示例代码:
#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;
}
}
int main() {
int arr[] = {5, 2, 8, 12, 1};
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printf("排序后的数组:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
注意事项:
1.插入排序是一种稳定的排序算法,相等元素的相对位置在排序前后不会发改变。
2.插入排序适用于小规模数据或者基本有序的数据集,对大规模乱序的数据集效率较低。
3.在实际应用,可以考虑使用其他高效的排序算法,如快速排序、归并排序等,以提高排序效率。
3.选择排序
选择排序(Selection Sort)是一种简单直观的排序算法,它的基本思想是每次从待排序的数据中选择最小(或最大)的元素,放到已排序序列的尾,直到全部元素排序完成。
算法原理:
1.遍历待排序序,将第一个元素设为最小。
2.从第二个元素开始依次与当前最小值进行比较,如果找到更小元素,则更新最小值的索引。
3.遍历完一轮后,将最小值与第一个元素交换位置,即将最小值放到已排序序列的末尾。
4.重复步骤2
和3
,直到所有元素都排序完成。
示例代码:
#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[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
main() {
int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
注意事项:
1.选择排序是一种不稳定的排序算法,即相等元素的相对顺序可能会改变。
2.选择排序的时间复杂度为O(n^2)
,其中n
是待排序序列的长度。因此对于大规模数据排序效率较低。
3.选择排序是一种原地排序法,不需要额外的空间。
4.选择排序用于小规模数据或部分有序的数据排序。
4.快速排序
快速排序(Quick Sort)是一种常用的排序算法,它的基本想是通过分治的策略将一个大问题划分为多小问题来解决。
算法原理:
1.选择一个基元素(通常是数组的第一个或最后一个元素)作为参考点。
2.将数组分成两部分,使得左边的元素都小于等于基准元素,右边的元素都大于等于基准元素。
3.对左右两部分递归地应用快速排序算法。
使用方法:
1.创建一个待排序的数组。
2.调用 quickSort
函数,传入数组、起始索引和结束索引。
3.数组将被原地排序即在原数组进行修改。
示例代码:
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 - 1; 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, - 1);
quickSort(arr, pi + 1, high);
}
}
注意事项:
1.快速排序是一种原排序算法,不需要额外的空间。
2.在最坏情况下快速排序的时间复杂度为 O(n^2)
,但平情况下的时间复杂度为 O(nlogn)
。
3.由快速排序是递归法,对于大规模数据集可能导致栈溢出,因此可以考虑使用尾递归优化或非递归实现。
4.在实际应用中,可以选择随机选取基准元素避免最坏情况的发生。
5.归并排序
归并排序(Merge Sort)是一种常见的排序算法,它采用分治法(Divide and Conquer)的思想。
算法原理:
1.归并排序将待排序的数组不断地二分,直每个子数组只有一个元素。
2.然后将相邻的两个子数组合并,合并过程中按照从小到大的顺序将素放入新临时数组。
3.最后将临时数组中的元复制回原数组。
分割
:将待排序数组从中位置分成两个子数组,递归对子数组进行分割,直到每个子数组只一个元素。
合并
:将相邻的两个数组合并为一个有序的子数组,重复执行该操作,直到最终合并成一个完整的有序数组。
示例代码:
#include <stdio.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];
// 将数据复制到临数组
for (i = 0; i < n1; i++)
L[i] = arr[left + i];
for (j = 0; j < n2; j++)
R[j] = arr[mid + 1 + j];
// 合并临时数组到原数组
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++;
}
// 复制剩余元素
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);
}
}
// 测试示例
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
mergeSort(arr, 0, n - 1);
printf("排序后的数组:");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
注意事项:
1.归排序是一种稳的排序算法,时间复杂度为O(nlogn)
。
2.在实际使用中,归并排序需要额外的空间来存储临时数组,因此空间复杂度O(n)
。
3.当待排序数组较小时,插入排序等简单排序法可能更高效。
4.归并排序适用于链表等不随机访问的数据结构。
6.堆排序
堆排序是一种基于二叉堆数据结构的排序算法。它的主要思想将待排序的序列构建成一个大顶堆(或小顶堆),然后依次取出堆顶元素,再调整剩余元素使其重新满足堆的性质,重复这个过程直所有元素都被取出,即可得到有序序列。
算法原理:
1.构建最大堆(或最小堆):将待排序的数组看作是一个完全二叉树,并按照从左到右、从下到上的顺序依次对每个非叶子节点进行调整,使得每个节点的值都大于(或小于)其子节点的值。个过程称为堆化(heapify)。最大堆的堆顶元素为数组中的最大值。
2.将堆顶元与最后一个元素交换位置,然后将堆的大小减1
,相当于将最大值到了数组的末尾。
3.对剩余的堆进行调整,使其满足堆的性质。
4.重复步骤2
和步骤3
,直到堆的大小为1
,此时整个数组就已经有序。
示例代码:
#include <stdio.h>
// 交换两个元素的值
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 调整堆使其满足堆的性质
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); 调整剩余素,使其满足堆的性质
}
}
// 打印数组元素
void printArray(int arr[], int n) {
for (int i = 0; i < n; ++i)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = { 12, 11, 13, 5, 6, 7 };
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组:\n");
printArray(arr, n);
heapSort(arr, n);
printf("排序后的数组:\n");
printArray(arr, n);
return 0;
}
注意事项:
1.堆排序是一种不稳定的排序算法,即相等素的相对顺可能会改变。
2.在堆排序中,数组下标从0
开始,左子节点的引为2 * i + 1
,右子节点的索引为2 * i + 2
。
3.构建堆过程是从最一个非叶子节点开始,依次前进行调整。
4.构建最大堆或最小堆)的时间复杂度O(n)
,其中n
是待排序数组的长度每次调整堆的时间杂度为O(log n)
。
7.希尔排序
希尔排序(Shell Sort)是一种基于插入排序的排序算法,也称缩小增量排序。它通过将待排序的元素分组,对每个分组进行插入排序,逐步减小分组的大小,最终完成整体的排序。
希尔排序的优点是在大部分情况下比插入排序和冒泡排序更快,尤其是于较大规的数据集。然而,希尔排序的时间复杂度并不容易确定,因为它取决于选的增量序。
算法原理:
1.首先,选择一个增量序列(increment sequence),通常使用希尔增量(Shell’s increments)。希尔增量可以是任意正数序列,但常用的是N/2
、N/4
、N/8
…直到增量为1
。其中N
待排序数组的长度。
2.根据选定的增序列,将待排序数组分成若个子序列,每个子序列包含相隔增量个位置的元素。
3.对每个子序列进行插排序,即使用插排序算法对每个子序列进行排序。
4.不断小增量,重复步骤2
和步骤3
,直到增量为1
。
5.最后,使用增量为1
的入排序对整个数组进行最后一次排序。
示例代码:
#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];
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
arr[j] = arr[j - gap];
}
arr[j] = temp;
}
}
}
int main() {
int arr[] = {9, 5, 1, 4, 3, 7, 6, 2, 8};
int n = sizeof(arr) / sizeof(arr[0]);
printf("Original array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
shellSort(arr, n);
printf("\nSorted array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
注意事项:
1.希尔排序是一种不稳定的排序算法,相等元素相对顺序可能会改变。
2.增序列的选择希尔排序的性能影响很大,同的增量列可能导致不同的时间复度。
3.希尔排序的时间复杂度通常介于O(n)
和O(n^2)
之间,体取决于增量序列的选择。
4.希尔排序对于小规模数据集可能不如其他简单的排序算法效率高,因此在际应用中需要根数据规模进行选择。
8.结语
望这篇博客对你有所帮助!如果你还有其他问题,请随时提问。