前言:

最近正好在学习字符串相关的算法,写了下LSD、MSD以及三向快速排序算法,写完了过后有很大的感触,所以决定把这几天所学习的东西记录下来。

原理:

键索引计数法:
        在开始介绍LSD以及MSD算法之前,我们要介绍一个算法——键索引计数法,这是LSD以及MSD的基础。
键索引计数法允许我们通过一定的键值来对字符串进行排序,一个很好的例子就是分组——每个学生对应一个小组,将他们按照小组分开。在这个问题中,学生姓名就是字符串,而小组的组号就是对应的键值,我们需要将相同小组的同学分到一起(如图一)
为了完成上述的功能,我们先统计每个键值的个数并计入一个Count数组中,Count[n + 1]中统计键值为n的字符串的个数,在上图中Count[2] = 2,Count[3] = 1,Count[4] = 2;在统计完成后,我们需要将频数转换为索引,这是至关重要的一步,在这里你也会发现为什么我们用Count[n + 1]来统计键值n而不是Count[n]。在这一步中我们使Count[n + 1] += Count[n],这样你会发现Count[0] = 0,Count[1] = 0,Count[2] = 2,Count[3] = 3;可以发现这样变换了以后Count[n]正是下一个键值为n的字符串的索引下标!最后我们只需要根据索引将字符串存入我们预先准备的字符串数组及可以完成分组,具体的代码我们可以在后面的LSD以及MSD中看到!
低位优先排序(LSD):
低位优先排序在我们的生活中经常见到,比如银行卡号的排序、车牌的排序以及电话号码的排序等,我们可以发现一个规律,那就是这些东西的长度都是相等的!这并不是巧合,而是LSD算法的性质决定的,LSD适用于较短的定长字符。
LSD算法要求用于排序的字符串数组中的每一个字符串的长度相同,然后从最低位(即最右边的字符)开始,用键索引计数法对字符串排序,一直到用最高位进行键索引排序,则退出算法完成排序(如图2所示)
从图中我们可以发现LSD算法正如其名字一样,从低位开始保证其有序(红框内部分);当然,只要熟练掌握键索引计数法,LSD的编写将会很简单;具体代码如下:
/* 低位优先排序:将字符串进行低位优先排序(试用情况: 字符串的长度相同)* 参数:a:参与排序的字符串数组,N:字符串中元素的个数* 返回值:无*/
void LSD(string *a, int N) {int W = a[0].length(), R = 256;string *Aux = new string[N];int *Count = new int[R + 1];for (int k = W - 1; k >= 0; k--) {// 初始化索引数组for (int i = 0; i < R + 1; i++)Count[i] = 0;// 计算位置k上字符频数for (int i = 0; i < N; i++)Count[a[i][k] + 1]++;// 将频数转换为索引for (int i = 0; i < R; i++)Count[i + 1] += Count[i];// 将元素分类for (int i = 0; i < N; i++)Aux[Count[a[i][k]]++] = a[i];// 回写for (int i = 0; i < N; i++)a[i] = Aux[i];}Aux = NULL;delete Aux;Aux = NULL;delete Count;Count = NULL;
}
PS:在上述代码中,我认为最重要的是Aux[Count[a[i][k]]++] = a[i]这一步,即将元素进行分类,因为后面是通过a[i]赋值,所以保持原数组的相对有序性,即在同一更高位下,低位是有序的,这样才能完成LSD算法!
高位优先排序(MSD):
MSD与LSD比较起来,拥有更强的普适性,它不需要字符串的长度相同即可对字符串数组进行排序;在生活中的使用也比
LSD更多一些,比如字典里的排序就是MSD的情况,当然还有很多,这里就不再举例了。
MSD的核心思想是分治算法,即将大问题分为小问题来解决,其思想与快速排序类似,具体做法是:先对最高位的字符进行排序,将排序后的字符串进行分组——最高位相同的在一组;在对同一组的进行MSD排序,不过此时以第二位字符进行排序,直到排完最低位,算法结束。(如图3所示)
思想讲起来总是很简单,不过当中的一些细节确实我们需要注意的。一个显而易见的问题是怎么处理结尾字符的问题,因为MSD运行字符的长度不同,那么总会有字符串先结束,这是我们就需要对这些字符串进行处理。如果我们每个字符都去判断显然会很麻烦,因此我们选择一种巧妙的方式使用一个CharAt(string, int)函数来返回字符串对应下标的字符,当对应下标不存在的时候我们返回-1;
/* 转换函数:返回字符串中对于索引的字符* 参数:s:想要进行转换的字符串,i:字符索引* 返回值:对应索引的字符,若超出字符串长度返回-1*/
char CharAt(string s, int i) {if (i < s.length())return s[i];elsereturn -1;
}

这样我们就可以把字符串结尾的情况同其余情况一起处理,同时保证了已结尾的字符串会在未结尾的字符串之前!

完成了这些我们就可以开始编写MSD算法了,具体代码如下:
/* 高位优先排序驱动* 参数:a:进行排序的数组,Aux:用于储存本次排序结果的中间数组,Count:用于储存索引的数组,lo:排序的起始位置,hi:排序终止位置,d:进行排序的字符下标* 返回值:无*/
void MSD(string *a, string *Aux, int *Count,int lo, int hi, int d) {int const M = 3, R = 256;// 小的子数组进行插入排序// 与快速排序相同if (hi <= lo + M) {InsertionSort(a, lo, hi, d);return;}// 初始化索引数组for (int i = 0; i < R + 2; i++)Count[i] = 0;// 统计频数// 特别说明:Count[0]:无用;Count[1]:长度为d的字符串个数;for (int i = lo; i <= hi; i++)Count[CharAt(a[i], d) + 2]++;// 转换频数为索引for (int i = 0; i < R + 1; i++)Count[i + 1] += Count[i];// 数组分类for (int i = lo; i <= hi; i++)Aux[Count[CharAt(a[i], d) + 1]++] = a[i];// 回写for (int i = lo; i <= hi; i++)a[i] = Aux[i - lo];// 递归排序// 特别说明:Count[0]:长度为d的字符串索引的终止坐标for (int i = 0; i < R; i++)MSD(a, Aux, Count, lo + Count[i], lo + Count[i + 1] - 1, d + 1);
}/* 高位优先排序:将目标字符串进行高位优先排序* 参数:a:想要进行高位优先排序的数组,N:数组中元素的个数* 返回值:无*/
void MSD(string *a, int N) {int R = 256;int *Count = new int[R + 2];string *Aux = new string[N];MSD(a, Aux, Count, 0, N - 1, 0);Aux = NULL;delete Aux;Aux = NULL;delete Count;Count = NULL;
}
PS:MSD算法我们分为了两部分来写,是因为我们要多次使用Count以及Aux数组,这样可以避免多次申请内存空间,节省效率;同时这用我们可以便于递归操作的时候又可以将底层与接口分开,更利于抽象!
PPS:可以发现,当子字符串少的时候,我们直接调用插入排序来对字符串数组进行排序,这和快速排序是很类似的,因为MSD以及快速排序在字符串少的时候并没有什么优势,我们可以直接使用现成的排序,如插入排序来进行排序。

C++实现:

接下来是整个LSD以及MSD实现的代码,同时包含了插入排序的代码:
#ifndef STRINGSORT_H
#define STRINGSORT_H#include <iostream>
#include <string>
using namespace std;/* 交换函数:交换两个字符串* 参数:s1:进行交换的字符串1,s2:进行交换的字符串2* 返回值:无*/
void exch(string &s1, string &s2) {string tmp = s1;s1 = s2;s2 = tmp;
}/* 小于函数:判断前d位相等的字符串的大小* 参数:s1:进行比较的字符串1,s2:进行比较的字符串2,d:不同字符开始出现的索引* 返回值:若s1<s2返回true,否则返回false*/
bool Less(string s1, string s2, int d) {return s1.substr(d).compare(s2.substr(d)) < 0;
}/* 转换函数:返回字符串中对于索引的字符* 参数:s:想要进行转换的字符串,i:字符索引* 返回值:对应索引的字符,若超出字符串长度返回-1*/
char CharAt(string s, int i) {if (i < s.length())return s[i];elsereturn -1;
}/* 插入排序:将前d位相同的字符串进行插入排序* 参数:a:想要进行插入排序的字符串数组,lo:排序的开始位置,hi:排序的终止位置,d:不同字符出现的索引* 返回值:无*/
void InsertionSort(string *a, int lo, int hi, int d) {for (int i = lo; i <= hi; i++)for (int j = i; j > lo && Less(a[j], a[j - 1], d); j--)exch(a[j], a[j - 1]);
}/* 低位优先排序:将字符串进行低位优先排序(试用情况: 字符串的长度相同)* 参数:a:参与排序的字符串数组,N:字符串中元素的个数* 返回值:无*/
void LSD(string *a, int N) {int W = a[0].length(), R = 256;string *Aux = new string[N];int *Count = new int[R + 1];for (int k = W - 1; k >= 0; k--) {// 初始化索引数组for (int i = 0; i < R + 1; i++)Count[i] = 0;// 计算位置k上字符频数for (int i = 0; i < N; i++)Count[a[i][k] + 1]++;// 将频数转换为索引for (int i = 0; i < R; i++)Count[i + 1] += Count[i];// 将元素分类for (int i = 0; i < N; i++)Aux[Count[a[i][k]]++] = a[i];// 回写for (int i = 0; i < N; i++)a[i] = Aux[i];}Aux = NULL;delete Aux;Aux = NULL;delete Count;Count = NULL;
}/* 高位优先排序驱动* 参数:a:进行排序的数组,Aux:用于储存本次排序结果的中间数组,Count:用于储存索引的数组,lo:排序的起始位置,hi:排序终止位置,d:进行排序的字符下标* 返回值:无*/
void MSD(string *a, string *Aux, int *Count,int lo, int hi, int d) {int const M = 3, R = 256;// 小的子数组进行插入排序// 与快速排序相同if (hi <= lo + M) {InsertionSort(a, lo, hi, d);return;}// 初始化索引数组for (int i = 0; i < R + 2; i++)Count[i] = 0;// 统计频数// 特别说明:Count[0]:无用;Count[1]:长度为d的字符串个数;for (int i = lo; i <= hi; i++)Count[CharAt(a[i], d) + 2]++;// 转换频数为索引for (int i = 0; i < R + 1; i++)Count[i + 1] += Count[i];// 数组分类for (int i = lo; i <= hi; i++)Aux[Count[CharAt(a[i], d) + 1]++] = a[i];// 回写for (int i = lo; i <= hi; i++)a[i] = Aux[i - lo];// 递归排序// 特别说明:Count[0]:长度为d的字符串索引的终止坐标for (int i = 0; i < R; i++)MSD(a, Aux, Count, lo + Count[i], lo + Count[i + 1] - 1, d + 1);
}/* 高位优先排序:将目标字符串进行高位优先排序* 参数:a:想要进行高位优先排序的数组,N:数组中元素的个数* 返回值:无*/
void MSD(string *a, int N) {int R = 256;int *Count = new int[R + 2];string *Aux = new string[N];MSD(a, Aux, Count, 0, N - 1, 0);Aux = NULL;delete Aux;Aux = NULL;delete Count;Count = NULL;
}/* 三向快速排序驱动* 参数:a:进行排序的数组,lo:排序的起始位置,hi:排序的终止位置,d:进行排序的字符下标* 返回值:无*/
void Quick3Sort(string *a, int lo, int hi, int d) {// 排序终止条件if (hi <= lo)return;// lt:小子串的结束位置,gt:大子串的开始位置// v:枢纽元int lt = lo, gt = hi;int v = CharAt(a[lo], d);int i = lo + 1;// 以枢纽元为界进行区分while (i <= gt) {int t = CharAt(a[i], d);if (t < v)exch(a[lt++], a[i++]);else if (t > v)exch(a[i], a[gt--]);elsei++;}// 对小子串进行排序Quick3Sort(a, lo, lt - 1, d);// 对当前字符串进行进一步排序if (v >= 0)Quick3Sort(a, lt, gt, d + 1);// 对大子串进行排序Quick3Sort(a, gt + 1, hi, d);
}/* 三向快速排序:对目标数组进行三向快速排序* 参数:a:进行排序的字符串数组,N:字符串的个数* 返回值:无*/
void Quick3Sort(string *a, int N) {Quick3Sort(a, 0, N - 1, 0);
}#endif // !STRINGSORT_H
那么LSD以及MSD排序就讲完了,下次有空的时候我再来补上一个三向快速排序,这也是一个很好的字符串排序算法!
转载请注明出处哦~~
参考文献:《算法 —— 第四版》

字符串低位优先排序(LSD)和高位优先排序(MSD)原理及C++实现相关推荐

  1. 算法-22-字符串的排序算法(四种排序)

    目录 1.字符串 1.1.属性 1.2.字母表 2.字符串排序方法 3.键索引计数法 3.1.第一步:频率统计 3.2.第二步:将频率转换为索引 3.3.第三步:数据分类排序 3.4.第四步:回写排序 ...

  2. 各种排序笔记---基于非比较排序部分

    在计算机科学中,排序是一门基础的算法技术,许多算法都要以此作为基础,不同的排序算法有着不同的时间开销和空间开销.排序算法有非常多种,如我们最常用的快速排序和堆排序等算法,这些算法需要对序列中的数据进行 ...

  3. 字节顺序:高位优先(big-endian)和低位优先(little-endian)

    字节顺序:高位优先(big-endian)和低位优先(little-endian) 网络字节序: MSB 高字节前存法 Most Significant Bit   (Big Edian) 主机字节序 ...

  4. 排序算法10——图解基数排序(次位优先法LSD和主位优先法MSD)

    排序算法1--图解冒泡排序及其实现(三种方法,基于模板及函数指针) 排序算法2--图解简单选择排序及其实现 排序算法3--图解直接插入排序以及折半(二分)插入排序及其实现 排序算法4--图解希尔排序及 ...

  5. mysqlorderby数字字符串排序_Python中的元组排序和深度比较

    比较Python中的东西.这听起来几乎是不需要教的,但是我发现Python的比较运算符经常被Python新手误解和低估. 我们来回顾一下Python的比较运算符如何处理不同类型的对象,然后看看如何使用 ...

  6. boost::sort模块实现使用字符串键和索引函子对结构进行排序的示例

    boost::sort模块实现使用字符串键和索引函子对结构进行排序的示例 实现功能 C++实现代码 实现功能 boost::sort模块实现使用字符串键和索引函子对结构进行排序的示例 C++实现代码 ...

  7. C语言编程>第二十三周 ③ 下列给定程序中,函数fun的功能是:利用插入排序法对字符串中的字符按从小到大的顺序进行排序。插入法的基本算法是:先对字符串中的头两个元素进行排序;然后把第三字符插入

    例题:下列给定程序中,函数fun的功能是:利用插入排序法对字符串中的字符按从小到大的顺序进行排序.插入法的基本算法是:先对字符串中的头两个元素进行排序:然后把第三字符插入到前两个字符中,插入后前三个字 ...

  8. 算符优先系列之(二)算符优先关系表

    算符优先系列之(二)算符优先关系表 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Problem Description 学 ...

  9. ①编写一个程序,从键盘接收一个字符串,然后按照字符顺序从小到大进行排序,并删除重复的字符。②集合A、B的差集③对分行输入的若干字符串按字典序(由小到大)进行排序并输出。

    文章目录 ①编写一个程序,从键盘接收一个字符串,然后按照字符顺序从小到大进行排序,并删除重复的字符 ②集合A.B的差集 ③对分行输入的若干字符串按字典序(由小到大)进行排序并输出. ①编写一个程序,从 ...

最新文章

  1. win7,windowsXP安装mysql-5.1.49-win32,中文版、英文版,通吃
  2. PTA 基础编程题目集 7-16 求符合给定条件的整数集 C语言
  3. vc中常用文件操作(二) Ini文件操作
  4. SSH port forwarding: bind: Cannot assign requested address
  5. 宇瞻U盘出现无法格式化 写保护的完美解决办法 厂家提供的
  6. 本地存储localStorage用法详解
  7. spring boot mybatis 整合_MyBatis学习:MyBatis和Spring整合
  8. 关于.NET微服务最热门的问题解答
  9. Storm精华问答 | 遇到这些错误日志该如何解决?
  10. 【报告分享】2020新中国人才报告:共赢人力资本新生态-哈佛商业评论.pdf(附下载链接)...
  11. asp网上书店的代码_使用Helm将ASP.NET Core应用程序部署到Kubernetes容器集群
  12. Bettertouchtool for Mac(鼠标增强软件)
  13. windows下python调用C/C++以及使用C扩展python python通过pythonnet调用C# dll
  14. 远程服务器mstsc命令,远程桌面连接命令mstsc怎么用
  15. 谷歌火狐等浏览器Flash安装失败,安装后进入网站仍提示未安装Flash
  16. 在网易咔哒上面制作SCRATCH小程序
  17. 简单聊聊为什么说外包不好?
  18. 什么可以代替pencil?pencil代替品推荐
  19. 我讨厌计算机作文500,我讨厌谎言作文500字(精选5篇)
  20. win10在BIOS开启Intel VT-x

热门文章

  1. 大学生综测评分计算管理系统
  2. 网页 变黑白网页(灰色)
  3. 卡尔曼滤波理论小释之卡尔曼增益
  4. 一张图解析FastAdmin中的FormBuilder表单生成器
  5. 图像基本处理——图像阈值和平滑
  6. @Component和@ComponentScan
  7. 阿里大人都在读的10本Java实战书籍,Java开发进阶必备书单
  8. BUUCTF·[WUSTCTF2020]大数计算·WP
  9. php1.6t发动机机油选择,1.6t用什么机油选择啊!?
  10. MySql 中 varchar 和varchar 的区别