折半插入排序(Binary Insertion Sort)
折半插入排序是一种改进的插入排序算法。它通过二分查找来确定插入位置,从而减少比较次数,但移动元素的次数与普通插入排序相同。以下是详细的算法实现,包括原理、步骤、图示法表示步骤、代码关键行注释和时间复杂度。
1. 算法原理
折半插入排序的核心思想是在已排序序列中使用二分查找来确定新元素的插入位置,从而减少比较次数。具体步骤如下:
初始化:
- 假设第一个元素已经是有序的。
插入元素:
- 从第二个元素开始,依次将每个元素插入到前面已排序的序列中。
- 使用二分查找在已排序序列中确定插入位置:
- 如果
key
小于中间元素,则在左半部分继续查找。 - 否则,在右半部分继续查找。
- 如果
- 找到插入位置后,将插入位置及其后面的元素向后移动一位。
- 将
key
插入到正确的位置。
重复步骤:
- 重复上述步骤,直到所有元素都被插入到已排序序列中。
2. 算法步骤
初始化:
- 假设第一个元素
A[0]
已经是有序的。
插入元素:
- 从第二个元素开始,依次将每个元素
A[i]
插入到前面已排序的序列中。 - 使用变量
key
存储当前要插入的元素。 - 使用二分查找确定
key
的插入位置:- 初始化
low = 0
,high = i-1
。 - 计算中间位置
mid = (low + high) / 2
。 - 如果
key < A[mid]
,则更新high = mid - 1
。 - 否则,更新
low = mid + 1
。
- 初始化
- 找到插入位置后,将插入位置及其后面的元素向后移动一位。
- 将
key
插入到位置low
。
重复步骤:
- 重复上述步骤,直到所有元素都被插入到已排序序列中。
3. 图示法表示步骤
假设我们有以下数组:
[12, 11, 13, 5, 6, 7]
步骤 1:初始化
- 第一个元素
12
已经是有序的。
步骤 2:插入第二个元素 11
key = 11
- 二分查找插入位置:
low = 0
,high = 0
mid = 0
11 < 12
,更新high = -1
- 插入位置为
0
,将12
向后移动一位,数组变为[11, 12, 13, 5, 6, 7]
。
步骤 3:插入第三个元素 13
key = 13
- 二分查找插入位置:
low = 0
,high = 1
mid = 0
13 > 12
,更新low = 1
- 插入位置为
2
,无需移动,数组保持不变。
步骤 4:插入第四个元素 5
key = 5
- 二分查找插入位置:
low = 0
,high = 2
mid = 1
5 < 12
,更新high = 0
- 插入位置为
0
,将11
和12
向后移动两位,数组变为[5, 11, 12, 13, 6, 7]
。
步骤 5:插入第五个元素 6
key = 6
- 二分查找插入位置:
low = 0
,high = 3
mid = 1
6 < 12
,更新high = 0
- 插入位置为
1
,将11
和12
向后移动一位,数组变为[5, 6, 11, 12, 13, 7]
。
步骤 6:插入第六个元素 7
key = 7
- 二分查找插入位置:
low = 0
,high = 4
mid = 2
7 < 13
,更新high = 1
- 插入位置为
2
,将11
和12
向后移动一位,数组变为[5, 6, 7, 11, 12, 13]
。
最终结果
- 排序后的数组为
[5, 6, 7, 11, 12, 13]
。
4. 代码关键行注释
#include <iostream>
using namespace std;
// 折半插入排序函数
void BinaryInsertSort(int A[], int n) {
for (int i = 1; i < n; i++) { // 从第二个元素开始
int key = A[i]; // 当前要插入的元素
int low = 0; // 初始化low为0
int high = i - 1; // 初始化high为i-1
while (low <= high) { // 二分查找插入位置
int mid = (low + high) / 2; // 计算中间位置
if (key < A[mid]) { // 如果key小于中间元素
high = mid - 1; // 在左半部分继续查找
} else { // 否则
low = mid + 1; // 在右半部分继续查找
}
}
for (int j = i - 1; j >= low; j--) { // 将元素向后移动一位
A[j + 1] = A[j];
}
A[low] = key; // 插入元素到正确的位置
}
}
int main() {
int arr[] = {12, 11, 13, 5, 6, 7}; // 定义数组
int n = sizeof(arr) / sizeof(arr[0]); // 计算数组大小
cout << "Original array: "; // 输出原始数组
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
BinaryInsertSort(arr, n); // 调用折半插入排序函数
cout << "Sorted array: "; // 输出排序后的数组
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0; // 程序结束
}
5. 时间复杂度
- 时间复杂度:
- 二分查找的时间复杂度为 O(logn)。
- 移动元素的时间复杂度为 O(n)。
- 总时间复杂度为 O(n2),与普通插入排序相同。
6. 总结
折半插入排序通过二分查找减少了比较次数,但移动元素的次数没有减少,因此时间复杂度与普通插入排序相同。尽管如此,折半插入排序在某些情况下可以提供更好的性能,尤其是在数据量较大且接近有序的情况下。