归并排序算法

一、什么是归并排序?算法

归并排序是利用递归和分而治之的技术将数据序列划分红为愈来愈小的序列,将两个(或两个以上)有序子序列合并成一个新的有序序列,即把待排序序列分为若干个子序列,每一个子序列是有序的,而后再把有序子序列合并为一个新的有序序列,最终将整个序列变得有序。数组

时间复杂度: O(nlogn)函数

 

二、效果演示性能

  这个序列为待排序序列测试

 

  将这个序列分红2个子序列,分别对它们进行排序,再合并为一个新的有序序列优化

 

  同理在对上面的两个子序列排序的时候,也会将他们各自分为2个子序列,分别动画

                                            对它们进行排序,再合并spa

 

   继续沿用上面的思想,再对它们进行分半,此时每个子序列只有一个元素了,code

                                             他们就是有序的blog

 

  因此咱们此时要作的就是向上合并

 

     

 

 

 

  当排序合并到最后一层的时候,整个序列就变得有序了,至此排序完毕

 

三、动画演示

 

 

四、算法的实现(基于C++)

程序中使用了3个变量分别指向以下所示的位置

    

红色的箭头表示最终在归并的过程当中须要跟踪的位置,两个蓝色的箭头表示已经排好序的两个子序列中当前须要考虑的元素

 1 /************************************* 归并排序算法实现 **********************************/
 2 /* 将arr[left, mid]和arr[mid+1, right]两部分进行归并 */
 3 template<typename T>
 4 void __merge (T arr[], int left, int mid, int right)
 5 {
 6     T *temp = new T[right - left + 1];  // 申请分配堆内存(这个地方仍是建议不要使用栈,由于若是数据量太大可能会致使栈溢出)
 7     if (NULL == temp) {                 // 判断申请是否成功
 8         return;
 9     }
10 
11     for (int i = left; i <= right; i++) { // 给临时空间赋值(注意他们之间的一个偏移量)
12         temp[i - left] = arr[i];
13     }
14 
15     /* 如下就是对两个子序列进行向上归并的操做 */
16     int i = left, j = mid + 1;            // i指向第一部分的起始位置,j指向第二部分的起始位置
17     for (int k = left; k <= right; k++) { // k指向当前须要排序的位置
18         if (i > mid) {                    // 判断第一部分中的数据是否已经所有归位了
19             arr[k] = temp[j - left];
20             j++;
21         }
22         else if (j > right) {             //  判断第二部分中的数据是否已经所有归位了
23             arr[k] = temp[i - left];
24             i++;
25         }
26         else if (temp[i - left] > temp[j - left]) {// 若是第二部分中对应的数据更小,则将它放在须要排序的位置中
27             arr[k] = temp[j - left];
28             j++;
29         }
30         else {
31             arr[k] = temp[i - left];  // 若是是第一部分中对应的数据更小,则将它放在须要排序的位置中
32             i++;
33         }
34     }
35 
36     delete[] temp;         // 释放堆内存空间
37 }
38 
39 /* 对arr[left, right]进行直接插入排序 */
40 template<typename T>
41 void __insertSortMG (T arr[], int left, int right)
42 {
43     for (int i = left + 1; i <= right; i++) {
44         int j;
45         T temp = arr[i];
46 
47         for (j = i; j > left && arr[j - 1] > temp; j--) {
48             arr[j] = arr[j - 1];
49         }
50 
51         arr[j] = temp;
52     }
53 }
54 
55 /* 递归使用归并排序,对arr[left....right]范围进行排序 */
56 template<typename T>
57 void __mergeSort (T arr[], int left, int right)
58 {
59     int mid = (left + right) / 2;     // 找出两部分的分界点位置    
60 
61     __mergeSort<T>(arr, left, mid);      // 对第一部分子序列递归调用该函数
62     __mergeSort<T>(arr, mid + 1, right); // 对第二部分子序列递归调用该函数
63     
64     __merge<T>(arr, left, mid, right);    //  对两部分数据进行归并操做
65 }
66 
67 template<typename T>
68 void mergeSort (T arr[], int count)
69 {
70     __mergeSort<T>(arr, 0, count - 1);     //  对arr[0...count-1]范围进行归并排序 
71 }
72 /******************************************************************************************/

 

五、算法的性能测试

将归并排序算法与前面讲的两种排序算法进行一个时间上的测试:

测试数据量50000:

 

测试数据量100000:

 

六、归并排序算法的优化

(1)第一种优化方法(在__mergeSort函数中进行优化):

 1 /* 递归使用归并排序,对arr[left....right]范围进行排序 */
 2 template<typename T>
 3 void __mergeSort (T arr[], int left, int right)
 4 {
 5     if (left >= right) {           // 递归终止的判断条件
 6         return;
 7     }
 8 
 9     int mid = (left + right) / 2;   //  将数组分红2部分的中间索引值    
10 
11     __mergeSort<T>(arr, left, mid);      // 对第一部分子序列递归调用该函数
12     __mergeSort<T>(arr, mid + 1, right); // 对第二部分子序列递归调用该函数
13     
14     if (arr[mid] > arr[mid + 1]) {       // 优化措施1: 适用于自己有序程度很高的序列
15         __merge<T>(arr, left, mid, right);    //  将两部分数据进行归并操做
16     }
17         
18 }

咱们须要作的就是在调用__merge函数以前进行一个判断,判断第二个子序列的首元素是否大于第

一个子序列的最后一个元素,若是确实要大或者相等,则不须要调用__merge函数来进行后续的操

做了,由于这两个子序列合在一块儿自己就是一个有序的序列了,这个特性归结于归并排序自己的各个

子序列已经就是有序的状态了、如此一来就能够省去必定的时间开销,尤为是在序列自己的有序程度

高的时候,这个效果越可以体现出来。须要说明的是,若是你的数组序列自己有序程度很是低,建议

就不要加这个优化,毕竟条件判断也是须要消耗必定的时间的。

 

(2)第二种优化方法(在__merge函数中进行优化)

 1 /* 对arr[left, right]进行直接插入排序 */
 2 template<typename T>
 3 void __insertSortMG(T arr[], int left, int right)
 4 {
 5     for (int i = left + 1; i <= right; i++) {
 6         int j;
 7         T temp = arr[i];
 8 
 9         for (j = i; j > left && arr[j - 1] > temp; j--) {
10             arr[j] = arr[j - 1];
11         }
12 
13         arr[j] = temp;
14     }
15 }
16 
17 /* 递归使用归并排序,对arr[left....right]范围进行排序 */
18 template<typename T>
19 void __mergeSort (T arr[], int left, int right)
20 {
21     if (right - left <= 40) {    // 优化措施2: 对小数据量排序使用直接插入排序的方法
22         __insertSortMG<T>(arr, left, right);
23         return;           
24     }
25 
26     int mid = (left + right) / 2;   //  将数组分红2部分的中间索引值    
27 
28     __mergeSort<T>(arr, left, mid);      // 对第一部分子序列递归调用该函数
29     __mergeSort<T>(arr, mid + 1, right); // 对第二部分子序列递归调用该函数
30     
31     __merge<T>(arr, left, mid, right);    //  将两部分数据进行归并操做
32 }

为何须要这么作,理由有2个:

a、当数据量比较小的时候,序列的有序性强,那么使用插入排序会有优点。

b、虽然插入排序的时间复杂度是O(n^2),而归并排序的时间复杂度是O(nlogn),可是前面是存在

一个系数的,而插入排序的系数实际上是要小于归并排序的系数,当在数据量较小的时候,每每总体的

时间复杂度仍是插入排序的小。这种优化方法几乎在全部的高级算法中都是可使用的。那咱们再来

看看,使用了优化方式2以后的性能指示:

测试数据量50000:

 

测试数据量100000:

 

至于效果有多大,你们与上面的比较就能知道了,好了今天的归并排序暂时先告一段落了。

相关文章
相关标签/搜索