【备战2014笔面试】排序算法(2)

1.堆排序

【算法引出】:是简单选择排序算法的改进算法,简单选择排序中每一趟比较选出一个最小值,但是后一趟的比较中会重复前面的比较结果,存在重复。堆排序对其的改进体现在—— 每次选择最小值的同时,根据结果对其他的值进行调整。

【堆的概念】:堆是具有以下性质的完全二叉树,每个节点都大于或等于左右孩子节点的值,称为大顶堆;每个节点都小于等于左右孩子节点的值,称为小顶堆。

【算法思想】:以大顶端堆为例,(1)首先构造一个大顶堆,即堆顶为所有元素的最大值;(2)其次,将该堆顶元素与堆末尾元素互换,此时堆末尾便存储了最大元素;(3)除去堆末尾元素,对于剩余的N-i个元素反复执行(1)、(2)操作即可完成排序。


//堆排序,i表示当前节点编号,N表示全部节点编号,N从0开始 
void heapAdjust(int arr[], int i, int N)  
{  
       int j;  
       j = i*2+1;         //左孩子节点  
   
       while(j< N)  
       {  
              if( j+1 < N && arr[j+1] > arr[j])  
              {  
                     j++;                //取左右孩子中的较大值,若只有左儿子没有右儿子则不用  
              }
                
              if(arr[j] <= arr[i])  
              {  
                     break;             //大顶堆,如果出现儿子节点值小,则终止循环.  
              }  
              
              //否则交换子节点和父节点  
              swap(arr[j],arr[i]);  
              
              //循环变量增加 
              i= j;  
              j= 2*i + 1;  
       } 
} 

void heapSort(int arr[], int N)  
{  
       
       //构建大顶堆,初始是凌乱的,调整后成一个大顶堆,对每个有子节点的分支节点都需要进行比较才能完成一个大顶堆。
       //时间复杂度为NlogN 
       for(int i = N-1; i >= 0; i--)  
       {  
              heapAdjust(arr,i,N-1);//比较的位置从最后开始.  
       }     
        
       //把顶取出,放入最末元素进行调整,总共进行N次,时间复杂度NlogN  
       for(int  i = N-1; i >= 0; i--)  
       {  
              swap(arr[i],arr[0]);  
              heapAdjust(arr,0,i);  //终止的大小为i,每次只调整根节点即可。  
       }         
}


归并排序


参考:http://blog.csdn.net/morewindows/article/details/6678165


 归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。

首先考虑下如何将将二个有序数列合并。这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。


可以看出合并有序数列的效率是比较高的,可以达到O(n)。

解决了上面的合并有序数列问题,再来看归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。如何让这二组组内数据有序了?

可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递的分解数列,再合数列就完成了归并排序。


归并排序的效率是比较高的,设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。


//将有序数组//将有二个有序数列a[first...mid]和a[mid...last]合并。 
void mergearray(int a[], int first, int mid, int last, int c[])  
{  
   int i, j, n, m, k ;
   
   i = first;
   j = mid + 1;
   n = mid;
   m = last;
   k = 0;
   
   while( i <= n && j <= m)
   {
          if(a[i] <= a[j])
          {
                  c[k] = a[i];                  
                  i++;
                  k++;
          }else
          {       
                  c[k] = a[j];                  
                  j++;
                  k++;
          }          
   }
   
   while(i <= n)
   {
             c[k] = a[i];
             i++;
             k++;
   }
   
   while(j <= m)
   {
             c[k] = a[j];
             j++;
             k++;
   }
   
   for (i = 0; i < k; i++)  
   {
       a[first + i] = c[i];     
   }
                           
}

void mergesort(int a[], int first, int last, int temp[])  
{  
    //若first<last,即分组中元素大于一个时,继续划分,否则停止. 
    if (first < last)  
    {  
        int mid = (first + last) / 2;  
        mergesort(a, first, mid, temp);    //递归左边有序  
        mergesort(a, mid + 1, last, temp); //递归右边有序  
        mergearray(a, first, mid, last, temp); //再将二个有序数列合并  
    }  
}   


//归并排序
bool MergeSort(int a[], int n)  
{  

    //申请临时转存空间,空间复杂度为O(N) 
    //用于转存用,不必每次都重新申请存储空间 
    int *p = new int[n];  
    
    if (p == NULL)  
        return false;  
    
    //进行归并排序 
    mergesort(a, 0, n - 1, p);  
    
    //删除临时转存空间 
    delete[] p;  
    return true;  
}

快速排序

参考:http://blog.csdn.net/morewindows/article/details/6684558


快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

该方法的基本思想是:

1.先从数列中取出一个数作为基准数。

2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。

3.再对左右区间重复第二步,直到各区间只有一个数。

 

虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤。因此我的对快速排序作了进一步的说明:挖坑填数+分治法

先来看实例吧,定义下面再给出(最好能用自己的话来总结定义,这样对实现代码会有帮助)。

 

以一个数组作为示例,取区间第一个数为基准数。

0

1

2

3

4

5

6

7

8

9

72

6

57

88

60

42

83

73

48

85

初始时,i = 0;  j = 9;   X = a[i] = 72

由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。

从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; i++;  这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3]; j--;

 

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

88

60

42

83

73

88

85

 i = 3;   j = 7;   X=72

再重复上面的步骤,先从后向前找,再从前向后找

从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;

从i开始向后找,当i=5时,由于i==j退出。

此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。

 

数组变为:

0

1

2

3

4

5

6

7

8

9

48

6

57

42

60

72

83

73

88

85

可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。

 

 

对挖坑填数进行总结

1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。

2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。

3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。

4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中。

照着这个总结很容易实现挖坑填数的代码:


int AdjustArray(int s[], int l, int r) //返回调整后基准数的位置     
{
    
    int i = l;
    int j = r;
    
    int x = s[l]; //s[l]即s[i]就是第一个坑  
    
    while(i < j)
    {
            
            //从右向左找小于x的数来填s[i]  
            while(i < j && s[j] >= x)
            {
                    j--;
            }
            
            //将s[j]填到s[i]中,s[j]就形成了一个新的坑  
            if(i < j)
            {
                 s[i] = s[j];
                 i++; //i值右移 
            }
            
             //从左向右找大于或等于x的数来填s[j]  
             while(i < j && s[i] < x)  
             {
                     i++;
             }
                 
             //将s[i]填到s[j]中,s[i]就形成了一个新的坑  
             if(i < j)   
             {  
                  s[j] = s[i]; 
                  j--;//j值左移 
             }             
    }
    
    //退出时,i等于j。将x填到这个坑中。  
    s[i] = x;    
    return i;  
}           
      
      
void quick_sort1(int s[], int l, int r)  
{  
   if (l < r)  
   {  
      int i = AdjustArray(s, l, r);//先成挖坑填数法调整s[]  
      quick_sort1(s, l, i - 1); // 递归调用   
      quick_sort1(s, i + 1, r);  
   }  
} 


排序算法归类

插入排序类

选择排序类

交换排序类

归并排序类

直接插入排序

希尔排序

直接选择排序

堆排序

冒泡排序

快速排序

归并排序

 

排序算法汇总:

 

平均时间复杂度

最好情况

最差情况

空间复杂度

稳定性

直接插入排序

O(n2)

O(n)

O(n2)

O(1)

稳定

冒泡排序

O(n2)

O(n)

O(n2)

O(1)

稳定

直接选择排序

O(n2)

O(n2)

O(n2)

O(1)

不稳定

希尔排序

O(nlogn)~O(n2)

O(n1.3)

O(n2)

O(1)

不稳定

快速排序

O(nlogn)

O(nlogn)

O(n2)

O(logn)

不稳定

堆排序

O(nlogn)

O(nlogn)

O(nlogn)

O(1)

不稳定

归并排序

O(nlogn)

O(nlogn)

O(nlogn)

O(n)

稳定

 








你可能感兴趣的:(C++,排序算法)