1.插入排序
基本思想
插入排序的基本思想是将一个元素插入到已经排好序的有序表中,从而形成一个新的、记录数增加1的有序表。具体步骤如下:
- 将原数组分为有序数组和无序数组两部分。将数组的第一个元素视为有序数组,其余部分视为无序数组。
- 从第二个元素开始,遍历无序数组,将当前元素与有序数组中的元素进行比较(有序数组从后往前依次比较)。如果当前元素小于有序数组中的元素,则将有序数组中的元素向后移动一位,继续比较直到找到大于当前元素的位置。
- 如果当前元素大于有序数组中的最后一个元素,则直接将当前元素添加到有序数组的末尾。
代码实现
#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;
// 将arr[i]与已排序好的序列arr[0...i-1]中的元素进行比较,找到合适的位置插入
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j]; // 将大于key的元素向后移动
j = j - 1;
}
arr[j + 1] = key; // 将key插入到合适的位置
}
}
int main() {
int arr[] = {5, 2, 4, 6, 1, 3};
int n = sizeof(arr) / sizeof(arr[0]);
insertionSort(arr, n);
printf("排序后的数组: \n");
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
示例输出
对于输入数组{5, 2, 4, 6, 1, 3}
,排序后的结果为1 2 3 4 5 6
。
时间复杂度
插入排序的时间复杂度为O(n^2),其中n是数组中的元素数量。在最坏情况下(即输入数组是逆序的),需要进行n(n-1)/2次比较和交换。在最好情况下(即输入数组已经有序),只需要进行n-1次比较,不需要交换。
2.希尔排序
基本思想
- 分组:选择一个增量序列(例如n/2, n/4, n/8等),将待排序的数组分成若干个子序列,每个子序列中的元素间隔为当前的增量。
- 插入排序:对每个子序列分别进行直接插入排序。
- 缩小增量:逐步减小增量,重复上述分组和排序过程,直到增量减小到1。
- 最终排序:当增量为1时,整个数组被分成一个子序列,此时进行最后一次插入排序,完成整个排序过程。
代码实现
#include <stdio.h>
// 希尔排序函数
void shellSort(int arr[], int n) {
int gap, i, j, temp;
// 从n/2开始,每次减半,直到增量为1
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;
}
}
}
// 打印数组函数
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {9, 5, 2, 7, 8, 1, 3, 5, 4, 8, 7, 6, 2, 1, 5};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前的数组: \n");
printArray(arr, n);
shellSort(arr, n);
printf("排序后的数组: \n");
printArray(arr, n);
return 0;
}
-
在外层循环中,
j
是从i
开始递减的,每次减去gap
的值,直到arr[j - gap]
不再大于temp
。这意味着j
最终停在了第一个不大于temp
的元素的下一个位置。 -
当
j
停止递减时,arr[j]
实际上是temp
应该插入的位置。此时,arr[j - gap]
是temp
应该替换的元素,而不是之前移动的元素。因此,当我们执行arr[j] = temp;
时,我们并没有覆盖之前的交换操作,而是将temp
放到了正确的位置。
举例:
-
假设我们有一个数组
arr = {9, 8, 7, 6, 5, 4, 3, 2, 1}
,gap
为3,我们正在处理索引5(即值5)。 -
i = 5
,temp = arr[5] = 4
。 -
j = 5
,j - gap = 2
,arr[2] = 7
,因为7 > 4,我们执行arr[5] = arr[2] = 7
。 -
j = 2
,j - gap = -1
(但我们不会执行这个比较,因为j
已经小于gap
了)。 -
现在,
j
停在了2,arr[2]
是第一个不大于temp
(4)的元素。因此,我们将temp
(4)放到arr[2]
的位置,覆盖了之前的7。 -
最终,数组变为
{9, 8, 4, 6, 5, 3, 2, 1, 7}
,并且temp
(4)被正确地插入到了数组中。
示例输出
排序前的数组: 9 5 2 7 8 1 3 5 4 8 7 6 2 1 5 排序后的数组: 1 1 2 2 3 4 5 5 5 6 7 7 8 8 9
时间复杂度
希尔排序的时间复杂度受增量序列的选择影响较大。在最坏情况下,时间复杂度可能退化为O(n^2),但在实际应用中,通过合理选择增量序列,时间复杂度可以达到O(nlogn)甚至更优。
3.选择排序
基本实现步骤
- 初始化最小索引:从数组的第一个元素开始,假设该元素为当前最小值。
- 寻找最小值:遍历数组的未排序部分,找到比当前最小值更小的元素,并更新最小值的索引。
- 交换元素:将找到的最小值与当前未排序部分的第一个元素交换位置。
- 重复步骤:将已排序部分向右扩展一个元素,重复上述过程,直到所有元素排序完成。
代码实现
#include <stdio.h>
// 交换两个整数的值
void swap(int *xp, int*yp) {
int temp = *xp;
*xp =*yp;
*yp = temp;
}
// 选择排序函数
void selectionSort(int arr[], int n) {
int i, j, min_idx;
// 遍历数组
for (i = 0; i < n-1; i++) {
// 假设当前元素为最小值
min_idx = i;
// 在未排序部分寻找最小值
for (j = i+1; j < n; j++)
if (arr[j] < arr[min_idx])
min_idx = j;
// 如果找到的最小值不是当前元素,则交换
if(min_idx != i)
swap(&arr[min_idx], &arr[i]);
}
}
// 打印数组
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]);
printf("排序前的数组: \n");
printArray(arr, n);
selectionSort(arr, n);
printf("排序后的数组: \n");
printArray(arr, n);
return 0;
}
示例输出
排序前的数组: 64 25 12 22 11 排序后的数组: 11 12 22 25 64
时间复杂度
选择排序的时间复杂度为O(n^2),空间复杂度为O(1),是一种不稳定的排序算法。