(算法课大报告)大数据的查找与排序

    前段时间写的,把老师留的作业写得详细了些,现在把它贴上来,有错误欢迎指正,有需要改进的地方也欢迎提出!

1. 题目要求

1. 数据: sentencesFile.txt是英文语句集合文件。句子之间有字符‘\n’分割,sentencesFile.txt文件大小<=1GB, 其中最短句子长度为6个字符,最长句子长度超过1000Kbyte。

计算环境:机器内存为2GB,2个CPU。

要求:设计对于随机输入的句子X判断sentencesFile.txt内是否存在相同句子的算法。

2. 数据: sentencesFile.txt是英文语句集合文件。句子之间有字符‘\n’分割,sentencesFile.txt文件大小<=10GB, 其中最短句子长度为6个字符,最长句子长度超过1000Kbyte。

计算环境:机器内存为2GB,2个CPU。

要求:设计对于随机输入的句子X判断sentencesFile.txt内是否存在相同句子的算法。

2. 简易分析

    当文件大小为1GB的时候,这些数据可以轻易放入内存,在内存中进行处理即可。而当文件大小为10GB的时候,这些数据无法一次放入内存。但是尽管当文件大小为1GB的时候可以将它们整个放入内存,普通的内部排序方法(比如快速排序、堆排序、冒泡排序等)虽然可以实现排序的功能,但是在如此大的数据面前,效率是很低下的。所以可以选择桶作为存储单位,这样可以减少排序所需做的工作。

3. 算法介绍

3.1 桶排序

    桶排序可以利用函数的映射关系,将大量的数据分配到多个“桶”中,然后在桶内进行排序,这样的话工作量会远远小于单纯使用内部排序方法。一个合适的映射函数可以直接使桶的顺序符合题目要求,这样的话只需要在桶内进行排序即可。

    桶排序的时间主要消耗在这么几个地方:对每个句子计算映射函数,时间复杂度为o(n);对每个桶内的所有数据分别进行排序,时间复杂度有多种选择,在3.2中介绍。

    减少时间复杂度有两种方法:一种是尽量使每个桶中的数据量相差不多,另一种是增大桶的数量。

    需要注意的是,桶内的数据量很不稳定。由于txt文件是英文语句集合字符串,所以”You are”、”What is”、”I am”等出现在首位的频率比较高,而类似”aaaaa”、”qqqqq”出现在首位的频率一定很低(有很大的可能根本不会出现),但是如果对每个”aaaaa”、”aaaab”……都创建一个桶的话,势必会造成空间的浪费,26的3次方为17576,但是26的4次方就已经达到了456 976 。所以需要改进一下桶排序方案。

    所以要先统计句子的前几个字节出现的频率,再将它们进行映射并排序。老师给出的思路是3个,但是我认为如果英文句子符合词法以及语法规则的话,使用4个字节也是完全可以的(当然这只是我自己的想法)。粗略思考一下,4个连续的除aeiou之外的字母在英文单词中是很少见的,这样就可以从28的4次方(在此假设包括了单引号和空格)中减去21的4次方,还可以减去很多无意义的字母排列组合,这样估算下来我认为桶的数量可以控制在10000以内,如果将频率低的关键字合并的话,会进一步减少桶的数量。使用4个字节还可以通过增大桶的数量来减少时间复杂度(虽然代价是空间复杂度),所以我认为还可以选择统计句子前四个字节出现的频率。然后就可以通过在桶间和在桶内的方法对大数据进行排序。

3.2 桶内排序方法的选用

    需要为桶内的排序挑选一个适当的排序方法。我认为使用快速排序比较好。因为句子长度从6个字符到超过1000kb,句子本身就比较无序、随机,所以不适合选用堆排序、归并排序、插入排序、冒泡排序。待排记录的关键字只有前6位在一个明显有限的范围内,而第6位之后的关键字不能保证。而且程序对稳定性没有要求,通过以上的要求,并结合各种排序方法的优缺点,选择对桶内的数据使用快速排序的方法。此排序方法平均时间复杂度为o(nlogn),最好情况为o(nlogn),最坏情况为o(n2),是不稳定的,不适合序列局部或整体有序的情况,所以我认为在本程序的情况下快速排序是最好的选择。

3.3 使用索引

    可以对比较大的文件建立一个索引表,以加快查找速度。数据库中就使用到了索引,可以用来对数据库表中一列或多列的值进行排序,大大提升了数据库性能。

    无论主文件是否按关键字有序,索引表中的索引项总是按关键字顺序排列,这时可以使用折半查找法在索引中检索。需要注意的是,对于数据量比较大的文件,需要视情况而定使用多级索引。

    对于英文句子,可以分段建立索引。比如一级索引为前三个字节,而二级索引为第三到第十个字节。

    假设使用了三级索引。可以这样设计,比如一级索引可以依照前三个字节划分:

编号

index

0

0

1

597

2

1124

3

1806

……

其中某个编号(假设为编号0)又对应着如下的二级索引。因为字符串长度不等,所以此索引会对应好几种情况,比如遇到句子结尾或者直接读取磁盘文件等:

编号

index

数目

标记

0

0

1

1

1

143

3

3

2

266

2

2

3

379

0

1

……

其中数目为下一级索引(或磁盘文件中的剩余句子)中的数目,而标记各自有着不同的含义。0  表示句子结束;

1  4个字节,下一级索引;

2  8个字节,下一级索引;

3  读取磁盘文件。

而最后的一级索引便可以这样设计:

编号

index

指向的文件行

0

0

41299

1

1

1078

2

2

91107

3

3

63702

……

这样可以保证每次访问磁盘的次数为1。

4. 详细设计

4.1 思考题一

大概流程如下(图就不贴了,直接写文字):

开始->统计句子前三个字节出现的频率->映射到按顺序存放的各个桶->合并数据量较少的桶->划分为文件->分别在每个文件中排序->结束

伪码:

	while(!EOF) {
	line = readline(file);
	hash(line);
	}
	if(the number in some barrels are little)
	merge(barrels);
	for every barrel {
	saveToFile(file);
	quicksort(file);
	}

    首先统计句子前三个字节出现的频率。需要设计N 个按顺序排放的桶,这些桶依照前三个字节进行划分。需要注意的是在英文句子中不是任意三个连起来的字符都是有意义的(比如”aaa”,”bbb”这样就是无意义的,绝大多数情况之下也不可能出现),所以桶的数量应该不会太过庞大,并且后面还会说到,对于字节出现频率低的几个桶,完全可以把它们合并起来。

    具体做法就是遍历这些英文句子,然后对它们进行映射并且把它们按桶的顺序分割成小文件。映射之后前三个字节的顺序就已经排好了。比如遍历之后可能是这样:

编号

所代表的字节

数量

索引

0

“aba”

215

0

1

“abb”

310

215

……

15

“att”

871

1846

16

“atu”

109

2717

……

    但是关键字出现的频率很不稳定。因为句首常用词出现的频率肯定远大于生僻单词出现的频率,而且对于桶排序,在各桶中数据量差不多的情况下,效率可以得到提高。所以可以将几个数据量较少的桶(可以根据数量和索引决定)合并起来,然后再进行下面的步骤。

    比如说以下这种情况:

编号

所代表的字节

数量

索引

……

40

“cab”

93

17829

41

“cac”

159

17922

42

“cad”

54

18081

……

    由表格可以看出,40-42这三个桶内数据量比较少,所以完全可以合并到一起,它们的索引就是“cab”的索引,即为17829。

    最后可以得出类似这样的一个表格,而且也依照顺序划分为了一些小文件。其中每个编号代表一个文件,文件的内容就是以这个编号所代表的字节开头的语句集合:

编号

所代表的字节

数量

索引

0

“aba”、”abb”、”abc”

679

0

1

“abe”、”abo”、”abr”、”aca”

801

679

2

“ace”

586

1480

3

“ach”、”ack”

732

2066

4

……

    可以看出,经过合并之后,每个桶的数据量比较近似,可以进行接下来的步骤。

现在已经划分出了多个小文件,这时就可以对这些小文件分别进行排序了,在内存允许的情况下完全可以使用多线程来加快速度,因为它们互相都是独立的。上面也介绍到,对于每个桶中的数据,可以使用快速排序的方法。每个桶中的数据排好序之后,整个英文语句集合也就排好了序。比如说,可能是以下的情况:

桶0中:

A bottle of……

A boy is……

A little girl……

……

桶1中:

Abolish this……

Abolished……

……

……

    可以看到每个桶中的英文句子都是有序的,桶也是有序的,这样就算排好了顺序。如果目的只是判断某个句子在文件中是否存在,那么排好序的小文件就没有必要再写入一个大的结果文件了,可以省去一次写数据的工作。总体来说,本方法需要读两次全部的数据,写一次全部的数据。

    使用这种方法的话,如果需要查找某一个句子,可以先根据这个句子的前三个字节查找这个句子在哪个桶,然后到对应的桶中再去查找,这样可以节省很多时间。

4.2 思考题二

    当句子文件为10GB的时候,上面的方法就变的不是很合适了,开销很大。而另一个需要注意的问题是,读写磁盘与读写内存比起来,所花的时间肯定更多,所以查找时需要尽量减少读写磁盘的次数以提高程序的效率。可以把10GB大小的文件分割成好几部分(比如说分割成10份1GB大小的文件),每部分文件分别排序(使用上一题中的方法)。虽然这样可以排序成功,但是查找的时候就比较费力气。这时可以使用索引,即把位置等信息存入内存,而数据信息留在文件中,查找的时候可以先读内存,从索引中查找然后再到磁盘上查找文件中的内容即可。

    由于排序过程与思考题一比较类似,这里不再赘述。由于此题中句子文件比思考题一大了10倍,所以比思考题一多了索引的内容。

索引的设计参照3.3。

假设有一个句子:

He is responsible for the administration of justice. 

按照3.3所设计的索引,大概面貌应该是下图这个样子:

《(算法课大报告)大数据的查找与排序》

    而在索引中添加了如3.3中所述的标记后,可以保证每次访问磁盘的次数为1。

5. 总结

    对于大数据来说,最重要的是算法的选用,一个合适的算法可以省去大量的时间。本题最重要的一个方法就是“分而治之”。

    在完成这个大报告的过程中,我也了解到了针对不同的操作大数据的需求,可以有不同的方法,还了解了一些以前未曾了解过的概念。比如败者树、trie树、倒排索引、MapReduce等。大数据在当今时代变得越来越重要,以后在这方面还是需要多加了解。

    原文作者:排序算法
    原文地址: https://blog.csdn.net/agul_/article/details/9170841
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞