C语言实现希尔排序算法

简介

希尔排序(Shell Sort)是插入排序的一种改进版本,也被称为“缩小增量排序”。它由 Donald Shell 于 1959 年发明。希尔排序通过将原始数据分成多个子序列,对每个子序列进行插入排序,逐步减少增量,最终使得整个序列有序。这种方法能够在一定程度上减少数据移动的次数,提高排序效率。相比于简单的插入排序,希尔排序在处理大规模数据时表现更为出色。

目录

  1. 基础概念
  2. 使用方法
  3. 常见实践
  4. 最佳实践
  5. 代码示例
  6. 小结
  7. 参考资料

基础概念

1. 插入排序回顾

在理解希尔排序之前,先来回顾一下插入排序。插入排序的基本思想是将数组分为已排序和未排序两部分,每次从未排序部分选择一个元素,插入到已排序部分的合适位置。插入排序在数据基本有序时效率较高,但对于大规模无序数据,效率较低。

2. 希尔排序原理

希尔排序引入了“增量”(increment)的概念。它首先将数组按照一个较大的增量分成多个子序列,每个子序列中的元素间隔为增量值。对每个子序列分别进行插入排序,使得数组中的元素大致有序。然后逐步减小增量,重复上述过程,直到增量为 1 时,整个数组就基本有序了,最后进行一次普通的插入排序。

例如,对于数组 [9, 1, 5, 8, 3, 7, 4, 6, 2],初始增量设为 4,则分成的子序列为 [9, 3][1, 7][5, 4][8, 6][2]。对这些子序列分别进行插入排序后,数组变为 [3, 1, 4, 6, 2, 7, 5, 8, 9]。随着增量的减小,子序列越来越接近最终的有序状态。

使用方法

1. 选择增量序列

增量序列的选择对希尔排序的性能有重要影响。常见的增量序列有原始希尔增量序列(h = n/2, h = h/2,..., h = 1),还有 Hibbard 增量序列(1, 3, 7,..., 2^k - 1)等。不同的增量序列在不同的数据规模和分布下表现各异。

2. 实现步骤

  1. 确定初始增量值。
  2. 按照增量值将数组分成多个子序列,对每个子序列进行插入排序。
  3. 减小增量值,重复步骤 2,直到增量值为 1。
  4. 当增量值为 1 时,对整个数组进行一次插入排序,此时数组已经基本有序。

常见实践

1. 简单实现

下面是使用原始希尔增量序列的简单实现:

#include <stdio.h>

// 交换两个整数的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 希尔排序函数
void shellSort(int arr[], int n) {
    int gap, i, j;
    // 初始化增量
    for (gap = n / 2; gap > 0; gap /= 2) {
        for (i = gap; i < n; i++) {
            int temp = arr[i];
            for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = temp;
        }
    }
}

// 打印数组函数
void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {9, 1, 5, 8, 3, 7, 4, 6, 2};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("Original array: ");
    printArray(arr, n);

    shellSort(arr, n);

    printf("Sorted array: ");
    printArray(arr, n);

    return 0;
}

2. 性能分析

希尔排序的时间复杂度依赖于增量序列的选择。对于原始希尔增量序列,时间复杂度大致在 $O(n^2)$ 到 $O(n^{1.3})$ 之间。而 Hibbard 增量序列的时间复杂度为 $O(n^{3/2})$。空间复杂度为 $O(1)$,因为它是在原数组上进行排序,不需要额外的大量存储空间。

最佳实践

1. 选择合适的增量序列

经过大量实验和研究,一些特定的增量序列在不同情况下表现更好。例如,Sedgewick 增量序列在许多实际应用中展现出了较好的性能。Sedgewick 增量序列的元素为 1, 5, 19, 41, 109, 209, 505, 929, 2161, 3905,...。使用该增量序列可以有效减少数据移动次数,提高排序效率。

2. 针对不同数据类型的优化

如果排序的数据类型不是整数,而是结构体等复杂类型,需要根据具体情况对比较和交换操作进行优化。例如,可以将比较操作封装成一个函数指针,以便在不同的数据类型下灵活使用。

#include <stdio.h>
#include <stdlib.h>

// 定义一个比较函数指针类型
typedef int (*CompareFunc)(const void *, const void *);

// 交换两个 void* 指针所指向的值
void swap(void *a, void *b, size_t size) {
    char temp[size];
    char *pa = (char *)a;
    char *pb = (char *)b;
    for (size_t i = 0; i < size; i++) {
        temp[i] = pa[i];
        pa[i] = pb[i];
        pb[i] = temp[i];
    }
}

// 希尔排序通用函数
void shellSortGeneric(void *base, size_t num, size_t size, CompareFunc compare) {
    size_t gap, i, j;
    for (gap = num / 2; gap > 0; gap /= 2) {
        for (i = gap; i < num; i++) {
            void *temp = (void *)malloc(size);
            char *src = (char *)base + i * size;
            for (size_t k = 0; k < size; k++) {
                ((char *)temp)[k] = src[k];
            }
            for (j = i; j >= gap; j -= gap) {
                void *prev = (void *)((char *)base + (j - gap) * size);
                if (compare(temp, prev) < 0) {
                    char *dst = (char *)base + j * size;
                    for (size_t k = 0; k < size; k++) {
                        dst[k] = ((char *)prev)[k];
                    }
                } else {
                    break;
                }
            }
            char *dst = (char *)base + j * size;
            for (size_t k = 0; k < size; k++) {
                dst[k] = ((char *)temp)[k];
            }
            free(temp);
        }
    }
}

// 整数比较函数
int intCompare(const void *a, const void *b) {
    return (*(int *)a - *(int *)b);
}

// 打印整数数组函数
void printIntArray(int arr[], int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {9, 1, 5, 8, 3, 7, 4, 6, 2};
    int n = sizeof(arr) / sizeof(arr[0]);

    printf("Original array: ");
    printIntArray(arr, n);

    shellSortGeneric(arr, n, sizeof(int), intCompare);

    printf("Sorted array: ");
    printIntArray(arr, n);

    return 0;
}

小结

希尔排序是一种高效的排序算法,通过合理选择增量序列,可以显著提高排序效率。在实际应用中,需要根据数据规模、数据分布以及数据类型等因素,选择合适的增量序列和优化策略。希望通过本文的介绍,读者能够深入理解希尔排序的原理和实现方法,并在实际编程中灵活运用。

参考资料

  1. 《算法导论》(第 3 版),Thomas H. Cormen 等著
  2. Donald Shell, “A High-Speed Sorting Procedure”, Communications of the ACM, 2(7): 30–32, July 1959
  3. Robert Sedgewick, “A New Lower Bound for Shellsort”, Journal of Algorithms, 7(1): 159–173, 1986