数据结构(11)之排序

1 前言

    这节我们学习一下排序的相关知识。

2 详述

2.1 排序概念

    假设含有n个记录的序列为{r1,r2,......,rn},其中响应的关键字分别为{k1,k2,......,kn},需确定1,2,......,n的一种排序p1,p2,......,pn,使其相应的关键字满足kp1<=kp2<=......<=kpn(非递减或递增)关系,即使得序列称为一个按关键字有序的序列{rp1,rp2,......,rpn},这样的操作就称为排序。

 

   

2.1.1 排序的稳定性

假设ki = kj(1<=i<=n,1<=j<=n,i!=j),且在排序前的序列中ri领先于rj(即i<j)。如果排序后ri仍领先于rj,则称所用的排序方法是稳定的;反之,若可能使得排序后的序列中的rj领先于ri,则称所用的排序方法是不稳定的。

排序前,令狐冲在前,那么他们总分排序后,对于分数相等的令狐冲和张无忌来说,当令狐冲在前时,则是稳定排序;反之则在后。

2.1.2 内排序与外排序

内排序是在排序整个过程中,待排序的所有记录全部被放置在内存中。外排序是由于排序的记录个数太多,不同同事放置在内存,整个排序过程需要在内存外之间多次交换数据才能进行。

对于内排序来说,排序算法的性能主要受3个方面影响:

(1)时间性能:在内排序中,主要进行两种操作:比较和移动。而高效的内排序算法应该是具有尽可能少的关键字比较次数和尽可能少的记录移动次数。

(2)辅助空间:辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间。

(3)算法的复杂性:这里指的是算法本身的复杂度,而不是指算法的时间复杂度。

根据排序过程中借助的主要操作,我们把内排毒分为:插入排序,交换排序,选择排序和归并排序。

2.1.3 排序用到的结构与函数

我们使用顺序表结构:

#define MAXSIZE 10  /*用于要排序数组个数最大值,可根据需要修改*/
typedef struct
{
    int r[MAXSIZE+1];     /*用于存储要排序数组,r[0]用作哨兵或临时变量*/
    int length;                  /*用于记录顺序表的长度*/
}SqList;


数组交换函数:

/*交换L中数组r的下标为i和j的值*/
void swap(SqList *L,int i,int j)
{
    int temp = L->r[i];
    L->r[i] = L->r[j];
    L->r[j] = temp;
}

2.2 冒泡排序

    冒泡排序(Bubble Sort)一种交换排序,它的基本思路是:量量比较相邻记录的关键字,如果反序则交换,只到没有反序的记录为止。

/*对顺序表L作冒泡排序*/
void BubbleSort(SqList *L)
{
    int i,j;
    for(i=1;i<L->length;i++)
    {
        for(j=L->length-1;j>=i;j--)           /*注意j是从后往前循环*/
        {
            if(L->r[j]>L->r[j+1])         /*若前者大于后者*/
            { 
                swap(L,j,j+1);            /*交换L->r[j]与L->[j+1]的值*/
            }
        }
    }
}


2.2.1 冒泡排序的优化

如果排序的序列是{2,1,3,4,5,6,7,8,9},除了第一个和第二个的关键字需要交换外,别的都是正常的顺序。如果还是按照原来的操作,则后面的循环就都是多余的了。

/*对顺序表L作改进冒泡算法*/
void BubbleSort2(SqList *L)
{
    int i,j
    Statue flag = True;  /*用flag来做标记*/
    for(i = 1;i<L->length && flag;i++)
    {
        flag = FALSE;        /*初始化为false*/
        for(j = L->length-1;j>=i;j--)
        {
             if(L->r[j] > L->r[j+1])
             {
                  swap(L,j,j+1);       /*交换L->r[j] 与 L->r[j+1]的值*/
                  flag = TRUE;       /*如果有数据交换,则flag为true*/
             }
        }
    }
}


代码关键为增加了flag是否为true的判断,避免了无意义的循环判断。

2.2.2 冒泡排序复杂度分析

1+2+3+...+(n-1) = n(n-1)/2,总得时间复杂度为

2.3 简单选择排序

简单选择排序法(Simple Selection Sort)就是通过n-1次关键字间的比较,从n-i+1个记录中选中关键字最小的记录,并和第i(1<=i<=n)个记录交换之。

/*对顺序表L作简单选择排序*/
void SelectSort(SqList *L)
{
    int i,j,min;
    for(i = 1;i<L->length;j++)
    {
        min = i;           /*将当前下标定义为最小值下标*/
        for(j = i+1;j<=L->length;j++)    /*循环之后的数据*/
        {
            if(L->r[min] > L->r[j])      /*如果有小于当前最小值的关键字*/
                min = j;                  /*将次关键字的下标赋值给min*/
        }
        if(i!=min)           /*若min不等于i,说明找到最小值,交换*/
            swap(L,i,min);      /*交换L->r[i] 与 L->r[min]的值*/
    }
}


时间复杂度为:

2.4 直接插入排序

直接插入排序(Straight Insertion Sort)的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的,记录数增1的有序表。

/*对顺序表L作直接插入排序*/
void InsertSort(SqList *L)
{
    int i,j;
    for(i = 2;i<=L->length;i++)
    {
        if(L->r[i]<L->r[i-1])/*需要将L->r[i]插入有序子表*/
        {
      L->r[0] = L->r[i];  /*设置哨兵*/
    for(j=i-1;L->[j]>L->r[0];j--)
L->r[j+1] = L->r[i];/*记录后移*/
    L->r[j+1] = L->r[0];/*插入到正确位置*/
        }  
    }
}


时间复杂度:.

2.5 希尔排序(Shell Sort)

    所谓的基本排序,就是小的关键字在前面,大的基本在后面,不大不小的基本在中间。

    将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。

算法代码:

/*对顺序表L作希尔排序*/
void ShellSort(SqList *L)
{
    int i,j;
    int increment = L->length;
    do
    {
        increment = increment/3+1;   /*增量序列*/
        for(i=increment+1;i<=L->length;i++)
        {/*需要将L->r[i]插入有序增量字表*/
            if(L->r[i]<L->r[i-increment])
            {
                L->r[0] = L->r[i];       /*暂存L->r[0]*/
                for(j=i-increment;j>0&&L->r[0] < L->r[j];j-=increment)
                    L->r[j+increment] = L->r[j]; /*记录后移,查找插入位置*/
                L->r[j+increment] = L->r[0];    /*插入*/
            }
        }
    }
    while(increment > 1);
}


希尔排序的关键并不是随便分组后排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式的移动,使得排序的效率提高。

大量增量表明当增量序列为 时,可获得不错的效率。需要注意的是,增量序列的最后一个增量值必须等于1才行。另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法。

2.6 堆排序

堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子的节点的值,称为小顶堆。

如果按照层序的方式给结点从1开始编号,则结点之间满足如下关系:

2.6.1 堆算法排序

堆排序(Heap Sort)就是利用堆(假设利用大顶堆)进行排序的方法:将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶得根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。

堆排序的时间复杂度为O(nlogn)。

2.7 归并排序

归并排序(Merging Sort)就是利用归并的思想实现的排序算法。原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后量量归并,得到[n/2]([x]表示不小于x的最小整数)个长度为2或1的有序子序列;再量量归并,...,如此重复,直至得到一个长度为n的有序序列为止,这种排序方法称为2路归并排序。

归并时间复杂度:O(nlogn)。

2.8 快速排序

快速排序(Quick Sort)思想:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录进行继续排序。

时间复杂度:O(logn)。

在整体性能上,快速排序是排序的王者。

2.9 总结

从算法的简单性来看,7中算法分为:

·简单算法:冒泡,简单选择,直接插入。

·改进算法:希尔,堆,归并,快速。

3 结语

    以上是所有内容,希望对大家有所帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值