设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间01 现在时间01 缓存数据 当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

当你走完一段之后回头看,你会发现,那些真正能被记得的事真的是没有多少,真正无法忘记的人屈指可数,真正有趣的日子不过是那么一些,而真正需要害怕的也是寥寥无几。

查看: 390|回复: 1

Java实现按字典顺序查找的booth算法的完整代码

[复制链接]

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:18
  • 打卡月天数:0
  • 打卡总奖励:300
  • 最近打卡:2024-02-20 09:42:07
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
48
主题
40
精华
0
金钱
448
积分
104
注册时间
2023-8-13
最后登录
2025-8-28

发表于 2025-8-28 00:16:35 | 显示全部楼层 |阅读模式

一、项目背景详细介绍

在字符串算法领域,“最小表示法”是一种用于求取一个环形字符串(或循环右移后形成的新串)中字典序最小的排列位置的经典问题。它在字符串匹配、环形模式检测、基因序列比对、字符串哈希与压缩等场景均有重要应用。

朴素做法需要枚举所有 N 个旋转,分别比较长度为 N 的字符串,时间复杂度 O(N²),对大规模文本不具备实用性。Booth 算法利用双指针与跳跃策略,将求最小表示的时间复杂度降到 O(N),且只需常量级额外空间,极大地提升了性能。

本项目将基于 Java 完整实现 Booth 算法,帮助读者深入理解算法原理,学会在工程中高效地对循环字符串进行字典序比较,并掌握相关代码优化和边界处理技巧。

二、项目需求详细介绍

功能性需求

提供静态方法:

  1. public static int booth(String s)
复制代码

输入:非空字符串

  1. s
复制代码

输出:返回最小表示的起始下标

  1. k
复制代码
,使得
  1. s[k..]+s[0..k-1]
复制代码
在所有旋转中最小。

性能需求

  • 时间复杂度:O(N),其中 N 为字符串长度;
  • 空间复杂度:O(1) 或 O(N)(若需要构造双倍字符串辅助比较)。

鲁棒性需求

  • 支持任意字符(包括 Unicode);
  • 对于全部字符相同的字符串,返回 0;
  • 输入
    1. null
    复制代码
    或空串时抛出
    1. IllegalArgumentException
    复制代码

质量需求

  • 代码注释清晰、逻辑分明;
  • 单元测试覆盖正常场景、边界场景(如全部相同、长度为 1、含重复模式);
  • 使用 Maven 管理,集成 CI 运行测试。

三、相关技术详细介绍

字符串拼接与索引映射

  • 为避免频繁对比环绕下标,可将
    1. s
    复制代码
    与自身拼接成
    1. ss = s + s
    复制代码
    ,长度 2N,比较时只需在
    1. [i, i+N)
    复制代码
    区间内。

双指针策略

  • 使用
    1. i
    复制代码
    1. j
    复制代码
    两个起始候选下标,以及一个偏移量
    1. k
    复制代码
    表示当前比较位置,循环推进。

跳跃优化

  • 当在比较
    1. ss[i+k]
    复制代码
    1. ss[j+k]
    复制代码
    时,一旦不等,可根据大小关系直接把较小起点后面的整个区间跳过,从而避免重复比较。

时间与空间分析

  • 每次跳跃至少进步
    1. k+1
    复制代码
    或跳转一个起点,保证指针移动总和 O(N)。
  • 辅助空间只需存放拼接后的字符数组,或直接通过索引映射访问原串。

四、实现思路详细介绍

参数检查

    1. s == null
    复制代码
    1. s.length() == 0
    复制代码
    ,抛出
    1. IllegalArgumentException
    复制代码

构造双倍字符串

    1. String ss = s + s;
    复制代码
    1. char[] ss = new char[2*N]
    复制代码
    并填充;

初始化指针

  1. int i = 0, j = 1, k = 0, N = s.length();
复制代码

循环比较

  1. while (i < N && j < N && k < N) {
  2. char a = ss[i + k];
  3. char b = ss[j + k];
  4. if (a == b) {
  5. k++;
  6. } else if (a > b) {
  7. // i 的表示比 j 大,i 跳过这段
  8. i = i + k + 1;
  9. if (i == j) i++;
  10. k = 0;
  11. } else {
  12. // j 的表示比 i 大,j 跳过
  13. j = j + k + 1;
  14. if (i == j) j++;
  15. k = 0;
  16. }
  17. }
  18. return Math.min(i, j);
复制代码
  • 每次失配时,将落后者起点向前跳过已比较过的区间,然后重置
    1. k
    复制代码
  • 循环结束后,
    1. min(i, j)
    复制代码
    即为最小旋转起点。

返回结果

  • 直接返回起点下标,可用于通过
    1. s.substring(k) + s.substring(0, k)
    复制代码
    构造最小旋转串。

五、完整实现代码

  1. // 文件:src/main/java/com/example/string/Booth.java
  2. package com.example.string;
  3. /**
  4. * Booth 算法:在线性时间内求取循环字符串的字典序最小旋转起始下标
  5. */
  6. public class Booth {
  7. /**
  8. * 求字符串 s 的最小旋转起始下标
  9. * @param s 非空字符串
  10. * @return 起始下标 k,使得从 k 开始的旋转最小
  11. * @throws IllegalArgumentException 当 s 为 null 或长度为 0 时
  12. */
  13. public static int booth(String s) {
  14. if (s == null || s.length() == 0) {
  15. throw new IllegalArgumentException("输入字符串不能为空");
  16. }
  17. int n = s.length();
  18. // 构造双倍字符数组,避免 substring 操作
  19. char[] ss = new char[2 * n];
  20. for (int i = 0; i < 2 * n; i++) {
  21. ss[i] = s.charAt(i % n);
  22. }
  23. int i = 0, j = 1, k = 0;
  24. while (i < n && j < n && k < n) {
  25. char a = ss[i + k];
  26. char b = ss[j + k];
  27. if (a == b) {
  28. k++;
  29. } else if (a > b) {
  30. // i 对应旋转较大,跳过
  31. i = i + k + 1;
  32. if (i == j) {
  33. i++;
  34. }
  35. k = 0;
  36. } else {
  37. // j 对应旋转较大,跳过
  38. j = j + k + 1;
  39. if (i == j) {
  40. j++;
  41. }
  42. k = 0;
  43. }
  44. }
  45. return Math.min(i, j);
  46. }
  47. /**
  48. * 获取最小旋转后的字符串形式
  49. * @param s 原始字符串
  50. * @return 最小字典序旋转字符串
  51. */
  52. public static String minimalRotation(String s) {
  53. int k = booth(s);
  54. return s.substring(k) + s.substring(0, k);
  55. }
  56. public static void main(String[] args) {
  57. String s = "bbaaccaadd";
  58. int k = booth(s);
  59. System.out.printf("最小旋转起始下标:%d,最小旋转:%s%n", k, minimalRotation(s));
  60. }
  61. }
复制代码
  1. // 文件:src/test/java/com/example/string/BoothTest.java
  2. package com.example.string;
  3. import org.junit.jupiter.api.Test;
  4. import static org.junit.jupiter.api.Assertions.*;
  5. /**
  6. * 单元测试:验证 Booth 算法功能
  7. */
  8. public class BoothTest {
  9. @Test
  10. public void testSimple() {
  11. assertEquals(0, Booth.booth("abc"));
  12. assertEquals("abc", Booth.minimalRotation("abc"));
  13. }
  14. @Test
  15. public void testRotate() {
  16. String s = "cba";
  17. // 旋转后最小为 "acb",起点为 2
  18. assertEquals(2, Booth.booth(s));
  19. assertEquals("acb", Booth.minimalRotation(s));
  20. }
  21. @Test
  22. public void testRepeatPattern() {
  23. String s = "bbaaccaadd";
  24. // 所有旋转:"bbaaccaadd","baaccaaddb",... 最小为 "aaccaaddbb"
  25. assertEquals("aaccaaddbb", Booth.minimalRotation(s));
  26. }
  27. @Test
  28. public void testAllSame() {
  29. assertEquals(0, Booth.booth("aaaaa"));
  30. assertEquals("aaaaa", Booth.minimalRotation("aaaaa"));
  31. }
  32. @Test
  33. public void testSingleChar() {
  34. assertEquals(0, Booth.booth("z"));
  35. assertEquals("z", Booth.minimalRotation("z"));
  36. }
  37. @Test
  38. public void testInvalid() {
  39. assertThrows(IllegalArgumentException.class, () -> Booth.booth(""));
  40. }
  41. }
复制代码

六、代码详细解读

booth 方法

检查输入有效性;

将原串映射到长度为 2N 的字符数组

  1. ss
复制代码
,避免复杂的下标计算;

使用双指针

  1. i
复制代码
  1. j
复制代码
和偏移量
  1. k
复制代码
,在
  1. while
复制代码
循环中比较
  1. ss[i+k]
复制代码
  1. ss[j+k]
复制代码

  • 相等时
    1. k++
    复制代码
    1. ss[i+k] > ss[j+k]
    复制代码
    ,表示从
    1. i
    复制代码
    起的旋转较大,将
    1. i
    复制代码
    跳过已比较部分;
  • 否则跳过
    1. j
    复制代码
  • 每次跳跃后重置
    1. k = 0
    复制代码
    ,并确保
    1. i != j
    复制代码
  • 返回
    1. min(i, j)
    复制代码
    ,即最小旋转起点。

minimalRotation 方法

调用

  1. booth
复制代码
得到起点
  1. k
复制代码
,通过两次
  1. substring
复制代码
拼接出最终旋转字符串。

Test 类

覆盖基本无旋转、单字符、全相同、普通旋转、重复模式、非法输入等场景,确保算法正确与鲁棒。

七、项目详细总结

通过本项目,您深入掌握了 Booth 算法在 Java 中的端到端实现:从理论推导、边界分析,到代码优化与单元测试,全面覆盖常见使用场景。Booth 算法能在线性时间内解决循环字符串的最小字典序问题,对于大规模文本和高性能要求的系统具有重要实用价值。

八、项目常见问题及解答

问:为何要构造双倍字符数组?
答:避免在比较时对环绕下标进行复杂的取模计算,简化逻辑且性能更优。

问:i、j、k 指针为何能保证 O(N) 复杂度?
答:每次跳跃要么移动

  1. i
复制代码
,要么移动
  1. j
复制代码
,且每次移动至少进步
  1. k+1
复制代码
,整个过程指针总移动量 ≤ 2N。

问:如何支持 Unicode 超出 BMP 的字符?
答:可将

  1. char[]
复制代码
改为
  1. int[]
复制代码
存放
  1. codePoint
复制代码
,并相应地按 code point 比较。

问:如何改造为返回所有最小旋转起点?
答:在找到

  1. min(i,j)
复制代码
后,可继续比较另一个指针是否也等价,或在原串中统计所有相同最小串的起点。

九、扩展方向与性能优化

  • 内存优化:不构造双倍数组,比较时通过
    1. (idx % n)
    复制代码
    取模访问原串,同时通过局部变量缓存长度减少重复成员访问。
  • 并行预处理:在超长串中,可分段并行计算局部最小旋转,再在小规模结果中二次比较得全局最小。
  • GPU 加速:对于海量基因序列,可将字符比较并行映射到 GPU 核心,加速大批量最小旋转计算。
  • 泛型支持:改造算法以支持任意可比对象数组的循环最小表示,应用于环形图像或环状数据结构比对。
  • 与最小表达式结合:将 Booth 算法与压缩算法结合,生成循环最小表示再做后续哈希或字典索引,提升全文检索性能。

以上就是Java实现按字典顺序查找的booth算法的完整代码的详细内容,更多关于Java booth算法的资料请关注晓枫资讯其它相关文章!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
25
积分
30
注册时间
2022-12-26
最后登录
2022-12-26

发表于 昨天 20:29 | 显示全部楼层
路过,支持一下
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表