zoukankan      html  css  js  c++  java
  • 各种算法的C#实现系列1 合并排序的原理及代码分析

    本文算法主要整理自《计算机算法设计与分析(第3版)》电子工业出版社,出于学习目的写下此文。 

    合并排序算法是用分治策略实现对n个元素进行排序的算法。

    基本思想是:将待排序元素分成大小大致相同的两个子集合,分别对两个子集合进行排序,最终将排好序的子集合合并成所要求的排好序的集合。

    合并排序算法递归实现的程序:

     1 class MergeRecursion<T> where T: new()
     2 {
     3     public void MergeSort(List<T> li, int left, int right)
     4     {
     5         //临时存储数据区
     6         List<T> li_b = new List<T>(100);
     7         for (int i = 1; i <= 100; i++)
     8         {
     9             T item = new T(); 
    10             li_b.Add(item);
    11         }
    12 
    13             //判断,保证至少有个元素
    14             if (left < right)
    15             {
    16                 //取中点
    17                 int m = (left + right) / 2;
    18 
    19                 MergeSort(li, left, m);
    20                 MergeSort(li, m + 1, right);
    21                 //合并到数组b
    22                 MergeCommon<T>.Merge(li, li_b, left, m, right);
    23                 //复制回数组a
    24                 Copy(li, li_b, left, right);
    25             }
    26             else
    27             {
    28                 return;
    29             }
    30     }
    31 
    32     private static void Copy(List<T> li, List<T> li_b, int left, int right)
    33     {
    34         for (int i = left; i <= right; i++)
    35         {
    36             li[i] = li_b[i];
    37         }
    38     }
    39 }

    Merge方法将两个排好序的数组段合并到一个新的数组b中,然后由Copy将合并后的数组段再复制回a中。合并算法Merge的实现如下:

     1 class MergeCommon<T>
     2 {
     3     public static void Merge(List<T> li, List<T> li_b, int left, int m, int right)
     4     {
     5         //比较用
     6         BComparer<T> bc = BComparer<T>.Instance;
     7         //合并li[left:m]和li[m+1:right]到d[left:right]
     8         int i = left;
     9         int j = m + 1;
    10         int k = left;
    11         while ((i <= m) && (j <= right))
    12         {
    13             if(bc.Compare(li[i],li[j]) <= 0)
    14             {
    15                 li_b[k++] = li[i++];
    16             }
    17             else
    18             {
    19                 li_b[k++] = li[j++];
    20             }
    21         }
    22         if (i > m)
    23         {
    24             for (int q = j; q <= right; q++)
    25             { 
    26                 li_b[k++] = li[q];
    27             }                    
    28         }
    29         else
    30         {
    31             for (int q = i; q <= m; q++)
    32             {
    33                 li_b[k++] = li[q];
    34             }
    35         }
    36     }
    37 }

    合并排序的非递归实现

    由于递归操作是一种较消耗资源的操作,可以考虑实现无递归的合并排序

    思想:首先将数组a中相邻的元素两两配对。用合并算法将他们排序,构成n/2组长度为2排好序的子数组段,然后再将它们排序成长度为4的排好序的子数组段,如此继续下去,直至整个数组排序好。

    此思想的算法实现:

     1 class MergeNRecursion<T> where T: new()
     2 {
     3     public void MergeSort(List<T> li, int n)
     4     {
     5         //临时存储数据区
     6         List<T> li_b = new List<T>(100);
     7         for (int i = 1; i <= 100; i++)
     8         {
     9             T item = new T();
    10             li_b.Add(item);
    11         }
    12         
    13         int s = 1;
    14         while (s < n)
    15         {
    16             MergePass(li,li_b,s,n);
    17             s += s;
    18             MergePass(li_b,li,s,n);
    19             s += s;
    20         }
    21     }
    22 
    23     private static void MergePass(List<T> li, List<T> li_b, int s, int n)
    24     {
    25         … …
    26     }
    27 }

    其中MergePass用于合并排好序的相邻数组段。而具体的合并算法同样由Merge来实现。  

    MergePass算法实现: 

     1 private static void MergePass(List<T> li, List<T> li_b, int s, int n)
     2 {
     3     //合并大小为s的相邻子数组
     4     int i = 0;
     5     while (i <= n - 2 * s)
     6     { 
     7         //合并大小为s的相邻2段子数组
     8         MergeCommon<T>.Merge(li,li_b,i,i+s-1,i+2*s-1);
     9         i = i + 2 * s;
    10     }
    11 
    12     //剩下的元素个数少于2s
    13     if (i + s < n)
    14     {
    15         MergeCommon<T>.Merge(li, li_b, i, i + s - 1, n - 1);
    16     }
    17     else
    18     {
    19         for (int j = i; j <= n - 1; j++)
    20         { 
    21             li_b[j] = li[j];
    22         }
    23     }
    24 }

    解释:剩余元素个数少于2s后的处理程序中,i + s < n 这句判断可以得出个数不足2s的那段数组是否已排过序,如果if条件成立,说明剩余元素还未排过序,需要调用Merge方法排序,否则说明在前一次MergePass执行过程中就已经对相同的数组段进行过排序,不用重复进行,只需要直接复制到另一个数组即可(else实现)。

    这种非递归的合并排序拥有自然合并排序的基本思想

    算法中一个很重要的代码是对泛型对象的大小比较,代码来自光辉的晨星Blog原文见此

    把代码粘贴于此:

     1 public class BComparer<T>
     2 {
     3     //比较委托
     4     Comparison<T> _comparison;
     5 
     6     static readonly Dictionary<Type, Delegate> _map = new Dictionary<Type, Delegate>();
     7 
     8     //实现单例(代替构造函数的功能)
     9     static readonly BComparer<T> _instance = new BComparer<T>();
    10     //构造函数
    11     private BComparer() { }
    12 
    13     public static BComparer<T> Instance
    14     {
    15         get
    16         {
    17             System.Diagnostics.Debug.Assert(_map.ContainsKey(typeof(T)));
    18             //强转为具体的比较委托
    19             _instance._comparison = (Comparison<T>)_map[typeof(T)];
    20             return _instance;
    21         }
    22     }
    23     //情态构造,初始化
    24     static BComparer()
    25     {
    26         //基础类型比较器
    27         Type t = typeof(T);
    28         if (t == typeof(bool))
    29         {
    30             Comparison<bool> cmp_bool = delegate(bool t1, bool t2)
    31                                 { return t1 == t2 ? 0 : (t1 ? 1 : -1); };
    32             _map.Add(typeof(bool), (Delegate)cmp_bool);
    33         }
    34         if (t == typeof(int))
    35         {
    36             Comparison<int> cmp_int = delegate(int t1, int t2)
    37                                 { return t1 > t2 ? 1 : (t1 == t2 ? 0 : -1); };
    38             _map.Add(typeof(int), (Delegate)cmp_int);
    39         }
    40         //....其他
    41     }
    42     //注册自定义比较
    43     public static void Register<NT>(Comparison<NT> comparison)
    44     {
    45         System.Diagnostics.Debug.Assert(_map.ContainsKey(typeof(NT)) == false);
    46 
    47         _map.Add(typeof(NT), (Delegate)comparison);
    48     }
    49     //比较函数,以后用来实现IComparer用
    50     public int Compare(T t1, T t2)
    51     {
    52         System.Diagnostics.Debug.Assert(_comparison != null);
    53 
    54         return _comparison(t1, t2);
    55     }
    56 }

    原书文章中提到了自然合并排序,自然合并排序是上述非递归合并排序算法的一个变形。其基本思想是初始序列中有自然排好序的子数组,用1次对数组线性扫描就足以找出所有这些排好序的子数组段。将相邻的排好序的子数组段两两合并,构成更大的排好序的子数组。这样继续下去直至真个数组排好序。这样就比从1开始排相邻的元素效率要高很多。

    下面是个人实现的一种自然排序,请指点:

     1 class MergeNature<T> where T : new()
     2 {
     3     public static List<int> GetChildArray(List<T> li)
     4     {
     5         //保存子数组长度(最后一个元素位置)
     6         List<int> arr = new List<int>(100);
     7 
     8         int i = 0;
     9         arr.Add(-1);
    10         
    11         BComparer<T> bc = BComparer<T>.Instance;
    12 
    13         while (i < li.Count - 1)
    14         {
    15             if (bc.Compare(li[i], li[i + 1]) <= 0)
    16             {
    17                 i++;
    18                 continue;
    19             }
    20             else
    21             {
    22                 arr.Add(i);
    23                 i++;
    24             }
    25         }
    26         if (arr[arr.Count - 1] != 29)
    27         {
    28             arr.Add(29);
    29         }
    30         return arr;
    31     }
    32 
    33     /// <summary>
    34     /// 合并子数组(即列表)
    35     /// </summary>
    36     /// <param name="li"></param>
    37     public static void MergeSort(List<T> li)
    38     {
    39         //调用GetChildArray获取arr
    40         List<int> arr = GetChildArray(li);
    41 
    42         //临时存储数据区
    43         List<T> li_b = new List<T>(100);
    44         for (int i = 1; i <= 100; i++)
    45         {
    46             T item = new T();
    47             li_b.Add(item);
    48         }
    49 
    50         int s = 1;
    51         while (s < arr.Count - 1)
    52         { 
    53             MergePass(li, li_b, s, arr);
    54             s += s;
    55             MergePass(li_b, li, s, arr);
    56             s += s;
    57         }            
    58     }
    59 
    60     private static void MergePass(List<T> li, List<T> li_b, int s, List<int> arr)
    61     {
    62         int i = 0;
    63         int length = arr.Count - 1;
    64 
    65         while (i <= length - 2 * s )
    66         {
    67             MergeCommon<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[i + 2 * s]);
    68             i = i + 2 * s;
    69         }
    70 
    71         //arr数组中剩余元素个数少于s
    72         if (i + s < length)
    73         {
    74             MergeCommon<T>.Merge(li, li_b, arr[i] + 1, arr[i + s], arr[length]);
    75         }
    76         else
    77         {
    78             for (int j = arr[i] + 1; j <= arr[length]; j++)
    79             {
    80                 li_b[j] = li[j];
    81             }
    82         }
    83     }
    84 }
  • 相关阅读:
    阿里笔试题
    springboot-security-jwt
    java 面试架构篇
    java 面试题 mybatis 篇
    Java 多线程并发工具类
    java 面试题 高阶版
    给你的右键菜单添加“VScode”
    HTML重点知识点汇总
    HTML5知识点小结
    给博客园添加百度统计方法
  • 原文地址:https://www.cnblogs.com/lsxqw2004/p/1345576.html
Copyright © 2011-2022 走看看