C#中使用基数排序算法对字符串进行排序的示例

开始之前

假设最长字符串的长度是L,以L作为输入的长度, 然后假定所有的字符串都"补齐"到此长度,这个补齐只是逻辑上的,我们可以假想有一种"空字符", 它小于任何其它字符,用此字符补齐所有长度不足的字符串。例如:最长的字符串长度为9,有一个字符串A长度为6, 那么当比较第7位字符的时候,我们让A[7]为"空字符"。

如果要包含所有的字符似乎并不容易,我们先定义一个字符集, 待排序字符串中的所有字符都包含在这个字符集里

//字符集
private string _myCharSet = "0123456789qwertyuiopasdfghjklzxcvbnm";

再来一个生成随机字符串的方法(C#实现):

private Random _random = new Random();

string[] GetRandStrings(int size, int minLength, int maxLength)
{
  string[] strs = new string[size];
  int len = 0;
  StringBuilder sb = new StringBuilder(maxLength);

  for (int i = 0; i < strs.Length; i++)
  {
    //先随机确定一个长度
    len = _random.Next(minLength, maxLength);
    for (int j = 0; j < len; j++)
    {
      //随机选取一个字符
      sb.Append(_myCharSet[_random.Next(_myCharSet.Length)]);
    }
    strs[i] = sb.ToString();
    sb.Clear();
  }
  return strs;
}

这里按照字符的整数表示来确定桶的范围,再为"空字符"准备一个桶。 为了表示"空字符"这个特例,这里用default(char),即'\0'表示它, 因为当调用string.ElementAtOrDefault(int)方法时,如果超出索引会返回'\0'。

初级版本(C#)

void StringRadixSort(string[] strArray)
{
  if (strArray == null
    || strArray.Length == 0
    || strArray.Contains(null))
  {
    return;
  }

  //获得字符串的最大长度
  int maxLength = 0;
  foreach (string s in strArray)
  {
    if (s.Length > maxLength)
    {
      maxLength = s.Length;
    }
  }

  //确定字符的整数范围
  int rangeStart = _myCharSet[0];
  int rangeEnd = _myCharSet[0];
  foreach (char ch in _myCharSet)
  {
    if (ch < rangeStart)
      rangeStart = ch;
    if (ch >= rangeEnd)
      rangeEnd = ch + 1;
  }

  //也要为"空字符"分配一个桶,其索引为0
  int bucketCount = rangeEnd - rangeStart + 1;
  LinkedList<string>[] buckets = new LinkedList<string>[bucketCount];

  //初始化所有的桶
  for (int i = 0; i < buckets.Length; i++)
  {
    buckets[i] = new LinkedList<string>();
  }

  //从最后一个字符开始排序
  int currentIndex = maxLength - 1;
  while (currentIndex >= 0)
  {
    foreach (string theString in strArray)
    {
      //如果超出索引,返回'\0'字符(default(char))
      char ch = theString.ElementAtOrDefault(currentIndex);
      if (ch == default(char))
      {  //"空字符"的处理
        buckets[0].AddLast(theString);
      }
      else
      {  //将字符映射到桶
        int index = ch - rangeStart + 1;
        buckets[index].AddLast(theString);
      }
    }
    //从桶里依次取回字符串,完成一趟排序
    int i = 0;
    foreach (LinkedList<string> bucket in buckets)
    {
      while (bucket.Count > 0)
      {
        strArray[i++] = bucket.First();
        bucket.RemoveFirst();
      }
    }
    currentIndex--;
  }
}

稍作"改良"

用作确定字符的整数范围的代码略显蛋疼,而且根据字符集来看, 并不是区间内所有的整数对应的字符都可能出现,因此会有这样的情况: 我们给某些根本不会出现的字符分配了桶,这纯属浪费。 我们可以用一个字典(散列)来记录字符和它的桶之间的映射。于是有了下面的代码。

private Dictionary<char, int> _charOrderDict =
        new Dictionary<char, int>(_myCharSet.Length);
void BuildCharOrderDict()
{
  char[] sortedCharSet = _myCharSet.ToArray();
  //使用默认的比较器排序
  Array.Sort(sortedCharSet);
  //为"空字符"单独创建映射
  _charOrderDict.Add(default(char), 0);
  for (int i = 0; i < sortedCharSet.Length; i++)
  {
    // 保存的是字符及其对应的桶的索引
    _charOrderDict.Add(sortedCharSet[i], i + 1);
  }
}

也可以不用默认的字符排序来作为映射,而完全自己定义字符之间的大小关系。 下面是调整后的代码:

void StringRadixSort(string[] strArray)
{
  if (strArray == null
    || strArray.Length == 0
    || strArray.Contains(null))
  {
    return;
  }
  //获得字符串的最大长度
  int maxLength = 0;
  foreach (string s in strArray)
  {
    if (s.Length > maxLength)
    {
      maxLength = s.Length;
    }
  }

  //为每一个字符(包括空字符'\0')分配一个桶
  //"空字符"索引应为0
  int bucketCount = _myCharSet.Length + 1;
  LinkedList<string>[] buckets = new LinkedList<string>[bucketCount];

  //初始化所有的桶
  for (int i = 0; i < buckets.Length; i++)
  {
    buckets[i] = new LinkedList<string>();
  }

  //从最后一个字符开始排序
  int currentIndex = maxLength - 1;
  while (currentIndex >= 0)
  {
    foreach (string theString in strArray)
    {
      //如果超出索引,返回'\0'字符(default(char))
      char ch = theString.ElementAtOrDefault(currentIndex);
      //根据字符顺序的定义查询字符
      int index = _charOrderDict[ch];
      buckets[index].AddLast(theString);
    }
    //从桶里依次取回字符串,完成一趟排序
    int i = 0;
    foreach (LinkedList<string> bucket in buckets)
    {
      while (bucket.Count > 0)
      {
        strArray[i++] = bucket.First();
        bucket.RemoveFirst();
      }
    }
    currentIndex--;
  }
}

Now, it works! 如果采用的快速排序来做, 其时间复杂度为O(n∗logn)O(n∗logn)。表面上看,基数排序更好,不过严格来说, 基数排序的时间复杂度应该是O(k∗n)O(k∗n),其中k和字符串长度正相关。 此时两种算法的比较可以通过比较k和lognlogn的比较结果近似得出。 如果字符串的长度很长,即k很大,而输入规模n不大的时候, 就会有k>lognlogn,此时快速排序反而更有优势。反之,则基数排序可能更优。

最后...

杯具的是,当我扩大字符集,将键盘上所有字符都加进去后, 发现基数排序的结果和Array.Sort(string[]方法的排序结果并不一样。 仔细观察资源管理器对文件名的排序,才发现其字符串排序的规则要复杂的多,并非简单的比较字符。 查询相关资料后发现,字符串的排序甚至还要考虑区域文化的影响,即使都是拉丁字母, 不同地区的排序规则都可能不一样,因此, 使用基数排序实现的字符串排序算法好像并无多大实用价值<T-T>。

(0)

相关推荐

  • C#插入法排序算法实例分析

    本文实例讲述了C#插入法排序算法.分享给大家供大家参考.具体如下: public static void InsertSort (int[] list) { for (int i = 1; i < list.Length; i++) { int Temp = list [i]; int j = i - 1; while (j > = 0 && list [j] > Temp) { list [j + 1] = list [j]; j-; } list [j + 1] =

  • 关于C#中排序函数的总结

    sort 函数对数组中的数据进行升序排序,(其中,sort函数有很多重载的形式,这里不再一一的说明) Reverse函数对数组中的数据进行降序排序, static void Main(string[] args) { // sort ,Reverse 排序的应用举例 int[] intArr = { 1,4,2,3,99,34,22,16,8,100}; Console.WriteLine("原数组为:"); for (int i = 0; i < intArr.Length;

  • C#选择法排序实例分析

    本文实例讲述了C#选择法排序实现方法.分享给大家供大家参考.具体实现方法如下: public int[] SelectionSort(int[] arr) { //1. Find min //2. Swap it with first element //3. Repeat starting from secong position onwards. int _min = 0; for (int i = 0; i < arr.Length; i++) { _min = i; for (int j

  • C#实现冒泡排序算法的代码示例

    1.原理:从数组的第一个位置开始两两比较array[index]和array[index+1],如果array[index]大于array[index+1]则交换array[index]和array[index+1]的位置,止到数组结束; 从数组的第一个位置开始,重复上面的动作,止到数组长度减一个位置结束; 从数组的第一个位置开始,重复上面的动作,止到数组长度减二个位置结束; .... 2.时间复杂度:O(N²),进行了(n-1)*(n-2)....=n*(n-1)/2次比较和约比较次数一半的交

  • 逐步讲解快速排序算法及C#版的实现示例

    算法思想 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序.它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod). 该方法的基本思想是: 1.先从数列中取出一个数作为基准数. 2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边. 3.再对左右区间重复第二步,直到各区间只有一个数. 虽然快速排序称为分治法,但分治法这三个字显然无法很好的概括快速排序的全部步骤.因此我的对快速排序作了进一步的说明:挖坑填数+分治法:

  • C#递归算法之归并排序

    归并排序是利用递归和分而治之的技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归步骤将排好序的半子表合并成为越来越大的有序序列,归并排序包括两个步骤,分别为: 1)划分子表 2)合并半子表 首先我们来讨论归并算法,归并算法将一系列数据放到一个向量中,索引范围为[first,last],这个序列由两个排好序的子表构成,以索引终点(mid)为分界线,以下面一个序列为例 7,10,19,25,12,17,21,30,48 这样的一个序列中,分为两个子序列 7,10,19,25  和

  • C#中哈希表(HashTable)用法实例详解(添加/移除/判断/遍历/排序等)

    本文实例讲述了C#中哈希表(HashTable)用法.分享给大家供大家参考,具体如下: 1.  哈希表(HashTable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似keyvalue的键值对,其中key通常可用来快速查找,同时key是区分大小写:value用于存储对应于key的值.Hashtable中keyvalue键值对均为object类型,所以Hashtable可以支持任何类型的keyvalue键

  • C#堆排序实现方法

    本文实例讲述了C#堆排序实现方法.分享给大家供大家参考.具体如下: private static void Adjust (int[] list, int i, int m) { int Temp = list[i]; int j = i * 2 + 1; while (j <= m) { //more children if(j < m) if(list[j] < list[j + 1]) j = j + 1; //compare roots and the older childre

  • C#递归算法之快速排序

    上两片第归算法学习: 1)递归算法之分而治之策略 2)递归算法之归并排序 上一篇学习中介绍了了递归算法在排序中的一个应用:归并排序,在排序算法中还有一种算法用到了递归,那就是快速排序,快速排序也是一种利用了分而治之策略的算法,它由C.A.R发明,它依据中心元素的值,利用一系列递归调用将数据表划分成越来越小的子表.在每一步调用中,经过多次的交换,最终为中心元素找到最终的位置.与归并算法不同,快速排序是就地排序,而归并排序需要把元素在临时向量中拷贝,下面通过对以下向量进行排序来理解和加深快速排序算法

  • C#中使用基数排序算法对字符串进行排序的示例

    开始之前 假设最长字符串的长度是L,以L作为输入的长度, 然后假定所有的字符串都"补齐"到此长度,这个补齐只是逻辑上的,我们可以假想有一种"空字符", 它小于任何其它字符,用此字符补齐所有长度不足的字符串.例如:最长的字符串长度为9,有一个字符串A长度为6, 那么当比较第7位字符的时候,我们让A[7]为"空字符". 如果要包含所有的字符似乎并不容易,我们先定义一个字符集, 待排序字符串中的所有字符都包含在这个字符集里 //字符集 private

  • PHP 中使用explode()函数切割字符串为数组的示例

    explode()函数的作用:使用一个字符串分割另一个字符串,打散为数组. 例如: 字符串 $pizza = "第1 第2 第3 第4 第5 第6"; 根据空格分割后:$pieces = explode(" ", $pizza); $pieces是分割后的数组,我们打印出来看下 <?php $pizza = "第1 第2 第3 第4 第5 第6"; $pieces = explode(" ", $pizza); fore

  • Python将列表中的元素转化为数字并排序的示例

    本文实例讲述了Python中列表元素转为数字的方法.分享给大家供大家参考,具体如下: 有一个数字字符的列表: numbers = ['2', '4', '1', '3'] 想要把每个元素转换为数字: numbers = [2, 4, 1, 3] 1. Python2.x,可以使用map函数: numbers = map(int, numbers) 2. Python3.x,map返回的是map对象,当然也可以转换为List: numbers = list(map(int, numbers)) 排

  • 深入解析Radix Sort基数排序算法思想及C语言实现示例

    基本思想: 将待排数据中的每组关键字依次进行桶分配. 具体示例: 278.109.063.930.589.184.505.269.008.083 我们将每个数值的个位,十位,百位分成三个关键字: 278 -> k1(个位)=8,k2(十位)=7,k3=(百位)=2. 然后从最低位个位开始(从最次关键字开始),对所有数据的k1关键字进行桶分配(因为,每个数字都是 0-9的,因此桶大小为10),再依次输出桶中的数据得到下面的序列. 930.063.083.184.505.278.008.109.58

  • Python中使用插入排序算法的简单分析与代码示例

    问题描述 将一组随机排列的数字重新按照从小到大的顺序排列. 插入算法 每次从数组中取一个数字,与现有数字比较并插入适当位置. 如此重复,每次均可以保持现有数字按照顺序排列,直到数字取完,即排序成功. 这很像打牌时的抓牌情况, 第一个条件:保持手上的牌的顺序是正确的 第二个条件:每次抓到新的牌均按照顺序插入手上的牌中间. 保证这两条不变,那么无论抓了几张牌,最后手上的牌都是依照顺序排列的. Python 实现: def insertion_sort(n): if len(n) == 1: retu

  • Yii2-GridView 中让关联字段带搜索和排序功能示例

    情境要求: 要在订单(Order)视图的gridview中显示出客户(Customer)姓名,并使其具有与其它字段相同的排序和搜索功能. 数据库结构 订单表order含有字段customer_id 与 客户表customer的id字段关联 首先确保在Order Model中包含以下代码: public function getCustomer() { return $this->hasOne(Customer::className(), ['id' => 'customer_id']); }

  • PHP中strnatcmp()函数“自然排序算法”进行字符串比较用法分析(对比strcmp函数)

    本文实例讲述了PHP中strnatcmp()函数"自然排序算法"进行字符串比较用法.分享给大家供大家参考,具体如下: PHP中strnatcmp()函数使用"自然"算法来比较两个字符串(区分大小写),通常在自然算法中,数字 2 小于数字 10.而在计算机排序中,10 小于 2,这是因为 10 中的第一个数字小于 2. strnatcmp()函数的定义如下: strnatcmp(string1,string2) 参数说明: string1  必需.规定要比较的第一个字

  • Go Java算法之字符串中第一个唯一字符详解

    目录 字符串中第一个唯一字符 方法一:哈希表(Java) 方法二:队列(Go) 字符串中第一个唯一字符 给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 .如果不存在,则返回 -1 . 示例 1: 输入: s = "leetcode" 输出: 0 示例 2: 输入: s = "loveleetcode" 输出: 2 示例 3: 输入: s = "aabb" 输出: -1 提示: 1 <= s.length <= 10

  • Python实现的基数排序算法原理与用法实例分析

    本文实例讲述了Python实现的基数排序算法.分享给大家供大家参考,具体如下: 基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些"桶"中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率

  • Python数据结构与算法之常见的分配排序法示例【桶排序与基数排序】

    本文实例讲述了Python数据结构与算法之常见的分配排序法.分享给大家供大家参考,具体如下: 箱排序(桶排序) 箱排序是根据关键字的取值范围1~m,预先建立m个箱子,箱排序要求关键字类型为有限类型,可能会有无限个箱子,实用价值不大,一般用于基数排序的中间过程. 桶排序是箱排序的实用化变种,其对数据集的范围,如[0,1) 进行划分为n个大小相同的子区间,每一个子区间为一个桶,然后将n非记录分配到各桶中.因为关键字序列是均匀分布在[0,1)上的,所以一般不会有很多记录落入同一个桶中. 以下的桶排序方

随机推荐