排序总结

按排序过程中依据的不同原则可分为:
1 插入排序
2 选择排序
3 交换排序
4 归并排序
5 计数排序

按照时间复杂度可分为:
1 简单的排序方法 O(n^2), 包括直接插入排序、冒泡排序、选择排序
2 先进的排序方法O(nlogn), 包括归并排序、快速排序、堆排序
3 基数排序 O(d.n)
各种算法的实现默认一般都是按升序排序。

稳定性
稳定排序,所有复杂度为O(n^2)的简单排序都是稳定的,基数排序也是稳定的。
不稳定排序,所有性能较好的排序方法都是不稳定的。

插入排序:依次插入一个未排序元素到已排序的序列中。插入排序是稳定排序,因它是每次考虑连续的两个元素。
说一种方法是稳定的,是说为实现排序,它可以是稳定的(当然我们可以以非稳定的方法实现),那么该方法就是稳定的,即稳定优先。

最好O(n), 最坏O(n^2)
插入排序与STL sort的比较,单位ms

View Code
/*
    稳定排序
    要注意保持稳定
*/
template<typename T>
void InsertionSort(T *begin, T *end)
{
    T *p, *q;
    for (p=begin+1; p!=end; p++)
    {
        q=p-1;
        T t = *p;
        //排序的稳定是由t<*q保证的,换成其它形式也可以排序,但是不能保证稳定
        while (q >= begin && t<*q)  
        {
            *(q+1)=*q;
            q--;
        }        
        *(q+1) = t;
    }
}

 

 

希尔排序:是对直接插入排序的改进
当序列基本正序时,直接插入排序的效率接近O(n)。另一方面直接插入排序算法简单,因此在n很小时,效率很高。, 希尔排序正是基于这两点做改进。
主要策略:把序列分成很多小的子序列,分别进行直接插入排序。不断扩大子序列的长度直到长度为n,即完成排列。
这里的子序列并不是连续元素构成的,而是间隔相同的一系列元素构成的。这样随着子系列长度的增大(对应于增量的减小,子系列数的减小),整个系列就会基本有序。
希尔排序的性能还和使用的增量系列有关。一个比较好的增量系列是:dlta[k] = 2^(t-k+1)-1。t为总的排序趟数,k为趟序。t = floor(log2(n+1))。
假设n=100, 那么t=6, dlta = {63, 31, 15, 7, 3, 1}
Shell排序与STL sort的比较,单位ms。可见Shell 排序的性能确实不错。不过实现会稍有些复杂。

View Code
template<typename T>
void ShellSort(T *begin, T *end)
{
    T *p, *q;
    //先求增量系列
    int t=0, sum=1; //t总的排序趟数
    int n=end-begin;
    while (sum<n)
    {
        sum *= 2;
        t++;
    }
    //多于一个元素
    if (sum > 1)
    {
        sum /= 2;
        t--;
    }
    int *dlta = new int[t];
    int i;
    for (i=0; i<t; i++)
    {
        dlta[i] = sum-1;
        sum /= 2;
    }

    i=0;
    while (i<t)
    {
        for (p=begin+dlta[i]; p!=end; p++)
        {
            if (*p<*(p-dlta[i]))
            {
                q = p-dlta[i];
                T temp = *p;
                while (q>=begin && temp<*q)
                {
                    *(q+dlta[i]) = *q;
                    q -= dlta[i];
                }
                *(q+dlta[i]) = temp;
            }
        }
        i++;
    }
    delete [] dlta;
}

 

 交换排序
冒泡排序是最简单的一种交换排序。交换:如果两个元素不符合某个顺序,则交换它们的值。
冒泡排序的性能始终是O(n^2)。最坏交换次数是O(n^2)
冒泡排序与STL sort的比较。可见冒泡排序的性能比直接插入排序的性能要差不少。基本不会用到,主要作为理解交换排序这个概念。

View Code
template<typename T>
void BubbleSort(T *begin, T *end)
{
    T *p, *q;
    for (p=end-1; p>begin; p--)
    {
        for (q=begin; q<p; q++)
        {
            if (*(q+1)<*q)
                Swap(*(q+1), *q);
        }
    }
}

 

 

快速排序:最常用到的排序。其平均性能是最好的。
是对冒泡排序的一种改进。它的基本思想是,通过一趟排序根据中轴值将待排记录分成两个系列,一个系列中的所有元素均大于中轴值,另一个系列中的元素均小于中轴值。然后分别利用同样的方法排序两个系列直到整个系列有序。
快速排序与STL sort的比较。两者性能基本一样,实现应该也差不多。

View Code
template<typename T>
void _QuickSort(T *begin, T *end)
{    
    if (begin < end)
    {
        T *p=begin, *q=end;
        int n = end - begin+1;
        T pivotKey = *(begin+n/2); //中轴值

        int flg=1; 
        while (p < q)
        {
            //从前面找一个大于中轴值的值,从后面找一个小于中轴值的值,交换两者的值
            while (p<=q && *p<pivotKey) p++; 
            while (p<=q && *q>pivotKey) q--;  
            if (*p == *q)
            {
                if (p<q) //如果待排序记录都一样,这里的flg就是必须的
                {
                    if (flg==1) {p++; flg=0;}
                    else { q--; flg=1; }
                }
            }
            else
                Swap(*p, *q);
            
        }
        _QuickSort(begin, p-1);
        _QuickSort(p+1, end);
    }
}
//这个函数是为了使QuickSort的接口和其它方法保持一致
template<typename T>
void QuickSort(T *begin, T *end)
{
    _QuickSort(begin, end-1);
}

 

 选择排序
简单选择排序:每次从未排序的记录中选择一个最小的加入到已排序记录中。
选择排序的性能还是不错的,因为交换次数少。在几个简单排序中是性能最好的一个。性能也很稳定,都是O(n^2)
选择排序与STL sort比较

 

View Code
template<typename T>
void SelectionSort(T *begin, T *end)
{
    T *p, *q;
    for (p=begin; p<end-1; p++)
    {
        T *t = p;
        for (q=p+1; q<end; q++)
        {
            if (*q < *t)
                t = q;
        }
        Swap(*t, *p);
    }
}

 

显然,选择排序的主要操作是关键字的比较。那么要改进的突破口就在于减少关键字的比较。

堆排序:利用“堆”来减少选择“最小关键字”时所做的比较操作。利用堆可以在O(log2n)内找出一个集合中的最小关键字。从而使堆排序的时间复杂度将为O(nlog2n)。
堆排序的主要操作是建堆、调整堆、交换。
建堆:把一个集合调整为一个堆。升序排序要用到大根堆。
堆的主要应用不在排序,而在于选出一个集合中的最大或最小值。
堆排序与STL sort比较,性能上和快速排序差不多。

感觉这个比快速排序容易实现。而且只需要一个单位的辅助空间,也没有递归的开销。不过,可能对于线性结构需要知道位置信息,对于链式结构必须组织成树形结构。所以应用场景不如快排广泛。下面的代码中有一处错误: T orgin = *p 应改为 T orgin = p[k];

View Code
/*
    下标从0开始
    应尽量减少元素的交换次数
*/
template<typename T>
void AdjustHeap(T *p, int k, int n)
{
    T orgin = *p;
    int t;
    while (k<n)
    {
        t=k;
        if (2*(k+1)-1<n)
            t = 2*(k+1)-1;
        if (2*(k+1)<n && p[t]<p[2*(k+1)])
            t = 2*(k+1);
        if (t==k || p[t]<orgin ) //最大值小于根
        {
            break;
        }
        p[k] = p[t];
        k = t;
    }
    p[k] = orgin;
}

template<typename T>
void CreateHeap(T *p, int n)
{
    for (int i=n/2; i>=0; i--)
        AdjustHeap(p, i, n);
}

template<typename T>
void HeapSort(T *begin, T *end)
{
    int n=end-begin;
    
    CreateHeap(begin, n);
    for (int i=n-1; i>0; i--)
    {
        Swap(begin[i], begin[0]);
        AdjustHeap(begin, 0, i);
    }
}

 归并排序
把待排序记录分成两个子系列,分别排序两个子系列,然后再把这两个子系列归并。对子系列的排序一应用归并排序。
可知 f(n) = 2f(n/2) + n ,即f(n)=O( nlog2n )
归并排序最大的特点是它是一种稳定的排序。
与STL sort的比较,相比快速排序和堆排序,性能会稍微慢一点。

View Code
template<typename T>
void MergeSort(T *begin, T *end)
{
    if (begin >= end-1)
        return;
    if (begin+1 == end-1)
    {
        if (*(end-1) < *begin)
            Swap(*begin, *(end-1));
    }
    else
    {
        T *mid=begin+(end-begin)/2;
        MergeSort(begin, mid+1); //not include mid+1
        MergeSort(mid+1, end);
        T *tarry = new T[end-begin];
        T *pstart = tarry;
        T *p = begin;
        T *q = mid+1;
        int i=0;
        while (p<=mid && q<end)
        {
            if (*q<*p)
            {
                *tarry++ = *q;
                q++;
            }
            else
            {
                *tarry++ = *p;
                p++;
            }
        }
        while (p<=mid)
        {
            *tarry++ = *p++;
        }
        while (q<end)
        {
            *tarry++ = *q++;
        }
        tarry = pstart;
        while (begin < end)
        {
            *begin++ = *tarry++;
        }
        delete [] pstart;
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值