一步一步写算法(之字符串查找 下篇)

简介: 原文: 一步一步写算法(之字符串查找 下篇) 【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】     前面我们谈到了KMP算法,但是讲的还不是很详细。
原文: 一步一步写算法(之字符串查找 下篇)

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】


    前面我们谈到了KMP算法,但是讲的还不是很详细。今天我们可以把这个问题讲的稍微详细一点。假设在字符串A中寻找字符串B,其中字符串B的长度为n,字符串A的长度远大于n,在此我们先忽略。

    假设现在开始在字符串A中查找,并且假设双方在第p个字符的时候发现查找出错了,也就是下面的情况:

/*      
*    A: A1 A2 A3 A4 ... Ap ............
*    B: B1 B2 B3 B4 ... Bp ...Bn 
*                       (p)        
*/    

    那么这时候,A有什么选择呢?它可以左移1位,用A2~A(p-1)比较B1~B(p-2),然后再用A(p)~A(n+1)比较B(p-1)~B(n)位;或者左移2位,用A3~A(p-1)比较B1~B(p-3),然后再用A(p)~A(n+2)比较B(p-2)~B(n)位; 依次类推,直到左移(p-2)位,用A(p-1)比较B(1),然后再用A(p)~A(p+n-2)比较B(2)~B(n)位。

    不知道细心的朋友们发现什么规律没?因为A和B在前面(p-1)个数据是相等的,所以上面的计算其实可以这样看:用A2~A(p-1)比较B1~B(p-2),实际上就是B2~B(p-1)比较B1~B(p-2); 用A3~A(p-1)比较B1~B(p-3),实际上就是B3~B(p-1)比较B1~B(p-3);最后直到B(p)和B(1)两者相比较。既然这些数据都是B自身的数据,所以当然我们可以提前把这些结果都算出来的。

    那么这么多的选择,我们应该左移多少位呢?

    其实判断很简单。假设我们左移1位,发现A2~A(p-1)的结果和B1~B(p-2)是一致的,那么两者可以直接从第(p-1)位开始比较了; 如果不成功呢,那么只能左移2位,并判断A2~A(p-1)和B1~B(p-2)的比较结果了,......,这样以此类推进行比较。如果不幸发现所有的数据都不能比较成功呢,那么只能从头再来,从第1位数据依次进行比较了。

    不知道讲清楚了没,还没有明白的朋友可以看看下面的代码:

int calculate_for_special_index(char str[], int index)
{
	int loop;
	int value;
	
	value = 0;
	for(loop = 1; loop < index; loop ++){
		if(!strncmp(&str[loop], str, (index - loop))){
			value = index - loop;
			break;
		}
	}
	
	return (value == 0) ? 1 : (index - value);
}

void calculate_for_max_positon(char str[], int len, int data[])
{
	int index;
	
	for(index = 0; index < len; index++)
		data[index] = calculate_for_special_index(str, index);
}

    当然,上面当然都是为了计算在索引n比较失败的时候,判断此时字符应该向左移动多少位。

char* strstr_kmp(const char* str, char* data)
{
	int index;
	int len;
	int value;
	int* pData;

	if(NULL == str || NULL == str)
		return NULL;

	len = strlen(data);
	pData = (int*)malloc(len * sizeof(int));
	memset(pData, 0, len * sizeof(int));
	calculate_for_max_positon((char*)str, len, pData);

	index = 0;
	while(*str && ((int)strlen(str) >= len)){
		for(; index < len; index ++){
			if(str[index] != data[index])
				break;
		}
		
		if(index == len){
			free(pData);
			return (char*) str;
		}
	
		value = pData[index];
		str += value;

		if(value == 1)
			index = 0;
		else
			index = index -value;
	}
	
	free(pData);
	return NULL;
}

    可能朋友们看到了,上面的strlen又回来了?说明代码本身还有优化的空间。大家可以自己先试一试。

int check_valid_for_kmp(char str[], int start, int len)
{
	int index;

	for(index = start; index < len; index++)
		if('\0' == str[index])
			return 0;
	return 1;
}

char* strstr_kmp(const char* str, char* data)
{
	int index;
	int len;
	int value;
	int* pData;

	if(NULL == str || NULL == str)
		return NULL;

	len = strlen(data);
	pData = (int*)malloc(len * sizeof(int));
	memset(pData, 0, len * sizeof(int));
	calculate_for_max_positon((char*)str, len, pData);

	index = 0;
	while(*str && check_valid_for_kmp((char*)str, index, len)){
		for(; index < len; index ++){
			if(str[index] != data[index])
				break;
		}
		
		if(index == len){
			free(pData);
			return (char*) str;
		}
	
		value = pData[index];
		str += value;

		if(value == 1)
			index = 0;
		else
			index = index -value;
	}
	
	free(pData);
	return NULL;
}

(三)、多核查找

    多核查找其实不新鲜,就是把查找分成多份,不同的查找过程在不同的核上面完成。举例来说,我们现在使用的cpu一般是双核cpu,那么我们可以把待查找的字符分成两份,这样两份查找就可以分别在两个核上面同时进行了。具体怎么做呢,其实不复杂。首先我们要定义一个数据结构:

typedef struct _STRING_PART
{
	char * str;
	int len;
}STRING_PART;
    接着,我们要做的就是把字符串分成两等分,分别运算起始地址和长度。

void set_value_for_string_part(char str[], int len, STRING_PART part[])
{
	char* middle = str + (len >> 1);

	while(' ' != *middle)
		middle --;

	part[0].str = str;
	part[0].len = middle - (str -1);

	part[1].str = middle + 1;
	part[1].len = len - (middle - (str - 1));
}
    分好之后,就可以开始并行运算了。

char* strstr_omp(char str[], char data[])
{
	int index;
	STRING_PART part[2] = {0};
	char* result[2] = {0};
	int len = strlen(str);

	set_value_for_string_part(str, len, part);

#pragma omp parellel for 
    for(index = 0; index < 2; index ++)
		result[index] = strstr(part[index].str, part[index].len, data);

	if(NULL == result[0] && NULL == result[1])
		return NULL;

	return (NULL != result[0]) ? result[0] : result[1];
}
注意事项:

    (1)这里omp宏要在VS2005或者更高的版本上面才能运行,同时需要添加头文件#include<omp.h>,打开openmp的开关;

    (2)这里调用的strstr函数第2个参数是目标字符串的长度,和我们前面介绍的普通查找函数稍微不一样,前面的函数不能直接使用,但稍作改变即可。


目录
相关文章
|
算法
【算法】滑动窗口——找到字符串中所有字母异位词
【算法】滑动窗口——找到字符串中所有字母异位词
211 0
|
算法
两个字符串匹配出最长公共子序列算法
本文介绍了最长公共子序列(LCS)问题的算法实现,通过动态规划方法求解两个字符串的最长公共子序列,并提供了具体的编程实现细节和示例。
315 1
两个字符串匹配出最长公共子序列算法
|
自然语言处理 算法 搜索推荐
字符串相似度算法完全指南:编辑、令牌与序列三类算法的全面解析与深入分析
在自然语言处理领域,人们经常需要比较字符串,这些字符串可能是单词、句子、段落甚至是整个文档。如何快速判断两个单词或句子是否相似,或者相似度是好还是差。这类似于我们使用手机打错一个词,但手机会建议正确的词来修正它,那么这种如何判断字符串相似度呢?本文将详细介绍这个问题。
940 1
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
401 1
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
算法 Java
掌握算法学习之字符串经典用法
文章总结了字符串在算法领域的经典用法,特别是通过双指针法来实现字符串的反转操作,并提供了LeetCode上相关题目的Java代码实现,强调了掌握这些技巧对于提升算法思维的重要性。
|
存储 算法 Cloud Native
C++ bcrypt算法 字符串加密,亲测有效
C++ bcrypt算法 字符串加密,亲测有效
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
603 0
|
存储 算法
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
|
算法
KPM算法求字符串的最小周期证明
公式 `ans = n - LPS[n-1]` 描述了最小周期,其中 `n` 是子串长度,`LPS[n-1]` 是前缀函数值。证明分为特殊情况和一般情况:对于完整周期字符串,`LPS[n-1] = 3*T`,故 `ans = T`;对于非完整周期,通过分析不同长度的 `[末部分]` 和 `[前部分]`,展示 `ans` 始终等于周期 `T` 或由 `[e][b]` 构成的最小周期,从而证明公式正确。
251 3
|
算法 搜索推荐 程序员
第六十五练 字符串匹配 - Rabin-Karp算法
第六十五练 字符串匹配 - Rabin-Karp算法
111 1