diff --git a/pre.md b/pre.md new file mode 100644 index 0000000..fbe830a --- /dev/null +++ b/pre.md @@ -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 + + + + +### 数据挖掘 + +在知乎的描述中。`数据挖掘`是指从大量的数据中自动搜索隐藏于其中的有着特殊关系性的信息和知识的过程。 + + + diff --git a/二叉树/README.md b/二叉树/README.md index 84b1bec..55d5555 100644 --- a/二叉树/README.md +++ b/二叉树/README.md @@ -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) diff --git a/哈希表/README.md b/哈希表/README.md index 4cf5c75..a528607 100644 --- a/哈希表/README.md +++ b/哈希表/README.md @@ -21,6 +21,22 @@ for (int i=0;i 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 函数 diff --git a/算法问题选编/数值问题.md b/算法问题选编/数值问题.md index 1a89f5f..3096727 100644 --- a/算法问题选编/数值问题.md +++ b/算法问题选编/数值问题.md @@ -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的值 diff --git a/算法问题选编/数组数列问题.md b/算法问题选编/数组数列问题.md index 68464f2..c5e3f6b 100644 --- a/算法问题选编/数组数列问题.md +++ b/算法问题选编/数组数列问题.md @@ -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} diff --git a/算法问题选编/测试代码/Power.c b/算法问题选编/测试代码/Power.c new file mode 100644 index 0000000..8ab9b20 --- /dev/null +++ b/算法问题选编/测试代码/Power.c @@ -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; + } \ No newline at end of file diff --git a/算法问题选编/测试代码/one_appear_count_by_binary.c b/算法问题选编/测试代码/one_appear_count_by_binary.c new file mode 100644 index 0000000..f12e2d1 --- /dev/null +++ b/算法问题选编/测试代码/one_appear_count_by_binary.c @@ -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; +} \ No newline at end of file diff --git a/算法问题选编/海量数据处理.md b/算法问题选编/海量数据处理.md index f2ca5d1..62c64aa 100644 --- a/算法问题选编/海量数据处理.md +++ b/算法问题选编/海量数据处理.md @@ -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的访问次数或是上次访问时间就不得不遍历整个树的叶子节点。或者可以建立二级索引,分别是时间和地点来建立索引。 + + + #### 文件中的内容排序并去重复