排序算法之希尔排序
介绍
希尔排序是直接插入排序的改进版,也称为“缩小增量排序”。希尔排序的基本思想是将待排序的数组元素按某个增量分成若干组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个数组被分为一组,算法便终止。
希尔排序的整个过程可以形象地描述为“分治”的思想,即先将大问题分解为若干个小问题,对小问题分别求解,然后再将这些小问题的解合并起来,得到原大问题的解。
希尔排序的优点在于其比较和交换的次数比直接插入排序要少得多,特别是当数据量较大时。
故事引入:分苹果
想象一个场景:你有一堆混在一起的苹果,你想按照大小将它们排好序。最简单的方法是一个个比较和交换,这就像直接插入排序。但这样效率不高,特别是当苹果数量很大时。
聪明的方法是:先大致分类,把大的和小的苹果分别放在不同的地方,然后再对每一类里的苹果进行详细比较和排序。这样,你就可以大大减少比较和交换的次数。
希尔排序就是这样一种“分而治之”的排序方法。
算法步骤
- 分组:首先,我们把数组分成若干组。比如,我们可以按照每组包含3个元素的方式来分(当然也可以是其他数字)。这样,每一组内部都是有序的。
- 排序:然后,我们对每一组进行直接插入排序,确保组内的元素是有序的。
- 合并:随着我们的分组大小逐渐减小(从3开始,每次减半,直到为1),我们开始合并这些组。在合并的过程中,我们仍然使用直接插入排序来确保合并后的数组是有序的。
- 递归:当我们合并到最后只剩下一组时,我们的数组就完全有序了。
举例说明:
假设我们有一个包含以下数字的数组:[7, 5, 3, 4, 2, 1]。
- 分组:首先,我们把数组分成若干组。例如,按照每组3个元素的方式分组:[7, 5, 3], [4, 2, 1]。
- 排序:对每一组进行直接插入排序,得到:[3, 5, 7] 和 [1, 2, 4]。
- 合并:然后,我们开始合并这些组。首先合并两个组的前两个元素:[3, 5, 7, 1, 2, 4]。再合并得到最终的有序数组:[1, 2, 3, 4, 5, 7]。
- 递归:当所有组都合并完毕后,我们的数组就完全有序了。
通过希尔排序,我们可以在更短的时间内完成大量数据的排序工作。特别是当数据量非常大时,希尔排序的优势更加明显。
代码
#include <iostream>
using namespace std;
void shellSort(int arr[], int n) {
int gap, i, j, temp;
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;
}
}
}
int main() {
int arr[] = {5, 3, 8, 4, 2, 7, 1, 6};
int n = sizeof(arr) / sizeof(arr[0]);
shellSort(arr, n);
cout << "Sorted array: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
return 0;
}
代码逐行讲解
#include <iostream> // 引入C++标准库中的输入输出流,用于输入输出操作。
using namespace std; // 使用C++标准命名空间,这样我们可以直接使用如cout、endl等而不需要每次都写std::cout、std::endl。
void shellSort(int arr[], int n) { // 定义一个名为shellSort的函数,它接受一个整数数组和数组的大小作为参数。
int gap, i, j, temp; // 声明一些变量,gap表示间隔值,i和j用于循环,temp用于交换元素。
for (gap = n/2; gap > 0; gap /= 2) { // 使用for循环逐渐减小间隔值。初始时,间隔值设为数组长度的一半,然后每次减半,直到间隔值为1。
for (i = gap; i < n; i++) { // 内部for循环,从间隔值的位置开始,遍历到数组的最后一个元素。
temp = arr[i]; // 将当前元素赋值给temp。
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) { // 再一个内部for循环,从当前位置开始向前遍历,直到找到合适的位置或到达间隔值的位置。
arr[j] = arr[j - gap]; // 将当前元素前移。
}
arr[j] = temp; // 将temp(即原数组的第i个元素)插入到正确的位置。
}
}
}
int main() { // 主函数开始。程序的执行从这里开始。
int arr[] = {5, 3, 8, 4, 2, 7, 1, 6}; // 定义并初始化一个整数数组。
int n = sizeof(arr) / sizeof(arr[0]); // 计算数组的大小(元素个数)。sizeof(arr)得到整个数组的大小(字节为单位),sizeof(arr[0])得到一个元素的大小(字节为单位)。两者相除即得到数组的长度。
shellSort(arr, n); // 调用shellSort函数对数组进行排序。
cout << "Sorted array: "; // 输出提示信息。
for (int i = 0; i < n; i++) { // 使用for循环遍历数组并输出每个元素。
cout << arr[i] << " "; // 输出当前元素并在其后添加一个空格。
}
return 0; // 主函数结束,返回0表示程序正常退出。
}
对三个for循环的理解
这段代码中确实涉及到三个嵌套的循环,循环条件有些复杂,让我来详细解释一下:
-
外层循环:
for (gap = n/2; gap > 0; gap /= 2)
这个循环控制的是“间隔”(gap)的大小。希尔排序的基本思想是先对大间隔的元素进行排序,然后逐渐减小间隔,对更小的组进行排序。这里,gap
?的初始值设为数组长度的一半(n/2
),然后每次循环后减小一半,直到?gap
?的值为 1。
2.?中间层循环:
for (i = gap; i < n; i++)
这个循环遍历从?gap
?到?n-1
?的位置。在希尔排序中,这些位置上的元素会先根据它们的“当前位置”和“当前位置-间隔”的值进行比较和交换。这样,每经过一次完整的间隔排序,这些位置上的元素就会基本有序。
3.?内层循环:
for (j = i; j >= gap && arr[j - gap] > temp; j -= gap)
这个循环更具体地处理元素之间的比较和交换。它的作用是找到?arr[i]
?应该插入的位置,并把它插入到正确的位置。循环从?i
?开始,向前遍历?gap
?个位置,并与前一个位置的元素进行比较。如果前一个位置的元素比?arr[i]
?大,则继续向前遍历;否则,停止遍历并插入?arr[i]
。
通过这三个循环的组合,希尔排序能够高效地对数组进行排序。这种排序方法在处理大数据集时比传统的插入排序更加高效,因为它能够减少比较和交换的次数。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!