面试+排序算法

一张各种排序算法的对比:


1、选择排序(Selection Sort)  
选择排序的基本思想是对待排序的记录序列进行n-1遍的处理,第i遍处理是将L[i..n]中最小者与L[i]交换位置。这样,经过i遍处理之后,前i个记录的位置已经是正确的了。 
2、 插入排序 (Insertion Sort)  

插入排序的基本思想是,经过i-1遍处理后,L[1..i-1]己排好序。第i遍处理仅将L[i]插入L[1..i-1]的适当位置,使得L[1..i] 又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较L[i]和L[i-1],如果L[i-1]≤ L[i],则L[1..i]已排好序,第i遍处理就结束了;否则交换L[i]与L[i-1]的位置,继续比较L[i-1]和L[i-2],直到找到某一个位置j(1≤j≤i-1),使得L[j] ≤L[j+1]时为止。

3、 堆排序  
堆排序是一种树形选择排序,在排序过程中,将A[n]看成是完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点 之间的内在关系来选择最小的元素。  
4、 归并排序  
设有两个有序(升序)序列存储在同一数组中相邻的位置上,不妨设为A[l..m],A[m+1..h],将它们归并为一个有序数列,并存储在A[l..h]。  
5、快速排序  

快速排序是对冒泡排序的一种本质改进。它的基本思想是通过一趟扫描后,使得排序序列的长度能大幅度地减少。在冒泡排序中,一次扫描只能确保最大数值的数移到正确位置,而待排序序列的长度可能只减少1。快速排序通过一趟扫描,就能确保某个数(以它为基准点吧)的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理它左右两边的数,直到基准点的左右只有一个元素为止。

6、希尔排序

在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为 增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。

一、直接插入排序(稳定):

思想:“一趟一个“地将待排序记录插入到已经排好序的部分记录的适当位置中,使其成为一个新的有序序列,直到所有待排序记录全部插入完毕。

流程:

分析:

  • 空间仅需使用一个辅助单元。故空间复杂度为O(1);
  • 最好情况待排序序列已经有序,每趟操作只需比较1次和移动0次,此时,总的比较次数为n-1,总移动次数为0,故最好情况算法复杂度为O(n)
  • 最坏情况待排序序列按逆序排序,这时在第j趟操作中,为插入元素需要同前面的j个元素比较,移动元素的次数为j+1.此时有:总的比较次数=1+2+...+(n-1)=n(n-1)/2;总移动次数=2+3+4+...+n=(n+2)(n-1)/2,故最坏情况算法复杂度为O(n^2)
  • 平均情况在第j趟操作中,插入记录大约需要同前面j/2个元素比较,移动记录的次数为j/2+1次,此时总的比较次数约为n^2/4;总移动次数为n^2/4;故时间复杂度为O(n^2)

代码:

void insertSort(int *a,int low,int high)//插入排序   
{  
    for(int i=low+1;i<high;i++)  
    {  
        if(a[i]<a[i-1])  //判断:需要移动位置吗?
        {  
            int tmp=a[i];  
            a[i]=a[i-1];  //设置哨兵为a[i-1].
            int j=i-2;  
            for(;j>=low&&tmp<a[j];j--)  //那就开始找到应该放的位置吧!
                a[j+1]=a[j];    记录后移
            a[j+1]=tmp;  
        }  
    }  
}   

二、二分插入排序(稳定):

思想:与直接插入类似,在找寻插入位置时采用二分查找方式;

分析:与直接插入相同,仅仅减少了元素的比较次数,并没减少元素的移动次数,因此仍为O(n^2);

代码:

void binaryInsertSort(int *a,int low,int high)//二分插入排序   
{  
    for(int i=low+1;i<high;i++)  
    {  
        int tmp=a[i];  
        int hi=i-1;  
        int lo=low;  
        while(lo<=hi)  
        {  
            int mid=(lo+hi)/2;  
            if(a[mid]>tmp) hi=mid-1;  
            else lo=mid+1;  
        }  
        for(int j=i-1;j>hi;j--)  
        {  
            a[j+1]=a[j];  
        }  
        a[hi+1]=tmp;  
    }  
}  

三、希尔排序

思想:”基本有序化“可以提升插入排序的效率,将记录序列分成若干子序列,每个子序列分别进行插入排序。需要注意的是,这种子序列是由间隔为某个增量的一组数据组成,即跳跃式选择数据组。(这个解释起来比较抽象,面试中也很少问,但编程贼简单)


流程:

分析:时间复杂度:开始时增量较大,分组较多,每组的记录数目较少,当排序规模n值较小时,n和n^2的差距也较小,但随着增量d逐渐缩小,分组逐渐减小,而各组的记录数目逐渐增多,但由于已经基本有序,所以新的一趟也较快。时间复杂度与增量的设置有关,约在O(n^1.25)和O(n^1.6)之间;

代码:

void shellInsert(int *a,int low,int high,int deltaK)//希尔排序   
{  
    for(int i=low+deltaK;i<high;i++)  //deltak是增量
    {  
        if(a[i]<a[i-deltaK])  
        {  
            int tmp=a[i];  
            int j=i-deltaK;  
            for(;j>=low&&tmp<a[j];j=j-deltaK)  
                a[j+deltaK]=a[j];  //记录后移,找到插入位置
            a[j+deltaK]=tmp;  //插入
        }  
    }  
}   
void shellSort(int *a,int low,int high,int *delta,int m)//希尔排序   
{  
    for(int k=0;k<m;k++)  
    {  
        shellInsert(a,low,high,delta[k]);     
    }     
}  

四、冒泡排序(稳定):

1、思想:对n个数进行n-1次扫描,每次扫描比较相邻两个数字,大的移到后面。这样每趟排序会把最大的数字扔到最后面;

规则:两两比较相邻的关键字,如果反序则交换,直到没有反序的记录为止。

2、流程:

3、分析:

  • 空间:需要一个辅助单元,故空间复杂度为O(1);
  • 最好情况:数据全部排好序,这时循环n-1次,时间复杂度为O(n),但是程序中可以设置flag,某一次未发生位置交换,程序退出;
  • 最坏情况:数据全部逆序存放,这时循环n-1次,比较次数为n-1+n-2+...+1=n(n-1)/2;移动次数为3*(n-1+n-2+...+1)=3n(n-1)/2,时间复杂度为O(n^2);
  • 平均情况:与直接插入分析一样,时间复杂度为O(n^2);

4、代码:

void bubbleSort(int *a,int low,int high)//冒泡排序普通实现   
{  
    int n=high-low;  
    for(int i=0;i<n-1;i++)  
    {  
        int flag=0;  
        for(int j=0;j<n-i-1;j++)  
        {  
            if(a[j]>a[j+1])  
            {  
                flag=1;  
                int tmp=a[j];  
                a[j]=a[j+1];  
                a[j+1]=tmp;  
            }  
        }  
        if(flag==0)  
            break;  
    }  
}  
腾讯面试——递归实现冒泡。

代码(冒泡排序的递归实现):

void bubbleSort2(int *a,int low,int high)//冒泡排序递归实现   
{  
    if(high==1)  
        return;  
    for(int i=0;i<high-1;i++)  
    {  
        if(a[i]>a[i+1])  
        {  
            int tmp=a[i];  
            a[i]=a[i+1];  
            a[i+1]=tmp;  
        }  
    }  
    bubbleSort2(a,low,--high);  
}   
五、快速排序(不稳定):

思想:每次选择一个元素放置到它应该在的位置,划分原来序列为前面的数都不大于它,后面的数都不小于它;

流程:

分析(消耗的时间空间主要在递归上):

  • 空间:最好情况,每次取到的元素刚好平分整个数组,此时空间复杂度为O(log n);最坏情况,每次取到的就是数组的最大或最小的,这种情况就是冒泡排序,此时空间复杂度为O(n),所以空间复杂度为O(log n)~O(n);
  • 最好情况:相当于一个完全二叉树结构,即每次标准元素都把当前数组分成两个大小相等的子数组,这是分解次数等于完全二叉树的深度lb n;每次快排过程无论把数组怎样划分,全部的比较次数都接近于n-1次,所以时间复杂度为O(n lb n);
  • 最坏情况:退化成冒泡排序,时间复杂度为O(n^2);
  • 平均情况:时间复杂度为O(n lb n);

代码:

void quickSort(int *a,int low,int high)//快速排序   
{  
    int i=low;  
    int j=high-1;  
    int tmp=a[low];  
    while(i<j)  
    {  
        while(i<j&&tmp<=a[j]) j--;  
        if(i<j)  
        {  
            a[i]=a[j];  
            i++;  
        }  
        while(i<j&&tmp>=a[i]) i++;  
        if(i<j)  
        {  
            a[j]=a[i];  
            j--;  
        }  
    }  
    a[i]=tmp;  
    if(low<i) quickSort(a,low,i-1);  
    if(i<high) quickSort(a,i+1,high);  
}  
六、直接选择排序(不稳定):


思想:在每趟过程中将待排序数组最小数放到最前面。

规则:


分析:

  • 空间:需要一个辅助单元,故O(1);
  • 最好最坏情况:无论初始状态如何,都需要进行n-1+n-1+...+1=n(n-1)/2次比较,所以时间复杂度为O(n^2);

代码:

void selectSort(int *a,int low,int high)//直接选择排序   
{  
    int n=high-low;  //获得长度
    for(int i=0;i<n-1;i++)  
    {  
        int min=i;  
        for(int j=i+1;j<n;j++)  
        {  
            if(a[min]>a[j])  
                min=j;    
        }     
        if(min!=i)  
        {  
            int tmp=a[min];  
            a[min]=a[i];  
            a[i]=tmp;  
        }  
    }   
}  

七、堆排序

思想:把待排序的数据元素构造成一个完全二叉树结构,则每次选择出一个最大的数据元素只需比较完全二叉树的高度次,即logn次,则排序算法的最好最坏时间复杂度均为O(n log n);


空间:O(1);

这个比较复杂,具体的讲解可以参考博文

http://www.cnblogs.com/skywang12345/p/3602162.html

代码:

void heapAdjust(int *a,int low,int high)//堆排序,调整堆   
{  
    int tmp=a[low];  
    for(int j=2*low+1;j<high;j=j*2+1)  
    {  
        if(j<high-1&&a[j]<a[j+1]) j++;  
        if(tmp>=a[j]) break;  
        a[low]=a[j];  
        a[j]=tmp;  
        low=j;  
    }  
    //a[low]=tmp;  
}  
void heapSort(int *a,int high)//堆排序   
{  
    for(int i=(high)/2-1;i>=0;i--)  
        heapAdjust(a,i,high);  
  
    for(int i=high-1;i>0;i--)  
    {         
        int tmp=a[0];  
        a[0]=a[i];  
        a[i]=tmp;  
        heapAdjust(a,0,i);  //将a[0...i]重新调整成最
    }         
}   

八、归并排序(稳定)

思想:将若干个已经排好序的子序列合并成一个有序的序列。

分析:

  • 时间:归并的次数约为lb n,任何一次的归并比较次数约为n-1,所以最好最坏时间复杂度均为O(n log n);
  • 空间:需要一个与原数组大小一样的辅助数组,故空间复杂度为O(n).

代码:

void merge(int *a,int p,int q,int r)//一次归并算法   
{  
    int n1=q-p+1;  
    int n2=r-q;  
    int *L=new int[n1+1];  
    int *R=new int[n2+1];  
    for(int i=0;i<n1;i++)  
    {  
        L[i]=a[p+i];  
    }  
    for(int j=0;j<n2;j++)  
    {  
        R[j]=a[q+j+1];  
    }  
    L[n1]=1000000;  
    R[n2]=1000000;  
    for(int i=0,j=0,k=p;k<=r;k++)  
    {  
        if(L[i]<=R[j])  
        {  
            a[k]=L[i];  
            i++;  
        }  
        else  
        {  
            a[k]=R[j];  
            j++;  
        }  
    }  
    delete []L;  
    delete []R;  
}  
void mergepass(int *a,int p,int r)//二路归并   
{  
    if(p<r)  
    {  
        int q=(p+r)/2;  
        mergepass(a,p,q);  
        mergepass(a,q+1,r);  
        merge(a,p,q,r);   
    }     
}   






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值