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

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

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱: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个参数是目标字符串的长度,和我们前面介绍的普通查找函数稍微不一样,前面的函数不能直接使用,但稍作改变即可。

时间: 2024-12-02 19:43:30

一步一步写算法(之字符串查找 下篇)的相关文章

一步一步写算法(之查找)

原文:一步一步写算法(之查找) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     无论是数据库,还是普通的ERP系统,查找功能数据处理的一个基本功能.数据查找并不复杂,但是如何实现数据又快又好地查找呢?前人在实践中积累的一些方法,值得我们好好学些一下.我们假定查找的数据唯一存在,数组中没有重复的数据存在.     (1) 普通的数据查找     设想有一个1M的数据,我们如何在里面找到我们想要的那个数据.此时数据本身没有特征,所以我

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

原文:一步一步写算法(之字符串查找 中篇) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     昨天我们编写了简单的字符查找函数.虽然比较简单,但是也算能用.然而,经过我们仔细分析研究一下,这么一个简单的函数还是有改进的空间的.在什么地方改进呢?大家可以慢慢往下看.     下面的代码是优化前的代码,现在再贴一次,这样分析起来也方便些: char* strstr(const char* str, char* data) { int i

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

原文:一步一步写算法(之字符串查找 上篇) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     字符串运算是我们开发软件的基本功,其中比较常用的功能有字符串长度的求解.字符串的比较.字符串的拷贝.字符串的upper等等.另外一个经常使用但是却被我们忽视的功能就是字符串的查找.word里面有字符串查找.notepad里面有字符串查找.winxp里面也有系统自带的字符串的查找,所以编写属于自己的字符串查找一方面可以提高自己的自信心,另外一

一步一步写算法(之 算法总结)

原文:一步一步写算法(之 算法总结) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]       自10月初编写算法系列的博客以来,陆陆续续以来写了几十篇.按照计划,还有三个部分的内容没有介绍,主要是(Dijkstra算法.二叉平衡树.红黑树).这部分会在后面的博客补充完整.这里主要是做一个总结,有兴趣的朋友可以好好看看,欢迎大家提出宝贵意见.       (1) 排序算法     快速排序           合并排序     堆排序

一步一步写算法(之 回数)

原文:一步一步写算法(之 回数) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     回数的概念比较好玩,就是说有这么一个字符串str, 长度为n, 现在index开始从0->index/2遍历,那么str[index] = str[n-1-index],那么这种数据就是我们通常说的回数.比如说a = "a"是回数, a = "aba"是回数, a = "strarts"也是回数

一步一步写算法(之 可变参数)

原文:一步一步写算法(之 可变参数) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     可变参数是C语言编程的一个特色.在我们一般编程中,函数的参数个数都是确定的,事先定下来的.然而就有那么一部分函数,它的个数是不确定的,长度也不一定,这中间有什么秘密吗?     其实,我们可以回忆一下哪些函数是可变参数的函数?其实也就是sprintf.printf这样的函数而已.那么这些函数有什么规律吗?关键就是在这个字符串上面.我们可以举一个例

一步一步写算法(之单词统计)

原文:一步一步写算法(之单词统计) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     在面试环节中,有一道题目也是考官们中意的一道题目:如果统计一段由字符和和空格组成的字符串中有多少个单词?     其实,之所以问这个题目,考官的目的就是想了解一下你对状态机了解多少.     (1) 题目分析     从题目上看,如果对一个字符串进行处理,那么可以有下面几种情形:初始状态,字符状态,空格状态,结束状态.那么这几种状态之间应该怎么迁移

一步一步写算法(之二叉树深度遍历)

原文:一步一步写算法(之二叉树深度遍历) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     深度遍历是软件开发中经常遇到的遍历方法.常用的遍历方法主要有下面三种:(1)前序遍历:(2)中序遍历:(3)后序遍历.按照递归的方法,这三种遍历的方法其实都不困难,前序遍历就是根-左-右,中序遍历就是左-根-右,后续遍历就是左-右-根.代码实现起来也不复杂.     1)前序遍历 void preorder_traverse(TREE_NOD

一步一步写算法(之克鲁斯卡尔算法 下)

原文:一步一步写算法(之克鲁斯卡尔算法 下) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com]     前面在讨论克鲁斯卡尔的算法的时候,我们分析了算法的基本过程.基本数据结构和算法中需要解决的三个问题(排序.判断.合并).今天,我们继续完成剩下部分的内容.合并函数中,我们调用了两个基本函数,find_tree_by_index和delete_mini_tree_from_group,下面给出详细的计算过程. MINI_GENERATE_T