数据结构-排序

排序:
    voidX_Sort( ElementTypeA[], intN ) 
    大多数情况下,为简单起见,讨论从小大的整数排序
    N是正整数,表示元素个数
    只讨论基于比较的排序(> = < 有定义) 
    只讨论内部排序 
    稳定性:任意两个相等的数据, 排序前后的相对位置不发生改变
    没有一种排序是任何情况下 都表现最好的


冒泡排序:
    void Bubble_Sort( ElementTypeA[], intN ) {  
        for( P=N-1; P>=0; P--){ 
            flag = 0; 
            for( i=0; i<P; i++ ) { /* 一趟冒泡*/ 
                if( A[i] > A[i+1] ) { 
                    Swap(A[i], A[i+1]); flag = 1; /* 标识发生了交换*/ 
                } 
            } 
            if( flag==0 ) break; /* 全程无交换*/ 
        } 
    }
    循环,相邻的比较大小,位置不对则交换,这样一次循环后最大的一个元素则在最后位置。第二次则循环n-1次。如此一直循环到排序结束

    最好情况:顺序T= O( N) 最坏情况:逆序T= O( N2) 
    为稳定排序
    
插入排序    
    void Insertion_Sort( ElementTypeA[], intN ) {  
        for( P=1; P<N; P++ ) { 
            Tmp= A[P];  /* 摸下一张牌*/ 
            for( i=P; i>0 && A[i-1]>Tmp; i--)
                A[i] = A[i-1]; /* 移出空位*/ 
                A[i] = Tmp;  /* 新牌落位*/ 
        } 
    }
    初始将第一个元素看成已经排好序的集合,将第二个元素插入到已经排好序的集合中,这样依次将所有元素插入到集合中完成排序
    
    最好情况:顺序T= O( N) 最坏情况:逆序T= O( N2)
    稳定排序

逆序对:对于下标i<j,如果A[i]>A[j],则称(i,j)是 一对逆序对(inversion)

交换2个相邻元素正好消去1个逆序对! 
    
定理:任意N个不同元素组成的序列平均具有 N ( N 1 ) / 4个逆序对。
定理:任何仅以交换相邻两元素来排序的算 法(冒泡,插入排序),其平均时间复杂度为( N^2)。 

希尔排序:
    是在插入排序基础上的一个改进,例如现在由十个数字需要排序,首先对间隔为5的子序列进行插入排序,然后对间隔为3的子序列进行排序,最后对间隔为1的序列进行排序
    定义增量序列DM> DM-1> …> D1= 1 
    对每个Dk进行“Dk-间隔”排序( k = M, M-1, …1 ) 
    注意:“Dk-间隔”有序的序列,在执行“Dk-1-间隔”排序后,仍然是“Dk间隔”有序的
    
    
    希尔增量序列
        上面例子中增量序列为5、3、1
        
        原始的希尔排序增量序列为    DM= N/ 2,  Dk=D(k+1)/ 2  即每次减半,如10个数,序列为5、2、1

        
    算法实现:
    void Shell_sort( ElementTypeA[], intN ) {  
        for( D=N/2; D>0; D/=2 ) { /* 希尔增量序列*/ 
            for( P=D; P<N; P++ ) { /* 插入排序*/ 
                Tmp= A[P]; 
                for( i=P; i>=D&& A[i-D]>Tmp; i-=D) 
                    A[i] = A[i-D]; 
                    A[i] = Tmp; 
            } 
        } 
    }
    
    算法复杂度最坏情况:
        T=( N^2)  //原因:增量元素不互质,则小增量可能根本不 起作用
    
    
    更好的增量序列:
        Hibbard 增量序列:
            Dk= 2^k–1   —相邻元素互质 
            最坏情况:T =  ( N^3/2)
            猜想:Tavg= O ( N^5/4)
        Sedgewick增量序列 
            {1, 5, 19, 41, 109, …} :
                9*4^i–9*2^i+1 或4^i–3*2^i+ 1
    为不稳定排序
    
选择排序:
    voidSelection_Sort( ElementTypeA[], intN ) {  
        for( i = 0; i < N; i ++ ) { 
            MinPosition= ScanForMin( A, i, N–1 ); /* 从A[i]到A[N–1]中找最小元,并将其位置赋给MinPosition*/ 
            Swap( A[i], A[MinPosition] ); /* 将未排序部分的最小元换到有序部分的最后位置*/ 
        } 
    }
    
    无论如何:T=( N2)

堆排序:是对选择排序的优化;
    使用堆的数据结构:
        
    算法1:
    void Heap_Sort( ElementTypeA[], intN ) {  
        BuildHeap(A);  /* O(N) */         //将数组转化为最小堆结构,时间复杂度为线性
        for( i=0; i<N; i++ ) 
            TmpA[i] = DeleteMin(A); /* O(logN) */     //取出最小值保存到临时数据
        for( i=0; i<N; i++ ) /* O(N) */     //复制临时数组到原数组
            A[i] = TmpA[i]; 
    }
    
    时间复杂度:T( N ) = O ( Nlog N)  但是需要额外的O(n)的空间
    
    算法2: //真正的堆排序
    void Heap_Sort( ElementTypeA[], intN ) {  
        for( i=N/2-1; i>=0; i--)/* BuildHeap*/     //将数组调整为最大堆
            PercDown( A, i, N ); 
        for( i=N-1; i>0; i--) {         
            Swap( &A[0], &A[i] ); /* DeleteMax*/ //将堆顶元素与数组最大位置元素交换
            PercDown( A, 0, i ); //将剩下元素调整为最大堆
        } 
    }
    定理:堆排序处理N个不同元素的 随机排列的平均比较次数是 2NlogNO(NloglogN) 。
    虽然堆排序给出最佳平均时间复 杂度,但实际效果不如用 Sedgewick增量序列的希尔排序

归并排序:
    把两个有序子列合并为一个有序子列:
        
    算法:
        /* L = 左边起始位置, R = 右边起始位置, RightEnd= 右边终点位置*/ 
        void Merge( ElementTypeA[], ElementTypeTmpA[], intL, intR, intRightEnd) {   
            LeftEnd= R -1; /* 左边终点位置。假设左右两列挨着*/ 
            Tmp= L; /* 存放结果的数组的初始位置*/ 
            NumElements= RightEnd-L + 1; 
            while( L <= LeftEnd&& R <= RightEnd) {
                if( A[L] <= A[R] ) TmpA[Tmp++] = A[L++]; 
                else TmpA[Tmp++] = A[R++]; 
            } 
            while( L <= LeftEnd) /* 直接复制左边剩下的*/ 
                TmpA[Tmp++] = A[L++]; 
            while( R <= RightEnd) /*直接复制右边剩下的*/ 
                TmpA[Tmp++] = A[R++]; 
            for( i = 0; i < NumElements; i++, RightEnd--) 
                A[RightEnd] = TmpA[RightEnd]; 
        }

    利用归并排序递归完成一般的排序   //分而治之的思想
        void MSort(ElementTypeA[], ElementTypeTmpA[], intL, intRightEnd) {   
            int Center; 
            if( L < RightEnd) { 
                Center = ( L + RightEnd) / 2; 
                MSort( A, TmpA, L, Center );     //左半边排序
                MSort( A, TmpA, Center+1, RightEnd);     //右半边排序
                Merge( A, TmpA, L, Center+1, RightEnd);     //左右归并排序
            } 
        }
        
        T( N) = T( N/2 ) + T( N/2 ) + O( N )  =》 T( N) = O( N logN)

    
    将上面的算法改造成统一函数接口:
        void Merge_sort( ElementTypeA[], intN ) {   
            ElementType *TmpA; 
            TmpA= malloc( N * sizeof( ElementType) ); 
            if( TmpA!= NULL ) { 
                MSort( A, TmpA, 0, N-1 );     //调用上面的方法
                free( TmpA); 
            } else Error( “空间不足" ); 
        }
    
    非递归实现归并排序:
        假如一个序列由多个有序子序列组成,那么将将相邻两个子序列进行归并排序算法:
        void Merge_pass( ElementTypeA[], ElementTypeTmpA[],intN, intlength ) /* length = 当前有序子列的长度*/ {
            for( i=0; i <= N–2*length; i += 2*length ) 
                Merge1( A, TmpA, i, i+length, i+2*length–1 ); 
            if( i+length< N ) /* 归并最后2个子列*/ 
                Merge1( A, TmpA, i, i+length, N–1); 
            else/*最后只剩1个子列*/ 
                for( j = i; j < N; j++ ) TmpA[j] = A[j]; 
        }
    
        真正的归并排序:
            void Merge_sort( ElementTypeA[], intN ) {  
                intlength = 1; /* 初始化子序列长度*/ 
                ElementType*TmpA; 
                TmpA= malloc( N * sizeof( ElementType) ); 
                if( TmpA!= NULL ) { 
                    while( length < N ) { 
                        Merge_pass( A, TmpA, N, length ); 
                        length *= 2; 
                        Merge_pass( TmpA, A, N, length ); 
                        length *= 2; 
                    } 
                    free( TmpA); 
                } 
                else Error( “空间不足" ); 
            } 

快速排序:
    在待排序的序列中选定一个数n(主元),将序列分为三个部分,小于n的,n, 大于n的。然后按此方法递归的堆大于n和小于n的序列排序

    主元的选取方式会影响快速排序的效率,当每次选取的主元均为被排序序列的中位数的时候,效率最高。选取主元的策略:
        1.直接选取序列的第一个数    //所选取的数可能偏离中位数较远
        2.随机选一个数        //随机数的获取比较耗资源
        3.取头、中、尾的中位数 
        
        第三种方式相比而言更好,使用第三中方式确定主元的算法:
            ElementType Median3( ElementTypeA[], intLeft, intRight ) {   
                intCenter = ( Left + Right ) / 2; 
                if( A[ Left ] > A[ Center ] ) 
                    Swap( &A[ Left ], &A[ Center ] ); 
                if( A[ Left ] > A[ Right ] ) 
                    Swap( &A[ Left ], &A[ Right ] ); 
                if( A[ Center ] > A[ Right ] ) 
                    Swap( &A[ Center ], &A[ Right ] ); 
                /* A[ Left ] <= A[ Center ] <= A[ Right ] */ 
                Swap( &A[ Center ], &A[ Right-1] );    
                /* 将pivot藏到右边*/ /* 只需要考虑A[ Left+1 ] …A[ Right–2 ] */ 
                return A[ Right-1];  /* 返回pivot */ 
            }

            将头中尾三个数按大小调整位置,然后将主元(中间位置的数)放在倒数第二个位置上
            
    快速排序的问题 
        用递归…… 对小规模的数据(例如N不到100)可能还不如插 入排序快 
    解决方案 
        当递归的数据规模充分小,则停止递归,直接调用 简单排序(例如插入排序)

    快速排序算法:
        void Quicksort( ElementTypeA[], int Left, int Right ) {  
            if( Cutoff <= Right-Left ) {     //Cutoff为阈值
                Pivot = Median3( A, Left, Right );     //采用上面的方法确定主元
                i = Left;              
                j = Right –1;         //两个指针分别指向第1个和倒数第二个元素(主元)
                for( ; ; ) { 
                    while( A[ ++i ] < Pivot ) { }     //左边指针向右移动直到指向的位置的值大于主元
                    while( A[ ––j ] > Pivot ) { }     //右边指针向左移动知道指向的位置的值小于主元
                    if( i < j ) 
                        Swap( &A[i], &A[j] );     //将两个指针位置的值交换
                    else  
                        break;         //指针相遇,循环停止
                } 
                Swap( &A[i], &A[ Right-1 ] );     //将主元放在正确的位置
                Quicksort( A, Left, i-1 );         //递归
                Quicksort( A, i+1, Right );     //递归
            } else 
                Insertion_Sort( A+Left, Right-Left+1 );     //规模小时采用插入排序
        }

        算法调整为一般排序方法;
            void Quick_Sort(ElementTypeA[],intN) { 
                Quicksort( A, 0, N-1 ); 
            }

表排序://适合于待排序的元素比较大,元素之间交换位置成本高的排序
    定义一个指针(索引)数组作为“表”,排序的时候只改变指针的值,排序完成后,指针的顺序对应有序序列的顺序
    
基数排序:
    桶排序:
        假设我们有 N 个学生,他们的成绩是0到100之间
        的整数(于是有 M = 101 个不同的成绩值)。如
        何在线性时间内将学生按成绩排序?

        void Bucket_Sort(ElementType A[], int N){ 
            count[]初始化;
            while (读入1个学生成绩grade)
                将该生插入count[grade]链表;
                for ( i=0; i<M; i++ ) {
                    if ( count[i] )
                    输出整个count[i]链表;
                }
        }
        
        时间复杂度:O(m + n)  //适合于m<<n的情景
        
        思路:先建立101个通(比如大小为101的数组),每个桶表示一个分值,每个桶是一个链表,遍历学生成绩,将
            每个元素放在相应的位置,遍历完即完成排序
            
    基数排序是桶排序的改进:
        基数排序针对与待排序列中元素只出现在指定的范围内,这个指定的范围即是基数:
        
        假设我们有 N = 10 个整数,每个整数的值在0到999之间(于是有 M = 1000 个不同的值)。
        输入序列: 64, 8, 216, 512, 27, 729, 0, 1, 343, 125

        采用“次位优先”基数排序:
            次位优先:对于64这个数,6是主位(对数值影响较大)、4是次位(对数值影响较小),
                    次位优先表示先对个位(低位)进行排序再对十位(高位)进行排序
            
            思路:由于每位数均只有0到9十种可能,因为0到9十位数是基数,所以首先建立10个桶,
                第一步先根据个位数依次将数字放在指定的桶中:
                    桶编号    0    1    2    3    4    5    6    7    8    9
                            0    1    512    343    64    125    216    27    8    729
            
                第二步,在第一步的基础上根据十位数字依次重新调整数的位置:
                    桶编号    0    1    2    3    4    5    6    7    8    9
                            0    512    125        343        64            
                            1    216    27                
                            8        729
                第三步,在第二步的基础上根据百位数字依次重新调整数的位置:
                    桶编号    0    1    2    3    4    5    6    7    8    9
                            0    125    216    343        512        729        
                            1                        
                            8        
                            27
                            64
                依次取出桶中的数组,完成排序
                
                
            时间复杂度:设元素个数为N,整数进制为B,LSD的趟数为P,则最坏时间复杂度是:O(P(N+B))    
    
排序算法总结:
    排序方法    平均时间复杂度    最坏情况时间复杂度    额外空间复杂度    稳定性
    基数排序     O(P(N+B))         O(P(N+B))             O(N+B)             稳定
    归并排序     O(NlogN)         O(NlogN)             O(N)             稳定
    快速排序     O(NlogN)         O(N2)                 O(logN)         不稳定
    堆排序         O(NlogN)         O(NlogN)             O(1)             不稳定
    希尔排序      O(Nd)             O(N2)                 O(1)             不稳定
    直接插入排序 O(N2)             O(N2)                 O(1)             稳定
    冒泡排序      O(N2)             O(N2)                 O(1)             稳定
    简单选择排序 O(N2)             O(N2)                 O(1)             不稳定
                

 

posted @ 2019-11-24 14:18  foreast  阅读(295)  评论(0编辑  收藏  举报