数据结构之归并排序、基数排序

来源:互联网 发布:人机大战与网络象棋 编辑:程序博客网 时间:2024/06/06 09:39

                   

          小目录

            1.归并排序

            2.基数排序


         1.归并排序

         归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治思想的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

 归并操作的过程如下:

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

  过程如图(坑爹的csdn,又自动给图片加上了水印去不掉阿,如果有侵权请及时联系我删除):

                                

具体的实现代码:

/** @description:对左右两部分进行合并*/void Merge(ElemType *SR,ElemType *TR,int i,int m,int n) {int j,k;/*由于前后两部分都是有序的,故可以采用前后中一个元素比较的方法设置两个指针,k主要是起到了控制下标的作用,只有在两部分都是有序的情况下才可以用这种办法*/for(j = m + 1,k = i; i <= m && j <= n ; k++) {if(SR[i].key < SR[j].key)TR[k] = SR[i++];elseTR[k] = SR[j++];}//将前半部分剩余的移入TRif(i <= m)while(i <= m)TR[k++] = SR[i++];//将后半部分剩余的移入TR,注意这两种情况不会同时出现if(j <= n)while(j <= n)TR[k++] = SR[j++];}/** @description:具体的归并排序实现*/void MSort(ElemType *SR,ElemType *TR1,int s,int t) {int m;ElemType TR2[MAXSIZE + 1];if(s == t)TR1[s] = SR[s];else {//找到中间值m = (s + t) / 2;MSort(SR,TR2,s,m);//对左半部分进行归并排序MSort(SR,TR2,m + 1,t);//对右半部分进行归并排序Merge(TR2,TR1,s,m,t);//将左右两部分进行归并}}/** @description:归并排序*/void MergeSort(SqList *L) {MSort((*L).r,(*L).r,1,(*L).length);}

       性能分析:该算法是最坏和平均情况下的时间复杂度都为O(nlogn),辅助存储为O(n)



     2.基数排序

     基数排序是一种比较奇葩的排序算法,前面的排序算法都是通过两个元素间比较和移动来实现排序,可是技术排序却偏偏不是这样的。如果不是这样,那也能排序吗?我一想也觉得奇怪,后来才发现这真不是我等平凡人能想出来的。

     基数排序是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,然后在收集,以达到排序的作用。什么是部分资讯呢?比如我们要排序扑克牌,我们是按照花色来还是按照面值来排序呢?当然两者都可以。比如我们比较12和02是我们总是先比较1和0再比较2和2,基数排序中就有两种排序思想:最高位优先/最低位优先。下面我们的代码实现是基于数组实现的:

/** @description:基数排序* @more:并没有采用递归的思想*/void RadixSort(int *a, int n) {int i,m,exp;int b[MAX];m = a[0];exp = 1;//找到最大值for (i = 1; i < n; i++) {if (a[i] > m) {m = a[i];}}/*为什么要找到最大值呢?因为可能会出现 1 和 999这样的两个数字,必须以最大的数字为基准来进行基数的收集和分配结束条件*/while (m / exp > 0) {//初始化int bucket[BASE] = { 0 };//统计出当前相同字码的个数,比如21和31则现在有两个个位数为1的数for (i = 0; i < n; i++) {bucket[(a[i] / exp) % BASE]++;}/*统计出每个字码的数组边界,比如个位数为0有1,个位数为1的有两个则个位数为1的在数组中的存储边界为3即1-2*/for (i = 1; i < BASE; i++) {bucket[i] += bucket[i - 1];}//分配,注意这里从后面扫描仅仅是为了保证排序的稳定性for (i = n - 1; i >= 0; i--) {b[--bucket[(a[i] / exp) % BASE]] = a[i];}//收集for (i = 0; i < n; i++) {a[i] = b[i];}//进行下一位数的收集和分配exp *= BASE;}}
      排序过程即不断收集和分配的过程:【521 310 72 373 15 546 385 856 187 147】序列为例,具体细节如下图所示:

                                    


       更详细的可以看看这个(其实他的代码风格很不好):链接

     性能分析:设待排序列为n个记录,d个关键码,关键码的取值范围为radix,时间复杂度O(d(n+radix)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(radix),共进行d趟分配和收集。辅助存储空间为O(radix).

      

   总结:基本排序算法的博客已经写完了,上一张图来总结下吧




在稳定性上,快速排序/堆排序/希尔排序都是不稳定排序,其他的都是稳定排序。

后面的博客会介绍常见的五种算法思想,敬请期待。


0 0
原创粉丝点击