存档

‘算法讨论’ 分类的存档

布隆过滤器 (Bloom Filter)

2010年10月5日 小卢 4 条评论

最近看到网上的一些算法总提到Bloom Filter,很多说明都是搬了一大堆的公式或者是一堆的英文,感觉看起来甚是费解。
Bloom Filter其实就是位图的一个扩展而已,位图会产生重复的冲突,Bloom Filter使用了多个hash函数,共同置位,这样就减少了冲突的误判率。布隆过滤器是由巴顿.布隆于一九七零年提出的。

详细讲一下 这个 思想:
假如我们有一亿个URL需要存储,我们先建立一个八亿的二进制位图,然后将这八亿个二进制全部清零。对于每个URL,我们用不同的K个(我们选择4个)随机函数产生器(不同的hash函数),产生4个值(h1,h2,h3,h4)。然后再把这四个值通过一个函数映射到0-八亿个数之间的某四个数,这样就可以,直接设置四个位图为1,如下图:


插入成功了的话,那么查找就很简单了。直接用插入过程的运算,判断h1,h2,h3,h4,对应的位是否为1,如果全部为1即是命中!但是,我们可以想到,Bloom Filter有一定的误判率,即该数据不存在,但是,如果hash后的函数数据对应值全部为1的话,那么既是伪命中!这个概率比较小,解决办法既是再重新做一个类似白名单的缓存。

通过上面的理解,很简单可以明白,该算法对数据的插入和查找是比较简单,但是,如果动态数据怎么办呢?也就是说,假如我想删除怎么办呢?很遗憾,Bloom Filter不支持删除操作。不过Bloom Filter改进以后是可以删除的。比如:Counting Bloom Filter,既是把位图中的位,更新为一个技术器,每次映射以后,不是简单的置1,而是+1,然后对应的删除时-1。
具体的应用就是 过滤了,比如过滤IP、URL、Email等。

分类: 算法讨论 标签:

由“中位数”想起的—-分治 or 计数排序?

2010年8月28日 小卢 15 条评论

前天在网上看到一个题目,说的是求中位数,但是这个中位数,不是我们所讨论的那样简单。看题目如下:

在32位机器中,int占4字节,存在文件中的4T个数中找出第2T大的数,内存只有2G。请列出解法。

这个题目,其中有个问题有些复杂,不过肯定是有解法的。其中,4T个数,每个数占用4个字节,那么就应该是16TB的内存才能存的下,这样算来,是不能在内存里面处理全部数据了,其实如果真的想不到好的办法,可以用最朴素(也可以说是最笨)的方法,对文件进行外排(归并),就可以求出来了。

看到这个问题,我首先想的到的是用分治法。分治是肯定可以解决这个问题的。

解法1 分治法

依次读取数据,按每一个数据的最高位分别写到文件(bit_32_1、bit_32_0)里面去,并同时记录最高位为1的个数,这样就可以找到第2T个数是在bit_32_1 or bit_32_0中,依次递归下去,就能找到第2T个数是多少。

我们考虑到,数据比较大,而且是放到文件中的,如果用这种方法,就多次(<=32次)读取文件,时间复杂度应该是O(32*N)。有没有更好的办法呢?答案肯定是有的。我们在群里讨论了这个问题。提出了一个基于计数的思想。

解法2 计数分治

既然全部的数据在内存中存放不下,那么我就考虑存放两个计数桶,分别求出第2T个数的高16位和低16位,这样就不把问题解决了吗。而且使用内存较小。64K x 8 x 2 = 1MBytes下面是我写的一个伪代码,可能有些疏忽的地方,但是思想肯定是对的啦!呵呵

long long High[1<<16];
long long Low[1<<16];
long long x = 0 , i = 0;
int  Number = 0 ;
while(x<= 4T)
{
     data = read();
     High[x>>16]++;//记录高16位–yqshare
     Low[x&0xFFFF]++;  //记录低16位–yqshare
}
x = 0 ;
while(x>=2T)
{
     x += High[i++];//累计大小–yqshare
}
Number = (i–)<<16 ;
x = 0 ;
i = 0 ;
while(x>=2T)
{
     x += Low[i++];
}
Number |= (i–)&0xFFFF;//添加上低16位–yqshare

上面是错误的解法,Harvey指出来以后,我思考了下,发现自己图略了一个问题。然后修改了一下算法。

long long High[1<<16] = {0} ;
long long Low[1<<16]= {0} ;
long long x = 0 , i = 0 ,HighCount;
int  Number = 0 ;
int HighNumber , LowNumber;;
while(x<= 4T)
{
   data = read();
   High[data>>16]++;//记录高16位–yqshare
   x++;
}
x = 0 ;
i = 0;
while(x>=2T)
{
   x += High[i++];//累计大小–yqshare
}
HighNumber = (–i)<<16 ;
HighCount = i ;
while(HighCount>=High[i])
{
   i++ ;//计算出小于2T数的个数–yqshare
}
//这里可以用堆的思想,也可以用桶的思想。
x = 0 ;
while(x<= 4T)
{
   data = read();
   if(data&0XFFFF0000 == HighNumber)
      Low[data&0XFFFF]++;//记录高16位为HighNumber的数的低16位–yqshare
   x++;
}
x = i ;
while(x>=2T)
{
   x += Low[i++]; //累计大小–yqshare
}
LowNumber = (–i)&0xFFFF;//添加上低16位–yqshare
Number = HighNumber | LowNumber ;

这样就可以求出该数了。

分类: 算法讨论, 编程 标签:

rabin-karp算法以及POJ-1200-Crazy Search

2010年8月13日 小卢 6 条评论

今天抽时间看了下字符串的模式匹配,觉得算法导论上讲的还是蛮好的,rabin-karp算法,有限状态机算法,以及KMP算法,呵呵。想深入研究下rabin-karp算法,算法导论上面用了那么多的公式说明这个问题,其实这个算法就是有点使用hash的思想了。把模式字符串进行一个预处理,并mod,主字符串进行逐个进行简单的hash映射,然后mod比较…虽然最差是O(m(n-m+1)),一般情况下是O(m)次。

比如:子串“421″和源串”4234212456″

首先把423对某个质数取模,比如7,把模值和421对7取模的值进行对比。如果相同,则再用朴素算法逐字符对比,如果不同,把423变成234.可以通过(423-400)*10+4=234计算。其实对于任何位数的xyz(具体xyz是几位,要看子串的长度)都可以划分成(xyz-x*100)*10+m

其中m表示父串的下一位。这里的100是以子串长度为3来作比较的. 其中如果换成字符的话应该就是26了。

另外还在百度的时候就看到了POJ上一道题,大概也是这个思想啦。反正又是hash,现在感觉hash真是有用,要速度的地方到处都是它,呵呵。

花了一个多小时写了这道题,唉!有个很弱智的地方耗费了好大一会时间。

#include <stdio.h>
#include <stdlib.h>
#define  M  20000000
int hash[M];
char str[16000001];
int word_map[256],hash_add=0;
int n=0,nc=0;

int strhash(char * key)
{
	int i,h=0;
	for (i=0;i<n;i++)
	{
		h = h*nc + word_map[*(key + i)]; //just like the rabin-karp.
	}
	return h%M;
}
int main()
{
	int i=0,sizeNC=0,count=0,j;
	scanf("%d %d",&n,&nc);
	sizeNC = nc;
	scanf("%s",str);

	//init the map,we can not use the acsiic code
	while (sizeNC)
	{
		if (!word_map[str[i]])
		{
			word_map[str[i]] = sizeNC;
			sizeNC--;
		}
		i++;
	}

	// use the bash to count the words
	i=0;
//这里曾一度写成str[i+nc-1],害的我调了10几分钟,低级错误!
	while (str[i+n-1]!='\0')
	{
		hash_add = strhash(str+i);
		if (!hash[hash_add])
		{
			hash[hash_add]++;
			count++;
		}
		i++;
	}
	printf("%d\n",count);
}
分类: 算法讨论, 编程 标签:

poj2503 Babelfish–hash解决实现快速匹配

2010年8月8日 小卢 1 条评论
http://acm.pku.edu.cn/JudgeOnline/problem?id=2503
其实这个题目,使用qsort+二分查找应该是比较简单可以实现的,使用trie应该也可以,但是不晓得速度怎么样,另外不晓得有没有人直接用STL中的map做了没有,呵呵,那个一定很水。
我使用hash来做的目的是想熟悉一下指针以及hash的操作。这个题目我想到了使用ELFHash,并且使用拉链法处理冲突问题。
但是在POJ上运行的时间并不是很理想,后来我查看了一下,才晓得。为了节省空间,每个单词,每次去malloc空间,其实这样不太好,每次都去浪费时间去malloc,既然题目中明显说出仅有10000条数据,且每个单词不超过10个字节,那么完全可以用数组固定大小,或者用malloc一次分配完成,这样不会每次都浪费时间。不过,我还是觉得不浪费空间适应性更强,更适合工程上的项目。
所以就不再改了,代码贴出来讨论,呵呵
另外,其中,在调试的时候,一直WA,调试好久,在我电脑上运行结果正常,而到POJ上确实WA,郁闷至极的时候突然发现:查不到单词输出 “eh” 而我输出的是”en”,真是悲剧,en 和 eh差别那么小,呵呵。低级错误!
Babelfish
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 15393 Accepted: 6765
Description
You have just moved from Waterloo to a big city. The people here speak an incomprehensible dialect of a foreign language. Fortunately, you have a dictionary to help you understand them.
Input
Input consists of up to 100,000 dictionary entries, followed by a blank line, followed by a message of up to 100,000 words. Each dictionary entry is a line containing an English word, followed by a space and a foreign language word. No foreign word appears more than once in the dictionary. The message is a sequence of words in the foreign language, one word on each line. Each word in the input is a sequence of at most 10 lowercase letters.
Output
Output is the message translated to English, one word per line. Foreign words not in the dictionary should be translated as “eh”.
Sample Input
dog ogday
cat atcay
pig igpay
froot ootfray
loops oopslay
atcay
ittenkay
oopslay
Sample Output
cat
eh
loops

/*
author:yqshare
*/
#include<stdio.h>
#include<string.h>
#include <stdlib.h>
#define M 30000
struct word_entry{
char * word;
char * foreign_word;
struct word_entry * next;
};
typedef struct word_entry WORD_ENTRY;
ELFHash(char * str)
{
unsigned int hash = 0;
unsigned int x    = 0;
while (*str)
{
hash = (hash << 4) + (*str++);
if ((x = hash & 0xF0000000L) != 0)
{
hash ^= (x >> 24);
hash &= ~x;
}
}
return (hash & 0x7FFFFFFF);
}
WORD_ENTRY  * alloc_entry(char * word , char * foreign)
{
WORD_ENTRY  * word_entry =NULL;
word_entry = (WORD_ENTRY *)malloc(sizeof(WORD_ENTRY));
word_entry->foreign_word = ( char * )malloc(strlen(foreign)+1);
strcpy(word_entry->foreign_word,foreign);
word_entry->word = ( char * )malloc(strlen(word)+1);
strcpy(word_entry->word,word);
word_entry->next = NULL;
return word_entry ;
}
int main()
{
char word[11],foreign_word[11],buff[50];
int hash_size=0,i;
WORD_ENTRY  ** hash_base = (WORD_ENTRY *)calloc(M,sizeof(WORD_ENTRY *));
WORD_ENTRY  * hash_tmp=NULL, * word_tmp = NULL;
memset(word,0,10);
memset(foreign_word,0,10);
while(gets(buff) && strcmp (buff,"")!= 0)
{
      for (i=0;buff[i]!=' ';i++)
word[i]=buff[i];
word[i++]='\0';
strcpy(foreign_word,buff+i);
hash_size = ELFHash(foreign_word)%M;
hash_tmp = *(hash_base+hash_size);
word_tmp = hash_tmp;
hash_tmp = alloc_entry(word,foreign_word);
hash_tmp->next = word_tmp;
       *(hash_base+hash_size) = hash_tmp;
       memset(word,0,10);
memset(foreign_word,0,10);
       memset(buff,0,50);
}
   while(gets(foreign_word)&& strcmp (foreign_word,"")!= 0)
{
hash_size = ELFHash(foreign_word)%M;
hash_tmp = *(hash_base+hash_size);
while(hash_tmp)
{
if(strcmp(hash_tmp->foreign_word,foreign_word)==0)
break;
hash_tmp = hash_tmp->next;
}
if(hash_tmp != NULL)
printf("%s\n",hash_tmp->word);
else
printf("eh\n");
}
}
分类: 算法讨论, 编程 标签:

由腾讯笔试题想到的–ELFHash分析

2010年8月3日 小卢 4 条评论
在一个cache系统中,需要实现一个域名白名单,域名为下列数据:
www.qq.com、www.baidu.com、sohu.com 等
该白名单需要在程序启动时加载一次,主要执行查询操作。请设计一个数据结构和相应的初始化查询函数,使得检索尽可能的快。(不能使用stl::map,等等key-value刑类库)。
我们可以看到,该题目提出了字符串的快速查找,并且只加载一次。使用Hash比较好。
我们可能首先就是想到使用 C++ 中的 MAP ,题目中给出了不允许使用MAP,那么肯定第二选择就是使用Berkeley DB (DB)这种的文件数据库了,但是题目中明显提出不允许使用key-value类型库。
我们思考Berkeley DB (DB)的原理可以晓得,这个就是一个Hash的过程,map其实也是hash的思想。
自己设计一个hash系统咯。冲突处理…
字符串hash可能就想到使用ELFhash算法,主要分析下ELFHash算法。
ELFhash函数在UNIX系统V 版本4中的“可执行链接格式”( Executable and Linking Format,即ELF )中会用到,ELF文件格式用于存储可执行文件与目标文件。ELFhash函数是对字符串的散列。它对于长字符串和短字符串都很有效,字符串中每个字符都有同样的作用,它巧妙地对字符的ASCII编码值进行计算,ELFhash函数对于能够比较均匀地把字符串分布在散列表中。
这些函数使用位运算使得每一个字符都对最后的函数值产生影响。

// ELF Hash Function

unsigned int ELFHash(char *str)

{

 unsigned int hash = 0;

 unsigned int x = 0;

 

 while (*str)

 {

 hash = (hash << 4) + (*str++);//hash左移4位,当前字符ASCII存入hash低四位。 

if ((x = hash & 0xF0000000L) != 0)

{//如果最高的四位不为0,则说明字符多余7个,如果不处理,再加第九个字符时,第一个字符会被移出,因此要有如下处理。

//该处理,如果对于字符串(a-z 或者A-Z)就会仅仅影响5-8位,否则会影响5-31位,因为C语言使用的算数移位

hash ^= (x >> 24);

//清空28-31位。

hash &= ~x;

}

}

//返回一个符号位为0的数,即丢弃最高位,以免函数外产生影响。(我们可以考虑,如果只有字符,符号位不可能为负)

return (hash & 0×7FFFFFFF);

}

 


 

分类: Linux专区, 算法讨论 标签:

有关“数独”游戏…

2010年7月13日 小卢 6 条评论

很有幸,通过了华为编程大赛的初赛,进入决赛。
决赛时间是7.9号 8:00–15:00,在重庆大学。这次大赛的题目,是关于sudoku的,关于判局、求解、生成游戏。

本次比赛的题目是数独游戏相关的,我就不说了吧,百度一下,网上一大把!

在整整一天的比赛时间里,我花了一个上午的时间写一个MFC的界面,好痛苦。比赛环境,连个MSDN都没得,而且,最近这一年都一直在linux下做,很久没有MFC了。不过还好的是,把界面给做出来了。
中午和下午想了两个算法。

1)判局,也就是要判断用户,填入的数据是不是合法的,首先要判断每一行、每一列、每个九宫格,是不是仅有1-9的全部数字组成。判断这个我使用了位图算法,即,在一个int中的底9位用来匹配行中出现的数字,如果该行是由1,2,3,4,5,6,7,8,9,组成,那么该int的第九位便全部为1,否则会有为0的位,因此该bitmap肯定小于511,这样通过一次遍历就实现了一个匹配。用同样的方法遍历列,以及九宫格。只有个O(N)就可以解决这个问题了,呵呵

2)求解
这个方法应该的比较多的,一般都是DFS来求解。

1.按下标blank由小到大的顺序逐个获取空白位置;
2.对空白位置blank试图填数,填数的规律为从l-9依次由小到大选择(有序);
3.若找到一个可以填人的数据(调用check(blank),检查当前字符所在的行、列、九宫格是否满足规定,若不满足,即找不到合适数据填人该位置),将数据填人sudoku[blank],并将blank入栈push;否则转5.;
4.获取下一个空白位置,若有空白位置则转2.,否则转6.;
5.进行回溯,即pop得到上一次填数的位置sp,重新对该位置试图填数,为了递推的高效性,此时可以仅从当前sp存放的数字大l开始试图填数,转(3);
6.填数结束。

注:当然还有很多其他的方法,这个是应该是比较想的,呵呵

3)生成游戏
我当时想到的就是用“挖洞”。即生成一个数独全局,然后随机挖去一部分数据即产生有解的数独布局。
用的就是编程之美的上的思想:产生一个不会重复的“九宫格”,然后对其他的九宫格,用行变换的思想去填充。这个比较简单。一下就把代码写出来了…

最后,感觉华为的这个编程大赛还可以,但是有些不健全。比如:可以带书进去,但是又没有集体通知,有的同学带了、有的同学没有带,这样就有些不公平,还有就是,写算法就不要让大家做MFC吗,做MFC就不能算是真正意义上的编程大赛咯!我就是浪费了很多时间到MFC上,估计其他同学也和我差不多,呵呵
这次回来,我突然有买一本《算法导论》回来看的冲动了,虽然自己平常也很注重学习算法,但是,总感觉知识储备不够…

分类: 算法讨论, 编程 标签:

判断单链表是否存在环,判断两个链表是否相交-的相关讨论

2010年3月21日 小卢 4 条评论

有一个单链表,其中可能有一个环,也就是某个节点的next指向的是链表中在它之前的节点,这样在链表的尾部形成一环。
问题:
1、如何判断一个链表是不是这类链表?
2、如果链表为存在环,如果找到环的入口点?

思考:

一、判断链表是否存在环,我们可以用循环实现(如算法二提到的),但是那样效率较低为:

设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)程序如下:

bool IsExitsLoop(slist *head)
{
slist *slow = head, *fast = head;
while ( fast && fast->next )
{
slow = slow->next;
fast = fast->next->next;
if ( slow == fast ) break;
}
return !(fast == NULL || fast->next == NULL);
}

二、找到环的入口点

(1)我们首先想到一个简单的方法,算法复杂度为O(N^2)的双循环的方法,如下

LinkedList* IsCyclicLinkedList(LinkedList *pHead)
{
if(pHead==NULL || pHead->pNext == NULL) return NULL; //返回空说明无环
LinkedList *pCur;
LinkedList *pStart;
pCur = pStart = pHead->pNext;  //头指针不用,指向头结点
while(pCur != NULL)
{
for(pStart = pCur;pStart != NULL;)  //加上控制条件
{
if(pStart->pNext == pCur)
return pCur; //返回环的起始位置
pStart = pStart->pNext;
}
pCur = pCur->pNext;
}
return pStart;
}

2.我们还是延续上面的算法,把这个计算出来:

当fast若与slow相遇时,slow肯定没有走遍历完链表,而fast已经在环内循环了n圈(n>=1)。假设slow走了s步,则fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr
s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。
a + x = nr
a + x = (n – 1)r +r = (n-1)r + L – a
a = (n-1)r + (L – a – x)

(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。程序描述如下:

(思考了一下,觉得胡满超分析的没有表达很明白,蓝色标示,如果存在环,相遇点和入口点必定两条路S1和S2,如果我们认定为S1=x的话,那么S2=L-a-x,这样就更严谨了,鉴于下面有位朋友不清楚,我添加了一个简单的示意图。呵呵)

示意图:

slist* FindLoopPort(slist *head)
{
slist *slow = head, *fast = head;
while ( fast && fast->next )
{
slow = slow->next;
fast = fast->next->next;
if ( slow == fast ) break;
}
if (fast == NULL || fast->next == NULL)
return NULL;
slow = head;
while (slow != fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}

扩展问题:

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。

本文参考了

http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html

http://www.rugesy.cn/it/u20090628_20_F90E2614-A675-4811-8120-79CD31B1E92B.html(该博客下面讨论的回复,很多不是楼主的意思)

【算法的魅力】给定一个长度为N的整数数组(元素有正有负),求所有元素之和,最大的一个子数组。

2010年3月20日 小卢 3 条评论

给定一整数序列A1, A2,… An (可能有负数),求A1~An的一个子序列Ai~Aj,使得Ai到Aj的和最大。
例如: 整数序列-2, 11, -12, 13, -5, 2, -5, -3, 12, -9的最大子序列的和为14。
算法1:动态规划O(n^2)
分别计算以A1, A2,… An开头子序列的和,计算过程中利用动态规划简化计算。
令Sum(i, j)是A[i] … A[j]的和,则
j>=i,Sum(i, i)=A[i]
Sum(i, j+1) = Sum(i, j) + A[j+1]。

int max_sub(int a[],int size)
{
int i,j,v,max=a[0];
for(i=0;i<size;i++)//分别以A1, A2,... An开头
{
v=0;//以Ai开头子序列累加和
for(j=i;j<size;j++)
{
v=v+a[j];//Sum(i, j+1) = Sum(i, j) + A[j+1]
if(v>max)
max=v;
}
}
return max;
}

算法2:进一步的优化O(n)

1 当Sum(i, j)<0时,Sum(i, j),Sum(i, j+1),Sum(i, j+2)…Sum(i, n)都不可能是最大值。
2 由1可知,最优子序列不可能以负数开头。
3 若Sum(i, j),Sum(i, j+1),Sum(i, j+2)…Sum(i, n)都不为负数,则以A[i+1]…A[n]开头的子序列和都不可能最大。
因而,算法变为:从左到右的一次扫描,不断累加,并更新最大值,当累加A[j]时值为负,则累加值清零,开始从A[j+1]累加。

int max_sub2(int a[], int size)
{
int i,max=0,temp_sum=0;
for(i=0;i<size;i++)
{
temp_sum+=a[i];
if(temp_sum>max)
max=temp_sum;
else if(temp_sum<0)
temp_sum=0;
}
return max;
}
分类: 算法讨论, 编程 标签: ,

Load time improved by PHP Speedy Load time improved by PHP Speedy