希尔排序性能测试与分析

希尔排序是直接插入排序的改进,通过引入递减增量,对待排序列分组,并对分组做直接插入排序,重复上述步骤,并逐步减小增量为1,最终使待排序列有序。
相对于直接插入排序O(N^2)的平均时间复杂度,希尔排序的平均时间复杂度为O(nlog2n)。另外希尔排序的时间复杂度和选择增量序列的方式有关,通常有N/2,Hibbard,Sedgewick等方法。
N/2,最坏情况T=Θ(N^2),性能不高的原因是增量序列不互质
Hibbard Dk=2^k-1, Tworst:T=Θ(N^3/2), 猜想:Tavg=O(N^5/4)
2^k-1,k=0,1,2,3,4, 常见序列如{15,7,3,1,0}
Sedgewick,由9*4i-92^i+1 或 4i-3*2i+1获得
最坏:Tworst=O(N^4/3), Tavg=O(N^7/6),常见序列如{929, 505, 209, 109, 41, 19, 5, 1, 0}

本文通过实际测试,直观了解希尔排序设置不同递减增量时排序性能的变化。

代码如下:

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

#define ElementType int

typedef struct LNode *List;
struct LNode
{
    int Data;
    List Next;
};

ElementType *initA(int N);
void showA(ElementType A[], int N);
void ShellSort(ElementType A[], int N);
void ShellSortWithSedgewick(ElementType A[], int N);
void ShellSortWithHibbard(ElementType A[], int N);

List FindKth(List L, int K);
List Insert(List L, int K, ElementType X);
void showList(List L);
List initList(int N);

double run(int N, int num, int flag);


int main()
{
    srand(time(0));
    int N = 10000;
    int num = 100;
    int flag = 0;
    // ElementType *A = initA(N);
    // showA(A, N);
    // // ShellSort(A, N);
    // // ShellSortWithSedgewick(A, N);
    // ShellSortWithHibbard(A, N);
    // showA(A, N);
    printf("%6.2e", run(N, num, flag));
    return 0;
}

ElementType *initA(int N)
{
    ElementType *A = (ElementType *)malloc(N * sizeof(ElementType));
    // ElementType B[] = {5, 4, 3, 2, 1};
    for (int i = 0; i < N; i++)
    {
        if (N < 100)
        {
            A[i] = rand() % 100;
            // A[i] = B[i];
        }
        else
        {
            A[i] = rand() % N; // 随机
            // A[i] = 1; // 全1
            // A[i] = i;//顺序
            // A[i] = N - 1 - i; // 逆序
        }
    }
    return A;
}

void showA(ElementType A[], int N)
{
    for (int i = 0; i < N; i++)
    {
        printf("%d ", A[i]);
    }
    printf("\n");
}

void ShellSort(ElementType A[], int N)
{ // 原始希尔排序,增量使用N/2
    int D, P, i;
    ElementType tmp;
    for (D = N / 2; D > 0; D /= 2)
    {
        for (P = D; P < N; P++)
        {
            tmp = A[P];
            for (i = P; i >= D && A[i - D] > tmp; i -= D)
            {
                A[i] = A[i - D];
            }
            A[i] = tmp;
        }
    }
}

void ShellSortWithSedgewick(ElementType A[], int N)
{ /* 希尔排序 - 用Sedgewick增量序列 */
    int Si, D, P, i;
    ElementType tmp;
    /* 这里只列出一小部分增量 */
    int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};

    for (Si = 0; Sedgewick[Si] >= N; Si++)
        ; /* 初始的增量Sedgewick[Si]不能超过待排序列长度 */

    for (D = Sedgewick[Si]; D > 0; D = Sedgewick[++Si])
        for (P = D; P < N; P++)
        { /* 插入排序*/
            tmp = A[P];
            for (i = P; i >= D && A[i - D] > tmp; i -= D)
                A[i] = A[i - D];
            A[i] = tmp;
        }
}

void ShellSortWithHibbard(ElementType A[], int N)
{
    int Hi, D, P, i;
    ElementType tmp;
    int Hibbard[] = {15, 7, 3, 1, 0};
    for (Hi = 0; Hibbard[Hi] >= N; Hi++)
        ;
    for (D = Hibbard[Hi]; D > 0; D = Hibbard[++Hi])
    {
        for (P = D; P < N; P++)
        {
            tmp = A[P];
            for (i = P; i >= D && A[i - D] > tmp; i -= D)
            {
                A[i] = A[i - D];
            }
            A[i] = tmp;
        }
    }
}

List FindKth(List L, int K)
{
    int loc = 1;
    List S = L;
    for (; S; S = S->Next)
    {
        if (K == loc)
        {
            break;
        }
        else
        {
            loc++;
        }
    }
    return S;
}

List Insert(List L, int K, ElementType X)
{
    List S, P;
    if (K == 1)
    {
        S = (List)malloc(sizeof(struct LNode));
        S->Next = L;
        S->Data = X;
        return S;
    }
    P = FindKth(L, K - 1);
    if (P != NULL)
    {
        S = (List)malloc(sizeof(struct LNode));
        S->Data = X;
        S->Next = P->Next;
        P->Next = S;
        return L;
    }
    else
    {
        printf("Error:Index %d out of range.\n", K);
        return NULL;
    }
}

void showList(List L)
{
    List S = L->Next;
    if (S)
    {
        for (; S; S = S->Next)
        {
            printf("%d ", S->Data);
        }
        printf("\n");
    }
}

List initList(int N)
{
    int i, K = 1;
    List L = NULL;
    
    for (i = 0; i < N; i++)
    {
        if (N < 100)
        {
            L = Insert(L, K, rand() % 100);
        }
        else
        {
            L = Insert(L, K, rand() % N);
            // L = Insert(L, K, 1);
            // L = Insert(L, K, i);
            // L = Insert(L, K, N-1-i);
        }
    }

    List H = (List)malloc(sizeof(struct LNode));
    H->Next = L;
    return H;
}

double run(int N, int num, int flag)
{
    clock_t start, stop;
    double duration, sum = 0.0;

    for (int i = 0; i < num; i++)
    {
        if (flag == 0)
        {

            int *A = initA(N);
            start = clock();
            // ShellSort(A, N);
            // ShellSortWithSedgewick(A, N);
            ShellSortWithHibbard(A, N);
            stop = clock();
        }
        else
        {
            List L = initList(N);
            start = clock();
            //
            stop = clock();
        }
        duration = ((double)(stop - start)) / CLK_TCK;
        sum += duration;
    }
    return sum / num;
}

测试逻辑:
对3种算法按设定数据规模,重复次数,数据样本执行,取单次运行的花费的时间。

实测结果:

/*
性能实测:

10000,100,随机数
数组:5.23e-01[bubble],1.56e-01[插入],3.40e-03[shell]
链表:1.65e+00[bubble],3.05e-01[插入]

N/2
100000,100,随机数 4.91e-02
100000,100,全1 1.25e-02
100000,100,顺序 1.16e-02
100000,100,逆序 1.58e-02

Sedgewick
100000,100,随机数 2.86e-03
100000,100,全1 6.10e-04
100000,100,顺序 5.60e-04
100000,100,逆序 1.70e-03

Hibbard
100000,100,随机数 1.23e-02
100000,100,全1 3.10e-04
100000,100,顺序 3.40e-04
100000,100,逆序 2.17e-02

 */

测试结果分析:
首先用希尔排序和冒泡,插入排序性能做了一个横向比较,测试样本为
10000随机数,运行100次,取平均单次运行时间
冒泡和插入平均时间复杂度是O(N^2),从测试结果可见,排序性能比希尔低2个数量级。

10000,100,随机数
数组:5.23e-01[bubble],1.56e-01[插入],3.40e-03[shell]
链表:1.65e+00[bubble],3.05e-01[插入]

其次是希尔不同增量序列选取的排序性能对比:
分别是N/2,Sedgewick,Hibbard。数据规模100,000[10万],重复运行100次,数据特征包括随机数,全1,顺序,逆序4种。
从测试结果看,选取不同的增量序列,对排序性能影响巨大,以N/2和Sedgewick随机数测试结果为例,N/2是【4.91e-02】
Sedgewick是【2.86e-03】,差距在一个数量级。另外,不同数据特征也对排序性能有巨大影响。同样是Sedgewick,全1【6.10e-04】和随机数【2.86e-03】相比,也有一个数量级的差别。

N/2
100000,100,随机数 4.91e-02
100000,100,全1 1.25e-02
100000,100,顺序 1.16e-02
100000,100,逆序 1.58e-02

Sedgewick
100000,100,随机数 2.86e-03
100000,100,全1 6.10e-04
100000,100,顺序 5.60e-04
100000,100,逆序 1.70e-03

Hibbard
100000,100,随机数 1.23e-02
100000,100,全1 3.10e-04
100000,100,顺序 3.40e-04
100000,100,逆序 2.17e-02

小结:
从测试结果看,希尔排序选取Sedgewick增量序列的性能最好,实现复杂度也没有提高,实际应用中希尔排序,应采用Sedgewick增量序列。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值