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

 更新时间:2025年07月21日 10:38:50   作者:Katie。  
在字符串算法领域,最小表示法是一种用于求取一个环形字符串中字典序最小的排列位置的经典问题,本项目将基于Java完整实现Booth算法,帮助读者深入理解算法原理,学会在工程中高效地对循环字符串进行字典序比较,并掌握相关代码优化和边界处理技巧,需要的朋友可以参考下

一、项目背景详细介绍

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

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

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

二、项目需求详细介绍

功能性需求

提供静态方法:

public static int booth(String s)

输入:非空字符串 s

输出:返回最小表示的起始下标 k,使得 s[k..]+s[0..k-1] 在所有旋转中最小。

性能需求

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

鲁棒性需求

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

质量需求

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

三、相关技术详细介绍

字符串拼接与索引映射

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

双指针策略

  • 使用 ij 两个起始候选下标,以及一个偏移量 k 表示当前比较位置,循环推进。

跳跃优化

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

时间与空间分析

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

四、实现思路详细介绍

参数检查

  • s == nulls.length() == 0,抛出 IllegalArgumentException

构造双倍字符串

  • String ss = s + s;char[] ss = new char[2*N] 并填充;

初始化指针

int i = 0, j = 1, k = 0, N = s.length();

循环比较

while (i < N && j < N && k < N) {
    char a = ss[i + k];
    char b = ss[j + k];
    if (a == b) {
        k++;
    } else if (a > b) {
        // i 的表示比 j 大,i 跳过这段
        i = i + k + 1;
        if (i == j) i++;
        k = 0;
    } else {
        // j 的表示比 i 大,j 跳过
        j = j + k + 1;
        if (i == j) j++;
        k = 0;
    }
}
return Math.min(i, j);
  • 每次失配时,将落后者起点向前跳过已比较过的区间,然后重置 k
  • 循环结束后,min(i, j) 即为最小旋转起点。

返回结果

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

五、完整实现代码

// 文件:src/main/java/com/example/string/Booth.java
package com.example.string;
 
/**
 * Booth 算法:在线性时间内求取循环字符串的字典序最小旋转起始下标
 */
public class Booth {
 
    /**
     * 求字符串 s 的最小旋转起始下标
     * @param s 非空字符串
     * @return 起始下标 k,使得从 k 开始的旋转最小
     * @throws IllegalArgumentException 当 s 为 null 或长度为 0 时
     */
    public static int booth(String s) {
        if (s == null || s.length() == 0) {
            throw new IllegalArgumentException("输入字符串不能为空");
        }
        int n = s.length();
        // 构造双倍字符数组,避免 substring 操作
        char[] ss = new char[2 * n];
        for (int i = 0; i < 2 * n; i++) {
            ss[i] = s.charAt(i % n);
        }
 
        int i = 0, j = 1, k = 0;
        while (i < n && j < n && k < n) {
            char a = ss[i + k];
            char b = ss[j + k];
            if (a == b) {
                k++;
            } else if (a > b) {
                // i 对应旋转较大,跳过
                i = i + k + 1;
                if (i == j) {
                    i++;
                }
                k = 0;
            } else {
                // j 对应旋转较大,跳过
                j = j + k + 1;
                if (i == j) {
                    j++;
                }
                k = 0;
            }
        }
        return Math.min(i, j);
    }
 
    /**
     * 获取最小旋转后的字符串形式
     * @param s 原始字符串
     * @return 最小字典序旋转字符串
     */
    public static String minimalRotation(String s) {
        int k = booth(s);
        return s.substring(k) + s.substring(0, k);
    }
 
    public static void main(String[] args) {
        String s = "bbaaccaadd";
        int k = booth(s);
        System.out.printf("最小旋转起始下标:%d,最小旋转:%s%n", k, minimalRotation(s));
    }
}
// 文件:src/test/java/com/example/string/BoothTest.java
package com.example.string;
 
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
 
/**
 * 单元测试:验证 Booth 算法功能
 */
public class BoothTest {
 
    @Test
    public void testSimple() {
        assertEquals(0, Booth.booth("abc"));
        assertEquals("abc", Booth.minimalRotation("abc"));
    }
 
    @Test
    public void testRotate() {
        String s = "cba";
        // 旋转后最小为 "acb",起点为 2
        assertEquals(2, Booth.booth(s));
        assertEquals("acb", Booth.minimalRotation(s));
    }
 
    @Test
    public void testRepeatPattern() {
        String s = "bbaaccaadd";
        // 所有旋转:"bbaaccaadd","baaccaaddb",... 最小为 "aaccaaddbb"
        assertEquals("aaccaaddbb", Booth.minimalRotation(s));
    }
 
    @Test
    public void testAllSame() {
        assertEquals(0, Booth.booth("aaaaa"));
        assertEquals("aaaaa", Booth.minimalRotation("aaaaa"));
    }
 
    @Test
    public void testSingleChar() {
        assertEquals(0, Booth.booth("z"));
        assertEquals("z", Booth.minimalRotation("z"));
    }
 
    @Test
    public void testInvalid() {
        assertThrows(IllegalArgumentException.class, () -> Booth.booth(""));
    }
}

六、代码详细解读

booth 方法

检查输入有效性;

将原串映射到长度为 2N 的字符数组 ss,避免复杂的下标计算;

使用双指针 ij 和偏移量 k,在 while 循环中比较 ss[i+k]ss[j+k]

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

minimalRotation 方法

调用 booth 得到起点 k,通过两次 substring 拼接出最终旋转字符串。

Test 类

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

七、项目详细总结

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

八、项目常见问题及解答

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

问:i、j、k 指针为何能保证 O(N) 复杂度?
答:每次跳跃要么移动 i,要么移动 j,且每次移动至少进步 k+1,整个过程指针总移动量 ≤ 2N。

问:如何支持 Unicode 超出 BMP 的字符?
答:可将 char[] 改为 int[] 存放 codePoint,并相应地按 code point 比较。

问:如何改造为返回所有最小旋转起点?
答:在找到 min(i,j) 后,可继续比较另一个指针是否也等价,或在原串中统计所有相同最小串的起点。

九、扩展方向与性能优化

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

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

相关文章

  • SpringBoot整合JavaMail邮件的两种方式

    SpringBoot整合JavaMail邮件的两种方式

    这篇文章主要介绍了SpringBoot整合JavaMail邮件的两种方式,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-05-05
  • 深入理解MyBatis中的一级缓存与二级缓存

    深入理解MyBatis中的一级缓存与二级缓存

    这篇文章主要给大家深入的介绍了关于MyBatis中一级缓存与二级缓存的相关资料,文中详细介绍MyBatis中一级缓存与二级缓存的工作原理及使用,对大家具有一定的参考性学习价值,需要的朋友们下面来一起看看吧。
    2017-06-06
  • springboot vue测试平台接口定义前后端新增功能实现

    springboot vue测试平台接口定义前后端新增功能实现

    这篇文章主要介绍了springboot vue测试平台接口定义前后端新增功能实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • 如何使用java制作假数据接口

    如何使用java制作假数据接口

    这篇文章主要介绍了如何使用java制作假数据接口,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • MongoDB 整合SpringBoot举例介绍

    MongoDB 整合SpringBoot举例介绍

    这篇文章主要介绍了MongoDB 整合SpringBoot的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-05-05
  • mybatis-plus的添加与修改详解

    mybatis-plus的添加与修改详解

    这篇文章主要介绍了mybatis-plus的添加与修改方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • SpringMVC上传文件的两种方法

    SpringMVC上传文件的两种方法

    这篇文章主要为大家详细介绍了SpringMVC上传文件的两种方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-11-11
  • Spring Security中自定义cors配置及原理解析

    Spring Security中自定义cors配置及原理解析

    在Spring框架中,通过自定义CORS配置可根据实际情况调整URL的协议、主机、端口等,以适应"同源安全策略",配置原理涉及CorsConfigurer和CorsFilter,自定义配置需要注意@Configuration注解、方法名以及可能的@Autowired注解
    2024-10-10
  • springboot 实战:异常与重定向问题

    springboot 实战:异常与重定向问题

    这篇文章主要介绍了springboot实战:异常与重定向问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • MybatisPlusException:Failed to process,Error SQL异常报错的解决办法

    MybatisPlusException:Failed to process,Error SQL异常报错的解决办法

    这篇文章主要给大家介绍了关于MybatisPlusException:Failed to process,Error SQL异常报错的解决办法,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2023-03-03

最新评论