Files
cKit/String/c_RabinKarp.c
T
2026-08-31 22:49:42 +08:00

129 lines
4.6 KiB
C

#include <c_RabinKarp.h>
/**
* @brief 内部静态辅助:为变长文本流计算初始固定视窗的霍纳法则(Horner's rule)哈希指纹
*/
static uint64_t c_RK_ComputeHash(const char* key, c_size_t m, uint64_t R, uint64_t Q) {
uint64_t h = 0;
for (c_size_t i = 0; i < m; i++) {
h = (R * h + (unsigned char)key[i]) % Q;
}
return h;
}
/**
* @brief 就地初始化 Rabin-Karp 模式串匹配器 (前置计算模式指纹与最高位系数)
*/
c_err_t c_RabinKarp_Init(c_RabinKarp_t* self, const char* pattern, c_Allocator_t* allocator) {
if (!self || !pattern) {
return C_ERR_PARAM;
}
self->allocator = allocator ? *allocator : c_DefaultAllocator;
self->m_len = strlen(pattern);
if (self->m_len == 0) {
return C_ERR_PARAM;
}
// 1. 为模式串深拷贝开辟独立空间
self->pattern = (char*)c_Allocator_Alloc(&self->allocator, (self->m_len + 1) * sizeof(char));
if (!self->pattern) {
return C_ERR_NOMEM;
}
memcpy(self->pattern, pattern, self->m_len + 1);
// 2. 配置商用数论高离散参数
self->R_base = 256; // 扩展 ASCII 字符表基数
self->Q_prime = 1000000007; // 经典防大规模冲突大素数 (10^9 + 7)
c_size_t m = self->m_len;
uint64_t R = self->R_base;
uint64_t Q = self->Q_prime;
// 3. 预先推演滚动最高位字符移除时所需要的物理权重乘子:RM = R^(M-1) % Q
self->RM = 1;
for (c_size_t i = 1; i <= m - 1; i++) {
self->RM = (R * self->RM) % Q;
}
// 4. 计算出当前子串模式串的绝对初始数字指纹
self->pattern_hash = c_RK_ComputeHash(self->pattern, m, R, Q);
return C_ERR_OK;
}
/**
* @brief 在主文本 text 中执行 Rabin-Karp 高性能滚动哈希检索(均摊时间复杂度 O(N))
*
* @param text 待扫描检索的主文本大字节流(以 '\0' 截止)
* @param out_index 找到时,通过二级指针物理填充并回传匹配位置在主文本中的【起始插槽下标】
* @return c_err_t 检索成功返回 C_ERR_OK,未找到匹配项返回 C_ERR_NOTFOUND
*/
c_err_t c_RabinKarp_Search(const c_RabinKarp_t* self, const char* text, c_size_t* out_index) {
if (!self || !text || !out_index) {
return C_ERR_PARAM;
}
c_size_t n = strlen(text);
c_size_t m = self->m_len;
if (n < m) return C_ERR_NOTFOUND; // 文本总长短于子串,安全拦截
uint64_t R = self->R_base;
uint64_t Q = self->Q_prime;
uint64_t RM = self->RM;
uint64_t pat_hash = self->pattern_hash;
// 1. 利用霍纳法则瞬间计算出主文本中 [0, M-1] 初始前缀视窗的哈希值
uint64_t txt_hash = c_RK_ComputeHash(text, m, R, Q);
// 2. 拉斯维加斯验证机制:前置哈希完全等价时,启动全貌核验,粉碎极端哈希碰撞
if ((txt_hash == pat_hash) && (strncmp(text, self->pattern, m) == 0)) {
*out_index = 0;
return C_ERR_OK;
}
// 3. 🌟🌟🌟【Rabin 指纹极致滚动滑窗内核】🌟🌟🌟
// 游标 i 指向当前滑窗即将【移出】的最高位字符,滑窗单调向右推移
c_size_t limit = n - m;
for (c_size_t i = 0; i < limit; i++) {
// A. 提取即将移出的高位字符与即将灌入的低位新字符特征
uint64_t char_to_remove = (unsigned char)text[i];
uint64_t char_to_add = (unsigned char)text[i + m];
// B. 滚动变换:
// (txt_hash + Q - (char_to_remove * RM) % Q) -> 🌟加 Q 防无符号减法算术下溢回绕!
// 随后乘 R 整体左移一格,最后加上低位新字符并整体取模,实现 O(1) 常数级哈希滑窗推演!
uint64_t high_remove_part = (char_to_remove * RM) % Q;
txt_hash = (txt_hash + Q - high_remove_part) % Q;
txt_hash = (txt_hash * R + char_to_add) % Q;
// C. 拉斯维加斯联锁判定:计算当前最新滑窗位置处的物理偏移量
c_size_t current_match_offset = i + 1;
if (txt_hash == pat_hash) {
if (strncmp(text + current_match_offset, self->pattern, m) == 0) {
*out_index = current_match_offset; // 精确传出匹配起点物理下标
return C_ERR_OK;
}
}
}
return C_ERR_NOTFOUND;
}
/**
* @brief 反初始化:彻底注销并回收 Rabin-Karp 状态机内存空间
*/
void c_RabinKarp_Destroy(c_RabinKarp_t* self) {
if (self) {
if (self->pattern) {
c_Allocator_Free(&self->allocator, self->pattern);
self->pattern = NULL;
}
self->m_len = 0;
self->pattern_hash = 0;
self->RM = 0;
}
}