六种常用排序算法的实现及其优化

2018-12-10-17:22:29

1.排序

  定义 : 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。

  分类 :  

    稳定排序:假设在待排序的文件中,存在两个或两个以上的记录具有相同的关键字,在
  用某种排序法排序后,若这些相同关键字的元素的相对次序仍然不变,则这种排序方法
  是稳定的。其中冒泡,插入,基数,归并属于稳定排序,选择,快速,希尔,堆属于不稳定排序。
    
    就地排序:若排序算法所需的辅助空间并不依赖于问题的规模n,即辅助空间为O(1),
  则称为就地排序。

2.具体排序算法描述

  ① 冒泡排序:

      算法描述: 

        将数组分为无序和有序两部分,初始时无序数列的边界下标为总数组的长度减去一的位置,从无序数列的第一个数开始做交换,如果发现这个数比下一个数大则交换这两个数,直至无序数列的最后一个数为一轮交换,这轮交换使得无序数列的最大元素放到了它边界值的下一个位置,每完成一轮交换都更新一次无序数列的边界下标,直至整个数组都变为有序则完成整个排序。

      优劣: 
 
        缺点一:当数组已经有序时不能及时推出,而是依然判断是否交换。
        缺点儿:当无序数列尾部存在部分有序时任然对有序部分进行判断是否交换。
 
      C++ 代码:
1 void Babble_Sort1(ElementType *array, int length) {
2     for(int i = 0; i < length - 1; i ++)
3         for(int j = 0; j < length - 1 - i; j ++)
4             if(array[j] > array[j + 1])
5                 swap(array[j], array[j + 1]);
6 }
 
      优化思路:当数组已有序结束排序,无序部分尾部有序时改变无序数列的边界值。
 
      优化后c++代码:
 
 1 void Babble_Sort2(ElementType *array, int length) {
 2     bool IsSorted;
 3     int LastPosition;//记录每回合最后一次交换位置的元素的下标
 4     int SortBorder = length - 1;//记录无序数列的边界
 5     for(int i = 0; i < length - 1; i ++) {
 6         IsSorted = true;
 7         for(int j = 0; j < SortBorder; j ++)
 8             if(array[j] < array[j + 1]) {
 9                 swap(array[j], array[j + 1]);
10                 IsSorted = false;
11                 LastPosition = j;
12             }
13         SortBorder = LastPosition;
14         if(IsSorted)
15             break;
16     }
17 }

 

 ② 直接插入排序:

      算法描述: 

         1.将原数组分为未排序和已排序两部分。

         2.从第一个元素开始,该元素可以认为已经被排序;

         3.取出下一个元素,在已经排序的元素序列中从后向前扫描;

         4.如果该元素(已排序)大于新元素(待插入),将该元素移到下一位置;

         5.重复步骤4,直到找到已排序的元素小于或者等于新元素的位置;

         6.将新元素插入到该位置后;

         7.重复步骤3~6。

 

      算法分析: 

         插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
 
      C++ 代码:
 1 void Insertion_Sort(ElementType *array, int length) {
 2     int i, j;
 3     ElementType Tmp;
 4     for(i = 1; i < length; i ++) {
 5         Tmp = array[i];
 6         for(j = i; j > 0 && array[j-1] > Tmp; j --)
 7             array[j] = array[j-1];
 8         array[j] = Tmp;
 9     }
10 }

 

 ③ 选择排序:

      算法描述: 

         将数组分为无序和有序两个部分,起初视数组都为无序,每次让数组无序部分的第一个元素作为擂主,让其后的元素开始打擂,每次打至数组的最后一个元素,如果擂主变了(后续数组中存在比擂主小的元素)

      ,则将擂主归并作为有序部分的最后一个元素并按照上述规则继续打擂,直至数组的最后一个元素。

 

      优劣:
          选择排序与其它交换排序相比减少了数组元素的交换次数,从而使得选择排序的平均效率一般比其他交换排序高。
     
      C++ 代码:
 1 void Seletion_Sort(ElementType *array, int length) {
 2     int index;
 3     for(int i = 0; i < length - 1; i ++) {
 4         index = i;
 5         for(int j = i + 1; j < length; j ++)
 6             if(array[j] > array[index])
 7                 index = j;
 8         if(index != i)
 9             swap(array[index], array[i]);
10     }
11 }

       

      改进思路:

         每次打擂只能找出无序数组元素中最大(或最小)的元素,可以考虑每次找出最大和最小的元素,减少循环的次数,从而提高查找的效率。

      

      二元选择排序

 

          c++代码:

 1 void Double_Seletion_Sort(ElementType *array, int length) {
 2     int MinPos, MaxPos;//这里我们将i看做是擂主,j看作是打擂者
 3     for(int i = 0; i < length / 2; i ++) {
 4         MinPos = MaxPos = i;//让擂主同时与最大最小值打比赛
 5         for(int j = i + 1; j < length - i; j ++) {
 6             if(array[j] > array[MaxPos]) {
 7                 MaxPos = j;
 8                 continue;//如果发现array[j] > array[MaxPos]则其一定不会小于array[MinPos]
 9             }
10             if(array[j] < array[MinPos])
11                 MinPos = j;
12         }
13         if(MinPos != i)//将新星擂主纳入有序序列的最后一位,如果擂主没变则不用纳入
14             swap(array[i], array[MinPos]);
15         if(MaxPos == i)//如果擂主位置为最大值,则刚刚交换最小值时已经将最大值换到了最小值打雷成功的打擂者身上即(MinPos)
16             MaxPos = MinPos;
17         if(MaxPos != length - 1 - i)//如果擂主不是无序部分最后一位则将其与最后一位交换,纳入有序序列
18             swap(array[length - 1 - i], array[MaxPos]);
19     }
20 }

 

       

 ④ 归并排序:

      定义:

        归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个

      子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

      算法描述:  

        1.把长度为n的输入序列分成两个长度为n/2的子序列;

        2.对这两个子序列分别采用归并排序;

        3.将两个排序好的子序列合并成一个最终的排序序列。

 

      优劣:
        归并排序是一种稳定的排序方法。和选择排序一样,归并排序的性能不受输入数据的影响,但表现比选择排序好的多,因为始终都是O(nlogn)的时间复杂度。
        代价是需要额外的内存空间。 
 
      C++ 代码:
 1 void MergeSort(ElementType *array, ElementType *TmpArray, int Left, int Right) {
 2     int Center;
 3     if(Left < Right) {
 4         Center = Left + (Right - Left) / 2; //避免数据类型溢出
 5         MergeSort(array, TmpArray, Left, Center);
 6         MergeSort(array, TmpArray, Center + 1, Right);
 7         Merge(array, TmpArray, Left, Center + 1, Right);
 8     }
 9 }
10 
11 void Merge(ElementType *array, ElementType *TmpArray, int LPos, int RPos, int RightEnd) {
12     int LeftEnd = RPos - 1, TmpPos = LPos, NumElements = RightEnd - LPos + 1;
13     while(LPos <= LeftEnd && RPos <= RightEnd) {
14         if(array[LPos] <= array[RPos])
15             TmpArray[TmpPos ++] = array[LPos ++];
16         else
17             TmpArray[TmpPos ++] = array[RPos ++];
18     }
19     while(LPos <= LeftEnd)
20         TmpArray[TmpPos ++] = array[LPos ++];
21     while(RPos <= RightEnd)
22         TmpArray[TmpPos ++] = array[RPos ++];
23     for(int i = 0; i < NumElements; i ++, RightEnd --) //Copy TmpArray back
24         array[RightEnd] = TmpArray[RightEnd];
25 }

 

 

 ⑤ 快速排序:

      基本思想: 

        通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到

      整个数据变成有序序列。

      

      算法描述:

        1.如果需排序元素大于指定数目则执行快速排序,否则执行其它简单排序。(一般元素多于10个时进入快速排序,事实证明,当元素个数过少时使用快速排序会比其它程序慢的多,并且这里我们选择三值取

      中法,如果元素个数过少会产生未预知的错误)。

        2.利用三值取中法获取一个枢纽元(于此同时将枢纽元放在待排序序列的最后一位)。

        3.将数组分为三个不相交的集合,即x < pivot , pivot , x > pivot 。

        4.对x < pivot执行上述1,2,3操作, pivot, 对 x > pivot执行上述1,2,3操作。

      优劣: 
 
        快速排序擅长对大量数据进行排序。
 
      C++ 代码:
ElementType Median3(ElementType *array, int Left, int Right) {
    int Center = Left + (Right - Left) / 2;//避免数据类型溢出
    if(array[Left] > array[Center])
        swap(array[Left], array[Center]);
    if(array[Left] > array[Right])
        swap(array[Left], array[Right]);
    if(array[Center] > array[Right])
        swap(array[Center], array[Right]);
    // Invariant : array[Left] <= array[Center] <= array[Right]
    swap(array[Center], array[Right - 1]);//Hide pivot
    return array[Right - 1];// Return pivot
}

#define CutoffRange (10)
void QuicklySort(ElementType *array, int Left, int Right) {
    ElementType pivot;
    if(Left + CutoffRange <= Right) { //当待排序元素个数多于CutoffRange时采用快速排序
        pivot = Median3(array, Left, Right);//选取枢纽元
        //注意下方对i和j的操作为++ i, ++ j操作,即跳过了第一个和最后一个元素,这是因为在进行三数取中法的时候待排序的第一个和最后一个数已经在它正确的位置上了
        int i = Left, j = Right - 1;
        while(true) {//将数组中小于pivot的元素放到左边,大于pivot的元素放到右边
            while(array[++ i] < pivot);
            while(array[-- j] > pivot);
            if(i < j)//当同时找到不满足上述条件的两个值时,将其交换就是最好的选择
                swap(array[i], array[j]);
            else
                break;
        }
        swap(array[i], array[Right - 1]);//最后将枢纽元放到他在数组中的正确位置
        QuicklySort(array, Left, i - 1);
        QuicklySort(array, i + 1, Right);
    }
    else
        Double_Seletion_Sort(array + Left, Right - Left + 1);
}

 

 ⑥希尔排序:

      1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫缩小增量排序。

 

      算法思路:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序。

 

      算法描述: 

         1.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;

         2.按增量序列个数k,对序列进行k 趟排序;

         3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

 

      C++ 代码:

 

 1 void ShellSort(ElementType *array, Length length) {
 2     int i, j, Increment = 1;
 3     ElementType Tmp;
 4     while(Increment < length / 3) Increment = 3 * Increment + 1;//找到起初最大的间隔序列之首
 5     while(Increment >= 1) {
 6         for(i = Increment; i < length; i ++) {
 7             Tmp = array[i];//用来保存待插入元素
 8             for(j = i;
 9                     j >= Increment && array[j - Increment] > Tmp;
10                     j -= Increment)//将原数组以Increment为间隔分开,然后对每部分分别进行插入排序
11                 array[j] = array[j - Increment];
12             array[j] = Tmp;
13         }
14         Increment /= 3;
15     }
16 }

 

 
      算法分析:
         希尔排序是按照不同步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,所以插入排序的元素个数很少,速度很快;当元素基本有序了,步长很小,插入排序对于有序的
      序列效率很高。所以,希尔排序的时间复杂度会比o(n^2)好一些。
      
      缩小增量的选取:
        起初,希尔的论文里只是简单的将初始增量规定为length/2,然后每次将增量缩小到原来的一半,当增量为1时即执行普通的插入排序,但是后来有人证明取半法并不是最优的,Knuth提出以Increment = 3 * b + 1的增量
      序列效率最为突出(Increment初值为1)。

 

 

 

 

 

 

 

 

 

posted @ 2018-12-17 13:38  Cruel_King  阅读(2289)  评论(0编辑  收藏  举报