交换排序是一类通过比较和交换元素位置来实现排序的算法。其核心思想是在序列中进行两两比较,若元素顺序不符合排序要求,则交换它们的位置。常见的交换排序算法包括冒泡排序和快速排序,它们在不同场景下各有优劣。
一种简单的交换排序算法,通过多次比较相邻元素并交换位置,使较大(或较小)的元素逐渐 “冒泡” 到数组的末尾(或开头)。其时间复杂度在最坏和平均情况下为 O (n²),空间复杂度为 O (1),适用于小规模数据的排序。
高效的交换排序算法,采用分治思想。通过选择基准元素并进行分区操作,将数组分为两部分,分别对两部分递归排序。平均时间复杂度为 O (n log n),最坏时间复杂度为 O (n²)(当基准元素选择不当,如数组已经有序时),空间复杂度在平均情况下为 O (log n),适用于大规模数据的排序。
快速排序中的关键操作。将数组按照基准元素分为两部分,左边部分的元素都小于基准元素,右边部分的元素都大于基准元素。这个操作确定了基准元素在最终有序数组中的正确位置,并且使得左边和右边的子数组都更接近有序状态,为后续递归排序做好准备。
#include
void bubbleSort(int arr[], int sz) {
int i = 0;
int j = 0;
int temp = 0; // 第三方存储器
int flag = 1; // 用于查看是否不需要排序,1 --- 不需要排序,0 --- 需要排序
// 遍历数组多趟
for (i = 0; i < sz - 1; i++) {
// 每一趟的操作
flag = 1; // 每趟都需要重置
for (j = 0; j < sz - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
flag = 0;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// 如果不需要排序,则直接结束函数
if (flag) {
return;
}
}
}
int main() {
int arr[] = { 1, 5, 2, 12, 54, 2, 23 };
int sz = sizeof(arr) / sizeof(arr[0]); // 数组大小
bubbleSort(arr, sz); // 冒泡排序
// 查看排序结果
int i = 0;
for (i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
return 0;
}
在这段代码中,外层循环控制排序的轮数,每一轮会将一个最大元素移到数组末尾。内层循环负责在每一轮中进行相邻元素的比较和交换,比较的范围随着轮数增加而逐渐减少(sz - 1 - i)。
同时我还引入了一个flag标志位,用于优化排序过程。在每一轮开始时,将flag置为 1,如果在某一轮的比较中发生了元素交换,就将flag置为 0。如果某一轮结束后flag仍为 1,说明在这一轮中没有元素交换,数组已经有序,此时可以提前结束排序,从而提高排序效率。
#include
// 分区函数:将数组分为两部分,返回基准值的位置
int partition(int arr[], int left, int right) {
int pivotKey = arr[left]; // 选择第一个元素作为基准值
while (left < right) { // 当左指针小于右指针时循环
// 从右向左扫描,找到第一个小于基准值的元素
while (left < right && arr[right] >= pivotKey) {
right--; // 右指针左移
}
arr[left] = arr[right]; // 将小于基准值的元素放到左边
// 从左向右扫描,找到第一个大于基准值的元素
while (left < right && arr[left] <= pivotKey) {
left++; // 左指针右移
}
arr[right] = arr[left]; // 将大于基准值的元素放到右边
}
arr[left] = pivotKey; // 将基准值放到正确的位置
return left; // 返回基准值的位置
}
// 快速排序函数
void qSort(int arr[], int left, int right) {
// 如果左指针小于右指针,继续排序
if (left < right) {
int pivotLoc = partition(arr, left, right); // 获取基准值的位置
// 递归排序左半部分
qSort(arr, left, pivotLoc - 1);
// 递归排序右半部分
qSort(arr, pivotLoc + 1, right);
}
}
int main() {
int arr[] = { 1, 5, 2, 12, 54, 2, 23 }; // 待排序的数组
int sz = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小
qSort(arr, 0, sz - 1); // 调用快速排序函数
// 输出排序后的数组
printf("排序后的数组: ");
for (int i = 0; i < sz; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0; // 程序正常结束
}
在这段代码中,partition 函数负责实现分区操作。首先选择数组的第一个元素 arr[left] 作为基准值 pivotKey。然后通过两个嵌套的 while 循环,从数组的两端向中间扫描。第一个内层 while 循环从右向左扫描,找到第一个小于基准值的元素,将其移动到左边;第二个内层 while 循环从左向右扫描,找到第一个大于基准值的元素,将其移动到右边。
这样不断交换,直到左指针 left 和右指针 right 相遇,此时将基准值 pivotKey 放到正确的位置 arr[left] ,并返回 left,即基准值在数组中的最终位置。
qSort 函数是快速排序的递归实现部分。当 left 小于 right 时,调用 partition 函数获取基准值的位置 pivotLoc,然后递归地对基准值左边的子数组 arr[left...pivotLoc - 1] 和右边的子数组 arr[pivotLoc + 1...right] 进行排序,从而实现整个数组的快速排序。
在这篇博客中,我们可以感受到两种算法的区别。一个就是冒泡排序,它简单直观,通过如上述代码中的标志位优化,在一定程度上能提升效率,但其整体效率仍较低,适用于小规模数据;另一个就是快速排序,虽然高效,但实现相对复杂,适用于大规模数据。
希望这篇博客对你有所帮助,感谢阅读。