排序算法之希尔排序

介绍

希尔排序是直接插入排序的改进版,也称为“缩小增量排序”。希尔排序的基本思想是将待排序的数组元素按某个增量分成若干组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个数组被分为一组,算法便终止。

希尔排序的整个过程可以形象地描述为“分治”的思想,即先将大问题分解为若干个小问题,对小问题分别求解,然后再将这些小问题的解合并起来,得到原大问题的解。

希尔排序的优点在于其比较和交换的次数比直接插入排序要少得多,特别是当数据量较大时。

故事引入:分苹果

想象一个场景:你有一堆混在一起的苹果,你想按照大小将它们排好序。最简单的方法是一个个比较和交换,这就像直接插入排序。但这样效率不高,特别是当苹果数量很大时。

聪明的方法是:先大致分类,把大的和小的苹果分别放在不同的地方,然后再对每一类里的苹果进行详细比较和排序。这样,你就可以大大减少比较和交换的次数。

希尔排序就是这样一种“分而治之”的排序方法。

算法步骤

  1. 分组:首先,我们把数组分成若干组。比如,我们可以按照每组包含3个元素的方式来分(当然也可以是其他数字)。这样,每一组内部都是有序的。
  2. 排序:然后,我们对每一组进行直接插入排序,确保组内的元素是有序的。
  3. 合并:随着我们的分组大小逐渐减小(从3开始,每次减半,直到为1),我们开始合并这些组。在合并的过程中,我们仍然使用直接插入排序来确保合并后的数组是有序的。
  4. 递归:当我们合并到最后只剩下一组时,我们的数组就完全有序了。

举例说明:

假设我们有一个包含以下数字的数组:[7, 5, 3, 4, 2, 1]。

  1. 分组:首先,我们把数组分成若干组。例如,按照每组3个元素的方式分组:[7, 5, 3], [4, 2, 1]。
  2. 排序:对每一组进行直接插入排序,得到:[3, 5, 7] 和 [1, 2, 4]。
  3. 合并:然后,我们开始合并这些组。首先合并两个组的前两个元素:[3, 5, 7, 1, 2, 4]。再合并得到最终的有序数组:[1, 2, 3, 4, 5, 7]。
  4. 递归:当所有组都合并完毕后,我们的数组就完全有序了。

通过希尔排序,我们可以在更短的时间内完成大量数据的排序工作。特别是当数据量非常大时,希尔排序的优势更加明显。

代码

#include   
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   // 引入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循环的理解

这段代码中确实涉及到三个嵌套的循环,循环条件有些复杂,让我来详细解释一下:

  1. 外层循环

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]

通过这三个循环的组合,希尔排序能够高效地对数组进行排序。这种排序方法在处理大数据集时比传统的插入排序更加高效,因为它能够减少比较和交换的次数。

你可能感兴趣的:(排序算法,java,算法,数据结构)