复习概要
- 算法思想与流程
- 分这么多组分别插入排序有什么用?为什么不直接整个插入排序?
- 时间复杂度
- 优化
- 要眼熟插入排序那一段的代码,和原本的插入排序对比着看!
- 插入排序那一段多了一层循环是干什么的,怎么是三层循环?
- 希尔的步长序列生成方式?
算法思想
- 我觉得完全没必要按照行和列的思想去理解希尔排序
- 因为一句话概括希尔排序就是,每隔几个元素一组,分组进行插入排序,然后重新分组,直到每个元素都是一组
- 内层先标记这是第几组,然后就是最简单的插入排序思想,只不过是对同一组(列)里的元素的插入排序,请想象元素之间的距离变成stepSequence了,不是1了,而且组的第一个元素索引是col(第几组,初始为0)不是0了,第一个无序元素的位置就是col+stepSequence了,把所有组分别进行插入排序就结束了
- 外层就是不断缩小分组所需的步长(元素增量),然后循环内层
- 步长:就是隔几个一组
- 建议和普通插入排序的代码结合着看,很容易就理解了
- 话说希尔排序居然是用发明者名字命名的
- 希尔排序比之间插入排序强的地方是,总共处理的逆序对数量比直接插入排序少了,每次分组排序同时会解决一些额外的逆序对。
算法流程
输入数组:
29 15 12 10 9 8 7 6 5 3 2 1
希尔排序正常版
(每隔几个元素就取出一个看作一组)(也就是列)步长为6,每组有length/步长 个元素
[29] 15 12 10 9 8 [7] 6 5 3 2 1
7 [15] 12 10 9 8 29 [6] 5 3 2 1
7 6 [12] 10 9 8 29 15 [5] 3 2 1
7 6 5 [10] 9 8 29 15 12 [3] 2 1
7 6 5 3 [9] 8 29 15 12 10 [2] 1
7 6 5 3 2 [8] 29 15 12 10 9 [1]
7 6 5 3 2 1 29 15 12 10 9 8
(每隔几个元素就取出一个看作一组)(也就是列)步长为3
[7] 6 5 [3] 2 1 [29] 15 12 [10] 9 8
3 [15] 12 7 [9] 8 10 [6] 5 29 [2] 1
3 2 [12] 7 6 [8] 10 9 [5] 29 15 [1]
3 2 1 7 6 5 10 9 8 29 15 12
(每隔几个元素就取出一个看作一组)(也就是列)步长为1
[3] [2] [1] [7] [6] [5] [10] [9] [8] [29] [15] [12]
1 2 3 5 6 7 8 9 10 12 15 29
算法复杂度分析及稳定性
-
希尔排序最好时间复杂度和插入排序相同为O(n)
-
最坏时间复杂度和平均时间复杂度取决于步长序列
-
希尔本人的步长序列生成法(length/2的k次幂)最坏时间复杂度为O(n2),目前最好的最坏时间复杂度的步长序列生成法可以做到O(n的4/3次方),如下,k=1,2,3.。。。注意是最坏时间复杂度(even代表奇数,odd代表偶数)
-
希尔排序不稳定,空间复杂度O(1),为原地算法
希尔排序代码正常版
//产生步长序列(按照希尔本人的生成方式)
void stepSequenceGenerator(vector<int> array, vector<int> &stepSequence)
{
for (int i = 2; i < array.size(); i = i * 2)
{
stepSequence.push_back(array.size() / i);
}
}
void sort(vector<int> array, int stepSequence)
{
//col:当前是第几组(列)
for (int col = 0; col < stepSequence; col++)
{
//下面其实就是最简单的插入排序的代码,只不过是对同一组(列)里的元素的插入排序
//请想象元素之间的距离变成stepSequence了,不是1了,而且组的第一个元素索引是col不是0了,第一个无序元素的位置就是col+stepSequence了
for (int chaosBeginIndex = col + stepSequence; chaosBeginIndex < array.size(); chaosBeginIndex = chaosBeginIndex + stepSequence)
{
for (int insertEIndex = chaosBeginIndex; insertEIndex > col; insertEIndex = insertEIndex - stepSequence)
{
if (array[insertEIndex] < array[insertEIndex - stepSequence])
{
int temp = array[insertEIndex];
array[insertEIndex] = array[insertEIndex - stepSequence];
array[insertEIndex - stepSequence] = temp;
}
}
}
vectorPrint(array);
}
}
void shellSort(vector<int> &array)
{
vector<int> stepSequence;
stepSequenceGenerator(array, stepSequence);
for (int i = 0; i < stepSequence.size(); i++)
{
cout << "(每隔几个元素就取出一个看作一组)(也就是列)步长为" << stepSequence[i] << endl;
sort(array, stepSequence[i]);
}
}
输入数组:
29 15 12 10 9 8 7 6 5 3 2 1
希尔排序正常版
(每隔几个元素就取出一个看作一组)(也就是列)步长为6
7 15 12 10 9 8 29 6 5 3 2 1
7 6 12 10 9 8 29 15 5 3 2 1
7 6 5 10 9 8 29 15 12 3 2 1
7 6 5 3 9 8 29 15 12 10 2 1
7 6 5 3 2 8 29 15 12 10 9 1
7 6 5 3 2 1 29 15 12 10 9 8
(每隔几个元素就取出一个看作一组)(也就是列)步长为3
3 15 12 7 9 8 10 6 5 29 2 1
3 2 12 7 6 8 10 9 5 29 15 1
3 2 1 7 6 5 10 9 8 29 15 12
(每隔几个元素就取出一个看作一组)(也就是列)步长为1
1 2 3 5 6 7 8 9 10 12 15 29
算法用时:(微秒)
[AlgoTime: 12002 us]
希尔排序与插入排序代码对比
void sort2nd(vector<int> array, int stepSequence)
{
//col:当前是第几组(列)
for (int col = 0; col < stepSequence; col++)
{
//下面其实就是最简单的插入排序的代码,只不过是对同一组(列)里的元素的插入排序
//请想象元素之间的距离变成stepSequence了,不是1了,而且组的第一个元素索引是col不是0了,第一个无序元素的位置就是col+stepSequence了
for (int chaosBeginIndex = col + stepSequence; chaosBeginIndex < array.size(); chaosBeginIndex = chaosBeginIndex + stepSequence)
{
for (int insertEIndex = chaosBeginIndex; insertEIndex > col; insertEIndex = insertEIndex - stepSequence)
{
if (array[insertEIndex] < array[insertEIndex - stepSequence])
{
int temp = array[insertEIndex];
array[insertEIndex] = array[insertEIndex - stepSequence];
array[insertEIndex - stepSequence] = temp;
}
}
}
vectorPrint(array);
}
}
void insertionSort1th(vector<int> &array) //这里以数组左侧作为有序的那一边
{
// chaosBeginIndex:未排序序列的起始元素下标
// insertEIndex:拿去插入有序序列的那个元素的下标,就是未排序序列的起始元素,但随着交换位置,下标会改变
for (int chaosBeginIndex = 1; chaosBeginIndex < array.size(); chaosBeginIndex++)
//外循环控制从第二个元素开始插入到有序序列里,直到最后一个元素
{
for (int insertEIndex = chaosBeginIndex; insertEIndex > 0; insertEIndex--)
//内循环控制从未排序序列的首元素开始,逐渐与有序序列元素比较和交换位置,找到有序序列中的合适位置
{
if (array[insertEIndex] < array[insertEIndex - 1])
{
int temp = array[insertEIndex];
array[insertEIndex] = array[insertEIndex - 1];
array[insertEIndex - 1] = temp;
}
else
{
break;
}
}
vectorPrint(array);
}
}
希尔排序个人青春版(别看以免走上歧途)
看输出就能看出来,我改写了插入排序,可以自由选择一段进行插入排序,然后又建了一个空容器,把元素按组排序压入容器,然后覆盖主数组,但是毫无意义,看输出就知道,把一组元素放到一起又会导致不同组之间产生新的逆序对
void insertionSort1th(vector<int> &array, int begin, int end) //这里以数组左侧作为有序的那一边
{
// chaosBeginIndex:未排序序列的起始元素下标
// insertEIndex:拿去插入有序序列的那个元素的下标,就是未排序序列的起始元素,但随着交换位置,下标会改变
for (int chaosBeginIndex = begin + 1; chaosBeginIndex <= end; chaosBeginIndex++)
//外循环控制从第二个元素开始插入到有序序列里,直到最后一个元素
{
for (int insertEIndex = chaosBeginIndex; insertEIndex > begin; insertEIndex--)
//内循环控制从未排序序列的首元素开始,逐渐与有序序列元素比较和交换位置,找到有序序列中的合适位置
{
if (array[insertEIndex] < array[insertEIndex - 1])
{
int temp = array[insertEIndex];
array[insertEIndex] = array[insertEIndex - 1];
array[insertEIndex - 1] = temp;
}
else
{
break;
}
}
}
}
void sort(vector<int> &array, int stepSequence)
{
vector<int> temp;
for (int j = 0; j < stepSequence; j++)
{
int i = 0;
for (int index = j; index < array.size(); index = index + stepSequence)
{
temp.push_back(array[index]);
i = i + 1;
}
insertionSort1th(temp, temp.size() - i, temp.size() - 1);
vectorPrint(temp);
}
array = temp;
}
void shellSort(vector<int> &array)
{
vector<int> stepSequence;
stepSequenceGenerator(array, stepSequence);
for (int i = 0; i < stepSequence.size(); i++)
{
cout << "(每隔几个元素就取出一个看作一组)(也就是列)步长为" << stepSequence[i] << endl;
sort(array, stepSequence[i]);
}
}
输入数组:
29 15 12 10 9 8 7 6 5 3 2 1
希尔排序个人青春版
(每隔几个元素就取出一个看作一组)(也就是列)步长为6
7 29
7 29 6 15
7 29 6 15 5 12
7 29 6 15 5 12 3 10
7 29 6 15 5 12 3 10 2 9
7 29 6 15 5 12 3 10 2 9 1 8
(每隔几个元素就取出一个看作一组)(也就是列)步长为3
3 7 9 15
3 7 9 15 1 5 10 29
3 7 9 15 1 5 10 29 2 6 8 12
(每隔几个元素就取出一个看作一组)(也就是列)步长为1
1 2 3 5 6 7 8 9 10 12 15 29
算法用时:(微秒)
[AlgoTime: 9002 us]