您的位置:首页 > 编程语言

总结八大排序算法的基本思想与代码实现

2015-09-16 15:19 706 查看
在写这篇文章之前,先点一根红烛默哀我腾讯挂掉的笔试/(ㄒoㄒ)/~~小女子尽力了,真的真的尽力了/(ㄒoㄒ)/~~奈何缘浅,就此别过吧ヾ( ̄▽ ̄)Bye~Bye~

在这里先提前说明一下,在下面讲解中,会涉及到时间复杂度和空间复杂度的计算,那么先来说一下怎样计算时间复杂度和空间复杂度?

按照书上所说,时间复杂度的计算方法如下:

1.计算出基本操作的执行次数T(n);

2.计算出T(n)的数量级,令f(n)=T(n)的数量级;

3.用大O来表示时间复杂度,当n趋近于无穷大时,如果lim(T(n)/f(n))的值不等于0,则T(n)=O(f(n)),称为算法的时间复杂度。

吧啦吧啦吧啦~~~是不是很复杂的感觉,瞬间感到了深深的恶意吗?那我们简化一下,用学渣的思想去计算:

1.找到执行次数最多的语句;

2.计算语句中执行次数的数量级;

3.OK~那就是时间复杂度了,不要怀疑~~~

言回正传啦,咳咳,说排序了。

排序分为内部排序和外部排序,我们接触最多的也就是内部排序,所谓内部就是数据记录在内存中,八大排序也就是典型的内部排序。

1.直接插入排序

基本思想:

假设我们存在一个待排元素数组R[0...n-1],在排序过程中,我们将R分成两个子序区,R[0..i-1]称为有序区,R[1..n-1]称为无序区,然后我们再从无序区开头位置R[i]开始扫描,每一次在有序区中只增加一个元素到合适的位置。

C++代码:
void InsertSort(RecType R[],int n){
    int i,j;
    RecType temp;
    for(i=1;i<n;i++){
        temp = R[i];
        j=i-1;              //从右向左,在有序区中找到插入的位置
        while(j>=0 && temp.key<R[j].key){
            R[j+1]=R[j];        //将关键字大于R[i]的元素后移
            j--;
        }
        R[j+1]=temp;
    }
}
2.折半插入排序

基本思想:

类似于直接插入,但是我们可以在有序区找插入位置的时候,采用折半查找法,再通过移动元素进行插入。我们可以把R[0..n-1]设为R[low..high],找到插入位置为R[high+1],再将R[high+1..i-1]中的元素后移一个位置,并置R[high+1]=R[i]。

C++代码:

void InsertSort(RecType R[],int n){
    int i,j,low,high,mid;
    RecType tmp;
    for(i=1;i<n;i++){
        tmp=R[i];
        low=0;
        high=i-1;
        while(low<=high){
            mid=(low+high)/2;
            if(tmp.key<R[mid].key)
                high=mid-1;
            else
                low=mid+1;
        }
        for(j=i-1;j>=high+1;j--)
            R[j+1]=R[j];
            R[high+1]=tmp;
            
        
    }
}
3.希尔排序

基本思想:

首先先确定增量d1的值,一般取d1=n/2,作为增量,将数组分为d1个组并且将相隔距离为d1的元素全部放在同一组内,在组内进行直接插入排序。以此类推,知道增量dt=1,也就是全部元素在同一个组内时,再将元素排序一遍即可。

C++代码:

void shellSort(RecType R[],int n){
    int i,j,gap;
    RecType tmp;
    gap = n/2;
    while(gap>0){
        for(i=gap;i<n;i++){           //将所有相隔gap元素组采用直接插入排序
            tmp = R[i];
            j=i-gap;
            while(j>=0 && tmp.key<R[j].key){
                R[j+gap]=R[j];
                j=j-gap;
            }
            R[j+gap]=tmp;
            
        }
        gap=gap/2;                   //减少增量
    }
}
4.冒泡排序

基本思想:

通过无序区中相邻元素中关键字的比较和位置的交换,是关键字最小的元素如气泡一般逐渐往上“漂浮”直至水面。整个算法从最下面开始,对相邻元素之间进行比较,经过一趟排序后,最小的元素浮之水面。

C++代码:

void BubbleSort(RecType R[],int n){
    int i,j;
    RecType tmp;
    bool flag;
    for(i=0;i<n-1;i++){
        flag=false;
        for(j=n-1;j>i;j--)
            if(R[j].key<R[j-1].key){
                tmp=R[j];
                R[j]=R[j-1];
                R[j-1]=tmp;
                flag=true;
            }
        if(!flag)
            return ;
    }
}
5.快速排序

基本思想:

在待排序的数组中,找到一个基准元素通常为R[0],将该元素放置合适的位置后,数据序列被划分为两部分,所有比关键字小的元素放到前面,而比关键字大的部分放到后面。先来说一下一趟快速排序的过程吧。首先我们设置两个指针,分别指向数组的头和尾,再将基准元素R[s]用tmp保存,令j从n-1开始扫描直到R[j].key<tmp.key,将R[j]移到i的位置。然后令i开始扫描直至R[i].key>tmp.key,将R[i]移到j的位置,以此类推,直到i=j。

C++代码:

void quickSort(RecType R[],int s,int t){
    int i=s,j=t;
    RecType tmp;
    if(s<t){
        tmp=R[s];
        while(i!=j){
            while(j>i && R[j].key>=tmp.key)
                j--;             //从右向左扫描,找到第一个小于tmp的R[j]
            R[i]=R[j];
            while(i<j && R[i].key<=tmp.key)
                i++;           //从左到右扫描,找到第一个大于tmp的R[i] 
            R[j]=R[i];
        }
        R[i]=tmp;
        quickSort(R,s,i-1);
        quickSort(R,i+1,t);
    }
}


6.直接选择排序

基本思想:

第i趟排序开始时,当前有序区和无序区分别为R[0..i-1]和R[i..n-1],该趟排序是从当前无序区中选出关键字最小的元素R[k],将它与无序区的第一个元素R[i]交换,是R[0..i]和R[i+1..n-1]分别变为新的有序区和新的无序区。直接选择排序每趟产生的有序区一定是全局有序区,也就是每趟产生的有序区中所有元素都归位了。

C++代码:

void SelectSort(RecType R[],int n){
    int i,j,k;
    RecType tmp;
    for(i=0;i<n-1;i++){
        k=i;
        for(j=i+1;j<n;j++)           //在当前无序区R[i..n-1]中选key最小的R[k] 
            if(R[j].key<R[k].key)
            k=j;
            if(k!=i){
            tmp=R[i];
            R[i]=R[k];
            R[k]=tmp;
        }
            
        
    }
}
7.堆排序

基本思想:

是一种树形选择排序方法,在排序过程中,将R[1..n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子节点之间的内在关系,当前无序区中选择关键字最大的元素。

void sift(RecType R[],int low,int high){
    int i=low,j=2*i;
    RecType tmp=R[i];
    while(j<=high){
        if(j<high && R[j],key<R[j+1].key)
            j++;
        if(tmp.key<R[j].key){
            R[i]=R[j];
            i=j;
            j=2*i;
        }
        else
            break;
    }
    R[i]=tmp;
}
void HeapSort(RecType R[],int n){
    int i;
    RecType tmp;
    for(i=n/2;i>=1;i--)
        sift(R,i,n);
    for(i=n;i>=2;i--)
        {
        tmp=R[1];
        R[1]=R[i];
        R[i]=tmp;
        sift(R,1,i-1);
    }
        
    
}
8.归并排序

基本思想:

将R[0..n-1]看成是n个长度为1的有序序列,然后进行两两归并,得到[n/2]个长度为2的有序序列,在进行两两归并。。。直到得到一个长度为n的有序序列。

C++代码:

void Merge(RecType R[],int low,int mid,int high){
    RecType *R1;
    int i=low,j=mid+1,k=0;
    R1=(RecType*)malloc((high-low+1)*sizeof(RecType));
    while(i<=mid && j<=high)
        if(R[i].key<=R[j].key)
            {
            R1[k]=R[i];
            i++;
            k++;
        }else{
            R1[k]=R[j];
            j++;
            k++;
        }
        while(i<=mid){
            R1[k]=R[i];
            i++;
            k++;
        }
        while(j<=high)
            {
            R1[k]=R[j];
            j++;
            k++;
        }
        for(k=0;i=low;i<=high;k++,i++)
            R[i]=R1[k];
        free(R1);
    
    
}


总结:

各种排序方法的性能



稳定性:

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序,
这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。

稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序



选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。

设待排序元素的个数为n.

1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;

堆排序 : 如果内存空间允许且要求稳定性的,

归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2) 当n较大,内存空间允许,且要求稳定性 =》归并排序

3)当n较小,可采用直接插入或直接选择排序。

直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

5)一般不使用或不直接使用传统的冒泡排序。

6)基数排序

它是一种稳定的排序算法,但有一定的局限性:

  1、关键字可分解。

  2、记录的关键字位数较少,如果密集更好

  3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: