排序算法----希尔排序

希尔排序(Shell's Sort)详解

引言

希尔排序,又称"缩小增量排序"(Diminishing Increment Sort),是由美国计算机科学家Donald Shell于1959年提出的一种排序算法。它是直接插入排序算法的一种改进版本,旨在通过减少插入排序中的交换操作和比较次数,从而提高排序效率。希尔排序通过将待排序的数组元素分成多个子序列,然后对每个子序列进行直接插入排序,最后当整个序列"基本有序"时,再进行一次直接插入排序来完成排序过程。

基本思想

希尔排序的基本思想是将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序。这种分组是通过取一个增量(gap)来实现的,增量决定了子序列中元素的间隔。随着排序的进行,增量逐渐减小,直到增量为1时,整个数组就被分成了一个子序列,此时进行一次直接插入排序即可完成排序。

关键步骤

  1. 选择增量:首先,选择一个增量(gap),通常选择数组长度的一半作为初始增量。
  2. 分组排序:根据增量将数组分成多个子序列,每个子序列的元素间隔为增量。
  3. 子序列排序:对每个子序列进行直接插入排序。
  4. 减小增量:重复上述步骤,但每次迭代时减小增量。增量序列的选择对算法的性能有很大影响,常用的增量序列是每次将增量减半,直到增量为1。
  5. 整体排序:当增量为1时,整个数组被看作一个子序列,进行一次直接插入排序,此时数组已经接近有序,排序过程会很快完成。

复杂度分析:

希尔排序的时间复杂度不容易精确计算,因为它与增量序列的选择密切相关。在最好的情况下,希尔排序的时间复杂度可以达到O(nlog2n), 但在**最坏的情况下,时间复杂度可能会退化到O(n2)。**然而,在实际应用中,希尔排序的性能通常优于直接插入排序,特别是在处理中等规模的数据集时。

排序过程示例

假设有一个数组[64, 34, 25, 12, 22, 11, 90],我们按照希尔排序的步骤进行排序:

初始增量:假设初始增量为数组长度的一半,即3(这里为了简化,我们取整数,实际中可能选择更合适的增量序列)。

分组排序:将数组分为三组:[64, 22, 11]、[34, 12]、[25, 90],对这三组进行直接插入排序。

[64, _ , 25, _ , 22, _ , 90](其中 _ 表示不参与当前子序列排序的元素)

[ _ , 34, _ , 12, _ , 11, _ ]

对每个子序列进行直接插入排序(这里仅展示排序后的结果):

[25, _ , 64, _ , 22, _ , 90]

[ _ , 11, _ , 12, _ , 34, _ ]

组合起来看,数组变为 [25, 11, 64, 12, 22, 34, 90]。

减小增量:假设下一个增量为1(在实际应用中,增量会逐渐减小,直到为1)。

整体排序:此时整个数组被看作一个子序列,进行一次直接插入排序,排序结果为[11, 12, 22, 25, 34, 64, 90]。

代码:

cpp 复制代码
#include <stdio.h>  
  
// 希尔排序函数  
void shell_sort(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];  
            j = i;  
            // 插入排序  
            while (j >= gap && arr[j - gap] > temp) {  
                arr[j] = arr[j - gap];  
                j -= gap;  
            }  
            arr[j] = temp;  
        }  
    }  
}  
  
// 主函数  
int main() {  
    int arr[] = {64, 34, 25, 12, 22, 11, 90};  
    int n = sizeof(arr) / sizeof(arr[0]);  
      
    shell_sort(arr, n);  
      
    // 打印排序后的数组  
    printf("Sorted array is: ");  
    for (int i = 0; i < n; i++) {  
        printf("%d ", arr[i]);  
    }  
    printf("\n");  
      
    return 0;  
}

函数定义:shell_sort 函数接受一个整型数组 arr 和一个整数 n 作为参数,其中 n 是数组的长度。

循环设置:

外层循环用于设置增量 gap,从数组长度的一半开始,每次循环将 gap 减半,直到 gap 为0。注意,在C语言中,当 gap 为1时,循环就会停止,因为 gap /= 2 会使 gap 变为0(如果 gap 是奇数且 n 是2的幂次减1的奇数倍时)。但在这个特定的例子中,由于 gap 初始化为 n / 2,它总是能整除2直到1,所以不会有问题。

内层循环用于对每个子序列进行直接插入排序。它遍历从 gap 到 n-1 的所有元素,并将它们插入到前面已经排序好的间隔为 gap 的子序列中。

插入排序:在内层循环中,使用 temp 变量来保存当前要插入的元素,并通过一个 while 循环将其移动到正确的位置。

优缺点

优点

速度较快:希尔排序在大多数情况下比直接插入排序要快,特别是在处理中等规模的数据集时。

适应性强:希尔排序对于部分有序的数据集也能表现出较好的性能。

代码简单:希尔排序的算法实现相对简单,代码短而清晰。
缺点

不稳定:希尔排序是一种不稳定的排序算法,相同的元素可能在排序过程中改变相对位置。

时间复杂度不固定:希尔排序的时间复杂度取决于增量序列的选择,难以精确计算。

不适合大规模数据:虽然希尔排序在中等规模数据上表现良好,但在处理大规模数据时,其性能可能不如更高级的排序算法,如快速排序或归并排序。

应用场景

希尔排序适用于以下场景:

  1. 中等规模数据集:在处理中等规模的数据集时,希尔排序能够提供较好的性能和效率
  2. 部分有序数据集:如果数据集已经部分有序,希尔排序能够利用这种有序性进一步优化排序过程。
  3. 动态数据集:当数据集大小变化较大且需要频繁更新时,希尔排序由于其较小的常数项和较好的性能,是一个不错的选择。
  4. 实时系统:在实时系统中,希尔排序由于其较好的性能和较小的内存占用,也是一个合适的选择。
  5. 结论
  6. 希尔排序作为一种经典的排序算法,以其简单性和较高的效率在特定场景下得到了广泛应用。虽然它在理论上的最坏时间复杂度较高,但在实际应用中,其性能和稳定性通常能够满足需求。通过合理选择增量序列,可以进一步提高希尔排序的效率和性能。
相关推荐
ChoSeitaku1 小时前
链表循环及差集相关算法题|判断循环双链表是否对称|两循环单链表合并成循环链表|使双向循环链表有序|单循环链表改双向循环链表|两链表的差集(C)
c语言·算法·链表
Fuxiao___1 小时前
不使用递归的决策树生成算法
算法
我爱工作&工作love我1 小时前
1435:【例题3】曲线 一本通 代替三分
c++·算法
白-胖-子2 小时前
【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-统计数字
开发语言·c++·算法·蓝桥杯·等考·13级
workflower2 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
好睡凯2 小时前
c++写一个死锁并且自己解锁
开发语言·c++·算法
Sunyanhui12 小时前
力扣 二叉树的直径-543
算法·leetcode·职场和发展
一个不喜欢and不会代码的码农2 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode
前端郭德纲2 小时前
浏览器是加载ES6模块的?
javascript·算法
SoraLuna3 小时前
「Mac玩转仓颉内测版10」PTA刷题篇1 - L1-001 Hello World
算法·macos·cangjie