【算法】排序算法

对于一般的内部排序应用程序,选用的方法不是插入排序、希尔排序,就是快速排序。他们的选择主要是根据输入的大小来决定。

1、插入排序

对含N个元素的数组使用插入排序,,一共由N-1次排序组成。循环历遍到位置P的时候,保证位置P之前(0-P)的元素都是已经排序的。插入排序,嵌套循环,外层循环历遍到某一个位置,确保该位置之前的元素是排序好的。内层循环,通过和相邻元素交换,实现排序。插入排序,将大于当前元素的元素集体向后移动一格,然后让当前这个待排序的元素插入到空出来的那个位置中。

插入排序的算法复杂度为:O(n^{2}),由于这个界是精确的,计算可以得复杂度为\Theta(n^{2})

 

void
InsertionSort(int A[],int N)
{
    int j,p;
    int temp;
    for(p=1;p<N;p++)
    {
        temp=A[p];
        for(int j=p;j>0 && A[j-1]>temp; j--)//此处相当于讲大于当前元素temp的元素集体向后移动一格,然后将temp插入到对应位置
        {
            A[j]=A[j-1];
        }
        A[j]=temp;//将 j 定义在循环之外,通过这样的做法,避免明显地使用交换
    }
}

2、希尔排序

它通过比较相距一定间隔的元素来工作,各趟比较所用的距离随着算法的进行而减小,直到只比较相邻元素的最后一趟排序为止。一种流行的做法是使用折半的方法来进行,虽然这种做法的效果并不好。

void
shellsort(int A[],int N)
{
    int i,j,increment;
    int temp;
    for(increment=N/2;increment>0;increment/=2)
    {
        for(i=increment;i<N;i++)
        {
            temp=A[i];
            for(j=i; j>=increment;j-=increment)
            {
                if(temp<A[j-increment])
                {
                    A[j]=A[j-increment];
                }
                else
                    break;
            }
            A[j]=temp;
        }
    }
}

Hibbard提出一个稍微不同的增量序列,在实践中更好的结果。他的增量为 1, 3, 7, .......,2^{k}-1。使用这样的序列,和其他序列的差别在于相邻的增量没有公因子。hibbard增量的希尔排序的最坏情形运行时间为\TheTa\Theta(N^{3/2})

这里有一个视频演示,非常形象生动,进行的就是5,3,1序列的排序(其实就是插入排序)。非常有助于理解。https://www.bilibili.com/video/av17004970/

3、归并排序

归并排序以O(NlogN)最坏情形运行时间运行,所使用的比较次数几乎是最优的。

该算法是经典的分治策略,将问题分成小问题,然后递归求解。分治阶段将各个答案合并到一起。例如将一下两个已经排序的数列合并到一起,这个过程是线性的。

1132426

 

 

2152738

 

 

12131524262738

 

 

程序中的Merge函数使用是非常精妙的,Merge寒素合并的时候,需要一个临时数组,用于将左右两边排序好的数组合并。这里Merge位于最后一行,因此程序中任意时刻只需要一个临时数组活动。相反,如果Merge的每一个递归调用均在局部声明一个临时数组,那么在任意时刻就可能有logN个临时数组处于活动期,这对内存的占用是很过分的。

尽管归并排序的运行时间是O(NlogN),但是它很难用于内存排序,主要的问题在于,第一、使用归并排序的时候,需要附加合并左右两边数组的时间;第二、使用归并排序,合并的时候要将数据拷贝到一个临时数组中,之后要要将临时数组的数据拷贝到原来的数组中。这导致实际使用的时候速度变慢。在实际使用的时候,对内部排序还是使用快速排序。

补充:内排序是指所有的数据已经读入内存,在内存中进行排序的算法。排序过程中不需要对磁盘进行读写。同时,内排序也一般假定所有用到的辅助空间也可以直接存在于内存中。与之对应地,另一类排序称作外排序,即内存中无法保存全部数据,需要进

       //主程序(归并排序)
        void
        Mergesort( ElementType A[ ], int N )
        {
            ElementType *TmpArray;

            TmpArray = malloc( N * sizeof( ElementType ) );
            if( TmpArray != NULL )
            {
                MSort( A, TmpArray, 0, N - 1 );
                free( TmpArray );
            }
            else
                FatalError( "No space for tmp array!!!" );
        }

        void
        MSort( ElementType A[ ], ElementType TmpArray[ ], int Left, int Right )
        {
            int Center;

            if( Left < Right )
            {
                Center = ( Left + Right ) / 2;
                MSort( A, TmpArray, Left, Center );//排左边
                MSort( A, TmpArray, Center + 1, Right );//排右边
                Merge( A, TmpArray, Left, Center + 1, Right );//合并,这里的merge使用是非常精妙的
            }
        }
        /*乍一看,这里连个比较都没有,让人有些困惑,实际上比较是在Merge函数中进行的。
        递归最终将将进行到左右都只有一个元素,合并这两个元素的时候,自然的做了比较*/

        void
        Merge( ElementType A[ ], ElementType TmpArray[ ], int Lpos, int Rpos, int RightEnd )
        {
            int i, LeftEnd, NumElements, TmpPos;

            LeftEnd = Rpos - 1;
            TmpPos = Lpos;
            NumElements = RightEnd - Lpos + 1;

            /* main loop */
            while( Lpos <= LeftEnd && Rpos <= RightEnd )
                if( A[ Lpos ] <= A[ Rpos ] )
                    TmpArray[ TmpPos++ ] = A[ Lpos++ ];
                else
                    TmpArray[ TmpPos++ ] = A[ Rpos++ ];
            //上一个循环完了之后,左右两边只有一边有元素可能剩下来(使用二分,两个元素个数最多差1)
            while( Lpos <= LeftEnd )  /* Copy rest of first half */
                TmpArray[ TmpPos++ ] = A[ Lpos++ ];
            while( Rpos <= RightEnd ) /* Copy rest of second half */
                TmpArray[ TmpPos++ ] = A[ Rpos++ ];
            
            //将临时数组中的元素拷贝到A中
            /* Copy TmpArray back */
            for( i = 0; i < NumElements; i++, RightEnd-- )
                A[ RightEnd ] = TmpArray[ RightEnd ];
        }

行磁盘访问,每次读入部分数据到内存进行排序。算法复杂度分析:

当n=1的时候,T(1)=N

当n=N的时候,T(N)=2T(N/2)+N  ,因为将数组分成了两部分,所以有2T(N/2),另外,要将数组合并,所以需要N的时间。于是就找到了一个递推关系式。

计算方法:两边同时除以N

\frac{T(N))}{N} = \frac{T(N/2))}{N/2} +1                                   

\frac{T(N/2))}{N/2} = \frac{T(N/4)}{N/4} + 1

...........

\frac{T(2)}{2} = \frac{T(1)}{1} + 1

这样的式子一共有log N 个(以2为底),将所有式子带入第一个式子可以得到

\frac{T(N)}{N} = \frac{T(1)}{1} + log N

于是可以得到最后的答案:T(N) = N log N +N = O(NlogN)

光说不练假把式 :关于归并排序的一道练习题leetcode 148. 排序链表C++解答

4、快速排序

快速排序是实践中最快的排序算法,平均运行时间为O(NlogN),最坏情形为O(N^{2}).

快速排序使用一个枢纽元将数据分为两部分,然后递归地快速排序这两部分。枢纽元的选取,随机选取是比较安全。另一种是使用三种元素来确定枢纽元。分别取最左边,最右边,中间的三个元素比价,取介于中间的元素作为枢纽元。

        ElementType
        Median3( ElementType A[ ], int Left, int Right )
        {
            int Center = ( Left + Right ) / 2;

            if( A[ Left ] > A[ Center ] )
                Swap( &A[ Left ], &A[ Center ] );
            if( A[ Left ] > A[ Right ] )
                Swap( &A[ Left ], &A[ Right ] );
            if( A[ Center ] > A[ Right ] )
                Swap( &A[ Center ], &A[ Right ] );

            /* Invariant: A[ Left ] <= A[ Center ] <= A[ Right ] */

            Swap( &A[ Center ], &A[ Right - 1 ] );  /* Hide pivot */
            return A[ Right - 1 ];                /* Return pivot */
        }

        //主程序
        void
        Qsort( ElementType A[ ], int Left, int Right )
        {
            int i, j;
            ElementType Pivot;

/* 1*/      if( Left + Cutoff <= Right )
            {
/* 2*/          Pivot = Median3( A, Left, Right );
/* 3*/          i = Left; j = Right - 1;
/* 4*/          for( ; ; )
                {
/* 5*/              while( A[ ++i ] < Pivot ){ }
/* 6*/              while( A[ --j ] > Pivot ){ }
/* 7*/              if( i < j )
/* 8*/                  Swap( &A[ i ], &A[ j ] );
                    else
/* 9*/                  break;
                }
/*10*/          Swap( &A[ i ], &A[ Right - 1 ] );  /* Restore pivot */

/*11*/          Qsort( A, Left, i - 1 );
/*12*/          Qsort( A, i + 1, Right );
            }
            else  /* Do an insertion sort on the subarray */
/*13*/          InsertionSort( A + Left, Right - Left + 1 );
        }

5、桶式排序

一些特殊情况下,排序可以以线性时间运行。例如桶式排序。

例如:对于一个数组A,他的所有元素都是由小于M的正整数够成,那么这就可以使用桶式排序。算法很简单,使用一个大小为M的数组count,并将所有元素初始化为零,循环历遍数组,每当读到A_{i}的时候,将count[A_{i}]的值加一。最后在通过一个循环历遍count数组,就可以完成排序。这个算法的时间界为O(N)。

参考:

学习书籍《数据结构与算法分析——C语言描述》

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值