vlambda博客
学习文章列表

Hash算法的概念及应用、如何判断链表中是否有环

Hash算法(Hash Algorithm),简称散列算法,也成哈希算法(英译),是将一个大文件映射成一个小串字符。与指纹一样,就是以较短的信息来保证文件的唯一性的标志,这种标志与文件的每一个字节都相关,而且难以找到逆向规律。


举个列子:


服务器存了10个文本文件,你现在想判断一个新的文本文件和那10个文件有没有一个是一样的。你不可能去比对每个文本里面的每个字节,很有可能,两个文本文件都是5000个字节,但是只有最后一位有所不同,但这样的,你前面4999位的比较就是毫无意义。那一个解决办法,就是在存储那10个文本文件的时候,都将每个文件映射成一个hash字符串。服务器只需要存储10个hash字符串,在判断的时候,只需要判断新的这个文本文件的hash值是否和那10个文件的hash值一致,那就可以解决这个问题了。


由于文件是无限的,而映射后的字符串能表示的位数是有限的。因此可能会存在不同的key对应相同的Hash值。这就存在碰撞的可能


 a、给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 

思路:还是老一套,先Hash映射降低数据规模,然后统计排序。 


具体做法: 

(1)可以估计每个文件安的大小为5G×64=320G,远远大于内存限制的4G。所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。


遍历文件a,对每个url求取hash(url)%1000,然后根据所取得的值将url分别存储到1000个小文件(记为a0,a1,…,a999)中。这样每个小文件的大约为300M。


遍历文件b,采取和a相同的方式将url分别存储到1000小文件(记为b0,b1,…,b999)。这样处理后,所有可能相同的url都在对应的小文件(a0vsb0,a1vsb1,…,a999vsb999)中,不对应的小文件不可能有相同的url。然后我们只要求出1000对小文件中相同的url即可。


求每对小文件中相同的url时,可以把其中一个小文件的url存储到hash_set中。然后遍历另一个小文件的每个url,看其是否在刚才构建的hash_set中,如果是,那么就是共同的url,存到文件里面就可以了


b、现有海量日志数据保存在一个超级大的文件中,该文件无法直接读入内存,要求从中提取某天出访问百度次数最多的那个IP。


从这一天的日志数据中把访问百度的IP取出来,逐个写入到一个大文件中;


注意到IP是32位的,最多有2^32个IP。同样可以采用映射的方法,比如模1000,把整个大文件映射为1000个小文件;


找出每个小文中出现频率最大的IP(可以采用hash_map进行频率统计,然后再找出频率最大的几个)及相应的频率;


在这1000个最大的IP中,找出那个频率最大的IP,即为所求。


参考分治法获取文件中出现频次最高100词


2、如何判断链表中是否有环

首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。


 


例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,指针1移动到了节点B,指针2移动到了C。第二轮循环,指针1移动到了节点C,指针2移动到了节点B。第三轮循环,指针1移动到了节点D,指针2移动到了节点D,此时两指针指向同一节点,判断出链表有环。

//判断是否有环bool isLoop(pNode pHead){ pNode fast = pHead; pNode slow = pHead; //如果无环,则fast先走到终点 //当链表长度为奇数时,fast->Next为空 //当链表长度为偶数时,fast为空 while( fast != NULL && fast->next != NULL) {
fast = fast->next->next; slow = slow->next; //如果有环,则fast会超过slow一圈 if(fast == slow) { break; } }
if(fast == NULL || fast->next == NULL ) return false; else return true;}
//计算环的长度int loopLength(pNode pHead){ if(isLoop(pHead) == false) return 0; pNode fast = pHead; pNode slow = pHead; int length = 0; bool begin = false; bool agian = false; while( fast != NULL && fast->next != NULL) { fast = fast->next->next; slow = slow->next; //超两圈后停止计数,挑出循环 if(fast == slow && agian == true) break; //超一圈后开始计数 if(fast == slow && agian == false) { begin = true; agian = true; }
//计数 if(begin == true) ++length; } return length;}

//求出环的入口点Node* findLoopEntrance(pNode pHead){ pNode fast = pHead; pNode slow = pHead; while( fast != NULL && fast->next != NULL) {
fast = fast->next->next; slow = slow->next; //如果有环,则fast会超过slow一圈 if(fast == slow) { break; } } if(fast == NULL || fast->next == NULL) return NULL; slow = pHead; while(slow != fast) { slow = slow->next; fast = fast->next; }
return slow;}


3、Linux根据端口号查看进程PID

命令lsof,以查找占用端口80为例,用法如下:

[root@localhost nginx]# lsof -i:80

命令netstat,以查找占用80端口为例,用法如下

netstat -nlp|grep :80

命令ps,可以查看已知进程PID的执行目录的详细信息

ps -ef | grep 8246