一、排序
1.1 直接插入排序
1.1.1 思想
插入排序的核心操作是将待排序元素与已排序序列中的元素进行比较,并找到合适的位置进行插入。这个过程可以通过不断地将元素向右移动来实现。
插入排序的优势在于对于小规模或基本有序的数组,它的性能非常好。然而,对于大规模乱序的数组,插入排序的性能相对较差,时间复杂度为 O(n^2)。
1.1.2 排序过程演示
示例数组:{6, 3, 8, 2, 9, 1}
初始状态:
6 | 3 8 2 9 1
第一轮迭代(i=1):3 6 | 8 2 9 1
在这一轮中,待插入元素为3,我们将3与前面的6进行比较,发现3小于6,所以将6后移一位,
然后将3插入到空出的位置。第二轮迭代(i=2):
3 6 8 | 2 9 1
在这一轮中,待插入元素为8,我们将8与前面的6和3进行比较,发现8大于6和3,所以不需要
进行任何操作。第三轮迭代(i=3):
2 3 6 8 | 9 1
在这一轮中,待插入元素为2,我们将2与前面的8、6和3进行比较,发现2小于8、6和3,所以
将8、6和3分别后移一位,然后将2插入到空出的位置。第四轮迭代(i=4):
2 3 6 8 9 | 1
在这一轮中,待插入元素为9,我们将9与前面的8、6、3和2进行比较,发现9大于2、3、6和8,所以不需要进行任何操作。第五轮迭代(i=5):
1 2 3 6 8 9 |
在这一轮中,待插入元素为1,我们将1与前面的9、8、6、3和2进行比较,发现1小于9、8、6、3和2,所以将9、8、6、3和2分别后移一位,然后将1插入到空出的位置。最终排序结果为:{1, 2, 3, 6, 8, 9}
1.1.3 代码
void insertSort(int arr[], int n) {
int i, j, temp;
for (i = 1; i < n; i++) {
// 待插入元素
temp = arr[i];
// 待插入元素前一个元素
j = i - 1;
// 将比待插入元素大的元素后移一位
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
// 插入待插入元素
arr[j + 1] = temp;
}
}
1.2 希尔排序
1.2.1 思想
希尔排序是一种改进的插入排序算法,它通过比较和交换不相邻的元素来加快排序速度。
希尔排序的基本思想是将待排序数组分成若干个子序列,然后对每个子序列进行插入排序。这样做可以让数组中距离较远的元素先进行比较和交换,从而加快排序速度。
希尔排序的优势在于对于小规模或基本有序的数组,它的性能非常好。通过使用递减的步长序列,希尔排序可以将大元素尽可能地向右移动,从而加快排序的速度。此外,希尔排序是一种原地排序算法,并且相对于其它 O(n^2) 排序算法,它的常数因子较小。然而,对于大规模乱序的数组,希尔排序的性能相对较差,时间复杂度为 O(n^2)。这是因为在这种情况下,即使使用较小的步长序列,元素仍然需要进行多次比较和交换,导致效率下降。
1.2.2 排序过程演示
对初始数据序列(8,3,9,11,2,1,4,7,5,10,6)进行希尔排序,两趟排序采用的增量(间隔)依次是5、3。
来源:408真题,王道
1.2.3 代码
void shellSort(int arr[], int n) {
int i, j, gap, temp;
// 使用 Knuth 序列确定初始步长,4,1
for (gap = 1; gap < n / 3; gap = gap * 3 + 1) {}
while (gap > 0) {
printf("%d ", gap);
// 对每个步长进行插入排序
for (i = gap; i < n; i++) {
temp = arr[i];
j = i;
// 插入排序
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
// 更新步长
gap = (gap - 1) / 3;
}
}
1.3 简单选择排序
1.3.1 思想
简单选择排序也是一种基于比较的排序算法,其基本思想是每次从待排序的元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余未排序的元素中继续选择最小(或最大)的元素,然后放到已排序的元素的末尾。以此类推,直到所有待排序的元素排完为止。
简单选择排序的优点在于实现简单,代码易于理解,而且不需要占用额外的内存空间,是一种原地排序算法。同时,由于它在每一轮中只交换一次元素,因此交换的次数相对较少,适用于数据移动较为敏感的排序场景。然而,简单选择排序的缺点也非常明显,即时间复杂度较高。在最坏情况下,简单选择排序的时间复杂度为 O(n^2),因此对于大规模的数据排序时,效率比较低。
1.3.2 排序过程演示
示例数组:{6, 3, 8, 2, 9, 1}
1、首先,找到最小的元素是 1,与数组的第一个元素 6 交换位置,得到数组 {1, 3, 8, 2, 9, 6}。
2、接着,在剩余的未排序部分 {3, 8, 2, 9, 6} 中,找到最小的元素是 2,与数组的第二个元素 3 交换位置,得到数组 {1, 2, 8, 3, 9, 6}。
3、然后,在剩余的未排序部分 {8, 3, 9, 6} 中,找到最小的元素是 3,与数组的第三个元素 8 交换位置,得到数组 {1, 2, 3, 8, 9, 6}。
4、继续,在剩余的未排序部分 {8, 9, 6} 中,找到最小的元素是 6,与数组的第四个元素 8 交换位置,得到数组 {1, 2, 3, 6, 9, 8}。
5、再次,在剩余的未排序部分 {9, 8} 中,找到最小的元素是 8,与数组的第五个元素 9 交换位置,得到数组 {1, 2, 3, 6, 8, 9}。
6、最后,在剩余的未排序部分 {9} 中,只剩下一个元素,无需比较。
1.3.3 代码
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;
}
}
1.4 冒泡排序
1.4.1 思想
冒泡排序的基本思想是重复地走访需要排序的元素序列,一次比较两个元素,如果它们的顺序错误就交换它们的位置,直到序列排序完成。
冒泡排序的时间复杂度为 O(n^2),空间复杂度为 O(1),算法简单易懂,实现也较为简单,是入门级别的排序算法。但是,冒泡排序的性能较差,在大多数情况下效率低下,不适合处理大规模数据集。
1.4.2 排序过程演示
1.4.3 代码
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;
}
}
}
}
二、全部代码的源文件
#include <stdio.h>
// 插入排序函数
void insertSort(int arr[], int n) {
int i, j, temp;
for (i = 1; i < n; i++) {
// 待插入元素
temp = arr[i];
// 待插入元素前一个元素
j = i - 1;
// 将比待插入元素大的元素后移一位
while (j >= 0 && arr[j] > temp) {
arr[j + 1] = arr[j];
j--;
}
// 插入待插入元素
arr[j + 1] = temp;
}
}
// 冒泡排序函数
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;
}
}
}
}
// 简单选择排序函数
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;
}
}
// 希尔排序函数
void shellSort(int arr[], int n) {
int i, j, gap, temp;
// 使用 Knuth 序列确定初始步长,4,1
for (gap = 1; gap < n / 3; gap = gap * 3 + 1) {}
while (gap > 0) {
printf("%d ", gap);
// 对每个步长进行插入排序
for (i = gap; i < n; i++) {
temp = arr[i];
j = i;
// 插入排序
while (j >= gap && arr[j - gap] > temp) {
arr[j] = arr[j - gap];
j -= gap;
}
arr[j] = temp;
}
// 更新步长
gap = (gap - 1) / 3;
}
}
int main() {
int arr[] = {6, 3, 8, 2, 9, 1};
int n = sizeof(arr) / sizeof(arr[0]);
int i;
printf("排序前数组的元素:");
for (i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// insertSort(arr, n); // 使用直接插入排序
// bubbleSort(arr, n); // 使用冒泡排序
// selectionSort(arr, n); // 使用简单选择排序
shellSort(arr, n); // 使用希尔排序
printf("排序后数组的元素:");
for (i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}