C++面试笔记--排序

    这里我们开始复习排序的一些面试题。

  首先我们来看一下各个排序方法的时间复杂度和稳定性的比较,见下面表格:

      

排序法平均时间最差情形稳定度额外空间备注
冒泡O(n2)    O(n2)稳定O(1)n小时较好
交换    O(n2)    O(n2)不稳定O(1)n小时较好
选择O(n2)O(n2)不稳定O(1)n小时较好
插入O(n2)O(n2)稳定O(1)大部分已排序时较好
基数O(logRB)O(logRB)稳定O(n)

B是真数(0-9),

R是基数(个十百)

ShellO(nlogn)O(ns) 1<s<2不稳定O(1)s是所选分组
快速O(nlogn)O(n2)不稳定O(nlogn)n大时较好
归并O(nlogn)O(nlogn)稳定O(1)n大时较好
O(nlogn)O(nlogn)不稳定O(1)n大时较好

  选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,                               冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。

 

    1.冒泡排序:

  算法原理:比较相邻的两个元素,如果第一个比第二个大,就交换他们两个,持续一直比下去,直到比完一轮,最后一个元素应该是最大的元素;之后再重新从首部开始比较,重复上述步骤,数字越来越少,直到没有足够一对的数字可以进行比较。

  代码实现:

  

 1 #include<iostream>
 2 using namespace std;
 3 void bubble_sort(int a[],int n){
 4     for(int i=0;i<n;i++)
 5     for(int j=0;j=n-1-i;j++){
 6         if(a[j]>a[j+1]){
 7             int temp=a[j];
 8             a[j]=a[j+1];
 9             a[j+1]=temp;
10         }
11     }
12 }
View Code

 

  算法分析:

      •    

        倒序(最糟情况)
        第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次)
        第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次)
        第一轮:7,8,10,9->7,8,9,10(交换1次)
        循环次数:6次
        交换次数:6次

        其他:
        第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次)
        第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次)
        第一轮:7,8,10,9->7,8,9,10(交换1次)
        循环次数:6次
        交换次数:3次

        上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换,
        显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。
        写成公式就是1/2*(n-1)*n。
        现在注意,我们给出O方法的定义:

             若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。(呵呵,不要说没
        学好数学呀,对于编程数学是非常重要的!!!)

        现在我们来看1/2*(n-1)*n,当K=1/2,n0=1,g(n)=n*n时,1/2*(n-1)*n<=1/2*n*n=K*g(n)。所以f(n)
        =O(g(n))=O(n*n)。所以我们程序循环的复杂度为O(n*n)。
        再看交换。从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的
        有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换),
        复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的
        原因,我们通常都是通过循环次数来对比算法。

    2.交换排序:

  算法原理:每次用当前的元素同之后的元素一一的做比较。

  代码实现:

 1 #include<iostream>
 2 using namespace std;
 3 void exchange_sort(int a[],int n){
 4     //这个算法和冒泡排序差不多,但是它是用当前的元素同之后的所有元素作比较
 5     //而冒泡是一对一对之间做比较,还是有差别的
 6     for(int i=0;i<n;i++)
 7     for(int j=i+1;j<n;j++){
 8         if(a[j]<a[i]){
 9             int temp=a[j];
10             a[j]=a[i];
11             a[i]=temp;
12         }
13     }
14 }
View Code

  算法分析:

      •     倒序(最糟情况)
        第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次)
        第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次)
        第一轮:7,8,10,9->7,8,9,10(交换1次)
        循环次数:6次
        交换次数:6次其他:
        第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次)
        第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次)
        第一轮:7,8,10,9->7,8,9,10(交换1次)
        循环次数:6次
        交换次数:3次从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样
        也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以
        只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。所以它是不稳定的。

    

    3.选择排序:

  选择排序算法就是每一趟从待排序的记录中选出关键字最小(最大)的记录,顺序放在已排好序的子文件的最后(最前),直到全部记录排序完毕。常见的选择排序有直接选择排序(Selection Sort),堆排序(Heap Sort),平滑排序(Smooth Sort),笛卡尔树排序(Cartesian Sort),锦标赛排序(Tournament Sort),循环排序(Cycle)。下面介绍前两种:堆排序和选择排序。

  直接选择排序:

  算法原理:这是一种简单直观的排序算法。它首先在未排序序列中找到最小(大)元素,存放到排序序列的其起始位置,然后再从剩余未排序的序列元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素排序完毕。

  算法的实现举例:

  

  算法代码:

 1 #include<iostream>
 2 using namespace std;
 3 void select_sort(int a[],int n){
 4     int min;//记录最小值元素的下标
 5     for(int i=0;i<n-1;i++){
 6         min=i;//赋予初值
 7         //寻找最小值,循环一遍
 8         for(int j=i+1;j<n;j++){
 9             if(a[j]<a[min])
10                 min=j;
11         }
12         //如果循环一遍之后的最小值不为a[i],则交换元素
13         if(min!=i){
14             int temp=a[i];
15             a[i]=a[min];
16             a[min]=temp;
17         }
18     }
19 }
View Code

 

 

 

  /此处转载博客:http://blog.csdn.net/left_la/article/details/8648582

  堆排序:是利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。堆排序的时间复杂度是O(nlgN),与快速排序达到相同的时间复杂度。但是在实际应用中,我们往往采用快速排序而不是堆排序。这是因为快速排序的一个好的实现,往往比堆排序具有更好的表现。堆排序的主要用途,是在形成和处理优先级队列方面。另外,如果计算要求是类优先级队列(比如,只要返回最大或者最小元素,只有有限的插入要求等),堆同样是很适合的数据结构。

  算法原理:

    • 通常堆是通过一维数组来实现的,在起始数组为0的情形中,对于节点i:
      其左子节点的下标为 (2*i+1);
      其右子节点的下标为 (2*i+2);
      其父节点的下标为 floor((i-1)/2)。
      在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义一下三个操作:
      1.最大堆调整(Max Heapify):在假定节点i的左右子节点为根的两颗二叉树都是最大堆的前提下,确保父节点大于子节点,否则下降原父节点,最终使以i为根的子树成为最大堆。
      2.创建最大堆(Build Max Heap):将堆所有数据重新排序,对所有非叶子节点调用一次Max Heapify。

      3.堆排序(Heap Sort):首先创建最大堆,然后依次将堆的根节点与末节点交换、剔除末节点、对根节点进行最大堆调整,直到堆中的节点数为1,排序结束。

      算法示意图:

 

  算法代码:

  

 1 // 最大堆调整
 2 void MaxHeapify(int *a, int i, int heapSize)
 3 {
 4     int l = (i+1)*2-1;
 5     int r = (i+1)*2;
 6     int largest;
 7 
 8     if (l<=heapSize && a[l]>a[i])
 9         largest = l;
10     else
11         largest = i;
12 
13     if (r<=heapSize && a[r]>a[largest])
14         largest = r;
15 
16     if (largest!=i)
17     {
18         swap(a[i], a[largest]);
19         MaxHeapify(a, largest, heapSize);
20     }
21 }
22 
23 // 创建最大堆
24 void BuildMaxHeap(int *a, int len)
25 {
26     for (int i=len/2-1; i>=0; i--)
27     {
28         MaxHeapify(a, i, len-1);
29     }
30 }
31 
32 // 堆排序
33 void HeapSort(int *a, int len)
34 {
35     BuildMaxHeap(a, len);
36     for (int i=len-1; i>0; i--)
37     {
38         swap(a[0], a[i]);
39         MaxHeapify(a, 0, i-1);
40     }
41 }
View Code

 

 

    4.插入排序:每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子文件中的适当位置,直到全部记录插入完成为止。插入排序有很多,这里我么讲两种:直接插入排序和希尔排序。

  直接插入排序:

  算法思想:是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对未排序的数据,在已排序序列中从后向前扫描,找到相应位置并插入。

  //此处转载:  http://blog.csdn.net/left_la/article/details/8656425

  插入排序算法的一般步骤:
    1.从第一个元素开始,该元素可以认为已被排序;
    2.取出下一个元素,在已经排序的元素序列中从后向前扫描;
    3.如果该元素(已排序)大于新元素,将该元素移到下一个位置;
    4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
    5.将新元素插入到该位置后,重复2~5

  算法示意图:

  

  算法代码:

 1 #include<iostream>
 2 using namespace std;
 3 void insert_sort(int a[],int n){
 4     for(int j=1;j<len;j++){
 5         int key=a[j];//该元素为未排序的部分的第一个元素
 6         int i=j-1;//已经排序的元素下标,从后向前扫描
 7         while(i>=0 && a[i]>key){//如果该元素大于新元素key
 8             a[i+1]=a[i];//将该元素移到下一个位置
 9             i--;
10         }
11         a[i+1]=key;//找到插入位置,插入新元素到排序数组中
12     }
13 }
View Code

 

  希尔排序:也称为递减增量排序算法,是插入排序的一种高速而稳定的改进版本。希尔排序是基于插入排序的以下两点性质而提出改进方法的:1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;2.但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位。

  算法步骤:

    1.先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中,在各组内进行直接插人排序。
    2.取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。

  步长的选择是希尔排序的重要部分。只要最终步长为1任何步长串行都可以工作。算法最开始以一定的步长进行排序。然后会继续以一定步长进行排序,最终算法以步长为1进行排序。当步长为1时,算法变为插入排序,这就保证了数据一定会被排序。

  算法示意图:

  

 

  算法代码:

 1 #include<iostream>
 2 using namespace std;
 3 void shellsort(int a[],int n){
 4     int h=1;
 5     while(h<n)
 6         h=3*h+1;//这个是希尔的增量
 7     while(h>0){
 8         //对每一个增强里面的元素都用插入排序进行排序
 9         for(int j=h;j<n;j++){
10             int key=a[j];
11             int i=j-h;
12             while(i>=0 && a[i]>key){
13                 a[i+1]=a[i];
14                 i=i-h;
15             }
16             a[i+h]=key;
17         }
18         h=h/3;//下一轮的希尔增量
19     }
20 }
View Code

 

    5.快速排序:详情查看:http://blog.csdn.net/liuchen1206/article/details/6954074

    6.归并排序:详情查看:http://blog.csdn.net/left_la/article/details/8656953

转载于:https://www.cnblogs.com/Kobe10/p/5573415.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值