数据结构之排序算法二:堆排序,快速排序,归并排序

  上一篇中简单的回顾了三种比较简单的排序算法:冒泡排序,直接插入排序,简单选择排序,这三种算法的空间复杂度为O(1),时间复杂度为O(N2)。这次我们来看看相对复杂的排序算法,前面介绍的排序算法并没有保存比较结果,导致重复比较,下面介绍的三种排序算法都会将比较结果保存下来,所以时间复杂度会相对低,包括快速排序,堆排序,归并排序(二路归并)。

快速排序原理:

* 快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。
     * 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,
     * 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
     [编辑本段]算法过程
  设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用第一个数据)作为关键数据,
     * 然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
     * 一趟快速排序的算法是:
  1)设置两个变量low、high,排序开始的时候:low=1,high=N-1;
  2)以第一个数组元素作为关键数据,赋值给X,即 X=A[0];
  3)从high开始向前搜索,即由后开始向前搜索(high=high-1),找到第一个小于X的值,
     * 让该值与X交换(找到就行.找到后low大小不变);
  4)从low开始向后搜索,即由前开始向后搜索(low=low+1),找到第一个大于X的值,
     * 让该值与X交换(找到就行.找到后high大小不变);
  5)重复第3、4步,直到 low=high; (3,4步是在程序中没找到时候high=high-1,low=low+1。找到并交换的时候low,
     * high指针位置不变。另外当low=high这过程一定正好是low+或high+完成的最后,循环结束)
  例如:待排序的数组A的值分别是:(初始关键数据:X=49) 注意关键X永远不变.
     * 永远是和X进行比较 无论在什么位置 最后的目的就是把X放在中间小的放前面大的放后面
  A[0] 、 A[1]、 A[2]、 A[3]、 A[4]、 A[5]、 A[6]:
  49 38 65 97 76 13 27
  进行第一次交换后: 27 38 65 97 76 13 49
  ( 按照算法的第三步从后面开始找)
  进行第二次交换后: 27 38 49 97 76 13 65
  ( 按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时:low=3 )
  进行第三次交换后: 27 38 13 97 76 49 65
  ( 按照算法的第五步将又一次执行算法的第三步从后开始找
  进行第四次交换后: 27 38 13 49 76 97 65
  ( 按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时:high=4 )
  此时再执行第三步的时候就发现low=high,从而结束一趟快速排序,那么经过一趟快速排序之后的结果是:27 38 13 49 76 97 65,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。

     * 时间复杂度:
     * 快速排序在最好情况下为O(nlog(2)(n)),此时待排序的数列每次都可以划分成等大小的两个数列,这样按根分解次数形成一个完全二叉树。
     * 最坏情况为O(n∧2),此时待排序的数列已经排好序,这样按根分解次数形成一个单支二叉树。
       空间复杂度:
     * O(log(2)(n))空間

 

数据结构之排序算法二:堆排序,快速排序,归并排序 代码
     public   void  Sort( int [] seq)
        {
            Quick_Sort(seq, 
0 , seq.Length  -   1 );
        }
        

        
// 采用原地快速排序
         private   void  Quick_Sort( int [] seq,  int  low,  int  high)
        {
            
int  tmp  =  seq[low];
            
int  i  =  low;
            
int  j  =  high;
            
// 一趟排序
             while  (low  <  high)
            {
                
while  (low  <  high)
                {
                    
if  (seq[high]  <  tmp)
                    {
                        seq[low] 
=  seq[high];
                        seq[high] 
=  tmp;
                        low
++ ;
                        
break ;
                    }
                    
else
                    {
                        high
-- ;
                    }
                }
                
while  (low  <  high)
                {
                    
if  (seq[low]  >  tmp)
                    {
                        seq[high] 
=  seq[low];
                        seq[low] 
=  tmp;
                        high
-- ;
                        
break ;
                    }
                    
else
                    {
                        low
++ ;
                    }
                }
            }

            
// 此时low=high,对seq中由low和high分拆的两边分别递归调用
             if  (i  <  low  -   1 )
            {
                Quick_Sort(seq, i, low 
-   1 );
            }
            
if  (j  >  high  +   1 )
            {
                Quick_Sort(seq, high 
+   1 , j);
            }
        }

 

 

 

堆排序原理:

 

/* “堆”定义

  n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
  (1) ki≤K2i且ki≤K2i+1
     * 或
    (2)ki≥Kn2i且ki≥K2i+1(1≤i≤ n)
  若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:
     * 树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。
     * (即如果按照线性存储该树,可得到一个不下降序列或不上升序列)
     *
     *
     *
     * 算法分析

  堆[排序的时间,主要由建立初始]堆和反复重建堆这两部分的时间开销构成。
  堆排序的最坏时间复杂度为O(nlog2n)。堆序的平均性能较接近于最坏性能。
  由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。
  堆排序是就地排序,辅助空间为O(1),
  它是不稳定的排序方法。
     *
     * 算法步骤:
     * 1)将输入的顺序表视为按顺序表存储的完全二叉树。
     * 2)将完全二叉树调整为堆。
     *
     *  附需用到的顺序存储完全二叉树性质:
     * 有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
  若I为结点编号则
    如果I<>1,则其父结点的编号为I/2;
  如果2*I<=N,则其左儿子(即左子树的根结点)的编号为2*I;若2*I>N,则无左儿子;
  如果2*I+1<=N,则其右儿子的结点编号为2*I+1;若2*I+1>N,则无右儿子。
     * 
     *
     *
     * 二叉树的性质

    性质1    满二叉树定理:非空二叉树树叶的数目等于其分支结点数加1。
    性质2    满二叉树定理推论:一个非空二叉树的空子树数目等于其结点数加1。
    性质3    任何一棵二叉树,度为0的结点比度为2的结点多一个。
    性质4    二叉树的第i层(根为第0层,i≥0)最多有2i次方个结点。
    性质5    高度为k的二叉树至多有2k-1个结点。
    性质6   有n个结点(n>0)的完全二叉树的高度为log2(n+1), 深度为 log2(n+1)-1。

数据结构之排序算法二:堆排序,快速排序,归并排序 代码
#region  ISort 成员

        
public   void  Sort( int [] seq)
        {
            
// 1.取最大节点为已排好序节点开始建立堆
            Heap_Sort(seq, seq.Length  -   1 , seq.Length  -   1 );

            
for  ( int  i  =  seq.Length  -   1 ; i  >=   0 ;i --  )
            {
                
// 2.从已建好的堆中取出顶点与堆尾元素交换
                 int  tmp  =  seq[ 0 ];
                seq[
0 =  seq[i];
                seq[i] 
=  tmp;

                
// 3.将此时队列视为除队列最后一个元素外顶点为seq[0](除根节点外左右子树已为堆)的新队列,
                
// 从堆顶重建即可,(相比简单选择排序保留中间的比对结果,减少比对次数)
                Heap_Sort(seq,  0 , i - 1 );
            }
        }

        
#endregion

        
#region  采用最大堆排序,节点排序方法
        
///   <summary>
        
///  采用最大堆排序,节点排序方法,形成以该节点为顶点的堆。
        
///  具体步骤为:
        
///  1.先判断待排序节点有无子节点(即有无左子节点)
        
///  2.如果有左子节点,给中间变量maxIndex赋值为左子节点索引
        
///  3.再判断有无右子节点,如果有,比较左右子节点的值,给maxIndex赋值为较大子节点的索引
        
///  4.判断较大子节点的值与当前节点的值,如果较大子节点值大于当前子节点值,则交换
        
///  5.将maxIndex值赋给当前节点索引,重复步骤1,2,3,4
        
///   </summary>
        
///   <param name="seq"> 待排序数组 </param>
        
///   <param name="startIndex"> 该节点为左右子树为堆的待排序节点在数组中的
        
///  索引,如果待排序数组完全未排序,则应将待排序数组的最后一个元素视为左右子树已排好序 </param>
        
///   <param name="endIndex"> 待排序数组中从第一个元素起需排序的元素索引 </param>
         private   void  Heap_Sort( int [] seq,  int  startIndex, int  endIndex)
        {
            
// 1.待排序节点seq[startIndex],节点编号为startIndex+1


            
for  ( int  i  =  startIndex; i  >=   0 ; i -- ) // 从编号为starIndex+1节点逐层遍历二叉树,也可改写为没有父节点就退出的while循环
            {
                
while  ( 2   *  (i  +   1 ) <=  endIndex + 1 ) // 循环退出条件为待调整节点没有左子节点
                {
                    
// 2.中间变量maxIndex赋值为左子节点索引
                     int  maxIndex  =   2   *  (i  +   1 -   1 ;
                    
// 3.判断是否有右子节点,2(i+1)+1>N则无右节点,
                    
// 如果有右子节点seq[2(i+1)+1-1](编号为i+1的右子节点编号为2(i+1)+1),与左子节点比较,
                    
// 如果右子节点较大,则maxIndex赋值为右子节点索引
                     if  ( 2   *  (i  +   1 +   1   <=  endIndex + 1   &&  seq[ 2   *  (i  +   1 - 1 <  seq[ 2   *  (i  +   1 )])
                    {
                        maxIndex 
=   2   *  (i  +   1 );
                    }

                    
// 4.seq[i]与较大子节点比较大小,如果小于子节点则值交换,并i调整为maxIndex,如果不交换,退出循环
                     if  (seq[i]  <  seq[maxIndex])
                    {
                        
int  tmp  =  seq[i];
                        seq[i] 
=  seq[maxIndex];
                        seq[maxIndex] 
=  tmp;
                        i 
=  maxIndex;
                    }
                    
else
                    {
                        
break ;
                    }
                }
            }


        }
        
#endregion

 

 

二路归并排序原理:

 

/*  归并排序其实是属于分治算法,算法思想是:把待排序序列分成相同大小的两个部分,
     * 依次对这两部分进行归并排序,完毕之后再按照顺序进行合并.
     假设顺序表中有n个记录,把它看成n个长度为1的有序表,
     * 从第一个有序表开始,把相邻的两个有序表进行两两合并成一个有序表,
     * 得到n/2个长度为2的有序表。如此重复,最后得到一个长度为n的有序表。

     归并排序的时间复杂度是O(nlogn),空间复杂度是O(n),

     * 单趟的排序思路:    
        1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并
        后的序列。
        2.设定两个指针,最初位置分别为两个已经排序序列的起始位置。
        3.比较两个指针所指向的元素,选择相对小的元素放入到合并空间,
        并移动指针到下一位置。
        4.重复步骤3直到某一指针达到序列尾。
        5.将另一序列剩下的所有元素直接复制到合并序列尾。
     * */

 

数据结构之排序算法二:堆排序,快速排序,归并排序 代码
#region  ISort Members

        
public   void  Sort( int [] seq)
        {
            
int  i  =   1 ;            
            
while  (i  <  seq.Length)
            {
                
// 1.按i大小将数组seq分隔成小数组,每相邻的两个数组进行单趟归并,如果有未能分组的不排序
                 int  j = 0 ;
                
while  (j  +  i  -   1   <  seq.Length) // 判断根据i划分的第一个数组的结束索引未超出数组长度
                {
                    
if  (j  +   2   *  i  -   1   <  seq.Length) // 判断根据i划分的第二个数组的结束索引未超出数组长度
                    {
                        MergeSortOperate(seq, j, j 
+  i  -   1 , j  +   2   *  i  -   1 );
                    }
                    
else   if  (j  +  i  !=  seq.Length) // 判断seq长度为奇数时第一次切割时最后一个元素不分组
                    {
                        MergeSortOperate(seq, j, j 
+  i  -   1 , seq.Length  -   1 );
                    }                                      
                    j
+= 2 * i;                                       
                }             

                
// 2.i=2i将i翻倍,重复步骤1
                i = 2 * i;
            }
        }

        
#endregion

        
///   <summary>
        
///  将数组中指定的位置连续的两个排好序的数组进行合并
        
///   </summary>
        
///   <param name="seq"> 待排序数组 </param>
        
///   <param name="startIndex1"> 第一个已排好序数组起始位置索引 </param>
        
///   <param name="endIndex1"> 第一个已排好序数组结束位置索引 </param>
        
///   <param name="endIndex2"> 第二个已排好序数组结束位置索引 </param>
         private   void  MergeSortOperate( int [] seq,  int  startIndex1,  int  endIndex1,  int  endIndex2)
        {
            
int  startIndex2  =  endIndex1  +   1 ;
            
int [] seqTemp  =   new   int [endIndex2  -  startIndex1  +   1 ];
            
int  tmpStartIndex1  =  startIndex1;
            
for  ( int  i  =   0 ; i  <  seqTemp.Length;  ++ i)
            {
                
if  (startIndex1  <=  endIndex1  &&  startIndex2  <=  endIndex2) // 判断两个待合并数组是否已经有一个已经插入完成
                {
                    
if  (seq[startIndex1]  <  seq[startIndex2])
                    {
                        seqTemp[i] 
=  seq[startIndex1];
                        
++ startIndex1;
                    }
                    
else
                    {
                        seqTemp[i] 
=  seq[startIndex2];
                        
++ startIndex2;
                    }
                }
                
else
                {
                    
if  (startIndex1  >  endIndex1) // 判断seq1是否已经插入完成
                    {
                        seqTemp[i] 
=  seq[startIndex2];
                        
++ startIndex2;
                    }
                    
else
                    {
                        seqTemp[i] 
=  seq[startIndex1];
                        
++ startIndex1;
                    }
                }
            }
            
for  ( int  i  =   0 ; i  <  seqTemp.Length;  ++ i)
            {
                seq[i 
+  tmpStartIndex1]  =  seqTemp[i];
            }
        }

 

至此,最基本的几个排序算法介绍完毕,仅看原理自己动手写一下感觉非常不错,欢迎交流。

你可能感兴趣的:(数据结构)