算法记录:MSD基数排序 + qsort 对字符串排序

本文介绍了一种基于标准模板库的字符串快速排序算法实现。通过定义类型别名简化字符串向量操作,并采用递归方式实现内部排序逻辑。该算法能够有效地对包含多个字符串的列表进行排序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

typedef std::vector<std::string>  t_vstr;
void exch(std::string &str1, std::string &str2)
{
  std::swap(str1, str2);
}

void str_qsort_internal(t_vstr &arr, int l, int r, size_t d)
{
  if(l >= r)return;

  int i = l - 1, j = r;
  int p = i, q = j;
  char v = arr[r][d];
  while(i < j)
  {
    while(arr[++i][d] < v); //i是第一个大于等于v的索引,可能等于r(到r时一定停止循环)
    while(v < arr[--j][d]) {if(j == l) break; } //j是第一个小于等于v的,当j == l时循环也会结束,例如所有元素都比arr[r]大
    
    if(i > j) break;
    exch(arr[i], arr[j]); //交换,要么是arr[i] >= v && arr[j] <= v 之后 arr[i] <= v && arr[j] >= v
    if(arr[i][d] == v) exch(arr[i], arr[++p]);//arr[p]一定小于等于v,
    if(arr[j][d] == v) exch(arr[j], arr[--q]); //arr[q]一定大于等于v
  }
  
  if(p == q)//如果p == q则证明l -> r区间全相等
  {
    if(v != '/0')str_qsort_internal(arr, l, r, d + 1);//如果不为字符串结尾,则转换到下一层,如果v为'/0'则证明排序完毕
    return;
  }
  
  if(arr[i][d] < v)i++;
/*
下面两个移动算法类似下图
    p  
4,4,4,1,2,3
k         j

3,4,4,1,2,4
  k     j

3,2,4,1,4,4
    k j
3,2,1,4,4,4
    j 4

      p
4,4,4,4,2
k       j
2,4,4,4,4
  k   j
2,4,4,4,4
    kj
2,4,4,4,4
  j   k

*/
  for(int k = l; k <= p; ++k, --j) exch(arr[k], arr[j]); //l -> j区间元素都是小于等于v的,而l -> p都是等于v的,
  for(int k = r; k >= q; --k, ++i) exch(arr[k], arr[i]);  //i->r区间元素都是大于等于v的,而i->r都是等于v的

  str_qsort_internal(arr, l, j, d);//因为i -> j区间内不一定都是相同的,但一定都小于等于v
  if((i == r) && (arr[i][d] == v)) i++;//这里,如果i == r && arr[i][d] == v,则证明从j + 1到 r都是相同的元素,所以i+=1照应下一步的 i - 1 和str_qsort_internal(arr, i, r, d);因为i + 1之后,i 就一定大于r了
  
  if(v != '/0') str_qsort_internal(arr, j + 1, i - 1, d+1);//如果v == 0,那么j+1 -> i-1区间内的所有字符串都为结尾,所以无需递归排序了

  str_qsort_internal(arr, i, r, d);//同理,因为i -> 区间内不应定都是相同的,但一定都大于等于v, 因此搜索层次还是d
}

void print_vstr(const t_vstr &vstr)
{
  for(size_t i = 0; i < vstr.size(); ++i)
  {

    printf("%s/n", vstr[i].c_str());
  }

}

int main()
{  
  t_vstr vstr;
  
  vstr.push_back("9");
  vstr.push_back("123");
  vstr.push_back("232");
  vstr.push_back("234");
  vstr.push_back("456");
  vstr.push_back("789");
  vstr.push_back("259");
  vstr.push_back("257");


  str_qsort_internal(vstr, 0, vstr.size() - 1, 0);
  print_vstr(vstr);
  
  
  



  printf("done/n");
  cin.get();

  return 0;
};

 
<think>我们正在分析MSD基数排序算法。根据用户要求,我们需要解释原理、时间复杂度和实现方式。 引用[1]提到基数排序有两种:LSD(从低位开始)和MSD(从高位开始)。用户特别关注MSD。 原理:MSD基数排序是从最高有效位开始,按照该位上的数字或字符对元素进行分组(分桶),然后递归地对每个非空桶进行下一有效位的排序,直到处理完所有位或每个桶只有一个元素为止。 时间复杂度:引用[1]提到基数排序的时间复杂度为O(N*最大位数)。对于MSD来说,由于每次分组后需要递归处理每个非空桶,最坏情况(当所有元素都相同)下,每次递归都要处理整个序列,那么时间复杂度为O(N*最大位数)。但实际中,由于分组后每个桶的大小会减小,平均情况较好。不过,最坏情况仍然是O(N*最大位数)。 空间复杂度:O(N+基数大小),因为需要额外的桶来存放分组。基数大小就是每一位的可能取值个数(例如十进制数字就是10,字母就是26)。 实现方式: 1. 确定最大位数(或字符串的最大长度)。 2. 从最高位开始,对当前位进行分桶(例如数字0-9,字母a-z等)。 3. 对每个非空桶(桶内元素个数大于1且还有下一位需要处理)递归进行MSD排序(处理下一位)。 4. 合并桶中的元素(按桶的顺序和桶内元素的顺序)。 注意:对于长度不足最大位数的元素,我们需要在低位补0(数字)或空字符(字符串),以保证所有元素都有相同的位数。但实际实现中,我们可以在比较时处理,例如当某一位不存在时视为比存在的小(或大,根据约定)。 下面给出一个MSD基数排序的伪代码(以字符串排序为例,按字典序,从高位到低位): ``` function MSD_radix_sort(arr, lo, hi, d): # arr:排序数组,lo: 当前子数组起始索引,hi: 当前子数组结束索引,d: 当前处理的位数(从0开始,即最高位) if hi <= lo or d >= 最大位数: return # 创建桶(例如,256个ASCII字符,或者根据字符集大小) 创建count数组,大小为基数大小+2(+2是为了方便计数,其中count[0]不用,count[1]到count[基数大小]对应每个字符,count[基数大小+1]用于表示字符串已经结束(即长度不足d)?实际上我们通常用count[0]来存放长度不足d的字符串,但具体实现可以调整) # 第一步:计数 for i from lo to hi: ch = 获取arr[i]的第d个字符 if arr[i]的长度 <= d: # 该字符串长度不足,我们将其归入一个特殊的桶(例如0号桶) count[0]++ else: count[ch+1]++ # 注意:这里+1是为了下一步计算索引位置 # 第二步:将计数转换为索引位置(即每个桶的起始位置) 计算count数组的前缀和,这样count[k]就表示第k-1个桶的结束位置(或者另一种方式:count[k]表示第k个桶的起始位置) # 第三步:将元素分配到临时数组(根据当前字符分桶) 创建临时数组temp,大小为(hi-lo+1) for i from lo to hi: ch = 获取arr[i]的第d个字符 if arr[i]的长度 <= d: index = count[0]的起始位置(通常为0) count[0]++ # 注意这里要更新,以便下一个相同桶的元素放在下一个位置 temp[index] = arr[i] else: index = count[ch+1]的起始位置 count[ch+1]++ temp[index] = arr[i] # 第四步:将临时数组拷贝回原数组的[lo, hi]部分 for i from 0 to len(temp)-1: arr[lo+i] = temp[i] # 第五步:递归排序每个桶(除了长度不足的桶,其他每个字符桶都需要递归) # 注意:现在arr的[lo, hi]部分已经按当前位分桶排好了,接下来对每个非空桶递归 初始化start = lo for each bucket in 桶的顺序(从0号桶(长度不足的桶)开始,然后到其他字符桶): if 桶的大小大于0: # 当前桶的范围是[start, start+桶大小-1] MSD_radix_sort(arr, start, start+桶大小-1, d+1) start += 桶大小 ``` 注意:上述伪代码中,计数排序部分采用了经典的计数排序步骤,但要注意桶的索引处理。对于字符串排序,我们通常使用ASCII值,基数大小可以是256(扩展ASCII)或128(标准ASCII)等。 性能分析:MSD基数排序在最坏情况下时间复杂度为O(N*最大位数),空间复杂度为O(N+基数大小)(递归栈空间未计,如果递归深度大,则栈空间也是O(最大位数))。另外,MSD基数排序是稳定排序吗?在分桶时,如果使用稳定的计数排序,那么整个排序就是稳定的。但是,由于递归处理,相同键值的元素在递归过程中可能会改变相对顺序?实际上,在每一轮分桶时,我们使用计数排序(稳定),所以相同键值的元素会保持原有的相对顺序。因此,MSD基数排序可以是稳定的。 然而,MSD基数排序的一个缺点是,当某些桶只有一个元素时,我们不需要再递归,但当桶很小时,递归的开销可能较大。另外,对于大量短字符串MSD可能不如LSD高效,因为MSD需要递归处理。 现在,我们根据要求生成相关问题。 §§相关问题§§ 1. MSD基数排序和LSD基数排序的主要区别是什么? 2. MSD基数排序在什么情况下比LSD基数排序更高效? 3. 如何优化MSD基数排序以减少递归开销? 4. MSD基数排序是否稳定?为什么?</think>### MSD基数排序算法分析 #### **算法原理** MSD(Most Significant Digit first)基数排序是一种**从高位向低位**逐位排序的非比较型算法,其核心原理如下: 1. **高位优先分组** 从最高位(如数字的百位、字符串的首字母)开始,根据该位的值将元素分配到对应的桶(bucket)中。例如,十进制数字按百位分配时,百位为0、1、2...9的元素分别进入10个桶。 2. **递归排序子桶** 对每个非空桶**递归执行MSD排序**,处理下一位(如十位)。当桶内元素数量≤1或所有位已处理完时终止递归。 3. **合并结果** 按桶的顺序(0→9或a→z)合并所有桶的元素,形成有序序列。 **关键特点**: - **分治策略**:高位分组后,子桶的排序互不影响,可并行处理。 - **稳定性**:在分桶和合并过程中保持相同键值元素的相对顺序(需稳定分桶算法)[^1]。 #### **时间复杂度** - **最优情况**:$O(N \cdot k)$ 其中 $N$ 为元素数量,$k$ 为最大位数。当元素均匀分布时,递归深度小,效率高。 - **最坏情况**:$O(N \cdot k)$ 所有元素拥有相同高位键值,退化到单个桶的递归,但每层仍需扫描所有位。 - **空间复杂度**:$O(N + r)$ $r$ 为基数大小(如十进制 $r=10$),用于存储桶和递归栈[^1][^2]。 > **对比LSD**: > - LSD从低位排序,需固定遍历所有位;MSD可提前终止(如桶大小=1时),实际更快。 > - MSD字符串排序中优势显著(短字符串无需处理低位)[^2]。 #### **实现方式(伪代码)** ```python def msd_radix_sort(arr, d=0): if len(arr) <= 1: return arr # 1. 初始化桶(按基数大小) buckets = [[] for _ in range(RADIX_SIZE)] # 如RADIX_SIZE=10(数字)或256(ASCII) # 2. 按当前位d分桶 for elem in arr: key = get_digit(elem, d) # 获取元素第d位的键值 if key is None: # 处理位数不足的情况(如数字位数不足) key = 0 # 缺位视为0(或最小字符) buckets[key].append(elem) # 3. 递归排序非空桶(下一位d+1) result = [] for bucket in buckets: if bucket: # 仅当桶非空且未处理完所有位时递归 if len(bucket) > 1 and d < MAX_DIGITS - 1: result.extend(msd_radix_sort(bucket, d+1)) else: result.extend(bucket) return result ``` **关键函数说明**: - `get_digit(elem, d)`:提取元素第 `d` 位的值(数字可用 `(elem // 10^d) % 10`)。 - `MAX_DIGITS`:预计算的最大位数(如数字为 `max(len(str(x)) for x in arr)`)。 #### **性能优化建议** 1. **桶结构优化** 使用链表避免数据搬移,或动态数组减少内存分配。 2. **小桶截断** 当桶大小低于阈值(如100)时,改用插入排序减少递归开销。 3. **并行处理** 不同桶的递归可并行执行(多线程/分布式)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值