add bitmap

This commit is contained in:
nonstriater
2015-12-05 00:55:44 +08:00
parent 168d7ca6d1
commit 0f309ff798
10 changed files with 614 additions and 335 deletions
+68
View File
@@ -0,0 +1,68 @@
### 数学是基础
线性代数(矩阵,向量)
数值数学(数值分析,线性规划,凸优化理论,常见数值优化算法)
概率论和统计学
实分析和泛函的基础
### 机器学习
从大量数据中找到规律和知识,然后用规律和知识做决策。
分类器
Naive Bayes
Linear Discriminant Analysis
Logistic Regression
Linear SVM
Kernel SVM
Adaboost
Decision
Neural network
非监督模型
数据处理与可视化:PCA,LDA,MDS
聚类算法
稀疏编码
基本不会去实现这些基础算法,都有现成的开源工具
概率图模型(Probabilistic graphical model)
两个核心的机器学习模型:Latent Dirichlet Allocation(LDA) Probabilistic Matrix Factorization(PMF)
统计计算(Statistical computing)
深度学习(Deep learning)
优化(optimization)
PAC学习理论(PAC Learning)
非参数贝叶斯统计(Non-parametric Bayesian statistics)
参考链接:http://www.zhihu.com/question/21714701
分类
聚类
模式识别
机器学习偏向数学问题推导,数据挖掘就是抽特征
不要沉迷于数学公式推导,理解如何运用数据
SVM
贝叶斯
学好python
### 数据挖掘
在知乎的描述中。`数据挖掘`是指从大量的数据中自动搜索隐藏于其中的有着特殊关系性的信息和知识的过程。
+90 -88
View File
@@ -125,6 +125,96 @@ n个节点的完全二叉树,其查找,删除的复杂度都是O(logN),但
## B树
平衡查找树,一种多路查找树。
能保证数据插入和删除情况下,任然保持执行效率。
一个M阶的B树满足:
1. 每个节点最多M个子节点
2. 除跟节点和叶节点外,其它每个节点至少有M/2个孩子
3. 根节点至少2个节点
4. 所有叶节点在同一层,叶节点不包含任何关键字信息
5. 有k个关键字的页节点包含k+1个孩子
也就是说:
根节点到每个叶节点的路径长度都是相同的。
## B+树
mysql索引使用B+树的数据结构
## B*树
## R树
## Treap 树
## 赫夫曼编码 Huffman
这是一个经典的压缩算法。通过`字符出现的频率`,`优先级`,`二叉树`进行的压缩算法。
对一个字符串,计算每个字符出现的次数,把这些字符放到优先队列(priority queue)
这这个priority queue转出二叉树
需要一个字符编码表来解码,通过二叉树建立huffman编码和解码的字典表
原始串:
二级制编码:
huffman编码:
###存储结构和基本操作
```
struct node{
char *huffCode; // 叶子节点的huff编码
int weight;
struct node *left,right;
}
```
###构建赫夫曼树
原则:出现频率越多的会在越上层,编码也越短,出现频率越少的在越下层,编码也越长。
不存在某一个编码是另一个编码的前缀,字符都在叶节点上,所以不会存在一个编码是另一个编码的前缀
二叉树每个节点要么是叶子节点,要么是双分支节点(且左分支编码为0,右分支编码为1)
###压缩
1. 扫描输入文件,统计各个字符出现的次数,对结构排序 (hash统计每个字符出现的次数)
2. 根据排序结构,构建赫夫曼树 (贪心策略,每次选频率值最低的2个节点合并,需要优先队列帮组(priority queue,又叫最小堆))
3. 对树进行遍历(左分支编码为0,右分支编码为1),得到各个字符的huffman编码,存到hash表中(这个就是编解码表,也可直接存储到节点中,如上面的char *huffCode)
4. 重新对文件扫描,根据hash表进行压缩
压缩的文件为了能够解压缩,需要一个文件头,用来重建赫夫曼树,包括:
被编码的文本长度 unsigned int size
字符频率表 unsigned char freqs[NUM_CHARS]
###解压缩
1. 读取文件头
2. 遍历编码后的bits,从赫夫曼树的根节点出发,遇到0,进入左子树,遇到1进入右子树,直到叶节点
## 字典树trie(前缀树,单词查找树)
@@ -209,94 +299,6 @@ double-array trie 参考作者的这篇论文 http://linux.thai.net/~thep/datri
## B树
平衡查找树,一种多路查找树。
能保证数据插入和删除情况下,任然保持执行效率。
一个M阶的B树满足:
1. 每个节点最多M个子节点
2. 除跟节点和叶节点外,其它每个节点至少有M/2个孩子
3. 根节点至少2个节点
4. 所有叶节点在同一层,叶节点不包含任何关键字信息
5. 有k个关键字的页节点包含k+1个孩子
也就是说:
根节点到每个叶节点的路径长度都是相同的。
## B+树
mysql索引使用B+树的数据结构
## B*树
## R树
## Treap 树
## 赫夫曼编码 Huffman
这是一个经典的压缩算法。通过`字符出现的频率`,`优先级`,`二叉树`进行的压缩算法。
对一个字符串,计算每个字符出现的次数,把这些字符放到优先队列(priority queue)
这这个priority queue转出二叉树
需要一个字符编码表来解码,通过二叉树建立huffman编码和解码的字典表
原始串:
二级制编码:
huffman编码:
###存储结构和基本操作
```
struct node{
char *huffCode; // 叶子节点的huff编码
int weight;
struct node *left,right;
}
```
###构建赫夫曼树
原则:出现频率越多的会在越上层,编码也越短,出现频率越少的在越下层,编码也越长。
不存在某一个编码是另一个编码的前缀,字符都在叶节点上,所以不会存在一个编码是另一个编码的前缀
二叉树每个节点要么是叶子节点,要么是双分支节点(且左分支编码为0,右分支编码为1)
###压缩
1. 扫描输入文件,统计各个字符出现的次数,对结构排序 (hash统计每个字符出现的次数)
2. 根据排序结构,构建赫夫曼树 (贪心策略,每次选频率值最低的2个节点合并,需要优先队列帮组(priority queue,又叫最小堆))
3. 对树进行遍历(左分支编码为0,右分支编码为1),得到各个字符的huffman编码,存到hash表中(这个就是编解码表,也可直接存储到节点中,如上面的char *huffCode)
4. 重新对文件扫描,根据hash表进行压缩
压缩的文件为了能够解压缩,需要一个文件头,用来重建赫夫曼树,包括:
被编码的文本长度 unsigned int size
字符频率表 unsigned char freqs[NUM_CHARS]
###解压缩
1. 读取文件头
2. 遍历编码后的bits,从赫夫曼树的根节点出发,遇到0,进入左子树,遇到1进入右子树,直到叶节点
## 后缀树(suffix tree)
+16
View File
@@ -21,6 +21,22 @@ for (int i=0;i<s.length();i++){
}
```
还有,比如下面的:
```
Hash hashCode(char *key){
int offset = 5;
Hash hashCode = 0;
while(*key){
hashCode = (hashCode << offset) + *key++;
}
return hashCode;
}
```
使用时 `hashCode(key) & (size-1) ` 就可以得到一个 `size-1` 范围内的hash值
当然,还有其他的散列函数,如`平方取中法`, `随机数法`等。
### 碰撞解决
+120 -7
View File
@@ -1,6 +1,11 @@
## 海量数据处理
所谓海量数据,就是数据量太大,要么在短时间内无法计算出结果,要么数据太大,无法一次性装入内存。
针对时间,我们可以使用巧妙的算法搭配合适的数据结构,如bitmap/堆/trie树等
针对空间,就一个办法,大而化小,分而治之。常采用hash映射
海量处理问题常用的分析解决问题的思路是:
@@ -12,24 +17,132 @@
* 分布处理之 Hadoop/Mapreduce
### 分而治之/Hash映射 + hash统计/trie树/红黑树/二叉搜索树 + 堆排序/快速排序/归并排序
### 估算
在处理海量问题之前,我们往往要先估算下数据量,能否一次性载入内存?如果不能,应该用什么方式拆分成小块以后映射进内存?每次拆分的大小多少合适?
以及在不同方案下,大概需要的内存空间和计算时间。也就是估算时间 和 空间 。比如:
```
8位的电话号码,最多有99 999 999个
IP地址
```
### 分而治之/Hash映射
这里的`Hash映射`是指通过一种映射散列的方式,将数据均匀分布在对应的内存或更小的文件中
使用hash映射有个最重要的特点是: `hash值相同的两个串不一定一样,但是两个一样的字符串hash值一定相等`
大文件映射成多个小文件。具体操作是,比如要拆分到100(M)个文件:
1. 对大文件中的每条记录求hash值,然后对M取余数,即 `hash(R)%M`,结果为K
2. 将记录R按结果K分配到第K个文件,从而完成数据拆分
这样,两条相同的记录肯定会被分配到同一个文件。
### Bitmap
也就是用1个(或几个)bit位来标记某个元素对应的value(如果是1bitmap,就只能是元素是否存在;如果是x-bitmap,还可以是元素出现的次数等信息)。使用bit位来存储信息,在需要的存储空间方面可以大大节省。应用场景有:
1. 排序(如果是1-bitmap,就只能对无重复的数排序)
2. 判断某个元素是否存在
比如,某文件中有若干8位数字的电话号码,要求统计一共有多少个不同的电话号码?
分析:8位最多99 999 999 , 如果1Byte表示1个号码,需要95MB空间,但是如果1bit表示1个号码,则只需要 95/8=12MB 的空间。
这时,数字k(0~99 999 999)与bit位的对应关系是:
```
#define SIZE 15*1024*1024
char a[SIZE];
memset(a,0,SIZE);
// a[k/8]这个字节中的 `k%8` 位命中,置为1
// 这里要注意 big-endian 和 little-endian的问题 ,假设这里是big-endian
a[k/8] = a[k/8] | (0x01 << (k%8))
```
### Bloom filter(布隆过滤器)
Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。
##### Bloom filter 特点
为了说明Bloom Filter存在的重要意义,举一个实例:假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案:
1. 将访问过的URL保存到数据库。
2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。
3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。
4. BitMap方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。
方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。
方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了?
方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。
方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。
方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。
实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。
##### Bloom filter 算法
Bloom filter可以看做是对bitmap的扩展。
实现示例:
```
```
##### Bloom filter应用
* 拼写检查
* 数据库系统
* 网络领域
参考:http://www.cnblogs.com/heaad/archive/2011/01/02/1924195.html
### Trie树
### 数据库索引
### 倒排索引(Inverted index)
### 双层桶划分
### Bloom filter 、Bitmap
### Trie树/数据库/倒排索引
### 外排序
### 分布处理之 Hadoop/Mapreduce
### 分布处理之Mapreduce
### Hadoop
+45 -51
View File
@@ -400,10 +400,11 @@ int longest_continuious_number(const char *input,char *output)
应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。
当end首次碰到数字时,且max=0,说明是首次出现数字,第一个/二个指针移到该数字,继续遍历
end首次碰到数字,且max!=0,说明出现第二个数字串,tmp=1
如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; 否则还是max += 1;
当end从数字到普通字符时,如果tmp!=0,就要比较 tmp 和 max
1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历
2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1;
3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0
```
int longest_continuious_number(const char *input,char *output){
@@ -449,6 +450,37 @@ int longest_continuious_number(const char *input,char *output){
```
#### 求最大连续递增数字串
如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。
#### 最长公共子串问题
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。
```
int longest_common_subsequence(const char *s1,const char *s2, char *common)
```
分析:求最长公共子串(Longest Common Subsequence,LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。如"abccade","dgcadde"的最大子串为"cad"
实例代码:
```
int longest_common_subsequence(const char *s1,const char *s2, char *common){
}
```
#### 对称子字符串的最大长度
题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。
@@ -467,7 +499,6 @@ int max_symmetrical_char_length(const char *scr);
2.
3.
```
int max_symmetrical_char_length(const char *scr){
@@ -476,46 +507,26 @@ int max_symmetrical_char_length(const char *scr){
#### 子串匹配的个数
已知一个字符串,比如asderwsde,寻找其中的一个子字符串比如sde的个数,如果没有返回0,有的话返回子字符串的个数。
```
char *sub_str_count(const char *src, const char *substr, int *count)
char *substr_count(const char *src, const char *substr, int *count)
```
#### 求最大连续递增数字串
#### 请编写能直接实现strstr()函数功能的代码。
如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。
> strstr(str1,str2) 判断str2是否是str1的子串。
```
int continuousnum(const char *input, char *output)
```
#### 最长公共子串问题
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。
```
int longest_common_subsequence(const char *s1,const char *s2, char *common)
```
分析:求最长公共子串(Longest Common Subsequence,LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。如"abccade","dgcadde"的最大子串为"cad"
实例代码:
```
int longest_common_subsequence(const char *s1,const char *s2, char *common){
/*
@ret 有就返回第一次出现子串的地址,否则返回NULL
*/
char *strstr(const char *source, const char *target){
}
```
@@ -569,24 +580,7 @@ int isBrother(const char *first,const char *secd){
给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。
#### 给出一个函数来复制两个字符串A和B
字符串A的后几个字节和字符串B的前几个字节重叠。这种题目往往就是考你对边界的考虑情况。
#### 请编写能直接实现strstr()函数功能的代码。
```
//返回第一次出现子串的地址,否则返回0
char *strstr(const char *haystack, const char * needle);
```
#### 编写strcpy 函数
+65 -40
View File
@@ -6,27 +6,6 @@
* 大数问题
### 字符串转整数
写一个函数,检查字符是否是整数,如果是,返回其整数值
使用递归实现
```
int string_to_integer(char *s,int length)
```
单要思考几个问题:
1. s是空字符串
2. s传参为非数字字符
3. 超出整数所能表示范围
类似的题目还有:
怎样只用4行代码编写出一个从字符串到长整形的函数?
请编写能直接实现int atoi(const char * pstr)函数功能的代码。 将字符串转出整数
#### 求1+2+…+n
要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。
@@ -46,15 +25,27 @@ int factorial(n){
#### 整数的二进制表示中1的个数
题目:输入一个整数,求该整数的二进制表达中有多少个1。
例如输入10,由于其二进制表示为1010,有两个1,因此输出2。
题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。
分析:
这是一道很基本的考查位运算的面试题。
解法1:一轮循环移位计数 (移位运算比除法运算效率要高,注意要考虑是负数的情况)
解法2:位运算
解法3:num &= num-1 巧妙之处在于,对高位没有影响。不断做 `num &= num-1` 直到num=0
解法3:num &= num-1 巧妙之处在于,对高位没有影响。不断做 `num &= num-1` 直到num=0。
1010 & 1001 = 1000
1000 * 0111 = 0000
```
int one_appear_count_by_binary(int num){
int count = 0;
while(num !=0 ){
num &= num-1;
count++;
}
return count;
}
```
#### 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句
@@ -72,19 +63,17 @@ int factorial(n){
```
#### 把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列
分析:
```
char *integer_to_hex(long i);
char *integer_to_bin(long i);
char *integer_to_hex(long i); //eg: 20 => 14
char *integer_to_bin(long i); //eg: 20 => 10100
```
注意,转16进制中,要判断tmp[i]是否是有符号的数
```
tmp[i]= tmp[i]>=0?tmp[i]:tmp[i]+16;
tmp[i] = tmp[i]>=0 ? tmp[i] : tmp[i]+16;
```
@@ -92,17 +81,26 @@ tmp[i]= tmp[i]>=0?tmp[i]:tmp[i]+16;
#### 判断一个自然数是否是某个数的平方
说明:当然不能使用开方运算。
```
square(25) YES
square(35) NO
```
方法1: 从1开始遍历,显然这种方法很差
方法2: 除数跟余数比较,除数从2开始,每一轮都跟结果比较;相等就是存在这个数,不相等就把除数++;时间复杂度为(根号n)。在一个数比较大时,效率不够好。如1194877489=(34567)^2,需要从2开始,一直比较到34566,做3万多次除法和比较运算。跟方法1一样。。
方法2: 除数跟余数比较,除数从2开始,每一轮都跟结果比较;相等就是存在这个数,不相等就把除数++;时间复杂度为(根号n)。在一个数比较大时,效率不够好。如1194877489 =(34567)^2,需要从2开始,一直比较到34566,做3万多次除法和比较运算。跟方法1一样。。
方法3:二分查找 O(logn)
方法3:二分查找 O(logn)。比如25:
a. 先取(0+25)/2=12.5,12.5*12.5>25,因此这个数应该小于12.5
b.(0+12)/2 = 6, 6*6>25
c. (0+6)/2 = 3
d. (3+6)/2 = 4.5
e. (5+6)/2 = 5.5
```
int is_powered(int num)
```
#### 1024! 末尾有多少个0?
@@ -110,9 +108,6 @@ square(35) NO
分析:
末尾0的个数取决于2和5的个数
能被2整除的数比能被5整除的数要多得多,因此只统计被5整除的数的个数
??
@@ -120,7 +115,6 @@ square(35) NO
编程实现两个正整数的除法,当然不能用除法操作符。
```
// return x/y.
int div(const int x, const int y)
```
@@ -143,8 +137,7 @@ int div(const int x, const int y)
1. 注意结果可能超出长整形的最大范围 2^64-1
2. 采用分治算法,将大整数相乘转换为小整数计算
规律分析:
任意位数的整数相乘,最终都可以转化为2位数相乘
规律分析:任意位数的整数相乘,最终都可以转化为2位数相乘
@@ -154,7 +147,7 @@ int div(const int x, const int y)
#### 数值的整数次方
题目:实现函数double Power(double base, int exponent),求base的exponent次方。
题目:实现函数`double Power(double base, int exponent)`,求base的exponent次方。
不需要考虑溢出。
@@ -164,15 +157,47 @@ double Power(double base, int exponent)
{
double result = 1.0;
for(int i = 1; i <= exponent; ++i)
result *= base;
result *= base;
return result;
}
```
上面的代码没有考虑 exponent<0的情况。
上面的代码没有考虑: exponent<=0
判断一个浮点数是不是等于0时,不是直接写 base == 0 ,而应该判断它们的差的绝对值是不是小于一个很小的范围
如果指数大于0,我们还可以使用递归实现:a^n = a^(n/2)*a^(n/2) (n为偶数), 通过这个思路也可以实现
```
2^16 = 2^8 * 2^8
2^8 = 2^4 * 2^4
2^4 = 2^2 * 2^2
2^2 = 2^1 * 2^1
2^1 = 2
```
用例:
```
2 3 = 8
0 3 = 0
2 0 = 1
2 -3 = 1/(2^3) = 0.125
0 -3
```
使用递归实现的代码示例:
```
double Power(double base, unsigned int exponent)
{
if (exponent == 0) return 1;
if (exponent == 1) return base;
double result = Power(base, exponent >> 1);
result *= result;
if (exponent & 1) result = result*base;
return reslut;
}
```
#### 求根号2的值
+139 -129
View File
@@ -7,14 +7,151 @@
* 多个数组合并,交集
解决这一类问题时,可以从以下几个方面考虑:
* 万能的蛮力琼剧
* 散列表空间换事件
* 分治法,然后归并 (归并排序)
* 选择合适的数据结构可以显著提高算法效率 (堆排序求top-k)
* 对无序的数组先排序,使用二分
* 贪心算法或动态规划
#### 数组中超过出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。
`+-1 计数法`
#### 找数组里面重复的一个数
找数组里面重复的一个数,一个含n个元素的整数数组至少存在一个重复数,请编程实现,在O(n)时间内找出其中任意一个重复数。
1. hash算法,空间要求多 (注意数组元素要是int类型),数的大小范围和数组长度N都可以是无穷大,这里使用hash算法,空间复杂度是O(n),空间可能无法满足条件。
2. 先排序,复杂度n(logn);然后遍历一遍就可以知道哪些数重复了。
2. 高级解法: 将问题转化为 `判断单链表中存在环`
类似问题:找出数组中唯一的重复元素
#### 数组中找出某数字出现的次数
在排序数组中,找出给定数字的出现次数。比如 [1, 2, 2, 2, 3] 中2的出现次数是3次。
分析:
1. 因为是排序数组,可以使用二分查找
2. 将二分查找坚持到底,这样在最坏的情况下([2,2,2,2,2,2,2])都有0(lgn)复杂度
```
int binary_search_first(int *a,int length,int key);
int binary_search_lash(int *a,int length,int key);
```
#### 查找最小的k个元素(top-k)
题目:输入n个整数,输出其中最小的k个。
例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。
```
topMinK(int *a,int length,int k);
topMaxK(int *a,int length, int k);
```
1. 全部排序 复杂度 NlogN , 数据了较大时,内存可能承受不住
2. 部分排序 维护一个大小为K的数组,由大到小排序,然后遍历所有数据,每个数据跟数组中最小元素比较,如果比最小元素大,就要插入数组了,这里还有寻找插入位置,移动数组元素的cpu消耗。复杂度是N*K
3. 堆排序 。在这的K较大时(比如这道题目:2亿个整数中求最大的100万之和),上面的算法还是有很多可以改进的地方,如采用二分查找定位插入位置,移动数组元素的计算是躲不过去了。那有没有什么数据结构即能`快速查找,还能快速移动元素`呢?最好是O(1)复杂度。
答案就是`二叉堆`。我们可以遍历总量中的每个元素来跟二叉堆中的堆顶元素比较(堆顶元素在`小根堆`中最小值,在`大根堆`中是最大值),这样在0(1)复杂度就可以完成查找操作,揭下来需要的操作就是重新调整推结构,复杂度是O(logk),因此整个操作复杂度是 O(n*logk)
`top-k 小的时候用 *大根堆* ,top-k 大得时候用 *小根堆*`
#### 最长公共子序列 (动态规划的经典题目)
【最大/小差问题】
求相邻元素的最大差值,有无序的实数列V[N],要求求里面大小相邻的实数的差的最大值,关键是要求线性空间和线性时间
如 【9,-1,-11,2】 最大差值 = 2-(-11) = 13
最小差 hash合并
最大差 hash分解
桶排序
`桶排序` 比快排还快,最耗空间
#### 找出和为m的2个数
输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组【1、2、4、7、11、15=和数字15。由于4+11=15,因此输出4和11。
```
//返回0找到,返回-1没找到
int findaddends(int *data,int length,int sum,int *a,int *b);
```
分析:
数组升序排列,查找可用二分查找,时间复杂度O(logn),这样问题就变成了找其中一个加数的问题,复杂度为N*logN
巧妙解法:数组2端向中间扫描,复杂度O(N)
#### 和为m的组合
编程求解:输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来.
如: n=10,m=25;可能的组合有【】【】【】【】
```
print_sum_detials(int n , int sum)
```
`动态规划`(类似背包问题)
1. 划分问题
2. 选择状态
3. 状态转移方程
4. 规划方程
#### 若干个数的和与M最为接近
给定一个实数数组,按序排列(从小到大),从数组从找出若干个数,使得这若干个数的和与M最为接近,描述一个算法,并给出算法的复杂度。
#### 递减数列左移后的数组中找数
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。
1. 右移,二分查找。 找到最小的数,右移到第一个位置的时候,右移完成 O(N)
2. 直接二分查找
#### 求子数组的最大和(最大字段和)
输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为`1, -2, 3, 10, -4, 7, 2, -5` ,和最大的子数组为`3, 10, -4, 7, 2` ,
因此输出为该子数组的和18。
例如输入的数组为`1, -2, 3, 10, -4, 7, 2, -5` ,和最大的子数组为`3, 10, -4, 7, 2` ,因此输出为该子数组的和18。
1. 蛮力法 fmax(i,j) 找出最大的值,3重循环 ,复杂度 0(n^3)
2. maxendindhere保存当前累加的和,如果<0,就把maxendinghere清零 , max保存最终的最大和
@@ -47,119 +184,7 @@
#### 查找最小的k个元素(top-k)
题目:输入n个整数,输出其中最小的k个。
例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。
```
topMinK(int *a,int length,int k);
topMaxK(int *a,int length, int k);
```
1. 全部排序 复杂度 NlogN , 数据了较大时,内存可能承受不住
2. 部分排序 维护一个大小为K的数组,由大到小排序,然后遍历所有数据,每个数据跟数组中最小元素比较,如果比最小元素大,就要插入数组了,这里还有寻找插入位置,移动数组元素的cpu消耗。复杂度是N*K
3. 堆排序 。在这的K较大时(比如这道题目:2亿个整数中求最大的100万之和),上面的算法还是有很多可以改进的地方,如采用二分查找定位插入位置,移动数组元素的计算是躲不过去了。那有没有什么数据结构即能`快速查找,还能快速移动元素`呢?最好是O(1)复杂度。
答案就是`二叉堆`。我们可以遍历总量中的每个元素来跟二叉堆中的堆顶元素比较(堆顶元素在`小根堆`中最小值,在`大根堆`中是最大值),这样在0(1)复杂度就可以完成查找操作,揭下来需要的操作就是重新调整推结构,复杂度是O(logk),因此整个操作复杂度是 O(n*logk)
`top-k 小的时候用 *大根堆* ,top-k 大得时候用 *小根堆*`
#### 最长公共子序列 (动态规划的经典题目)
【最大/小差问题】
求相邻元素的最大差值,有无序的实数列V[N],要求求里面大小相邻的实数的差的最大值,关键是要求线性空间和线性时间
如 【9,-1,-11,2】 最大差值 = 2-(-11) = 13
最小差 hash合并
最大差 hash分解
桶排序
`桶排序` 比快排还快,最耗空间
#### 和为m的组合
编程求解:输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来.
如: n=10,m=25;可能的组合有【】【】【】【】
```
print_sum_detials(int n , int sum)
```
`动态规划`(类似背包问题)
1. 划分问题
2. 选择状态
3. 状态转移方程
4. 规划方程
#### 找出和为m的2个数
输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。
例如输入数组【1、2、4、7、11、15=和数字15。由于4+11=15,因此输出4和11。
```
//返回0找到,返回-1没找到
int findaddends(int *data,int length,int sum,int *a,int *b);
```
分析:
数组升序排列,查找可用二分查找,时间复杂度O(logn),这样问题就变成了找其中一个加数的问题,复杂度为N*logN
巧妙解法:数组2端向中间扫描,复杂度O(N)
#### 若干个数的和与M最为接近
给定一个实数数组,按序排列(从小到大),从数组从找出若干个数,使得这若干个数的和与M最为接近,描述一个算法,并给出算法的复杂度。
#### 递减数列左移后的数组中找数
一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5}
是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。
1. 右移,二分查找。 找到最小的数,右移到第一个位置的时候,右移完成 O(N)
2. 直接二分查找
#### 数组中超过出现次数超过一半的数字
题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。
分析:这是一道广为流传的面试题,包括百度、微软和Google在内的多家公司
都曾经采用过这个题目。要几十分钟的时间里很好地解答这道题,除了较好的编程能力之外,
还需要较快的反应和较强的逻辑思维能力。
+-1 ,计数法
#### 找数组里面重复的一个数
找数组里面重复的一个数,一个含n个元素的整数数组至少存在一个重复数,
请编程实现,在O(n)时间内找出其中任意一个重复数。
类似问题:找出数组中唯一的重复元素
hash算法,空间要求多 (注意数组元素要位 int)
高级解法: 将问题转化为 `判断单链表中存在环`
#### 给出一个洗牌算法
@@ -257,20 +282,6 @@ hash算法,空间要求多 (注意数组元素要位 int)
#### 数组中找出某数字出现的次数
在排序数组中,找出给定数字的出现次数。比如 [1, 2, 2, 2, 3] 中2的出现次数是3次。
分析:
1. 因为是排序数组,可以使用二分查找
2. 将二分查找坚持到底,这样在最坏的情况下([2,2,2,2,2,2,2])都有0(lgn)复杂度
```
int binary_search_first(int *a,int length,int key);
int binary_search_lash(int *a,int length,int key);
```
#### 扑克牌的顺子
@@ -525,7 +536,6 @@ int one_appear_count(int n);
#### 求一个数组的最长递减子序列
比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}
+23
View File
@@ -0,0 +1,23 @@
#include "stdio.h"
double Power(double base, int exponent)
{
if (exponent == 0) return 1;
if (exponent == 1) return base;
double result = Power(base, exponent >> 1);
result *= result;
if (exponent & 1) result = result*base;
return result;
}
int main(int argc, char const *argv[])
{
printf("%f\n", Power(2,4));
printf("%f\n", Power(2.1,4));
printf("%f\n", Power(0,4));
printf("%f\n", Power(2,0));
printf("%f\n", Power(2,-3)); //负数就挂了
return 0;
}
@@ -0,0 +1,18 @@
#include "stdio.h"
int one_appear_count_by_binary(int num){
int count = 0;
while(num !=0 ){
num &= num-1;
count++;
}
return count;
}
int main(int argc, char const *argv[])
{
int test = 10;
printf("%d\n", one_appear_count_by_binary(test));
printf("%d\n", one_appear_count_by_binary(32+1));
return 0;
}
+30 -20
View File
@@ -1,5 +1,7 @@
## 海量数据处理
这部分内容是海量数据处理的问题。所谓海量:
时间上:无法在较短时间内迅速解决。可以设计巧妙的算法搭配合适的数据结构解决
@@ -15,28 +17,28 @@
6. 分布处理之 Hadoop/Mapreduce
#### top-1问题 -- 海量日志提取访问最多的IP
### top-1问题 —— 海量日志提取访问最多的IP
海量日志处理,提取某日访问百度次数最多的那个IP
分析
#### 估算
一个IP可以用32bit表示,总共可以表示 2^32 = 42亿九千万。。个IP
假设一个IP一天内访问百度的次数不查过40亿次,可以用unsigned表示。用数组统计处每个IP地址出现的最大次数,就可以得到访问次数最大的IP地址
我们先来估算下,一个IP可以用32bit表示,总共可以表示 2^32 = 42亿九千万个IP 。假设一个IP一天内访问百度的次数不超过40亿次,可以用unsigned 的数表示。用数组统计出每个IP地址出现的最大次数,就可以得到访问次数最大的IP地址
unsigned count[N],这个N=2^32=4G个IP地址 sizeof(count) = 4*N = 16G内存,原因超过32位机器所支持的4G内存大小。因此不能直接创建这个数组
unsigned count[N],这个N=2^32=4G个IP地址 sizeof(count) = 4*N = 16G内存,远超过32位机器所支持的4G内存大小。因此不能直接创建这个数组。
第一步 分治法/文件映射
#### 分治法/文件映射
假设运行使用的内存是512MB, 512M/4 = 128M个IP地址(一个IP地址32位,占4个字节)
注意理解:`512M内存可以统计128M个不同的IP地址的访问次数`,哪怕切割的小文件中的IP总数大于128M
假设运行使用的内存是512MB, 512M/4 = 128M个IP地址(一个IP地址32位,占4个字节),也就是内存中可以同时放 128M个内存地址;另一个角度理解就是,`512M内存可以统计128M个不同的IP地址的访问次数`,哪怕切割的小文件中的IP总数大于128M
4G/128M = 32,也就是把IP地址划分成 32 个不同的区间段,addr[1-xx],addr[xx-xx]..分别计算每个区间中访问次数最大的IP,然后再比较32个区间段中的最大次数,就可以得到所有IP的做大值了。
一个区间段的IP地址个数任然可能撑破128M个,从而撑爆内存
具体实施:
把大文件映射到小文件,有2种方式:
1. 取模映射 。 IP%32 映射到32个小文件,把model相同的IP保存到同一个文件
@@ -45,28 +47,26 @@ unsigned count[N],这个N=2^32=4G个IP地址 sizeof(count) = 4*N = 16G内存,
上面的映射中,`都不会出现同一个IP映射到不同小文件的情况`
第二步 统计每个小文件中的最大值
#### 统计每个小文件中的最大值
现在文件不同的IP数量达到内存承受的范围了,可以统计小文件中的IP次数了,使用常规的 hash_map(IP,count) 来 统计了。可以分块读取来减少磁盘IO
第三步 排序 堆排序/快速排序/归并排序
#### 排序 堆排序/快速排序/归并排序
类似问题:
a.怎么在海量数据中找出重复次数最多的一个?
b.服务器内存1G,有一个2G的文件,里面每行存着一个QQ号(5-10位数),怎么最快找出出现过最多次的QQ号。(10位数可以表示(不到)10亿个QQ号)
#### 代码示例
```
```
#### 统计指定时间段内ip访问量
#### 类似问题
1. 怎么在海量数据中找出重复次数最多的一个?
2. 服务器内存1G,有一个2G的文件,里面每行存着一个QQ号(5-10位数),怎么最快找出出现过最多次的QQ号。(10位数可以表示(不到)10亿个QQ号)
假设某个网站每天有超过10亿次的页面访问量,出于安全考虑,网站会记录访问客户端访问的ip地址和对应的时间,如果现在已经记录了1000亿条数据,想统计一个指定时间段内的区域ip地址访问量,那么这些数据应该按照何种方式来组织,才能尽快满足上面的统计需求呢,
设计完方案后,并指出该方案的优缺点,比如在什么情况下,可能会非常慢?
参考答案:用B+树来组织,非叶子节点存储(某个时间点,页面访问量),叶子节点是访问的IP地址。这个方案的优点是查询某个时间段内的IP访问量很快,但是要统计某个IP的访问次数或是上次访问时间就不得不遍历整个树的叶子节点。或者可以建立二级索引,分别是时间和地点来建立索引。
#### top-K 找出最大的k个数
@@ -157,6 +157,16 @@ s 求出每台电脑上的TOP10后,然后把这100台电脑上的TOP10组合
#### 统计指定时间段内ip访问量
假设某个网站每天有超过10亿次的页面访问量,出于安全考虑,网站会记录访问客户端访问的ip地址和对应的时间,如果现在已经记录了1000亿条数据,想统计一个指定时间段内的区域ip地址访问量,那么这些数据应该按照何种方式来组织,才能尽快满足上面的统计需求呢,
设计完方案后,并指出该方案的优缺点,比如在什么情况下,可能会非常慢?
参考答案:用B+树来组织,非叶子节点存储(某个时间点,页面访问量),叶子节点是访问的IP地址。这个方案的优点是查询某个时间段内的IP访问量很快,但是要统计某个IP的访问次数或是上次访问时间就不得不遍历整个树的叶子节点。或者可以建立二级索引,分别是时间和地点来建立索引。
#### 文件中的内容排序并去重复