插入排序
简述
插入排序是一种简单直观的排序方法,其基本思想在于每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成。插入排序主要有三个算法:直接插入排序、折半插入排序和希尔排序。
直接插入排序
算法思想
直接插入排序的简单思想就是不断地将一个元素插入到一个有序的序列中,使这个序列仍然保持有序。以序列L[1...i-1]和元素L(i)为例,算法步骤如下:
- 查找出L(i)在L[1...i-1]中的插入位置k;
- 将L[k...i-1]中所有元素全部后移一个位置;
- 将L(i)插入到L(k);
- 对L(i)后续的元素重复上述操作。
算法性能分析
- 空间效率:只使用了常数个辅助单元,空间复杂度为\(O(1)\)。
- 时间效率:在排序过程中,向有序子列中逐个插入元素进行n-1趟,每趟操作都进行了关键字比较和元素移动,其具体次数取决于序列状态。在最好的情况下,序列中元素已经有序,则每个元素都需要进行一次比较而不需要移动元素,因此时间复杂度为O(n);在最坏的情况下,序列中的元素逆序,总的比较次数达到最大,为\(\sum_{i=1}^{n-1}{i}\),总的移动次数也达到最大,为\(\sum_{i=1}^{n-1}{i}\),故总的时间复杂度我\(O(n^2)\)。
- 稳定性:直接插入排序是稳定的。
C++实现
#include <iostream>
using namespace std;
void insertSort(int a[], int n) {
for (int i = 1; i < n; ++i) { // 单个首元素默认有序
int val = a[i]; // 待插入元素
int j;
for (j = i; j > 0 && val < a[j - 1]; --j) // 查找合适位置
a[j] = a[j - 1];
a[j] = val;
}
}
int main() {
const int SIZE = 20;
int a[SIZE] = { 0 };
for (int i = 0; i < SIZE; ++i) {
a[i] = rand() % 100;
cout << a[i] << " ";
}
cout << "\n\n";
insertSort(a, SIZE);
for (auto elem : a)
cout << elem << " ";
return 0;
}
折半插入排序
算法思想
直接插入排序中有两项重要工作,分别是:找出待插入元素应该插入的位置;给待插入元素腾出空间,将该元素放入。折半插入排序则是将查找待插入位置的过程进行优化,使用折半查找法进行查找。
算法性能分析
- 空间效率:仍然是\(O(1)\)。
- 时间效率:减少了元素比较次数,比较次数约为\(O(nlog_2n)\),而移动次数没有改变,故整体时间复杂度仍然为\(O(n^2)\)。
- 稳定性:是稳定的。
C++实现
#include <iostream>
using namespace std;
void insertSort(int a[], int n) {
for (int i = 1; i < n; ++i) {
int val = a[i];
int low = 0, high = i - 1, mid;
while (low <= high) { // 折半查找插入元素的位置
mid = (low + high) / 2;
if (a[mid] > val)
high = mid - 1;
else
low = mid + 1;
}
for (int j = i; j > low; --j) // 移动元素
a[j] = a[j - 1];
a[low] = val;
}
}
int main() {
const int SIZE = 10;
int a[SIZE] = { 0 };
for (int i = 0; i < SIZE; ++i) {
a[i] = rand() % 10;
cout << a[i] << " ";
}
cout << "\n\n";
insertSort(a, SIZE);
for (auto elem : a)
cout << elem << " ";
return 0;
}
希尔排序
算法思想
将待排序序列分割成若干形如L[i, i+d, i+2d, ... i+kd]的“特殊”子序列,分别进行直接插入排序,当整个序列已经呈现“基本有序”时,再对全部序列进行一次直接插入排序。希尔排序的排序过程如下:
- 取一个小于n的步长\(d_1\),所有距离为\(d_1\)的倍数的元素放在同一个组;
- 在各组中进行直接插入排序;
- 取第二个步长\(d_2 < d_1\),重复上述过程,直至\(d_1 = 1\)。
算法性能分析
- 空间效率:使用常数个辅助单元,空间复杂度为\(O(1)\)。
- 时间效率:时间复杂度约为\(O(n^{1.3})\),最坏的情况下为\(O(n^2)\)。
- 稳定性:序列元素被划分到不同的分组之中时,可能会改变其相对次序,因此是一个不稳定的排序算法。
C++实现
#include <iostream>
using namespace std;
void shellSort(int a[], int n) {
for (int d = n / 2; d >= 1; d /= 2) { // 控制步长变化
for (int i = d; i < n; ++i) { // 一个分组内的直接插入排序
int val = a[i];
int j;
for (j = i; j >= d && val < a[j - d]; j -= d)
a[j] = a[j - d];
a[j] = val;
}
}
}
int main() {
const int SIZE = 10;
int a[SIZE] = { 0 };
for (int i = 0; i < SIZE; ++i) {
a[i] = rand() % 10;
cout << a[i] << " ";
}
cout << "\n\n";
shellSort(a, SIZE);
for (auto elem : a)
cout << elem << " ";
return 0;
}