mooc_02_排序

来源:互联网 发布:广州淘宝拍摄基地在哪 编辑:程序博客网 时间:2024/06/16 18:41

相关链接:http://www.cnblogs.com/zxy1992/p/4392347.html

1. 归并排序

图片出自:http://www.cnblogs.com/nullzx/p/5968170.html
时间复杂度 O(N*logN)

递归式

起初感觉归并排序原理并没有多复杂,但是自己写的时候还是遇到了一些问题 T.T,尴尬,所以详细记录一下过程主要过程:1. 分割 2. 合并    1.  把一个数组进行对半分,对半分,对半分,再对半分,一直分到只剩下1个元素  【明显 使用递归, 终止条件--只剩下一个元素】    2.  合并, 两个数组进行合并,并且过程中进行排序           我感觉重点在两个数组进行合并的时候,           必须要明确合并时每一个临时变量的含义,并且在其使用的过程中严格遵守这些含义,           不然容易把自己绕进去

MergeSort

// 进行 [left, mid] [mid+1, right] 两个数组合并               template <typename T>                                         void __merge(T arr[], int left, int mid, int right)           {                                                                 T aux[right-left+1];                                          for (int i = left; i <= right; i++) {                             aux[i-left] = arr[i];                                     }                                                             //开始进行合并 明确各个变量的含义                             //i aux left 数组的下标                                       //j aux right 数组的下标                                      //k 是i,j比较完之后,需要赋值给arr的下标                     int i = 0, j = mid-left+1;;                                   for(int k = left; k <= right; k++) {         if(i > mid-left) {                           arr[k] = aux[j];                         j++;                                 } else if (j > right-left) {                 arr[k] = aux[i];                         i++;                                 } else if (aux[i] < aux[j]){                 arr[k] = aux[i];                         i++;                                 } else {                                     arr[k] = aux[j];                         j++;                                 }                                    }                               }                                                             // 使用归并排序 a[left, right] 这个闭区间                     template <typename T>                                         void __mergeSort(T arr[], int left, int right)                {                                                                 if(left >= right)                                                 return;                                                   int mid = (right+left) / 2;                                   __mergeSort(arr, left, mid);                                  __mergeSort(arr, mid+1, right);                               __merge(arr, left, mid, right);                           }                                                             template <typename T>                                         void mergeSort(T arr[], int n) {                       __mergeSort(arr, 0, n-1);                                 }                                                             
在进行合并之前直接比较两个数组的第一个,left[end] < right[start], 就不需要在进行比较了,因为已经是有序的了
// 使用归并排序 a[left, right] 这个闭区间                     template <typename T>                                         void __mergeSort(T arr[], int left, int right)                {                                                                 if(left >= right)                                                 return;                                                   int mid = (right+left) / 2;                                   __mergeSort(arr, left, mid);                                  __mergeSort(arr, mid+1, right);                               if(arr[mid] < arr[mid+1])        return;                                __merge(arr, left, mid, right);                           }    

循环式

主要过程:        1. 首先以长度为1划分n个数组,然后俩俩数组进行排序合并        2. 然后再以长度2划分n数组,俩俩数组进行排序合并        3. 长度4        4. 长度8        5. 直到数组的长度 大于待排序数组总长度的1/2,就终止

MergeSortBU

template <typename T>                                                void mergeSortBU(T arr[], int n)                                  {                                                                        for( int size = 1; size < n; size+=size ){             for (int i = 0; i < n; i = size+size){            //min 函数确保 最后的那个数组在不足size的时候,也不会越界访问                        __merge(arr, i, i+size-1, min(i+size+size-1, n-1));                }                                                                }                                                                }                                                                                                                   
在这种不需要递归的归并排序中,不需要对数组的随机访问,就算是merge过程,只需要找到两个子数组的初始位置,之后就可以通过依次后移索引解决问题,索引和元素均不需要在数据间跳动。所以mergeSort的思路可以非常好的对链表这种数据结构进行O(nlogn)级别的排序。

2. 快速排序

时间复杂度 O(N*logN) 

2.1 一般实现

主要过程:    1. 在一个数组中,选取一个数A,通过运算,使得A左边的数都比A小,右边的数都比A大    2. 经过上述操作,一个数组就分为了两部分,一部分A小,一部分比A大,    3. 然后在这两个数组中,在选取一个B,重复1的操作主要工作还是在选择一个数,令其左边的比他小,右边的比他大    例如有一个数arr[l...r], 现在选择arr[v]作为基准数,v=l,也就是数组的第一个数    然后认为确定        区间[l+1, j]        小于 arr[v]        区间[j+1, i)        大于等于 arr[v],         arr[i]是当前循环准备比较的数,是个未定值        如果arr[i] >= arr[v], 继续向后循环,i++,这样[j+1, i)仍旧保持含义        如果arr[i] < arr[v], 那么交换(arr[j+1], arr[i]) ; j++, 如此一来,两个区间仍然能保证复合定义        循环结束后,交换 (arr[v],arr[i]), 此时满足定义
template <typename T>                           int partition(T arr[], int left, int right)     {                                                   int v = left;                                   int j = left;   //l左边都小于arr[v]             for(int i =j+1; i <= right; i++) {                  if(arr[i] < arr[v]) {                               swap(arr[i], arr[j+1]);                         j++;                                        }                                           }                                               swap(arr[v], arr[j]);                           return j;                                   }                                               //arr[left, right]                              template <typename T>                           void __quitSort(T arr[], int left, int right)   {                                                   if(left >= right)                                   return;                                     //找到中间的那个点                              int q = partition(arr, left, right);            __quitSort(arr, left, q-1);                     __quitSort(arr, q+1, right);                }                                               template <typename T>                           void quitSort(T arr[], int n)                   {                                                   __quitSort(arr, 0, n-1);                    }                                               ///////////////////////////number = 100000selectSort      :12.560545sinsertSort      :9.077882sbubbleSort      :44.754248sshellSort       :0.033294smergeSort       :0.027092smergeSortBU     :0.028499squitSort        :0.023331s

2.2 随机化基准数排序

一般情况下,我们是选择的数组的第一个数作为基准数,但是这么做是有局限性的恰好数组基本有序的情况下,快速排序的算法最差就会由 O(N*logN) 退化成一个 O(N^2)的算法,    这主要是快速并不是像归并排序那样对半分数组,而是选定一个基准数来进行分的,    这样就会导致左右两个数组不一样大,分的层数也就不是固定的,也就不会想归并一样是logN    当数组恰好有序的情况下,每次都选第一个,然后分的两个数组的长度都是1,n-1,    然后会递归n次,此时快速排序算法就退化成了一个O(N^2)级别的算法下列是归并和快排同时对一个已经有序的数组进行排序的时间    number = 100000    shellSort       :0.029492s    mergeSort       :0.012387s    mergeSortBU     :0.013129s    quitSort        :11.429034s解决方案:    在选择基准数的时候可以随机选择
int index = rand()%(right-left+1)+left;swap(arr[left], arr[index]);           int v = left;//////////            设置随机数之后:        number = 10000        shellSort       :0.001897s        mergeSort       :0.000999s        mergeSortBU     :0.001090s        quitSort        :0.001015s

2.3 双路快速排序

在针对拥有大量重复元素的数组进行排序是,经过随机化的快速排序一样会比归并满很多下列是针对一个1000000大小的数组,并且数组内的数都在0~9之间的时候,归并与快速排序的比较结果    number = 1000000    shellSort       :0.118652s    mergeSort       :0.184448s    mergeSortBU     :0.182237s    quitSort        :108.244668s可以清楚的看到,差异非常大这是因为我们之前的一般实现的算法是把一个数组根据基准数分为了 小于  和 大于等于  两个部分,如果重复元素非常多,就会导致下递归划分数组的时候,两个十分不平衡,极端这就会出现 这就和碰到近乎有序的数组的情况类似,最差情况下,快排会退化到O(N^2)的地步解决办法之一:是把重复的基准数尽量平均的分配到两边,使得两边尽量平衡    所以这次改变策略,从两边向中间主要过程:    1. 首先定义区间,我们的数组是arr[left, right],基准数下标是v = left       那么剩下来进行比较的区间是arr[left+1, right]    2. 这次是由两边向中间,所以两边的区间分别是            arr[left+1, l)   <= arr[v]               arr(r, right]  >= arr[v]           为什么是半开闭区间?  因为 l和r是我们即将要进行比较的数,不能作为闭区间来看待    3. 然后先从左边开始,直到碰到边界 或 <= 基准数arr[v] 的数时停止,    然后开始右边的,直到碰到边界 >= 基准数arr[v]时停止    4. 然后arr[l]与arr[r]交换, 然后l++,r--;重复步骤3    5. 当l>r时停止,此时 arr[r] 的位置的数肯定是<= arr[v]的,进行交换,然后return r。
 template <typename T>                                 int partition(T arr[], int left, int right)          {                                                         swap(arr[left], arr[rand()%(right-left+1)+left]);     int v = left;                                         int l = left+1;                                       int r = right;                                        //[left+1, l)          (r, right])]                            while(1) {                                                while(arr[l] < arr[v] && l <= right)                      l++;                                              while(arr[r] > arr[v] && r >= left+1)                     r--;                                              if(l > r)                                                 break;                                            else {                                                    swap(arr[l], arr[r]);                                 l++;r--;                                          }                                                 }                                                     swap(arr[v], arr[r]);                                 return r;                                         }         ///////////////number = 1000000shellSort       :0.117720smergeSort       :0.184379smergeSortBU     :0.183127squitSort        :0.106818s结果很明显                                                                  

2.4 三路快速排序

 三路快速排序 实际上是在有大量重复元素情况下,快速排序的另一种实现方法 就是把数组分为三部分   <   =   >  三部分,并不是和双路一样把=的分散到两边主要过程:    1. 带排序数组是arr[left, right],   基准数下标 是  v = left;        那这三个区间分别是 [left+1, l]   [l+1,  i)  [r, right]        初始值分别是 l = left,  i = left+1, r = right+1        这样,三个区间刚开始的时候都为空    2. 然后根据定义好的含义   (是在觉得语言叙述过于繁琐,又懒得画图,直接上代码,对照着上面的三个区间看)         i == r ;   break;         arr[i] < arr[v]        swap(arr[i], arr[l+1]); i++;l++;         arr[i] == arr[v]       i++;         arr[i] > arr[v]        swap(arr[i], arr[r-1]);  r--;    3. 最后, 把swap(arr[v], arr[l]);           然后三个区域此时就变成了        [left+1, l-1]   [l,  r)  [r, right]
template <typename T>                                         void __quitSort3way(T arr[], int left, int right)             {                                                                 if (left >= right){                                               return;                                                   }                                                             swap(arr[left], arr[rand()%(right-left+1)+left]);             int v = left;                                                 int l = left, r = right+1;                                    int i = left+1;;                                              while(1){                                                         if (i == r)                                                       break;                                                    if(arr[i] < arr[v]){                                              swap(arr[i], arr[l+1]);                                       i++; l++;                                                 } else if (arr[i] == arr[v]){                                     i++;                                                      } else if (arr[i] > arr[v]){                                      swap(arr[i], arr[r-1]);                                       r--;                                                      }                                                         }                                                             swap(arr[v], arr[l]);                                         __quitSort3way(arr, left, l-1);                               __quitSort3way(arr, r, right);                            }                                                             template <typename T>                                         void quitSort(T arr[], int n)                                 {                                                                 srand(time(NULL));                                            __quitSort3way(arr, 0, n-1);                              }                 /////////number = 1000000shellSort       :0.122769smergeSort       :0.187990smergeSortBU     :0.185249squitSort        :0.036165s      因为测试数组还是之前的范围在09之间的数组,所以三路特别的快                                      

3. 总结:

MergeSort和quitSort都很好的使用了分治算法的思想    就是将原问题分割成同等结构的子问题,之后将子问题逐一解决后,原问题也就得到了解决。

延伸问题 1. 求一个数组中逆序对的数量

解题思路:    归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。    在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当    a[i]>a[j]时,在前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。    因此,可以在归并排序中的合并过程中计算逆序数.    注意范围,逆序数最大为n*(n-1)/2,结果用long long保存。
long long g_count = 0;template <typename T>                                         void __merge(T arr[], int left, int mid, int right)           {                                                                 T aux[right-left+1];                                          for (int i = left; i <= right; i++) {                             aux[i-left] = arr[i];                                     }                                                             //开始进行合并 明确各个变量的含义                             //i aux left 数组的下标                                       //j aux right 数组的下标                                      //k 是i,j比较完之后,需要赋值给arr的下标                     int i = 0, j = mid-left+1;;                                   for(int k = left; k <= right; k++) {         if(i > mid-left) {                           arr[k] = aux[j];                         j++;                                 } else if (j > right-left) {                 arr[k] = aux[i];                         i++;                                 } else if (aux[i] < aux[j]){                 arr[k] = aux[i];                         i++;                                 } else {                                     arr[k] = aux[j];                  j++;              g_count += mid-i+1;         //--------------->累计逆序对的数量                            }                                    }                                                    }   

延伸问题 2. 取一个数组的第n大的元素

解题思路:    首先了解快排思想,即每次可以将一个元素放到最终位置上,    所以如果当前放置的元素是第n个位置,也就是第n大,即得到了最终的结果,不用继续进行排序操作
#include "SortTextHelper.h"                                           #include <iostream>                                                   template <typename T>                                                 int __partation(T arr[], int left, int right)                         {                                                                         int v = left;                                                         //[left+1, l) [l+1, right]                                            int l = left;                                                         for(int i = left+1; i <= right; i++) {                                    if (arr[i] < arr[v]) {                                                    swap(arr[i], arr[l+1]);                                               l++;                                                              }                                                                 }                                                                     swap(arr[v], arr[l]);                                                 return l;                                                         }                                                                     template <typename T>                                                 int _quitSort(T arr[], int left, int right, int k)                    {                                                                         if(left > right)                                                          return -1;                                                        if(left == right && left == k){                                           return arr[k];                                                    }                                                                     int num = __partation(arr, left, right);                              if(k == num){                                                             return arr[k];                                                    } else if (k < num) {                                                     return _quitSort(arr, left, num-1, k);                            } else {                                                                  return _quitSort(arr, num+1, right, k);                           }                                                                 }                                                                     template <typename T>                                                 int quitSort(T arr[], int n, int k)                                   {                                                                         return _quitSort(arr, 0, n-1, k-1);                               }                                                                     int main(int argc, char *argv[])                                      {                                                                         int n = 10;                                                           int *arr = SortTextHelper::generateRangeArray(n, 0, n*n);             int num = quitSort(arr, n, 3) ;                                       cout << "num = " <<  num << endl;                                     SortTextHelper::printArray(arr, n);                                   return 0;                                                         }
0 0