├── .gitignore ├── 0 Numeral ├── base58.md └── 数值.md ├── 1 String ├── KMP.md ├── README.md └── java String.md ├── 2 List ├── QPS Counter.md ├── README.md ├── Redis slowlog.md ├── 数组.md └── 条件表达式.md ├── 2 Queue ├── README.md ├── ZipList.md └── skip-list.md ├── 3 Hash Table ├── HashMap in Golang.md ├── HashMap in Java.md ├── HashTable.c ├── LinkedHashMap.md ├── README.md ├── TreeMap in Java.md ├── hash_ref.c └── hashmap.png ├── 4 Tree ├── 1-二叉树 │ ├── btree.c │ ├── btree │ │ ├── bintree.c │ │ ├── btree │ │ ├── public.h │ │ └── 队列.h │ ├── rbtree.c │ └── suffix_tree.c ├── 2-二叉查找树 │ ├── BiSearchTree │ │ ├── README.md │ │ ├── bisearch │ │ ├── bisearchtree.c │ │ ├── bisearchtree.h │ │ └── main.c │ └── 二叉查找树.md ├── 3-平衡树AVL │ ├── AVLTree.c │ └── README.md ├── 4-字典树Trie │ ├── README.md │ └── trie.c ├── 5-伸展树 │ └── 伸展树.md ├── 6-后缀树 │ └── 后缀树.md ├── 7-B树 │ ├── B+树.md │ └── B树.md ├── 8-堆 │ ├── Top-K 问题.md │ ├── heap.c │ ├── pq-1.jpg │ ├── pq-1.png │ └── 堆.md ├── 9-红黑树 R-B tree │ └── 红黑树.md ├── 92-并查集 │ └── 并查集.md ├── README.md └── huffman tree │ └── 赫夫曼编码.md ├── 5 Graph ├── DFS 和 BFS.md ├── README.md ├── 二分图 │ └── 二部图.md ├── 拓扑排序.md ├── 最小生成树.md └── 最短路径.md ├── 6 Sort ├── 8.c ├── README.md ├── bubblesort.gif ├── heapsort.gif ├── insert_sort.c ├── mergesort.gif ├── qsort.gif ├── selectsort.gif ├── shellsort.gif └── 外排序.md ├── 7 Search └── README.md ├── 8 Algorithms Analysis ├── README.md ├── 分治算法.md ├── 动态规划.md ├── 回溯法.md ├── 穷举搜索法.md ├── 贪心算法.md ├── 迭代法.md └── 递归.md ├── 9 Algorithms Job Interview ├── 1 字符串.md ├── 1.1 字符串-查找.md ├── 1.2 字符串-删除.md ├── 1.3 字符串-修改.md ├── 1.4 字符串-排序.md ├── 2 链表.md ├── 2.1 链表-排序.md ├── 2.2 链表-删除.md ├── 2.3 链表-2条.md ├── 3 堆和栈.md ├── 4 数值问题.md ├── 4.1 数值-加减乘除.md ├── 4.2 数值-指数.md ├── 4.3 数值-随机数.md ├── 4.4 数值-最小公倍数.md ├── 4.5 数值-素树.md ├── 5 数组数列问题.md ├── 5.1 数列-排序.md ├── 5.2 数列-nsum问题.md ├── 5.3 数列-交并集.md ├── 5.4 数列-查找.md ├── 6 矩阵.md ├── 7 二叉树.md ├── 7.1 二叉树-遍历.md ├── 7.2 二叉树-建树.md ├── 7.5 二叉搜索树.md ├── 7.9 树.md ├── 8 图.md ├── 9 智力思维训练.md ├── 91 系统设计.md ├── 97 其他.md ├── README.md ├── codes │ ├── 1 string │ │ ├── char_first_appear_once.c │ │ ├── proc.c │ │ ├── replce_blank.c │ │ ├── revert_by_word.c │ │ └── string.c │ ├── 10.c │ ├── 12.c │ ├── 20.c │ ├── 21.c │ ├── 22.c │ ├── 23.c │ ├── 24.c │ ├── 25.c │ ├── 26.c │ ├── 27.c │ ├── 28.c │ ├── 4 numer │ │ ├── Power.c │ │ ├── integer_to_bin.c │ │ ├── isSquare.c │ │ ├── one_appear_count_by_binary.c │ │ └── string_to_integer.c │ ├── 4-1.c │ ├── 5 array │ │ ├── delete_occurence_character.c │ │ ├── factorial.c │ │ ├── fibonacci.c │ │ ├── longest_continuious_number.c │ │ └── print_continuous_sequence_sum.c │ ├── 5.c │ ├── 6 matrix │ │ └── print_matrix.c │ ├── 7 bianrytree │ │ ├── binary_search.c │ │ └── bt1.c │ ├── 7.c │ ├── 8.c │ ├── c1.c │ ├── c10.c │ ├── c11-2.c │ ├── c11.c │ ├── c12.c │ ├── c13-1.c │ ├── c13-2.c │ ├── c14.c │ ├── c15.c │ ├── c16.c │ ├── c17.c │ ├── c18.c │ ├── c19.c │ ├── c2.c │ ├── c20.c │ ├── c3.c │ ├── c4.c │ ├── c5.c │ ├── c6.c │ ├── c7.c │ ├── c8.c │ ├── c9.c │ └── most_visit_ip.c ├── 剑指offer │ └── README.md └── 编程之美 │ └── README.md ├── 91 Algorithms In Big Data ├── Bitmap.md ├── Bloomfilter.md ├── Hash映射,分而治之.md ├── Inverted Index │ ├── btree_insert.gif │ ├── disk_search.png │ ├── 倒排索引(Inverted Index).md │ └── 数据库索引.md ├── README.md ├── mapreduce │ ├── Hash映射,分而治之.md │ └── 分布处理之Mapreduce.md ├── simhash算法.md ├── 双层桶划分.md ├── 外排序.md └── 海量数据处理.md ├── 92 Algorithms In DB ├── README.md ├── mysql │ └── README.md └── redis │ └── README.md ├── 93 Algorithms In Open Source ├── Bitcoin │ └── Merkle Tree.md ├── GeoHash │ └── 多维空间点索引算法.md ├── README.md ├── Timer │ └── timer.md ├── YYKit │ └── YYCache.md ├── kafka │ └── README.md ├── nginx │ └── README.md └── zoomkeeper │ └── README.md ├── 94 15-Classic-Algorithms └── README.md ├── C Language Code Specification.md ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.gitignore 3 | *.out 4 | *.dSYM 5 | 6 | *.dat 7 | *.txt 8 | *ipslice* 9 | *.log 10 | 11 | cmake-build-debug 12 | *.idea 13 | 14 | 15 | -------------------------------------------------------------------------------- /0 Numeral/base58.md: -------------------------------------------------------------------------------- 1 | # base58 2 | 3 | btc中 base58编码表是 4 | 5 | ``` 6 | 123456789 abcdefg hijk mn opqrst uvwxyz ABCDEFG HJ KLMN PQRST UVWXYZ 7 | ``` 8 | 9 | 10 | 相比 base64 去掉了 6个字符, 数字0, 大写字母 O, 大写字母 I (小写i) , 小写字母 l (大写L), 以及 + 和 / 11 | 12 | 13 | 14 | ### 编码实现 15 | 16 | ``` 17 | 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /0 Numeral/数值.md: -------------------------------------------------------------------------------- 1 | # 数值 2 | 3 | 4 | `n&(n-1)` 作用是消除数字 n 的二进制表示中的最后一个 1 5 | 6 | 7 | 一个数和它本身做异或运算(^)结果为 0,即 `a ^ a = 0` 8 | 9 | 10 | 不用临时变量交换两个数 11 | 12 | ``` 13 | int a = 1, b = 2; 14 | a ^= b; 15 | b ^= a; 16 | a ^= b; 17 | // 现在 a = 2, b = 1 18 | ``` 19 | 20 | 21 | -------------------------------------------------------------------------------- /1 String/KMP.md: -------------------------------------------------------------------------------- 1 | # 字符串匹配算法 KMP 2 | 3 | KMP于1977年被提出,全称 Knuth–Morris–Pratt 算法; 名字分别是:Donald Knuth(K), James H. Morris(M), Vaughan Pratt(P). 4 | 5 | KMP算法是一种字符串匹配算法,可以在 O(n+m) 的时间复杂度内实现两个字符串的匹配。 6 | 7 | 8 | -------------------------------------------------------------------------------- /1 String/README.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | 字符串在计算机中的应用非常广泛,这里讨论有关字符串的最重要的算法: 4 | 5 | * 排序 6 | * 查找 7 | * 单词查找树 8 | * 子串查找 9 | * 正则表达式:正则表达式是模式匹配的基础,是一个一般化了的子字符串的查找问题,也是搜索工具grep的核心。 10 | * 模式匹配 11 | * grep 12 | * 数据压缩 13 | * 赫夫曼树 14 | * 游程编码 15 | 16 | 17 | [Java中 String实现 参考这里](java%20String.md) 18 | 19 | ## 查找 20 | 21 | * KMP 22 | * BM 23 | 24 | 25 | ## 滑动窗口 26 | 27 | 28 | 使用滑动窗口解决字符串子串问题,代码框架 29 | ``` 30 | int left = 0, right = 0; 31 | 32 | while (right < s.size()) { 33 | // 增大窗口 34 | window.add(s[right]); 35 | right++; 36 | 37 | while (window needs shrink) { 38 | // 缩小窗口 39 | window.remove(s[left]); 40 | left++; 41 | } 42 | } 43 | ``` 44 | 45 | 46 | 47 | ## 参考 48 | 49 | 《Algorithms》 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /1 String/java String.md: -------------------------------------------------------------------------------- 1 | # java String 2 | 3 | Java 中 String 实现。 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /2 List/QPS Counter.md: -------------------------------------------------------------------------------- 1 | # QPS Counter 2 | 3 | 统计各接口QPS计数 4 | 使用一个双向循环链表结构 5 | 6 | 7 | 8 | 9 | ```Java 10 | static AtomicInteger qpsCount = 100; //线程安全 11 | static volatile long lastSenconds = System.currentTimeMillis()/1000; 12 | 13 | //1 计数器 14 | public static boolean tryAcquire() { 15 | 16 | long current = System.currentTimeMillis()/1000; 17 | if(current == lastSenconds){ 18 | 19 | if (qpsCount-- > 0) {//CAS api 20 | return true; 21 | } else { 22 | //限流 23 | return false; 24 | } 25 | 26 | } else{//下一个时间窗口 27 | lastSenconds = current; 28 | qpsCount = 100; 29 | return true; 30 | } 31 | } 32 | ``` 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /2 List/README.md: -------------------------------------------------------------------------------- 1 | # 链表 2 | 3 | * 链表 4 | * 双向链表 5 | * 双向循环链表 6 | 7 | 8 | ## 链表 9 | 10 | ### 应用场景 11 | 12 | * [redis slowlog](Redis%20slowlog.md) 13 | 14 | 15 | ### 链表 VS 数组 16 | 17 | 特点 18 | 19 | * 数组 : 内存连续, 更好利用局部性原理;内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,有界 20 | * 链表 : 不存在数组的扩容问题, 空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问; 需要前后元素位置的指针,会消耗相对更多的储存空间 21 | 22 | 优缺点: 23 | 24 | * 查询: 数组 O(1), 有序时可以用二分查找; 25 | * 删除: 链表只需要移动指针 O(1) ,数组的话删除元素需要移动后续的元素 O(N) 26 | 27 | 28 | 29 | 30 | ### 扩缩容 31 | 32 | 简单说下编程语言 java, golang中 LinkList的扩缩容的策略。 33 | 34 | java 中扩容,每次扩容新增原先容量的 1/2 35 | ``` 36 | int newCapacity = oldCapacity + (oldCapacity >> 1); 37 | ``` 38 | 39 | 这个就不介绍了。重点说下双向链表。 40 | 41 | 42 | 43 | ## 双向链表 44 | 45 | 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。 46 | 47 | 双向链表克服了单链表中访问某个节点前驱节点(插入,删除操作时),只能从头遍历的问题。 48 | 49 | 缺点是: 多了1倍的额外的指针空间大小。 50 | 51 | ``` 52 | typedef int Value 53 | typedef struct Entry{ 54 | struct Entry *next,*prev; 55 | Value value; 56 | }DoubleLink; 57 | 58 | ``` 59 | 60 | 应用场景 61 | 62 | * mysql B+树 叶子节点就使用 双向链表,方便 `age<10` 类似条件查询,或者 倒序查询 如 `order by desc` ,从后向前遍历数据 63 | * Java AQS 中的等待队列, 是一个双端 双向链表 的结构 (FIFO 结构) 64 | 65 | 66 | ## 循环链表 67 | 68 | 最后一个节点指针指向头节点的链表 69 | 70 | [QPS 计数器实现](QPS%20Counter.md) 71 | 72 | 73 | ## 双向循环链表 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /2 List/Redis slowlog.md: -------------------------------------------------------------------------------- 1 | # Redis slowlog 2 | 3 | 4 | redis中的slowlog使用链表来保存 5 | 6 | 7 | ``` 8 | struct redisServer { 9 | 10 | // ... 11 | 12 | 13 | // 下一条慢查询日志的 ID 14 | long long slowlog_entry_id; 15 | 16 | 17 | // 保存了所有慢查询日志的链表 18 | list *slowlog; 19 | 20 | 21 | // 服务器配置 slowlog-log-slower-than 选项的值 22 | long long slowlog_log_slower_than; 23 | 24 | 25 | // 服务器配置 slowlog-max-len 选项的值 26 | unsigned long slowlog_max_len; 27 | 28 | 29 | // ... 30 | 31 | }; 32 | ``` 33 | 34 | 35 | 一条slowlog entry标识 36 | 37 | ``` 38 | typedef struct slowlogEntry { 39 | 40 | 41 | // 唯一标识符 42 | long long id; 43 | 44 | 45 | // 命令执行时的时间,格式为 UNIX 时间戳 46 | time_t time; 47 | 48 | 49 | // 执行命令消耗的时间,以微秒为单位 50 | long long duration; 51 | 52 | 53 | // 命令与命令参数 54 | robj **argv; 55 | 56 | 57 | // 命令与命令参数的数量 58 | int argc; 59 | 60 | 61 | } slowlogEntry; 62 | 63 | ``` 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /2 List/数组.md: -------------------------------------------------------------------------------- 1 | # 数组 2 | 3 | 数据与链表差异 [参考这里](README.md) 4 | 5 | 6 | ## 循环数组 7 | 8 | 9 | 特点 10 | 11 | * 长度固定,下表不会越界,使用2个指针标识 头尾 下标; 默认都是 0 12 | * 方便用来实现 栈,队列;比如 队列实现时,新增元素,头下表+1; 删除元素,尾下标+1 13 | 14 | 比如Java的 ArrayBlockingQueue 就是一个 带有 takeIndex 和 putIndex 的环形数组。 15 | 16 | 17 | 缺点:头尾之间的元素不好维护,从中间删除了某个元素,会出现数组空隙。 18 | 19 | 20 | 21 | 数据结构 22 | 23 | ``` 24 | ``` 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /2 List/条件表达式.md: -------------------------------------------------------------------------------- 1 | # 条件表达式 2 | 3 | 4 | ### 应用场景 5 | 6 | * SQL 语句中的条件 7 | * 广告系统中配置定向的过滤条件 8 | * 编译器语法解析 9 | 10 | 11 | 12 | DNF 析取范式 13 | 14 | -------------------------------------------------------------------------------- /2 Queue/README.md: -------------------------------------------------------------------------------- 1 | # 队列 Queue 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /2 Queue/ZipList.md: -------------------------------------------------------------------------------- 1 | # ZipList 2 | 3 | 4 | ZipList 压缩列表 redis 中的 sset(有序集合) 使用跳表来实现,为什么不是用红黑树,而是跳表实现sset,带着这样的疑问,有了本文。 5 | 6 | 7 | ## 理解 8 | 9 | 10 | 有哪些应用场景? 11 | 12 | 13 | 14 | #### 为什么使用 15 | 16 | 17 | 18 | 19 | ## 结构 20 | 21 | 22 | 23 | 24 | ## 插入,删除,查找 实现 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /2 Queue/skip-list.md: -------------------------------------------------------------------------------- 1 | # skip-list 2 | 3 | skip-list 跳表。 redis 中的 sset(有序集合) 使用跳表来实现,为什么不是用红黑树,而是跳表实现sset,带着这样的疑问,有了本文。 4 | 5 | 6 | ## 跳表理解 7 | 8 | 9 | 有哪些应用场景? 10 | 11 | 12 | ## 跳表的结构 13 | 14 | 15 | 16 | 17 | ## 插入,删除,查找 实现 18 | 19 | 20 | 21 | ## sset 为什么使用 跳表 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /3 Hash Table/HashMap in Golang.md: -------------------------------------------------------------------------------- 1 | # HashMap in Golang 2 | 3 | java 中 hashmap的实现原理 4 | -------------------------------------------------------------------------------- /3 Hash Table/HashTable.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | 4 | 5 | 6 | typedef char * Key; 7 | typedef int Value; 8 | typedef unsigned int Hash; 9 | 10 | 11 | 12 | typedef struct Entry 13 | { 14 | struct Entry *next; 15 | Hash hash; // key 对应的hash值 16 | Key key; 17 | Value value; 18 | }Entry; 19 | 20 | 21 | // 拉链法实现,也就是链表的数组.也是 数组和链表优势的结合 22 | typedef struct HashTable{ 23 | 24 | Entry **head; 25 | 26 | unsigned int size; 27 | unsigned int usage; 28 | 29 | }HashTable; 30 | 31 | 32 | HashTable *create(unsigned int); 33 | HashTable *put(HashTable *,Key,Value); 34 | HashTable *putInHeads(HashTable *,Key,Value); // 数组 35 | HashTable *putInLists(HashTable *,Key,Value); // 链表 36 | Value find(HashTable *,Key ); 37 | HashTable*delete(HashTable *,Key ); 38 | void print(HashTable *); 39 | 40 | Hash hashCode(Key); // 哈希函数 41 | 42 | 43 | 44 | HashTable *create(unsigned int size){ 45 | 46 | HashTable *hashTable = malloc(sizeof(HashTable)); 47 | if(hashTable==NULL){ 48 | printf("malloc error\n"); 49 | return NULL; 50 | } 51 | 52 | hashTable->size=size; 53 | hashTable->usage = 0; 54 | hashTable->head = calloc(size,sizeof(Entry *)); 55 | 56 | return hashTable; 57 | } 58 | 59 | 60 | HashTable *put(HashTable *hashTable,Key key,Value value){ 61 | 62 | if (key==NULL) 63 | { 64 | return hashTable; 65 | } 66 | 67 | 68 | 69 | } 70 | 71 | Value find(HashTable *hashTable,Key key){ 72 | 73 | 74 | } 75 | 76 | void print(HashTable *hashTable){ 77 | 78 | Entry *entry; 79 | for (int i = 0; i < hashTable->size; ++i) 80 | { 81 | entry = hashTable->head[i]; 82 | while(entry!=NULL){ 83 | printf("%s=%d\n",entry->key,entry->value); 84 | entry=entry->next; 85 | } 86 | } 87 | 88 | } 89 | 90 | 91 | // ????? 92 | Hash hashCode(char *key){ 93 | 94 | int offset = 5; 95 | Hash hashCode = 0; 96 | while(*key){ 97 | hashCode = (hashCode << offset) + (*key++); 98 | } 99 | 100 | return hashCode; 101 | 102 | } 103 | 104 | 105 | int main(){ 106 | 107 | 108 | 109 | return 0; 110 | } 111 | 112 | -------------------------------------------------------------------------------- /3 Hash Table/LinkedHashMap.md: -------------------------------------------------------------------------------- 1 | # LinkedHashMap 2 | 3 | java 中 LinkedHashmap的实现原理。LinkedHashmap继承自 HashMap。 4 | 5 | HashMap是无序的,迭代访问顺序并不一定与插入(put)顺序一致。LinkedHashMap 是有序的, 迭代顺序与插入顺序一致,这种叫做 插入有序。 6 | 7 | 8 | ``` 9 | //插入有序 10 | Map linkedHashMap = new LinkedHashMap<>(); 11 | linkedHashMap.put("name1", "josan1"); 12 | linkedHashMap.put("name2", "josan2"); 13 | linkedHashMap.put("name3", "josan3"); 14 | Set> set = linkedHashMap.entrySet(); 15 | Iterator> iterator = set.iterator(); 16 | while(iterator.hasNext()) { 17 | Entry entry = iterator.next(); 18 | String key = (String) entry.getKey(); 19 | String value = (String) entry.getValue(); 20 | System.out.println("key:" + key + ",value:" + value); 21 | } 22 | 23 | output: 24 | key:name1, value:josan1 25 | key:name2, value:josan2 26 | key:name3, value:josan3 27 | ``` 28 | 29 | 30 | #### 特点 31 | 32 | * 维护一个所有entry的双向链表 33 | * 构造函数 有一个 accessOrder 参数,控制访问顺序 (插入顺序 和 访问顺序); 34 | * 访问顺序的意思是,当有一个entry被访问以后,这个entry就被移动到链表的表尾。这个特性非常适合 LRU 缓存 (最近最少使用); 35 | 36 | 37 | #### 引用场景 38 | 39 | * LUR 缓存 (最近最少使用) 40 | 41 | 42 | ## 原理 43 | 44 | 45 | ``` 46 | public class LinkedHashMap 47 | extends HashMap 48 | implements Map { 49 | 50 | static class Entry extends HashMap.Node { 51 | Entry before, after; 52 | Entry(int hash, K key, V value, Node next) { 53 | super(hash, key, value, next); 54 | } 55 | } 56 | 57 | transient LinkedHashMap.Entry head; 58 | 59 | transient LinkedHashMap.Entry tail; 60 | 61 | //构造函数如下, 62 | public LinkedHashMap(int initialCapacity, 63 | float loadFactor, 64 | boolean accessOrder) { 65 | super(initialCapacity, loadFactor); 66 | this.accessOrder = accessOrder; 67 | } 68 | 69 | } 70 | ``` 71 | 72 | 73 | ## 用LinkedHashMap实现LRU 74 | 75 | 76 | 构造函数中 accessOrder 参数是控制LinkedHashMap 访问顺序,默认为插入顺序(false), true 代表访问顺序。 77 | 78 | 访问顺序的意思是,当有一个entry被访问以后,这个entry就被移动到链表的表尾。 这个特性非常适合 LRU 缓存 (最近最少使用) ; 79 | 80 | 插入逻辑,运行自定义删除最老entry的逻辑 81 | 82 | ``` 83 | void afterNodeInsertion(boolean evict) { // possibly remove eldest 84 | LinkedHashMap.Entry first; 85 | if (evict && (first = head) != null && removeEldestEntry(first)) { 86 | K key = first.key; 87 | removeNode(hash(key), key, null, false, true); 88 | } 89 | } 90 | ``` 91 | 92 | 93 | 重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。 94 | 95 | ``` 96 | private static final int MAX_ENTRIES = 100; 97 | protected boolean removeEldestEntry(Map.Entry eldest) { 98 | return size() > MAX_ENTRIES; 99 | } 100 | ``` 101 | 102 | 103 | 基于 LinkedHashMap实现的 LRU Cache 104 | 105 | 106 | ```Java 107 | class LRUCache { 108 | int cap; 109 | LinkedHashMap cache = new LinkedHashMap<>(); 110 | public LRUCache(int capacity) { 111 | this.cap = capacity; 112 | } 113 | 114 | //访问元素 115 | public int get(int key) { 116 | if (!cache.containsKey(key)) { 117 | return -1; 118 | } 119 | // 将 key 变为最近使用 120 | makeRecently(key); 121 | return cache.get(key); 122 | } 123 | 124 | //添加元素到cache 125 | public void put(int key, int val) { 126 | if (cache.containsKey(key)) { 127 | // 修改 key 的值 128 | cache.put(key, val); 129 | // 将 key 变为最近使用 130 | makeRecently(key); 131 | return; 132 | } 133 | 134 | if (cache.size() >= this.cap) { 135 | // 链表头部就是最久未使用的 key 136 | int oldestKey = cache.keySet().iterator().next(); 137 | cache.remove(oldestKey); 138 | } 139 | // 将新的 key 添加链表尾部 140 | cache.put(key, val); 141 | } 142 | 143 | private void makeRecently(int key) { 144 | int val = cache.get(key); 145 | // 删除 key,重新插入到队尾 146 | cache.remove(key); 147 | cache.put(key, val); 148 | } 149 | } 150 | 151 | ``` 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /3 Hash Table/README.md: -------------------------------------------------------------------------------- 1 | # 散列表 2 | 3 | 本节围绕以下内容展开: 4 | 5 | * 散列表 6 | * 散列函数设计 7 | * 冲突处理 8 | * hashmap数据结构 9 | 10 | [Golang 中HashMap实现](HashMap%20in%20Golang.md) 11 | [Java 中HashMap实现](HashMap%20in%20Java.md) 12 | [Java 中LinkedHashMap实现](LinkedHashMap.md) 13 | [Java 中TreeMap实现](TreeMap%20in%20Java.md) 14 | 15 | 16 | 散列表使用某种算法操作(散列函数)将键转化为数组的索引来访问数组中的数据,这样可以通过Key-value的方式来访问数据,达到常数级别的存取效率。现在的nosql数据库都是采用key-value的方式来访问存储数据。 17 | 18 | 散列表是算法在时间和空间上做出权衡的经典例子。通过一个散列函数,将键值key映射到记录的访问地址,达到快速查找的目的。如果没有内存限制,我们可以直接将键作为数组的索引,所有的操作操作只需要一次访问内存就可以完成。但这种情况不太现实。 19 | 20 | 21 | 22 | ## Hashmap应用 23 | 24 | 1. cocos2d 游戏引擎 CCScheduler 25 | 2. linux 内核bcache。 缓存加速技术,使用SSD固态硬盘作为高速缓存,提高慢速存储设备HDD机械硬盘的性能 26 | 3. hash表在海量数据处理中有广泛应用。如海量日志中,提取出某日访问百度次数最多的IP 27 | 4. Java 中HashMap实现。编程语言中HashMap是如何实现的呢? 说说 Java , Golang 28 | 5. redis hash结构, set通常也是基于Hash结构实现 29 | 30 | 31 | 32 | ## 散列函数 33 | 34 | 散列函数就是将键转化为数组索引的过程。且这个函数应该易于计算且能够均与分布所有的键。 35 | 36 | 散列函数最常用的方法是`除留余数法`。这时候被除数应该选用`素数`,这样才能保证键值的均匀散布。 37 | 38 | 散列函数和键的类型有关,每种数据类型都需要相应的散列函数;比如键的类型是整数,那我们可以直接使用`除留余数法`;这里特别说明下,大多数情况下,键的类型都是字符串,这个时候我们任然可以使用`除留余数法`,将字符串当做一个特别大的整数。 39 | 40 | ``` 41 | int hash = 0; 42 | for (int i=0;isize = size; 149 | hashMap->usage = 0; 150 | hashMap->heads = calloc(size,sizeof(Entry *)); 151 | 152 | return hashMap; 153 | } 154 | 155 | HashMap *put(HashMap *hashMap,Key key,Value value){ 156 | if (key == NULL){ 157 | return hashMap; 158 | } 159 | Hash hash = hashCode(key); 160 | int index = hash & (size-1);/* */ 161 | if (hashMap->heads[index] == NULL){ 162 | _putInHead(hashMap,index,key,value); 163 | }else{ 164 | _putInList(hashMap,index,key,value); 165 | } 166 | } 167 | 168 | Value get(HashMap hashMap*,Key key){ 169 | if (key == NULL){ 170 | return hashMap; 171 | } 172 | Hash hash = hashCode(key); 173 | int index = hash & (size-1);/* */ 174 | 175 | Entry *entry = hashMap->heads[index]; 176 | while(entry != NULL){ 177 | if (entry->hash == hash){ 178 | return entry->value; 179 | } 180 | entry = entry->next; 181 | } 182 | return NULL; 183 | } 184 | 185 | HashMap *_putInHead(HashMap *hashMap,int index,Key key,Value value){ 186 | Entry *newHead = malloc(sizeof(Entry)); 187 | newHead->hash = hash; 188 | newHead->key = key; 189 | newHead->value = value; 190 | 191 | hashMap->heads[index] = newHead; 192 | (hashMap->usage)++; 193 | return hashMap; 194 | } 195 | 196 | HashMap *_putInList(HashMap *hashMap,int index,Key key,Value value){ 197 | Entry *lastEntry = hashMap->heads[index]; 198 | while(lastEntry != NULL){ 199 | if (lastEntry->hash == hash){ 200 | return hashMap; 201 | }else{ 202 | lastEntry = lastEntry->next; 203 | } 204 | } 205 | 206 | lastEntry = malloc(sizeof(Entry)); 207 | lastEntry->hash = hash; 208 | lastEntry->key = key; 209 | lastEntry->value = value; 210 | lastEntry->next = hashMap->heads[index]; 211 | 212 | hashMap->heads[index] = lastEntry; 213 | (hashMap->usage)++; 214 | return hashMap; 215 | } 216 | 217 | ``` 218 | 219 | 220 | ### 扩容 221 | 222 | 当hash表保存的键值对数量太多或太少,对hash 表进行扩容和缩容。合理控制内存的使用。 223 | 224 | redis hash中, rehash发生在扩容或缩容阶段,扩容是发生在元素的个数等于哈希表数组的长度时,进行2倍的扩容;缩容发生在当元素个数为数组长度的10%时,进行缩容 225 | 226 | 227 | ## 参考 228 | 229 | 《Algorithms》 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /3 Hash Table/TreeMap in Java.md: -------------------------------------------------------------------------------- 1 | # TreeMap in Java 2 | 3 | 4 | java 中 TreeMap的实现原理。 5 | 6 | 7 | 8 | #### 特点 9 | 10 | * 有序的Key-value 集合,通过红黑树实现; 11 | * 遍历时元素按照键key的自然顺序进行排序,也可以创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法 12 | * TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 13 | 14 | 15 | 16 | #### 引用场景 17 | 18 | * 一致性Hash算法 19 | 20 | 21 | 22 | ## 使用 23 | 24 | ``` 25 | //创建TreeMap对象: 26 | TreeMap treeMap = new TreeMap(); 27 | 28 | //新增元素: 29 | treeMap.put("hello",1); 30 | treeMap.put("world",2); 31 | treeMap.put("my",3); 32 | treeMap.put("name",4); 33 | 34 | 35 | //遍历元素, 按照key排序 36 | Set> entrySet = treeMap.entrySet(); 37 | for(Map.Entry entry : entrySet){ 38 | String key = entry.getKey(); 39 | Integer value = entry.getValue(); 40 | System.out.println("TreeMap元素的key:"+key+",value:"+value); 41 | } 42 | 43 | String firstKey = treeMap.firstKey();//获取集合内第一个元素 44 | 45 | String lastKey =treeMap.lastKey();//获取集合内最后一个元素 46 | 47 | String lowerKey =treeMap.lowerKey("jiaboyan");//获取集合内的key小于"jiaboyan"的第一个key 48 | 49 | String ceilingKey =treeMap.ceilingKey("jiaboyan");//获取集合内的key大于等于"jiaboyan"的第一个key 50 | 51 | ``` 52 | 53 | ## 原理 54 | 55 | 56 | 参考 [红黑树](../Tree/9-红黑树\ R-B\ tree/红黑树.md) 57 | 58 | 59 | ## 应用 60 | 61 | 62 | 63 | ### 一致性Hash算法 64 | 65 | 一致性Hash算法解决的问题是: 数据均匀的分片存储在不同的机器节点上,且在机器节点上发送增删时 (扩容或者缩容时) ,最少数据集的映射(rehash) 规则发生改变。 66 | 67 | 简单说下原理: 68 | 69 | treeMap 中 key 是 机器节点node 的 hash值, value 是机器节点 IP:port ; 使用TreeMap的 ceilingKey(hash) 这个 API 可以获得 第一个大于 这个 hash值的 节点 70 | 71 | ``` 72 | public class Demo { 73 | 74 | private static String[] servers = {“ip1”, “1p2”, “ip3"}; 75 | 76 | private TreeMap treeMap; // 77 | 78 | /* 一个数据key,会被分片到哪个机器上 */ 79 | public String shardingServer(String key) { 80 | 81 | int dataHash = hash(key); 82 | 83 | //怎么找大于 data_hash 值 的第一个节点? 借助 TreeMap 结构 84 | String node = getServer(dataHash) 85 | 86 | return node; 87 | } 88 | 89 | /* hash 函数*/ 90 | public int hash(String key){ 91 | 92 | } 93 | 94 | //寻找第一个大于 hash 值的 node 95 | private String getServer(String hash) { 96 | 97 | return treeMap.ceilingKey(hash) 98 | } 99 | 100 | } 101 | ``` 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /3 Hash Table/hash_ref.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | #include "stdlib.h" 3 | #include "string.h" 4 | #include "time.h" 5 | 6 | #define DEFAULT_MAP_SIZE 1<<5 7 | #define MAX_MAP_SIZE 1<<30 8 | #define CAPACITY_FACTOR 0.6f 9 | #define MAX_KEY_LENGTH 1<<3 10 | 11 | typedef unsigned int Hash; 12 | typedef char* Key; 13 | typedef int Value; 14 | typedef struct Entry{ 15 | struct Entry *next; 16 | Hash hash; 17 | Value value; 18 | Key key; 19 | }Entry; 20 | 21 | typedef struct HashMap{ 22 | Entry** heads; 23 | Value nul; 24 | unsigned int size; 25 | unsigned int usage; 26 | unsigned int realCapacity; //size * CAPACITY_FACTOR 27 | unsigned int sizeForIndex; //size - 1 28 | }HashMap; 29 | 30 | HashMap* create(unsigned int); 31 | HashMap* transfer(HashMap*, HashMap*); 32 | Hash hash(char[]); 33 | HashMap* put(HashMap*, Key, Value); 34 | HashMap* putInHeads(HashMap*, int, Hash, Key, Value); 35 | HashMap* putInList(HashMap*, int, Hash, Key, Value); 36 | HashMap* resize(HashMap*); 37 | int find(char[]); 38 | void print(HashMap*); 39 | 40 | HashMap* create(unsigned int size){ 41 | HashMap* hashMap = malloc(sizeof(HashMap)); 42 | int i; 43 | hashMap->size = size; 44 | hashMap->sizeForIndex = size - 1; 45 | hashMap->realCapacity = (unsigned int)(size * CAPACITY_FACTOR); 46 | hashMap->usage = 0; 47 | hashMap->heads = calloc(size, sizeof(Entry*)); 48 | hashMap->nul = 0; 49 | return hashMap; 50 | } 51 | 52 | HashMap* transfer(HashMap* newHashMap, HashMap* oldHashMap){ 53 | unsigned int oldIndex, newIndex; 54 | Entry *oldEntry, *oldNextEntry; 55 | for (oldIndex = 0; oldIndex < oldHashMap->size; oldIndex++){ 56 | if (oldHashMap->heads[oldIndex] != NULL){ 57 | oldEntry = oldHashMap->heads[oldIndex]; 58 | do{ 59 | newIndex = (oldEntry->hash) & (newHashMap->sizeForIndex); 60 | oldNextEntry = oldEntry->next; 61 | oldEntry->next = newHashMap->heads[newIndex]; 62 | newHashMap->heads[newIndex] = oldEntry; 63 | oldEntry = oldNextEntry; 64 | } while (oldEntry != NULL); 65 | } 66 | } 67 | newHashMap->usage = oldHashMap->usage; 68 | newHashMap->nul = oldHashMap->nul; 69 | free(oldHashMap->heads); 70 | oldHashMap->heads = NULL; 71 | free(oldHashMap); 72 | oldHashMap = NULL; 73 | return newHashMap; 74 | } 75 | 76 | Hash hashCode(char* key){ 77 | int offset = 5; 78 | Hash hashCode = 0; 79 | while (*key) hashCode = (hashCode << offset) + (*key++); 80 | return hashCode; 81 | } 82 | 83 | HashMap* put(HashMap* hashMap, Key key, Value value){ 84 | if (key == NULL){ 85 | hashMap->nul = value; 86 | return hashMap; 87 | } 88 | Hash hash = hashCode(key); 89 | int index = hash & (hashMap->sizeForIndex); 90 | 91 | if (hashMap->heads[index] == NULL){ 92 | return putInHeads(hashMap, index, hash, key, value); 93 | } else { 94 | return putInList(hashMap, index, hash, key, value); 95 | } 96 | } 97 | 98 | HashMap* putInHeads(HashMap* hashMap, int index, Hash hash, Key key, Value value){ 99 | Entry* newHead = malloc(sizeof(Entry)); 100 | newHead->hash = hash; 101 | newHead->key = _strdup(key); 102 | newHead->value = value; 103 | newHead->next = NULL; 104 | hashMap->heads[index] = newHead; 105 | (hashMap->usage)++; 106 | if (hashMap->usage > hashMap->realCapacity) return resize(hashMap); 107 | return hashMap; 108 | } 109 | HashMap* putInList(HashMap* hashMap, int index, Hash hash, Key key, Value value){ 110 | Entry* lastEntry = hashMap->heads[index]; 111 | while (lastEntry != NULL){ 112 | if (lastEntry->hash == hash){ 113 | lastEntry->value += value; 114 | return hashMap; 115 | } 116 | else lastEntry = lastEntry->next; 117 | } 118 | lastEntry = malloc(sizeof(Entry)); 119 | lastEntry->hash = hash; 120 | lastEntry->key = _strdup(key); 121 | lastEntry->value = value; 122 | lastEntry->next = hashMap->heads[index]; 123 | hashMap->heads[index] = lastEntry; 124 | (hashMap->usage)++; 125 | if (hashMap->usage > hashMap->realCapacity) return resize(hashMap); 126 | return hashMap; 127 | } 128 | 129 | HashMap* resize(HashMap* hashMap){ 130 | HashMap* newHashMap; 131 | if ((hashMap->size << 1) <= MAX_MAP_SIZE) { 132 | newHashMap = create((hashMap->size << 1)); 133 | newHashMap = transfer(newHashMap, hashMap); 134 | return newHashMap; 135 | } 136 | return hashMap; 137 | } 138 | 139 | Value get(HashMap* hashMap, Key key){ 140 | Hash hash = hashCode(key); 141 | int index = hash & (hashMap->sizeForIndex); 142 | Entry* entry = hashMap->heads[index]; 143 | while (entry != NULL){ 144 | if (entry->hash == hash) return entry->value; 145 | entry = entry->next; 146 | } 147 | return NULL; 148 | } 149 | 150 | void print(HashMap* hashMap){ 151 | unsigned int i; 152 | Entry* entry; 153 | for (i = 0; i < hashMap->size; i++){ 154 | entry = hashMap->heads[i]; 155 | while (entry != NULL){ 156 | printf("%s: %d\n", entry->key, entry->value); 157 | entry = entry->next; 158 | } 159 | } 160 | } 161 | 162 | int main(){ 163 | clock_t start, end; 164 | HashMap* hashMap = create(DEFAULT_MAP_SIZE); 165 | FILE* fp; 166 | char filePath[0xFF]; 167 | char line[5]; 168 | char key[MAX_KEY_LENGTH]; 169 | 170 | printf("input file path: "); 171 | scanf_s("%s", filePath, 0xFF); 172 | if (fopen_s(&fp, filePath, "r") == 0){ 173 | start = clock(); 174 | while (fgets(line, 5, fp) != NULL){ 175 | line[3] = '\0'; 176 | hashMap = put(hashMap, line, 1); 177 | } 178 | end = clock(); 179 | printf("\n%fs", (float)(end - start)/1000); 180 | fclose(fp); 181 | print(hashMap); 182 | } else { 183 | printf("can't open file"); 184 | } 185 | return 0; 186 | } -------------------------------------------------------------------------------- /3 Hash Table/hashmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/3 Hash Table/hashmap.png -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /btree.c: -------------------------------------------------------------------------------- 1 | /* 2 | ### B树基础 3 | 4 | B树即B-tree(B树就是B-tree),B是balanced,也就是平衡的意思。 5 | 6 | B树又叫平衡多路查找树,节点的直接点个数可以多于2个,一颗m阶的B树: 7 | 8 | 1) 树中每个节点做多含有m个子节点 9 | 2) 根节点不是叶子节点,至少有2个孩子 10 | 3) 所有叶子节点在同一层 11 | 4) 除根节点和叶子节点外,每个分支节点至少有[m/2]个子树 12 | 5) 有j个孩子的非叶子节点有 j-1 个关键码,关键码递增一次排列 13 | 14 | 15 | ### B树应用 16 | 17 | 数据库 18 | 文件系统 19 | 20 | 21 | ### B树存储结构 22 | 23 | 24 | 25 | ### B树问题和延伸阅读 26 | 27 | 28 | */ 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /btree/btree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/4 Tree/1-二叉树 /btree/btree -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /btree/public.h: -------------------------------------------------------------------------------- 1 | // 2 | // public.h 3 | // DS 4 | // 5 | // Created by mac on 13-9-8. 6 | // Copyright (c) 2013年 xiaoran. All rights reserved. 7 | // 8 | 9 | #ifndef DS_public_h 10 | #define DS_public_h 11 | 12 | //#include 13 | #include "stdlib.h" // 后来,换到mac环境下,#include 应该为 或 #include "stdlib.h" 14 | #include 15 | #include // for memset 16 | 17 | 18 | typedef char ElemType ; // 也可能是一个复杂的复合类型 19 | typedef int Status; 20 | 21 | 22 | 23 | #define OK 1 24 | #define ERROR 0 25 | 26 | typedef int bool; 27 | #define YES 1 28 | #define NO 0 29 | 30 | #define DEFAULT_SIZE 10 31 | #define OUT_OF_BOUND -1 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /btree/队列.h: -------------------------------------------------------------------------------- 1 | // 2 | // 队列.h 3 | // 只在一段进行插入,另一端删除元素 4 | // 5 | // Created by mac on 13-9-8. 6 | // Copyright (c) 2013年 xiaoran. All rights reserved. 7 | // 8 | 9 | #ifndef DS____h 10 | #define DS____h 11 | 12 | #include "public.h" 13 | #include "bintree.c" 14 | 15 | typedef BiTNode ElemType; 16 | 17 | //队列的顺序存储 18 | typedef struct Node{ 19 | 20 | ElemType *elem; 21 | ElemType *head; 22 | ElemType *tail; 23 | int length; // 当前队列的长度 24 | int size; // 队列容器的长度,在队列慢得时候可以扩容 25 | 26 | 27 | }SqQueue; 28 | 29 | // 30 | // 在队列采用顺序存储时,有一个毛病,就是队列操作一段时间后,头指针到了队列容器的尾部,而头指针前面的容器内存不可用了, 31 | //造成内存极大的浪费,这个问题可以通过循环队列来解决。但是在 32 | // 链式队列上则不存在这样的问题 33 | // 34 | 35 | 36 | typedef struct QNode{ 37 | 38 | struct QNode *next; 39 | ElemType elem; 40 | 41 | }LinkQueue; 42 | 43 | 44 | // 队列需要维护两个 指针 (队头指针,队尾指针) 45 | 46 | typedef struct{ 47 | 48 | LinkQueue *head; 49 | LinkQueue *tail; 50 | int length; 51 | 52 | }Queue; 53 | 54 | 55 | Status initQueue(Queue *queue);// 带头结点,没有引用传值,就用指针的指针吧 56 | bool isEmpty(Queue *q); 57 | int length(Queue *q); 58 | 59 | 60 | // 在头部插入,尾部删除 61 | Status getHead(Queue *q,ElemType *e); 62 | Status enQueue(Queue *q,ElemType e); 63 | Status deQueue(Queue *q,ElemType *e); 64 | 65 | void traveser(Queue *q); 66 | 67 | 68 | Status initQueue(Queue *queue){// 带头结点,没有引用传值,就用指针的指针吧 69 | 70 | LinkQueue *lq = (LinkQueue *)malloc(sizeof(LinkQueue)); 71 | if (!lq) { 72 | return ERROR; 73 | } 74 | 75 | lq->elem = 0; 76 | lq->next = NULL; 77 | 78 | (queue)->head = (queue)->tail = lq; // -> 优先级高于 * ,老老实实打上括号 79 | (queue)->length = 0; 80 | 81 | return OK; 82 | 83 | } 84 | 85 | bool isEmpty(Queue *q){ 86 | 87 | return (q->head == q->tail); 88 | 89 | } 90 | 91 | int length(Queue *q){ 92 | 93 | // int ret; 94 | // LinkQueue *p = q->head; 95 | // while (p != q->tail) { 96 | // ret++; 97 | // p = p->next; 98 | // 99 | // } 100 | 101 | return q->length; 102 | 103 | } 104 | 105 | Status getHead(Queue *q,ElemType *e){ 106 | 107 | LinkQueue *p = q->head->next; 108 | if (!p) { 109 | *e=0; 110 | return ERROR; 111 | } 112 | 113 | *e = p->elem; 114 | return OK; 115 | 116 | } 117 | 118 | // 入队(加入到队尾) 119 | Status enQueue(Queue *q,ElemType e){ 120 | 121 | LinkQueue *newNode = (LinkQueue *)malloc(sizeof(LinkQueue)); 122 | if (!newNode) { 123 | return ERROR; 124 | } 125 | newNode->elem = e; 126 | newNode->next = NULL; 127 | q->tail->next = newNode; 128 | q->tail = newNode; 129 | 130 | q->length++; 131 | 132 | return OK; 133 | 134 | } 135 | 136 | //出队(队头). 注意,可能只有1个元素,造成队尾指针丢失 137 | Status deQueue(Queue *q,ElemType *e){ 138 | 139 | LinkQueue *p = q->head->next; 140 | if (!p) {// 队列空 141 | return ERROR; 142 | } 143 | 144 | if (e) { 145 | *e = p->elem ; 146 | } 147 | 148 | LinkQueue *temp = p->next; 149 | q->head->next = temp; 150 | if (p == q->tail) { 151 | q->tail = q->head; 152 | } 153 | free(p); 154 | 155 | 156 | q->length--; 157 | 158 | return OK; 159 | 160 | } 161 | 162 | 163 | void traveser(Queue *q){ 164 | 165 | LinkQueue *p = q->head->next; 166 | while (NULL != p) { 167 | printf("%d\n",p->elem); 168 | p=p->next; 169 | } 170 | 171 | } 172 | 173 | 174 | 175 | #endif 176 | -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /rbtree.c: -------------------------------------------------------------------------------- 1 | /* 2 | ### 红黑树基础 3 | 4 | 一种自平衡的二叉查找树。在每个节点中增加一个存储位表示节点的颜色,可以是red或black 5 | 6 | 红黑树比AVL树优秀在哪? 7 | 8 | 9 | 10 | 11 | 12 | 5个性质 13 | 14 | 性质1. 节点是红色或黑色。 15 | 性质2. 根是黑色。 16 | 性质3. 所有叶子都是黑色(叶子是NIL节点)。 17 | 性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) 18 | 性质5. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。 19 | 20 | 21 | 22 | ### 红黑树应用 23 | 24 | 25 | ### 存储结构 26 | 27 | 28 | ### 问题与延伸阅读 29 | 30 | 31 | */ 32 | 33 | typedef int ElemType 34 | 35 | typedef struct node{ 36 | 37 | int color; 38 | ElemType key; 39 | struct node *lChild,*rChild,*pChild; 40 | 41 | }*RBTree; 42 | 43 | 44 | int rbtree_insert(RBTree *tree,ElemType key); 45 | int rbtree_remove(RBTree *tree,ElemType key); 46 | int rbtree_search(RBTree *tree,ElemType key); 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /4 Tree/1-二叉树 /suffix_tree.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | ### 后缀树(suffix tree)基础 4 | 5 | 又叫后缀trie,与trie最大不同在于:字符串集合由指定的后缀子串组成。 6 | 7 | 8 | 很适合用来操作字符串的子串。 用于字符串的匹配和查询 9 | 10 | 11 | ### 后缀树应用 12 | 13 | 14 | 从目标串T中判断是否包含模式串P(时间复杂度接近KMP算法); 15 | 从目标串T中查找最长的重复子串; 16 | 从目标串T1和T2中查找最长公共子串; 17 | Ziv-Lampel无损压缩算法; 18 | 从目标串T中查找最长的回文子串; 19 | 20 | 21 | 22 | ### 存储结构 23 | 24 | 25 | ### suffix tree 问题与延伸阅读 26 | 27 | 后缀数组 28 | 29 | 30 | */ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /4 Tree/2-二叉查找树/BiSearchTree/README.md: -------------------------------------------------------------------------------- 1 | 2 | ###环境 3 | 4 | 5 | ###编译 6 | 7 | 8 | -------------------------------------------------------------------------------- /4 Tree/2-二叉查找树/BiSearchTree/bisearch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/4 Tree/2-二叉查找树/BiSearchTree/bisearch -------------------------------------------------------------------------------- /4 Tree/2-二叉查找树/BiSearchTree/bisearchtree.h: -------------------------------------------------------------------------------- 1 | // 2 | // bisearchtree.h 3 | // BiSearchTree 4 | // 5 | // Created by nonstriater on 14-2-22. 6 | // 7 | // 8 | 9 | /** 10 | * 11 | 二叉查找树(Binary search tree),也叫有序二叉树(Ordered binary tree),排序二叉树(Sorted binary tree)。是指一个空树或者具有下列性质的二叉树: 12 | 13 | 1. 若任意节点的左子树不为空,则左子树上所有的节点值小于它的根节点值 14 | 2. 若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值 15 | 3. 任意节点左右子树也为二叉查找树 16 | 4. 没有键值相等的节点 17 | 18 | */ 19 | 20 | #ifndef BiSearchTree_bisearchtree_h 21 | #define BiSearchTree_bisearchtree_h 22 | 23 | typedef int ElemType; 24 | typedef struct BiSearchTree{ 25 | 26 | ElemType key; 27 | struct BiSearchTree *lChild; 28 | struct BiSearchTree *rChild; 29 | 30 | 31 | }BiSearchTree; 32 | 33 | 34 | /** 35 | * 创建二叉查找树 36 | * 37 | * @return 指向一颗空树的指针 38 | */ 39 | BiSearchTree *bisearch_tree_new(); 40 | 41 | /** 42 | * 插入节点 43 | * 44 | * @param tree tree 45 | * @param node 节点值 46 | */ 47 | BiSearchTree *bisearch_tree_insert(BiSearchTree *tree,ElemType node); 48 | 49 | /** 50 | * 查找节点 51 | * 52 | * @param tree tree 53 | * @param node 节点值 54 | * @return -1失败 0 成功 55 | */ 56 | int bisearch_tree_search(BiSearchTree *tree,ElemType node); 57 | 58 | //删除节点 59 | int bisearch_tree_delete(BiSearchTree **tree,ElemType node); 60 | 61 | // 遍历节点 62 | void bisearch_tree_inorder_traversal(BiSearchTree *tree); 63 | 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /4 Tree/2-二叉查找树/BiSearchTree/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // main.c 3 | // BiSearchTree 4 | // 5 | // Created by nonstriater on 14-2-22. 6 | // 7 | // 8 | 9 | #include 10 | #include "bisearchtree.h" 11 | 12 | int main(){ 13 | 14 | printf("begining...\n"); 15 | BiSearchTree *searchTree; 16 | searchTree = bisearch_tree_new(); 17 | searchTree = bisearch_tree_insert(searchTree,19);// 第一次insert 18 | bisearch_tree_insert(searchTree,3); 19 | bisearch_tree_inorder_traversal(searchTree); 20 | 21 | bisearch_tree_insert(searchTree,122); 22 | bisearch_tree_insert(searchTree,55); 23 | bisearch_tree_insert(searchTree,65); 24 | bisearch_tree_insert(searchTree,180); 25 | 26 | printf("inorder traversal\n"); 27 | bisearch_tree_inorder_traversal(searchTree); 28 | 29 | 30 | //=======search ============================================ 31 | printf("find node...\n"); 32 | int find = 55; 33 | if(bisearch_tree_search(searchTree,find)<0){ 34 | printf("node %d not exits\n",find); 35 | }else{ 36 | printf("node %d exits\n",find); 37 | } 38 | 39 | find = 33; 40 | if(bisearch_tree_search(searchTree,find)<0){ 41 | printf("node %d not exits\n",find); 42 | }else{ 43 | printf("node %d exits\n",find); 44 | } 45 | 46 | 47 | //====== delete,会影响到serachTree值,所以应该传递serachTree的指针 ====================================== 48 | printf("delete node..\n"); 49 | find = 4; 50 | if(bisearch_tree_delete(&searchTree,find)<0){ 51 | printf("delete node %d failure\n",find); 52 | }else{ 53 | printf("delete node %d success\n",find); 54 | bisearch_tree_inorder_traversal(searchTree); 55 | bisearch_tree_insert(searchTree,find); 56 | } 57 | 58 | 59 | find = 65;//叶子 60 | if(bisearch_tree_delete(&searchTree,find)<0){ 61 | printf("delete node failure\n"); 62 | }else{ 63 | printf("delete node %d success\n",find); 64 | bisearch_tree_inorder_traversal(searchTree); 65 | bisearch_tree_insert(searchTree,find); 66 | } 67 | 68 | 69 | 70 | find = 55;//单分支 71 | if(bisearch_tree_delete(&searchTree,find)<0){ 72 | printf("delete node failure\n"); 73 | }else{ 74 | printf("delete node %d success\n ",find); 75 | bisearch_tree_inorder_traversal(searchTree); 76 | bisearch_tree_insert(searchTree,find); 77 | } 78 | 79 | find = 122;//双分支 80 | if(bisearch_tree_delete(&searchTree,find)<0){ 81 | printf("delete node failure\n"); 82 | }else{ 83 | printf("delete node %d success\n",find); 84 | bisearch_tree_inorder_traversal(searchTree); 85 | bisearch_tree_insert(searchTree,find); 86 | } 87 | 88 | return 0; 89 | } 90 | 91 | /* 92 | 93 | 测试结果: 94 | 95 | begining... 96 | insert first node 19 97 | insert node 3 to left of 19 98 | 3 99 | 19 100 | insert node 122 to right of 19 101 | insert node 55 to left of 122 102 | insert node 65 to right of 55 103 | insert node 180 to right of 122 104 | inorder traversal 105 | 3 106 | 19 107 | 55 108 | 65 109 | 122 110 | 180 111 | find node... 112 | node 55 exits 113 | node 33 not exits 114 | delete node.. 115 | 树为空,或想要删除的节点不存在 116 | delete node 4 failure 117 | delete node 65 success 118 | 3 119 | 19 120 | 55 121 | 122 122 | 180 123 | insert node 65 to right of 55 124 | delete node 55 success 125 | 3 126 | 19 127 | 65 128 | 122 129 | 180 130 | insert node 55 to left of 65 131 | delete node 122 success 132 | 3 133 | 19 134 | 55 135 | 65 136 | 180 137 | insert node 122 to left of 180 138 | 139 | 140 | 141 | */ 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /4 Tree/2-二叉查找树/二叉查找树.md: -------------------------------------------------------------------------------- 1 | # 二叉查找树BST 2 | 3 | 二叉查找树(Binary search tree),也叫`有序二叉树(Ordered binary tree)`,`排序二叉树(Sorted binary tree)`。 4 | 5 | 是指一个空树或者具有下列性质的二叉树: 6 | 7 | 1. 若任意节点的左子树不为空,则左子树上所有的节点值小于它的根节点值 8 | 2. 若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值 9 | 3. 任意节点左右子树也为二叉查找树 10 | 4. 没有键值(key)相等的节点 11 | 12 | 13 | 有序的二叉查找树,中序遍历结果是递增的。 `左小右大` 14 | 15 | 16 | ``` 17 | typedef int ElemType; 18 | typedef struct BiSearchTree{ 19 | ElemType key; 20 | struct BiSearchTree *lChild; 21 | struct BiSearchTree *rChild; 22 | }BiSearchTree; 23 | BiSearchTree *bisearch_tree_insert(BiSearchTree *tree,ElemType node); 24 | int bisearch_tree_delete(BiSearchTree **tree,ElemType node); 25 | int bisearch_tree_search(BiSearchTree *tree,ElemType node); 26 | ``` 27 | 28 | 29 | ### 删除节点 30 | 31 | 删除节点,需要重建排序树 32 | 33 | 1) 删除节点是叶子节点(分支为0),结构不破坏 34 | 2)删除节点只有一个分支(分支为1),结构也不破坏 35 | 3)删除节点有2个分支,此时删除节点 ; 需要重建树 36 | 37 | 38 | 思路一: 选左子树的最大节点,或右子树最小节点替换 39 | 40 | ``` 41 | int bisearch_tree_delete(BiSearchTree **tree,ElemType node){ 42 | 43 | if (NULL==tree) { 44 | return -1; 45 | } 46 | // 查找删除目标节点 47 | BiSearchTree *target=*tree,*parent=NULL; 48 | while (NULL!=target) { 49 | if (nodekey) { 50 | parent=target; 51 | target=target->lChild; 52 | }else if(node==target->key){ 53 | break; 54 | }else{ 55 | parent=target; 56 | target=target->rChild; 57 | } 58 | } 59 | 60 | if (NULL==target) { 61 | printf("树为空,或想要删除的节点不存在\n"); 62 | return -1; 63 | } 64 | 65 | //该节点为叶子节点,直接删除 66 | if (!target->rChild && !target->lChild) 67 | { 68 | if (NULL==parent) {////只有一个节点的二叉查找树 69 | *tree=NULL; 70 | }else{ 71 | if (target->key>parent->key) { 72 | parent->rChild=NULL; 73 | }else{ 74 | parent->lChild=NULL; 75 | } 76 | 77 | } 78 | free(target);//父节点处理,不然野指针,造成崩溃 79 | } 80 | 81 | else if(!target->rChild){ //右子树空则只需重接它的左子树,用左子树替换掉当前要删除的节点 82 | BiSearchTree *del=target->lChild; 83 | target->key = target->lChild->key; 84 | target->lChild=target->lChild->lChild; 85 | target->rChild=target->lChild->rChild; 86 | 87 | free(del); 88 | } 89 | else if(!target->lChild){ //左子树空只需重接它的右子树 90 | BiSearchTree *del=target->rChild; 91 | target->key = target->rChild->key; 92 | target->lChild=target->rChild->lChild; 93 | target->rChild=target->rChild->rChild; 94 | 95 | free(del); 96 | } 97 | 98 | else{ //左右子树均不空,p,t 2个指针一前以后,将左子树最大的节点(肯定是一个最右的节点)替换到删除的节点后,还需要处理左子树最大节点的左子树 99 | 100 | BiSearchTree *p=target,*t=target->lChild; 101 | while (t->rChild) { 102 | p = t; 103 | t=t->rChild; 104 | }// 找到左子树最大的,是删除节点的直接“前驱” 105 | 106 | target->key = t->key; 107 | 108 | if (p!=target) { 109 | p->rChild = t->lChild; 110 | }else{ 111 | target->lChild = t->lChild; 112 | } 113 | 114 | free(t); 115 | } 116 | return 0; 117 | } 118 | ``` 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /4 Tree/3-平衡树AVL/AVLTree.c: -------------------------------------------------------------------------------- 1 | /* 2 | 记于2014-2-28 by @nonstriater 3 | */ 4 | 5 | #include 6 | #include 7 | 8 | 9 | #define LH 1 10 | #define EH 0 11 | #define RH -1 12 | 13 | 14 | typedef int KEY_TYPE; 15 | typedef struct node{ 16 | 17 | KEY_TYPE key; 18 | int height; // 平衡因子 19 | struct node *lChild; 20 | struct node *rChild; 21 | 22 | }AVLTree; 23 | 24 | 25 | 26 | // 27 | void avltree_rr_rotate(AVLTree **tree){ 28 | 29 | AVLTree *right= *tree->rChild; 30 | right->lChild = *tree; 31 | *tree->rChild = right->lChild; 32 | 33 | } 34 | 35 | // 36 | void avltree_ll_rotate(AVLTree **tree){ 37 | 38 | AVLTree *left = *tree->lChild; 39 | left->rChild = *tree; 40 | *tree->lChild = left->rChild 41 | } 42 | 43 | // 44 | void avltree_lr_rotate(AVLTree **tree){ 45 | 46 | 47 | } 48 | 49 | void avltree_rl_rotate(AVLTree **tree){ 50 | 51 | 52 | } 53 | 54 | 55 | void avltree_left_balance(AVLTree **root) 56 | { 57 | AVLTree *left,*lr; 58 | left=(*root)->lChild; 59 | switch(left->height) 60 | { 61 | //检查T的左子树平衡度,并作相应的平衡处理 62 | case LH://新节点插入在T的左孩子的左子树上,做单右旋处理 63 | (*root)->height=left->height=EH; 64 | avltree_ll_rotate(root); 65 | break; 66 | case RH://新插入节点在T的左孩子的右子树上,做双旋处理 67 | lr=left->rChild; 68 | switch(lr->height) 69 | { 70 | case LH: 71 | (*root)->height=RH; 72 | left->height=EH; 73 | break; 74 | case EH: 75 | (*root)->height=left->height=EH; 76 | break; 77 | case RH: 78 | (*root)->height=EH; 79 | left->height=LH; 80 | break; 81 | } 82 | lr->height=EH; 83 | L_Rotate(&(*T)->lChild); 84 | R_Rotate(T); 85 | } 86 | } 87 | 88 | 89 | void avltree_right_balance(AVLTree **root) 90 | { 91 | AVLTree right,rl; 92 | right=(*root)->rChild; 93 | switch(right->height) 94 | { 95 | case RH://新节点插在T的右孩子的右子树上,要做单左旋处理 96 | (*root)->height=right->height=EH; 97 | avltree_rr_rotate(root); 98 | break; 99 | case LH://新节点插在T的右孩子的左子树上,要做双旋处理 100 | rl=right->lChild; 101 | switch(rl->height) 102 | { 103 | case LH: 104 | (*root)->height=EH; 105 | right->height=RH; 106 | break; 107 | case EH: 108 | (*root)->height=right->height=EH; 109 | break; 110 | case RH: 111 | (*root)->height=LH; 112 | right->height=EH; 113 | break; 114 | } 115 | rl->height=EH; 116 | R_Rotate(&(*root)->rChild); 117 | L_Rotate(T); 118 | } 119 | } 120 | 121 | 122 | 123 | 124 | // 插入一个节点key 125 | /* 126 | 127 | 算法描述: 128 | 1)如果root为null,则插入一个数据元素为kx 的新结点作为T 的根结点 129 | 130 | 2)如果key和root->key相等,不插入 131 | 132 | 3)如果keykey, 插在root左子树上: 133 | 134 | 135 | 136 | 137 | */ 138 | AVLTree* avltree_insert(AVLTree* root, KEY_TYPE key){ 139 | 140 | 141 | if (NULL==root) 142 | { 143 | 144 | root = (AVLTree *)malloc(sizeof(struct AVLTree)); 145 | if (!root) 146 | { 147 | printf("内存分配失败,插入节点失败\n"); 148 | return root; 149 | } 150 | root.key = key; 151 | root.lChild = NULL; 152 | root.rChild = NULL; 153 | root.height = 0; 154 | } 155 | 156 | 157 | else if (key=root->key) 158 | { 159 | printf("节点 %d 已存在 \n", key); 160 | } 161 | 162 | else if (keykey)//插入左 163 | { 164 | root->lChild = avltree_insert(root->lChild,key); 165 | if (root->lChild->height-root->rChild->height == 2)//不平衡 166 | { 167 | if (keylChild->key)// LL型 168 | { 169 | root = avltree_ll_rotate(tree); 170 | } 171 | 172 | if (key>root->lChild->key)//LR 173 | { 174 | root = avltree_lr_rotate(tree); 175 | } 176 | 177 | } 178 | } 179 | 180 | else if (key>root->key){ 181 | 182 | root->rChild = avltree_insert(root->rChild,key); 183 | if (keyrChild->key)// RL 184 | { 185 | root = avltree_rl_rotate(tree); 186 | } 187 | 188 | if (key>root->rChild->key)// RR 189 | { 190 | root = avltree_rr_rotate(tree); 191 | 192 | } 193 | 194 | } 195 | 196 | return root; 197 | } 198 | 199 | // 删除一个节点 200 | AVLTree* avltree_delete(AVLTree* root, KEY_TYPE key){ 201 | 202 | 203 | } 204 | 205 | 206 | // 判断是否为AVL树 207 | int avltree_isbalance(AVLTree *root){ 208 | 209 | 210 | } 211 | 212 | 213 | // 查找 214 | AVLTree* avltree_search(AVLTree *root,KEY_TYPE key){ 215 | 216 | if (root==NULL) 217 | { 218 | return NULL; 219 | } 220 | 221 | if (root->key == key) 222 | { 223 | return root; 224 | } 225 | 226 | else if (root->key>key) 227 | { 228 | return avltree_search(root->lChild,key); 229 | } 230 | 231 | else{ 232 | 233 | return avltree_search(root->rChild,key); 234 | } 235 | 236 | 237 | } 238 | 239 | // 中序遍历 240 | void avltree_inorder_traversal(AVLTree* root){ 241 | 242 | if (root) 243 | { 244 | avltree_inorder_traversal(root->lChild); 245 | printf(节点值=%d,左右子树的高度差=%d\n,root->key,root->height); 246 | avltree_inorder_traversal(root->rChild); 247 | } 248 | 249 | 250 | } 251 | 252 | 253 | 254 | // test 255 | 256 | int main(){ 257 | 258 | AVLTree *avlTree=NULL; 259 | printf("插入节点,创建一个AVL树...\n"); 260 | 261 | int values[] = {11,7,222,456,23,8,65,124,88,2,54}; 262 | for (int i = 0; i < sizeof(values)/sizeof(int); ++i) 263 | { 264 | printf("插入节点 %d\n", values[i]); 265 | avlTree = avltree_insert(avlTree,values[i]); 266 | avltree_inorder_traversal(avltree); 267 | printf("\n\n"); 268 | } 269 | 270 | printf("中序遍历结果:\n"); 271 | avltree_inorder_traversal(avlTree); 272 | 273 | 274 | printf("删除一个存在的节点 %d\n", values[1]); 275 | avlTree=avltree_delete(avlTree,values[1]); 276 | printf("中序遍历结果:\n"); 277 | avltree_inorder_traversal(avlTree); 278 | 279 | 280 | printf("删除一个不存在的节点 %d\n",111 ); 281 | avltree_delete(avlTree,111); 282 | printf("中序遍历结果:\n"); 283 | avltree_inorder_traversal(avlTree); 284 | 285 | printf("查找一个存在的节点 %d\n", values[3]); 286 | avltree_search(avltree,values[3]); 287 | 288 | printf("查找一个不存在的节点 %d\n",51); 289 | avltree_search(avltree,51); 290 | 291 | 292 | return 0; 293 | } 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /4 Tree/3-平衡树AVL/README.md: -------------------------------------------------------------------------------- 1 | # 自平衡二叉查找树(AVL tree) 2 | 3 | 自平衡二叉查找树(AVL tree): 首先也是二次查找树,其实 任何2个子树的高度差不大于1 4 | 在删除,插入的过程中不断调整子树的高度,保证查找操作平均和最坏情况下都是O(logn) 5 | 6 | Adelson-Velskii 和 Landis 1962年 创造, 因此叫做 AVL 树。 7 | 8 | 1) 平衡因子 -1 0 1 节点是正常的。平衡因子 = 左子树高度-右字数高度 9 | 2) 除此之外的节点是不平衡的,需要重新平衡这个树。也就是AVL旋转 10 | 11 | 12 | AVL 实际使用案例 13 | 14 | * LLVM 的 ImmutableSet,其底层的实现选择为 AVL 树 15 | * 《一种基于二叉平衡树的P2P覆盖网络的研究》论文 16 | 17 | 18 | 19 | 20 | ## 插入节点 21 | 22 | a: 左旋转(RR型:节点x的右孩子的右孩子上插入新元素)平衡因子由-1 -》-2 时,需要绕节点x左旋转 23 | b:右旋转(LL型:节点X的左孩子的左孩子上插入新元素) 平衡因子有1-》2,右旋转 24 | c: 先左后右旋转:(LR型:树中节点X的左孩子的右孩子上插入新元素) 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转 25 | d: 先右后左旋转:(RL型:节点X的右孩子的左孩子上插入新元素) 26 | 27 | 28 | 6 6 6 6 29 | / \ / \ 30 | 5 7 3 9 31 | / \ \ / 32 | 3 8 5 7 33 | (LL型) (RR) (LR) (RL) 34 | 35 | 36 | 37 | ## 删除节点 38 | 39 | 可以看到,为了保证高度平衡,插入和删除操作代价增加 40 | 41 | 42 | ## AVL 实现过程中的问题 43 | 44 | AVL 是严格的平衡二叉树,平衡条件必须满足,即所有节点的左右子树高度差的绝对值不超过1; 45 | 46 | 执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。 47 | 48 | 由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部平衡而不是非常严格整体平衡的红黑树()。 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /4 Tree/4-字典树Trie/README.md: -------------------------------------------------------------------------------- 1 | # 字典树trie 2 | 3 | 4 | `字典树`,英文名`Trie树`,Trie一词来自retrieve,发音为/tri:/ “tree”,也有人读为/traɪ/ “try”, 5 | 又称`单词查找树` 或 `前缀树`,Trie树,是一种树形结构(多叉树)。 6 | 7 | trie,又称为前缀树或字典树,是一种有序树,用于保存关联数组。 8 | 9 | 1. 除根节点不包含字符,每个节点都包含一个字符 10 | 2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串 11 | 3. 每个节点的所有子节点包含的字符都不相同(保证每个节点对应的字符串都不一样) 12 | 13 | 比如: 14 | 15 | ``` 16 | / \ 17 | / | \ 18 | t a i 19 | / \ \ 20 | o e n 21 | /|\ / 22 | a d n n 23 | ``` 24 | 25 | 上面的Trie树,可以表示字符串集合{“a”, “to”, “tea”, “ted”, “ten”, “i”, “in”, “inn”} 。 26 | 27 | trie树把每个关键字保存在一条路径上,而不是一个节点中 28 | 两个有公共前缀的关键字,在Trie树中前缀部分的路径相同,所以Trie树又叫做前缀树(Prefix Tree)。 29 | 30 | 31 | 32 | ### trie 优缺点 33 | 34 | 它的优点是: 35 | 36 | 1. 插入和查询的效率很高,都是O(m),其中 m 是待插入/查询的字符串的长度 37 | 2. Trie树可以对关键字按字典序排序 38 | 3. 利用字符串的公共前缀来最大限度地减少无谓的字符串比较,提高查询效率 39 | 40 | 缺点: 41 | 42 | 1. trie 树比较费内存空间,在处理大数据时会内存吃紧 43 | 2. 当hash函数较好时,Hash查询效率比 trie 更优 44 | 45 | [知乎这里](http://www.zhihu.com/question/27168319)有个问题:`10万个串找给定的串是否存在`, 对trie和hash两种方案给出了讨论。 46 | 47 | 48 | [DATrie](https://github.com/kmike/datrie) 是使用python实现的双数组trie树, 双数组可以减少内存的使用量 。有关 double-array trie,可以参考[这篇论文](http://linux.thai.net/~thep/datrie/datrie.html) 49 | 50 | 51 | ### trie应用 52 | 53 | 典型应用是:前缀查询,字符串查询,排序 54 | 55 | * 用于统计,排序和保存大量的字符串(但不仅限于字符串) 56 | * 经常被搜索引擎系统用于文本词频统计 57 | * 排序大量字符串 58 | * 用于索引结构 59 | * 敏感词过滤 60 | 61 | 62 | ### 实际应用问题 63 | 64 | 1. 给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置 65 | 分析思路一:trie树 ,找到这个字符串查询操作就可以了,如何知道出现的第一个位置呢?我们可以在trie树中加一个字段来记录当前字符串第一次出现的位置。 66 | 67 | 2. 已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串 68 | 69 | 3. 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。 70 | 分析:trie树查询单词的应用。先建立N个熟词的前缀树,然后按文章的单词一次查询。 71 | 72 | 4. 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。 73 | 分析:先用不良单词建立trie树,然后过滤文本(每个单词都在trie树上查询,查询的复杂度O(1),效率非常高),这正是`敏感词过滤系统(或垃圾评论系统)`的原理。 74 | 75 | 5. 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出 76 | 分析:这是trie树排序的典型应用,建立N个单词的trie树,然后线序遍历整个树,就可以达到效果。 77 | 78 | 79 | 80 | 81 | ## trie树存储结构和基本操作 82 | 83 | 最简单实现 ---- 26个字母表 `a-z` (没有考虑数字,大小写,其他字符如 `=-*/`) 84 | 85 | trie 树存储结构 86 | 87 | * 用数组存储,浪费空间;如果系统中存在大量字符串,且这些字符串基本没有公共前缀,trie树将消耗大量内存 88 | * 用链表存储,查询时需要遍历链表,查询效率有所降低 89 | 90 | 91 | 92 | ``` 93 | define ALPHABET_NUM 26 94 | typedef struct trie_node{ 95 | char value; 96 | bool isKey;/*是否代表一个关键字*/ 97 | int count; /*可用于词频统计,表示关键字出现的次数*/ 98 | struct Node *subTries[ALPHABET]; 99 | }*Trie 100 | 101 | Trie Trie_create(); 102 | int Trie_insert(Trie trie,char *word); // 插入一个单词 103 | int Trie_search(Trie trie,char *word);// 查找一个单词 104 | int Trie_delete(Trie trie,char *word);// 删除一个单词 105 | 106 | Trie Trie_create(){ 107 | trie_node* pNode = new trie_node(); 108 | pNode->count = 0; 109 | for(int i=0; ichildren[i] = NULL; 111 | return pNode; 112 | } 113 | 114 | void trie_insert(trie root, char* key) 115 | { 116 | trie_node* node = root; 117 | char* p = key; 118 | while(*p) 119 | { 120 | if(node->children[*p-'a'] == NULL) 121 | { 122 | node->children[*p-'a'] = create_trie_node(); 123 | } 124 | node = node->children[*p-'a']; 125 | ++p; 126 | } 127 | node->count += 1; 128 | } 129 | 130 | /** 131 | * 查询:不存在返回0,存在返回出现的次数 132 | */ 133 | int trie_search(trie root, char* key) 134 | { 135 | trie_node* node = root; 136 | char* p = key; 137 | while(*p && node!=NULL) 138 | { 139 | node = node->children[*p-'a']; 140 | ++p; 141 | } 142 | 143 | if(node == NULL) 144 | return 0; 145 | else 146 | return node->count; 147 | } 148 | 149 | ``` 150 | 151 | trie树的增加和删除都比较麻烦,但索引本身就是写少读多,是否考虑添加删除的复杂度上升,依靠具体场景决定。 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /4 Tree/4-字典树Trie/trie.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | trie树 4 | */ 5 | 6 | 7 | #include 8 | #include 9 | 10 | #define ALPHABET_SIZE 26 // 256 11 | 12 | typedef int bool; 13 | #define YES 1 14 | #define NO 0 15 | 16 | typedef struct node 17 | { 18 | int count; //count 如果为0,则代表非黄色点,count>0代表是黄色点,同时表示出现次数; 19 | char value; //字符 20 | struct node * subtries[ALPHABET_SIZE]; //子树 21 | } Trie; 22 | 23 | 24 | 25 | Trie *trie_create(Trie **trie){ 26 | 27 | if (NULL==*trie) 28 | { 29 | *trie = (Trie *)malloc(sizeof(struct node)); 30 | if (NULL==*trie) 31 | { 32 | printf("malloc failure...\n"); 33 | } 34 | 35 | for (int i = 0; i < ALPHABET_SIZE; ++i) 36 | { 37 | (*trie)->subtries[i]=NULL; 38 | } 39 | } 40 | 41 | return *trie; 42 | 43 | } 44 | 45 | 46 | //插入字符串(插入一个单词),建立字典树. 返回值 < 0 表示插入失败 47 | int trie_insert(Trie *trie,char *c){ 48 | 49 | if (trie==NULL || c==NULL) // 对NULL指针解引用会崩溃 50 | { 51 | return -1; 52 | } 53 | 54 | char *p = c; 55 | Trie *temp = trie; 56 | while(*p != '\0'){ 57 | 58 | if (temp->subtries==NULL) 59 | { 60 | //temp->subtries = (struct node *)malloc(sizeof(struct node)*ALPHABET_SIZE); 61 | if (temp->subtries==NULL) 62 | { 63 | return -1; 64 | } 65 | } 66 | 67 | if (temp->subtries[*p-'a']==NULL) 68 | { 69 | struct node *newNode = (struct node *)malloc(sizeof(struct node)); 70 | if (!newNode) 71 | { 72 | printf("create new node fail \n"); 73 | return -1; 74 | } 75 | newNode->value = *p; 76 | //newNode->subtries = NULL; 77 | temp->subtries[*p-'a'] = newNode; 78 | } 79 | 80 | 81 | temp = temp->subtries[*p-'a']; 82 | p++; 83 | 84 | } 85 | 86 | return 0; 87 | 88 | } 89 | 90 | 91 | // 字符串查找,返回值<0表示没有查找到 92 | bool trie_query(Trie *trie,char *c){ 93 | 94 | if (trie==NULL ) 95 | { 96 | return NO; 97 | } 98 | 99 | char *p = c; 100 | Trie *temp = trie; 101 | bool ret=NO; 102 | if (temp->subtries == NULL) 103 | { 104 | return NO; 105 | } 106 | 107 | while (*p!='\0') 108 | { 109 | if (temp->subtries[*p-'a']!=NULL && temp->subtries[*p-'a']->value==*p)//匹配 110 | { 111 | temp = temp->subtries[*p-'a']; 112 | p++; 113 | continue; 114 | } 115 | 116 | break; 117 | } 118 | 119 | if (*p =='\0') 120 | { 121 | ret = YES; 122 | } 123 | 124 | return ret; 125 | 126 | } 127 | 128 | 129 | // 130 | void trie_remove(){} 131 | 132 | 133 | 134 | 135 | int main(){ 136 | 137 | Trie *trie = NULL; 138 | if (!trie_create(&trie)) 139 | { 140 | printf("trie init fail...\n"); 141 | } 142 | 143 | char *dict[10]={"int","integer","float","char","nonstriater","weibo"}; 144 | for (int i = 0; i < 6; ++i) 145 | { 146 | if (trie_insert(trie,dict[i])<0) 147 | { 148 | printf("%s 插入失败\n", dict[i]); 149 | } 150 | 151 | } 152 | 153 | // 查询cha 154 | printf("查询cha \n"); 155 | if (trie_query(trie,"cha")) 156 | { 157 | printf("YES\n"); 158 | }else{ 159 | printf("NO\n"); 160 | 161 | } 162 | 163 | // 查询char 164 | printf("查询char \n"); 165 | if (trie_query(trie,"char")) 166 | { 167 | printf("YES\n"); 168 | }else{ 169 | printf("NO\n"); 170 | 171 | } 172 | 173 | // 查询hello 174 | printf("查询hello \n"); 175 | if (trie_query(trie,"hello")) 176 | { 177 | printf("YES\n"); 178 | }else{ 179 | printf("NO\n"); 180 | 181 | } 182 | 183 | 184 | return 0; 185 | } 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /4 Tree/5-伸展树/伸展树.md: -------------------------------------------------------------------------------- 1 | # 伸展树 (splay tree) 2 | 3 | 伸展树是一种自平衡的二叉排序树。为什么需要这些自平衡的二叉排序树? 4 | 5 | n个节点的完全二叉树,其查找,删除的复杂度都是O(logN),但是如果频繁的插入删除,导致二叉树退化成一个n个节点的单链表,也就是`插入,查找复杂度趋于O(N)`,为了克服这个缺点,出现了很多二叉查找树的变形,如AVL树,红黑树,以及接下来介绍的 伸展树(splay tree)。 6 | 7 | -------------------------------------------------------------------------------- /4 Tree/6-后缀树/后缀树.md: -------------------------------------------------------------------------------- 1 | # 后缀树(suffix tree) 2 | 3 | ###后缀树的应用 4 | 5 | 可以解决很多字符串的问题 6 | 7 | 1. 查找字符串S1是否在字符串S中 8 | 2. 指定字符串S1在字符串S中出现的次数 9 | 3. 字符串S中的最长重复子串 10 | 4. 2个字符串的最长公共部分 11 | 12 | 13 | -------------------------------------------------------------------------------- /4 Tree/7-B树/B+树.md: -------------------------------------------------------------------------------- 1 | # B+树 2 | -------------------------------------------------------------------------------- /4 Tree/7-B树/B树.md: -------------------------------------------------------------------------------- 1 | # B树 2 | 3 | 一种多路平衡查找树。 4 | 5 | 能保证数据插入和删除情况下,任然保持执行效率。 6 | 7 | 一个M阶的B树满足: 8 | 9 | 1. 每个节点最多M个子节点 10 | 2. 除跟节点和叶节点外,其它每个节点至少有M/2个孩子 11 | 3. 根节点至少2个节点 12 | 4. 所有叶节点在同一层,叶节点不包含任何关键字信息 13 | 5. 有k个关键字的页节点包含k+1个孩子 14 | 15 | 也就是说:`根节点到每个叶节点的路径长度都是相同的。` 16 | 17 | 18 | 19 | ## 数据结构 20 | 21 | 22 | ``` 23 | typedef struct Item{ 24 | int key; 25 | Data data; 26 | } 27 | 28 | #define m 3 //B树的阶 29 | 30 | 31 | typedef struct BTNode{ 32 | int degree; //B树的度 33 | int keynums; //每个节点key的个数 34 | Item items[m]; 35 | struct BTNode *p[m]; 36 | }BTNode,* BTree; 37 | 38 | 39 | typedef struct{ 40 | BTNode *pt; //指向找到的节点 41 | int i; // 节点中关键字的序号 (0,m-1) 42 | int tag; //1:查找成功,0:查找失败 43 | }Result; 44 | 45 | 46 | Status btree_insert(root,target)//插入B树节点 47 | Result btree_find(root,target)//查找B树节点 48 | Status btree_delete(root,target)//删除B树节点 49 | ``` 50 | 51 | 52 | ## 插入B树节点 53 | 54 | 55 | 56 | 57 | ## 查找B树节点 58 | 59 | 60 | 61 | 62 | ## 删除B树节点 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /4 Tree/8-堆/Top-K 问题.md: -------------------------------------------------------------------------------- 1 | # Top-K 问题 2 | 3 | 4 | 问题描述:从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题 5 | 6 | 7 | 思路 8 | 9 | 1. 排序,全局都排序了,这也是这个方法复杂度非常高的原因 ; [排序算法参考这里](../../6%20Sort/REAME.md) 10 | 2. 冒泡排序,局部排序徐,只对最大的k个数排序 11 | 3. [堆排序](./堆.md), 只找最大的k个数,这k个数不需要排序; top-k大 问题就是用 小根堆, 小根堆 固定为 k 个元素大小 , 遍历 k-N (N为所有数据的个数),插入小根堆并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /4 Tree/8-堆/heap.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | void heap_build(int *a,int length){ 6 | 7 | 8 | } 9 | 10 | 11 | // 插入 12 | void heap_insert(int *a,int v){ 13 | 14 | 15 | 16 | } 17 | 18 | 19 | //删除,删除的元素放在value指向的内存中 20 | void heap_delete(int *a,int *value){ 21 | 22 | 23 | } 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /4 Tree/8-堆/pq-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/4 Tree/8-堆/pq-1.jpg -------------------------------------------------------------------------------- /4 Tree/8-堆/pq-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/4 Tree/8-堆/pq-1.png -------------------------------------------------------------------------------- /4 Tree/8-堆/堆.md: -------------------------------------------------------------------------------- 1 | # 大顶堆 和 小顶堆 2 | 3 | 4 | 先来了解下 `堆` 结构; 堆也被称为`优先队列`,`二叉堆` ; 5 | 6 | 特点 7 | 8 | * 堆总是一颗完全二叉树树, 使用数组作为其存储结构,因此也叫 `二叉堆`; 用链表存的就叫二叉树了 9 | * 任一节点小于(或大于)其所有的孩子节点; 10 | * 堆分小根堆和大根堆; 如果根节点大于所有孩子节点,这就是一颗大根堆,也就是根节点是堆上的最大值;如果节点小于所有的子节点,这就是一颗小跟堆,也即是根节点是堆上所有节点的最小值。 11 | * 小根堆: 每次取出来的元素都是队列中值最小的 12 | 13 | 14 | `堆用数组来存储`,因为是一颗完全二叉树,i节点的父节点索引就是(i-1)/2, 左右子节点小标是 2i+1,2i+2。 15 | 16 | 17 | 比如小根堆存储示例 18 | 19 | ![小根堆](./pq-1.jpg) 20 | 21 | 22 | 23 | ### 应用场景 24 | 25 | * 优先队列 如iOS中的 NSOperationQueue 就是维护一个优先队列 26 | * 堆排序 27 | * [top-K 大(小)](Top-K%20问题.md) , top-k大 问题就是用 小根堆, 小根堆 固定为 k 个元素大小 , 遍历 k-N (N为所有数据的个数),插入小根堆并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。 28 | 29 | 30 | 31 | ### Java PriorityQueue 32 | 33 | 34 | Java PriorityQueue 类就是 通过二叉小顶堆实现 ,也叫优先级队列实现。 有如下特点: 35 | 36 | * 实现 Queue 接口 37 | * 头部是基于自然排序或基于比较器的排序的最小元素 38 | * 不是线程安全的,PriorityBlockingQueue在并发环境中使用 39 | 40 | 41 | 42 | API 如下: 43 | 44 | * boolean add(object):将指定的元素插入此优先级队列。 45 | * boolean offer(object):将指定的元素插入此优先级队列。 46 | * boolean remove(object):从此队列中删除指定元素的单个实例(如果存在)。 47 | * Object poll():检索并删除此队列的*头部*,如果此队列为空,则返回null。 48 | * Object element():检索但不删除此队列的*头部*,如果此队列为空,则返回null。 49 | * Object peek():检索但不删除此队列的*头部*,如果此队列为空,则返回null。 50 | * void clear():从此优先级队列中删除所有元素。 51 | 52 | 53 | ``` 54 | { 55 | PriorityQueue pq = new PriorityQueue(); 56 | pq.add(new Employee(1L, "AAA", LocalDate.now())); 57 | pq.add(new Employee(4L, "BBB", LocalDate.now())); 58 | pq.add(new Employee(3L, "DDD", LocalDate.now())); 59 | pq.add(new Employee(7L, "GGG", LocalDate.now())); 60 | pq.add(new Employee(2L, "CCC", LocalDate.now())); 61 | 62 | while (true) { 63 | Employee head = pq.poll(); 64 | System.out.println(head); 65 | if (head == null) { 66 | return; 67 | } 68 | } 69 | } 70 | 71 | ``` 72 | 73 | 输出 74 | 75 | ``` 76 | Employee [id=1, name=AAA, dob=2021-12-22] 77 | Employee [id=2, name=CCC, dob=2021-12-22] 78 | Employee [id=3, name=DDD, dob=2021-12-22] 79 | Employee [id=4, name=BBB, dob=2021-12-22] 80 | Employee [id=7, name=GGG, dob=2021-12-22] 81 | ``` 82 | 83 | 84 | 如下,使用 PriorityQueue实现的 top-K 大问题,实现如下: 85 | 86 | ```Java 87 | /** 88 | * 小根堆实现 89 | */ 90 | public static int findMaxK(int[] nums, int k) { 91 | PriorityQueue pq = new PriorityQueue<>(k, (a, b) -> (a-b) ); 92 | 93 | for (int i = 0; i > { 138 | // 存储元素的数组 139 | private Key[] pq; 140 | // 当前 Priority Queue 中的元素个数 141 | private int N = 0; 142 | 143 | public MaxPQ(int cap) { 144 | // 索引 0 不用,所以多分配一个空间 145 | pq = (Key[]) new Comparable[cap + 1]; 146 | } 147 | 148 | /* 返回当前队列中最大元素 */ 149 | public Key max() { 150 | return pq[1]; 151 | } 152 | 153 | /* 插入元素 e */ 154 | public void insert(Key e) {...} 155 | 156 | /* 删除并返回当前队列中最大元素 */ 157 | public Key delMax() {...} 158 | 159 | /* 上浮第 k 个元素,以维护最大堆性质 */ 160 | private void swim(int k) {...} 161 | 162 | /* 下沉第 k 个元素,以维护最大堆性质 */ 163 | private void sink(int k) {...} 164 | 165 | /* 交换数组的两个元素 */ 166 | private void exch(int i, int j) { 167 | Key temp = pq[i]; 168 | pq[i] = pq[j]; 169 | pq[j] = temp; 170 | } 171 | 172 | /* pq[i] 是否比 pq[j] 小? */ 173 | private boolean less(int i, int j) { 174 | return pq[i].compareTo(pq[j]) < 0; 175 | } 176 | 177 | /* 还有 left, right, parent 三个方法 */ 178 | } 179 | 180 | ``` 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /4 Tree/9-红黑树 R-B tree/红黑树.md: -------------------------------------------------------------------------------- 1 | # 红黑树 (red-black tree) 2 | 3 | 红黑树(Red Black Tree) 是一种自平衡二叉查找树。一种特化的[AVL树](../3-平衡树AVL/README.md),在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它可以在O(log n)时间内做查找,插入和删除。 4 | 5 | 它的每个结点都被“着色”为红色或者黑色,这些结点的颜色被用来检测树的平衡性。 6 | 7 | 8 | ### 应用场景 9 | 10 | * C++ STL的map和set 11 | * java 中 HashMap、TreeMap 的底层实现,当HashMap中元素大于8个时,HashMap底层存储实现改为红黑树,以提高元素搜索速度。 12 | 关于 HashMap 实现解析参考 [这里](../../3%20HashTable/HashMap%20in%20Java.md) 13 | * 广泛应用Linux 的进程管理、内存管理,设备驱动及虚拟内存跟踪 14 | * epoll的的的实现采用红黑树组织管理的的的sockfd,以支持快速的增删改查 15 | * Nginx的的的中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器 16 | 17 | 18 | ### RB tree 特点 19 | 20 | * 每个节点非红即黑 21 | * 根节点是黑的 22 | * 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的 23 | * 如果一个节点是红的,那么它的两儿子都是黑的 24 | * 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点 25 | * 每条路径都包含相同的黑节点 26 | 27 | 28 | 在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑);通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍; 29 | 30 | 因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树)。相对于要求严格的[AVL树](../3-平衡树AVL/README.md)来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。 31 | 32 | 33 | 也就是说,红黑树牺牲掉一定的平衡性(牺牲查找性能),换来了 插入,删除操作时 更少的旋转次数带来的开销。 34 | 35 | 36 | ### 红黑树 & B+ 树对比 37 | 38 | * 红黑树多用在内部排序,即全放在内存中的 39 | * B+树多用于外存上时,B+也被成为一个磁盘友好的数据结构; 这也是为什么 mysql索引使用b+树而不使用红黑树 40 | 41 | 42 | 为什么使用 红黑树 而不是 B+ 树呢?原因如下: 43 | 44 | * 没有范围查找, 不需要 B+ 45 | * 不需要多路平衡树,使用二路平衡,实现简单,且红黑树能兼顾 查找,删除操作的性能 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /4 Tree/92-并查集/并查集.md: -------------------------------------------------------------------------------- 1 | # 并查集 -------------------------------------------------------------------------------- /4 Tree/README.md: -------------------------------------------------------------------------------- 1 | # 树🌲 2 | 3 | 介绍树相关的算法 4 | 5 | * 二叉树 6 | * 二叉查找树 7 | * AVL树 8 | * 红黑树 9 | * B树 : B树, B+树(mysql索引使用B+树的数据结构) 10 | * 字典树trie(前缀树,单词查找树) 11 | * 伸展树 12 | * 后缀树 13 | * 红黑树 14 | * 二叉堆(优先队列) 15 | * Treap 树 16 | * 赫夫曼编码 Huffman 17 | 18 | 19 | ## 二叉树 20 | 21 | 22 | [快速排序](../6%20Sort/README.md)就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 23 | 24 | 25 | ## [二叉查找树BST](2-二叉查找树/二叉查找树.md) 26 | 27 | 有序的二叉树,中序遍历结果是递增的 28 | 29 | ## [AVL树](3-平衡树AVL/README.md) 30 | 31 | 绝对的平衡二叉树; 32 | 33 | 34 | ## [红黑树](9-红黑树%20R-B%20tree/红黑树.md) 35 | 36 | 弱平衡二叉树;使用广泛 37 | 38 | 39 | ## [字典树trie](4-字典树Trie/README.md) 40 | 41 | 字典树也叫前缀树,单词查找树 42 | 43 | 44 | ## [伸展树](5-伸展树/伸展树.md) 45 | 46 | 47 | ## [后缀树](6-后缀树/后缀树.md) 48 | 49 | ## B树 50 | 51 | * [B树](7-B树/B树.md) 52 | * [B+树](7-B树/B+树.md) mysql 索引使用 B+树 的数据结构 53 | 54 | 55 | ## [二叉堆](8-堆/堆.md) 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /4 Tree/huffman tree/赫夫曼编码.md: -------------------------------------------------------------------------------- 1 | # 赫夫曼编码 Huffman 2 | 3 | Huffman在1952年根据香农(Shannon)在1948年和范若(Fano)在1949年阐述的这种编码思想提出了一种不定长编码的方法,也称霍夫曼(Huffman)编码。 4 | 5 | 这是一个经典的压缩算法。通过`字符出现的频率`,`优先级`,`二叉树` 进行的压缩算法。 6 | 7 | 对一个字符串,计算每个字符出现的次数, 把这些字符放到优先队列(priority queue); 这个priority queue转出二叉树 8 | 9 | 需要一个字符编码表来解码,通过二叉树建立huffman编码和解码的字典表 10 | 11 | 12 | 13 | 举一个例子: 14 | 15 | 原始串: 16 | 二级制编码: 17 | huffman编码: 18 | 19 | 20 | 21 | 22 | ### 存储结构和基本操作 23 | 24 | ``` 25 | struct node{ 26 | char *huffCode; // 叶子节点的huff编码 27 | int weight; 28 | struct node *left,right; 29 | } 30 | ``` 31 | 32 | ### 构建赫夫曼树 33 | 34 | 原则:出现频率越多的会在越上层,编码也越短,出现频率越少的在越下层,编码也越长。 35 | 不存在某一个编码是另一个编码的前缀,字符都在叶节点上,所以不会存在一个编码是另一个编码的前缀 36 | 二叉树每个节点要么是叶子节点,要么是双分支节点(且左分支编码为0,右分支编码为1) 37 | 38 | 39 | ### 压缩 40 | 41 | 1. 扫描输入文件,统计各个字符出现的次数,对结构排序 (hash统计每个字符出现的次数) 42 | 2. 根据排序结构,构建赫夫曼树 (贪心策略,每次选频率值最低的2个节点合并,需要优先队列帮组(priority queue,又叫最小堆)) 43 | 3. 对树进行遍历(左分支编码为0,右分支编码为1),得到各个字符的huffman编码,存到hash表中(这个就是编解码表,也可直接存储到节点中,如上面的char *huffCode) 44 | 4. 重新对文件扫描,根据hash表进行压缩 45 | 46 | 压缩的文件为了能够解压缩,需要一个文件头,用来重建赫夫曼树,包括: 47 | 被编码的文本长度 unsigned int size 48 | 字符频率表 unsigned char freqs[NUM_CHARS] 49 | 50 | 51 | ###解压缩 52 | 53 | 1. 读取文件头 54 | 2. 遍历编码后的bits,从赫夫曼树的根节点出发,遇到0,进入左子树,遇到1进入右子树,直到叶节点 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /5 Graph/DFS 和 BFS.md: -------------------------------------------------------------------------------- 1 | # DFS 和 BFS 搜索算法 2 | 3 | DFS: 深度优先搜索,以深度为准则,先一条路走到底,直到达到目标; 没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。 4 | 5 | BFS:广度优先搜素,在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。 6 | 7 | 8 | 9 | > DFS用递归的形式,用到了栈结构,先进后出; BFS选取状态用队列的形式,先进先出。 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /5 Graph/README.md: -------------------------------------------------------------------------------- 1 | # 图 2 | 3 | ### 什么是图 4 | 5 | 图论(Graph theory)是数学的一个分支,它以图为研究对象,研究顶点和边组成的图形的数学理论和方法。图论中的图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用顶点代表事物,用连接两顶点的边表示相应两个事物间具有这种关系。 6 | 7 | 图论的研究对象相当于一维的拓扑学。 8 | 9 | 10 | ### 应用场景 11 | 12 | 工业界有哪些应用场景? 13 | 14 | * 匹配,如打车中司乘匹配引擎,如何做到效率最优 15 | * 并行任务调度: 一组任务,任务有优先级,如何合理安排任务调度,在最短时间内完成 16 | * 导航路径规划:在使用导航软件时,用户在选择一个开始地点和目的地之后导航软件会给出各种如路程最短,不走高速,时长最短等方案 17 | * 社区发现: 在好友关系中,根据社区之间联系或紧密,利用图 louvain 算法或者其他算法对用户进行分群从而达到精准营销,个性化服务等 18 | * 金融贷后催收:利用图算法找出符合条件的失联人的联系人,从而提高催收失联修复的覆盖率、有效联系率,助力不良资产的回收 19 | * 套汇 20 | 21 | 22 | ### 基本概念 23 | 24 | ** 有向图 ** 25 | 26 | 有向图,就是有方向的图 27 | 28 | ** 无向图 ** 29 | 30 | 就是没有方向的图 31 | 32 | ** 环 ** 33 | 34 | 首尾相接的路径我们就把它叫做一个环。 35 | 36 | 37 | # 图的存储结构 38 | 39 | * 对象和指针 40 | * 邻接矩阵 (二维数组) 41 | * 邻接表 42 | 43 | 44 | 图数据结构表示: 45 | 46 | ``` 47 | 48 | ``` 49 | 50 | 51 | ## 图的操作 52 | 53 | #### 遍历 54 | 55 | * 广度优先 BFS 56 | * 深度优先 DFS 57 | 58 | Dijkstra 59 | A* 60 | 61 | 用于游戏编程和分布式计算 62 | 63 | 64 | ## 图延伸 65 | 66 | * 二部图 67 | * 有向无环图 DAG 68 | 69 | 70 | ### 参考 71 | 72 | https://www.jiqizhixin.com/articles/2019-05-16-14 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /5 Graph/二分图/二部图.md: -------------------------------------------------------------------------------- 1 | # 二部图 -------------------------------------------------------------------------------- /5 Graph/拓扑排序.md: -------------------------------------------------------------------------------- 1 | # 拓扑排序 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /5 Graph/最小生成树.md: -------------------------------------------------------------------------------- 1 | # 最小生成树 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /5 Graph/最短路径.md: -------------------------------------------------------------------------------- 1 | # 最短路径算法 2 | 3 | 4 | 最短路径问题: 寻找图(由结点和路径组成的)中两结点之间的最短路径 5 | 6 | 7 | 实现算法 8 | 9 | * A* 算法 10 | * Floyd 11 | * Dijkstra(迪杰斯特拉) 12 | * bellman-ford 13 | * spfa 14 | 15 | 16 | ## A* 算法 17 | 18 | `A*(A-Star)`算法是一种静态路网中求解最短路径最有效的直接搜索方法; 算法中的距离估算值与实际值越接近,最终搜索速度越快。 19 | 20 | 21 | 22 | ## Dijkstra:最短路径算法 23 | 24 | Dijkstra 解决图中一点到其余各点到最短路径的问题 25 | 26 | Dijkstra是荷兰的计算机科学家,提出”信号量和PV原语“,"解决哲学家就餐问题",”死锁“也是它提出来的) 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /6 Sort/8.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "stdio.h" 4 | 5 | int main(int argc, char const *argv[]) 6 | { 7 | 8 | int length=10; 9 | 10 | int j; 11 | for (int i = 0; i < length; ++i) 12 | { 13 | for (j = i+1 ; j < length; ++j) 14 | { 15 | printf("i=%d,j=%d\n",i,j ); 16 | } 17 | } 18 | 19 | 20 | for (int i = 0; i < length; ++i) 21 | { 22 | for (int k = i+1 ; k < length; ++k) 23 | { 24 | printf("i=%d,k=%d\n",i,k ); 25 | } 26 | } 27 | 28 | 29 | return 0; 30 | } -------------------------------------------------------------------------------- /6 Sort/bubblesort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/bubblesort.gif -------------------------------------------------------------------------------- /6 Sort/heapsort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/heapsort.gif -------------------------------------------------------------------------------- /6 Sort/insert_sort.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | #include "stdlib.h" 4 | #include "time.h" 5 | 6 | // 插入排序 7 | void insert_sort(int *a,int length){ 8 | 9 | int tmp ; 10 | int i,j; 11 | for (i = 1; i < length; ++i) 12 | { 13 | 14 | for ( tmp=a[i],j=i-1 ; j>=0 && a[j] > tmp ; j--) 15 | { 16 | a[j+1]=a[j]; 17 | } 18 | // j+1是插入的位置 19 | a[j+1]=tmp; 20 | 21 | } 22 | 23 | } 24 | 25 | 26 | // 选择排序 27 | 28 | void select_sort(int *a,int length){ 29 | 30 | int min_index,tmp; 31 | int j; 32 | 33 | for (int i = 0; i < length; ++i) 34 | { 35 | for (j = i+1 ,min_index=i; j < length; ++j)// 不能写成for (int j = i+1 ,min_index=i; j < length; ++j) 36 | { 37 | if (a[min_index]>a[j]) 38 | { 39 | min_index=j; 40 | } 41 | } 42 | 43 | //min_index是最小的元素的index 44 | if (min_index!=i) 45 | { 46 | tmp=a[i]; 47 | a[i]=a[min_index]; 48 | a[min_index]=tmp; 49 | 50 | } 51 | 52 | } 53 | 54 | } 55 | 56 | 57 | // 冒泡排序 58 | 59 | void bubble_sort(int *a, int length){ 60 | 61 | int tmp ; 62 | 63 | for (int i = 0; i < length-1; ++i) // 第i轮排序 64 | { 65 | for (int j = 0; j < length-i; ++j) 66 | { 67 | if (a[j] > a[j+1]) 68 | { 69 | tmp = a[j]; 70 | a[j] = a[j+1]; 71 | a[j+1] = tmp; 72 | } 73 | } 74 | 75 | } 76 | 77 | } 78 | 79 | 80 | // 快速排序 81 | 82 | // 挖坑填数,2边向中间扫描 83 | int partion(int *a, int start,int end){ 84 | 85 | int i=start,j=end; 86 | int tmp = a[i]; // 这里要做越界检查 87 | 88 | while(i=tmp){ 92 | j--; 93 | } 94 | if (i= a[max])// parent > parent >= max(left, right) 152 | { 153 | break; 154 | } 155 | else 156 | { 157 | heap_swop(&a[parent],&a[max]); 158 | parent = max; //继续向下, 比对, 交换, 保证所有树 父节点 >= 子节点 159 | max = 2 * parent + 1; 160 | } 161 | 162 | } 163 | 164 | } 165 | 166 | 167 | // 从第一个非叶子节点a[(length-2)/2],开始做调整, 跟自己的子节点比较,把最大的孩子换上来就是创建最大堆, 168 | //反之,把最小的孩子换上来就是创建最小堆 一直到a[0] 169 | void heap_build(int *a,int length){ 170 | 171 | 172 | for (int i = (length-2)/2; i >=0 ; --i) 173 | { 174 | // 三个数里取最大的一个 a[i],a[2i+1],a[2i+2],跟a[i]交换;然后是 a[(i-1)/2],a[i],a[i+1] .. 一直到a[0] 175 | heap_public_adjust(a,i,length); 176 | 177 | } 178 | 179 | } 180 | 181 | // 自顶向下调整 182 | void heap_adjust(int *a,int length){ 183 | 184 | heap_public_adjust(a,0,length); //对0号调整 185 | } 186 | 187 | 188 | void heap_sort(int *a, int length){ 189 | 190 | // 建立堆 大根堆,递增排序 191 | heap_build(a,length); 192 | 193 | for (int i = length-1; i >0; --i) 194 | { 195 | //交换 196 | heap_swop(&a[0],&a[i]); 197 | //调整 198 | heap_adjust(a,i); 199 | } 200 | 201 | } 202 | 203 | 204 | 205 | // 归并排序 206 | 207 | // 合并2个有序数组,分配一个临时空间,装a,b的结果,最后,将合并结果拷贝到数组A,是否临时空间 208 | void merge_array(int *a,int size_a,int *b, int size_b){ 209 | 210 | 211 | int *tmp = malloc( (size_a+size_b)*sizeof(int) ); 212 | int i,j,k; 213 | i=j=k=0; 214 | 215 | while(ib[j])?b[j++]:a[i++]; 218 | } 219 | 220 | while(i1) 244 | { 245 | 246 | merge_sort(a,length/2); 247 | merge_sort(a+length/2,length-length/2); 248 | 249 | merge_array(a,length/2,a+length/2,length-length/2); 250 | } 251 | 252 | 253 | 254 | } 255 | 256 | 257 | /////////////////////////////////////////////// 258 | 259 | 260 | #define Max_Number 5000 261 | 262 | int main(){ 263 | 264 | //int a[] = {4,87,2,32,5,2,9,49,49,23,45,2,41}; 265 | //准备5000个数 266 | int a[Max_Number]; 267 | for (int i = 0; i < Max_Number; ++i) 268 | { 269 | a[i]=rand()%Max_Number; 270 | } 271 | 272 | clock_t start,finish; 273 | 274 | 275 | 276 | start = clock(); 277 | //merge_sort(a,sizeof(a)/sizeof(int)); // 0.002s,可以看到,归并排序还是很快的 278 | heap_sort(a,sizeof(a)/sizeof(int)); // 279 | //quicksort(a,0,sizeof(a)/sizeof(int)-1); // 0.01s 280 | //insert_sort(a,sizeof(a)/sizeof(int)); // 3.85s 281 | //select_sort(a,sizeof(a)/sizeof(int)); // 5.3s 282 | //bubble_sort(a,sizeof(a)/sizeof(int)); // 12.5s 283 | finish = clock(); 284 | 285 | printf("after sort:\n"); 286 | for (int i = 0; i < sizeof(a)/sizeof(int); ++i) 287 | { 288 | printf(" %d ",a[i]); 289 | } 290 | printf("time eclipse: %.6f sec\n", (double)(finish-start)/CLOCKS_PER_SEC); // CLOCKS_PER_SEC 1000 clock()是毫秒 291 | 292 | return 0; 293 | } 294 | 295 | 296 | -------------------------------------------------------------------------------- /6 Sort/mergesort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/mergesort.gif -------------------------------------------------------------------------------- /6 Sort/qsort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/qsort.gif -------------------------------------------------------------------------------- /6 Sort/selectsort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/selectsort.gif -------------------------------------------------------------------------------- /6 Sort/shellsort.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonstriater/Learn-Algorithms/f19caf3ac6bb76bc67c9d2447fdc9f21ad11bf98/6 Sort/shellsort.gif -------------------------------------------------------------------------------- /6 Sort/外排序.md: -------------------------------------------------------------------------------- 1 | # 外排序 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /7 Search/README.md: -------------------------------------------------------------------------------- 1 | # 查找算法 2 | 3 | * 顺序查找 4 | * 二分查找 5 | * 分块查找 6 | * 动态查找 7 | * 哈希表 8 | 9 | 10 | ## 顺序查找 11 | 12 | 顺序表查找。复杂度O(n) 13 | 14 | ## 二分查找 15 | 16 | 有序表中查找我们可以使用二分查找。 17 | 18 | ``` 19 | /* 20 | eg: [1,3,5,6,7,9] k=6 21 | @return 返回元素的索引下表,找不到就返回-1 22 | */ 23 | int binary_search(int *a,int length,int k){ 24 | int low = 0; 25 | int high = length-1; 26 | int mid; 27 | 28 | while(low k) high = mid-1; 33 | } 34 | 35 | return -1; 36 | } 37 | ``` 38 | 39 | 40 | ``` 41 | 注意细节 42 | mid+1/mid-1 , 否则的话,有可能死循环 43 | 44 | while(low <= high) 而不是 while(low target) { 71 | right = mid; // 注意 , 这里没有 -1 72 | } 73 | } 74 | return left; 75 | } 76 | ``` 77 | 78 | 79 | 80 | 81 | ## 分块查找 82 | 83 | 块内无序,块之间有序;可以先二分查找定位到块,然后再到块中顺序查找。 84 | 85 | 86 | ## 动态查找 87 | 88 | 这里之所以叫 动态查找表,是因为表结构是查找的过程中动态生成的。查找结构通常是二叉排序树,AVL树,B- ,B+等。这部分的内容可以去看『二叉树』章节 89 | 90 | 91 | ## 哈希表 92 | 93 | 哈希表以复杂度O(1)的成绩位列所有查找算法之首,大量查找的数据结构中都可以看到哈希表的应用。 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/README.md: -------------------------------------------------------------------------------- 1 | # 算法分析思路 2 | 3 | 详细介绍每一种算法设计的思路,并为每种方法给出一个经典案例的详细解读,总结对应设计思路,最后给出其它案例,以供参考。 4 | 5 | 6 | * [递归](递归.md) 7 | * [分治算法](分治算法.md) 8 | * [动态规划](动态规划.md) 9 | * [回溯法](回溯法.md) 10 | * [迭代法](迭代法.md) 11 | * [穷举搜索法](穷举搜索法.md) 12 | * [贪心算法](贪心算法.md) 13 | 14 | 15 | 16 | 17 | ### 总结 18 | 19 | `贪心法`、`分治法`、`动态规划` 都是将问题归纳为根小的、相似的子问题,通过求解子问题产生全局最优解。 20 | 21 | 22 | 23 | ## 参考 24 | 25 | 《算法设计与分析基础》 Anany Levitin 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/分治算法.md: -------------------------------------------------------------------------------- 1 | # 分治算法 2 | 3 | 4 | 将一个难以直接解决的大问题,分割成一些规模较小的相同问题,各个击破,分而治之。 5 | 6 | 分治算法常用[递归](./递归.md) 实现 7 | 8 | 1)问题缩小的小规模可以很容易解决 9 | 2)问题可以分解为规模较小相同问题 10 | 3)子问题的解可以合并为该问题的解 11 | 4)各个子问题相互独立,(如果这条不满足,转为`动态规划`求解) 12 | 13 | 分治法的步骤: 14 | 15 | 1. 分解 16 | 2. 解决 17 | 3. 合并 18 | 19 | 20 | #### 大整数乘法 21 | 22 | 如 `26542123532213598*345987342245553677884` 23 | 24 | 25 | 26 | #### 其它案例 27 | 28 | * 快速排序 29 | * [归并排序](../6%20Sort/README.md) 30 | * 最大子数组和 31 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/回溯法.md: -------------------------------------------------------------------------------- 1 | # 回溯法 2 | 3 | 也叫 `试探法`。 是一种选优搜索法,按照选优条件搜索,当搜索到某一步,发现原先选择并不优或达不到目标,就退回重新选择。 4 | 5 | 回溯算法其实就是我们常说的 DFS 算法,本质上就是一种暴力穷举算法。 6 | 7 | 一般步骤 8 | 9 | 1. 针对问题,定义解空间( 这时候解空间是一个集合,且包含我们要找的最优解) 10 | 2. 组织解空间,确定易于搜索的解空间结构,通常组织成`树结构` 或 `图结构` 11 | 3. 深度优先搜索解空间,搜索过程中用剪枝函数避免无效搜索 12 | 13 | 回溯法求解问题时,一般是一边建树,一边遍历该树;且采用非递归方法。 14 | 15 | 16 | #### 案例 17 | 18 | * 迷宫问题 19 | * 全排列 20 | 21 | 22 | 23 | ## 代码框架 24 | 25 | ```python 26 | result = [] 27 | def backtrack(路径, 选择列表): 28 | if 满足结束条件: 29 | result.add(路径) 30 | return 31 | 32 | for 选择 in 选择列表: 33 | 做选择 34 | backtrack(路径, 选择列表) 35 | 撤销选择 36 | ``` 37 | 38 | 39 | 40 | ## 全排列问题 41 | 42 | > n 个不重复的数,全排列共有 n! 个 43 | 44 | 45 | ```Java 46 | // 路径:记录在 track 中 47 | // 选择列表:nums 中不存在于 track 的那些元素 48 | // 结束条件:nums 中的元素全都在 track 中出现 49 | void backtrack(int[] nums, LinkedList track) { 50 | // 触发结束条件 51 | if (track.size() == nums.length) { 52 | res.add(new LinkedList(track)); 53 | return; 54 | } 55 | 56 | for (int i = 0; i < nums.length; i++) { 57 | // 排除不合法的选择 58 | if (track.contains(nums[i])) 59 | continue; 60 | 61 | // 做选择 62 | track.add(nums[i]); 63 | 64 | // 进入下一层决策树 65 | backtrack(nums, track); 66 | // 取消选择 67 | track.removeLast(); 68 | } 69 | } 70 | ``` 71 | 72 | 73 | 74 | ## 八皇后问题 75 | 76 | 8x8的国际象棋棋盘上放置8个皇后,使得任何一个皇后都无法直接吃掉其他的皇后。任意2个皇后都不能处于同一个 横线,纵线,斜线上。 77 | 78 | 分析 79 | 80 | 1. 任意2个皇后不能同一行,也就是每个皇后占据一行,通用的,每个皇后也要占据一列 81 | 2. 一个斜线上也只有一个皇后 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/穷举搜索法.md: -------------------------------------------------------------------------------- 1 | # 穷举搜索法 2 | 3 | 或者叫蛮力法。对可能的解的众多候选按照某种顺序逐一枚举和检验。典型的问题如选择排序和冒泡排序。 4 | 5 | 6 | #### 背包问题 7 | 8 | 给定n个重量为 w1,w2,...,wn,定价为 v1,v2,...,vn 的物品,和一个沉重为W的背包,求这些物品中一个最有价值的子集,且能装入包中。 9 | 10 | 11 | 12 | #### 其它案例 13 | 14 | 选择排序 15 | 冒泡排序 16 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/贪心算法.md: -------------------------------------------------------------------------------- 1 | # 贪心算法 2 | 3 | 不追求最优解,只找到满意解。 4 | 5 | 6 | #### 其它案例 7 | 8 | * 跳跃游戏 9 | * 射击气球 10 | * 装箱问题 11 | 12 | 13 | 14 | ## 赫夫曼编码 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/迭代法.md: -------------------------------------------------------------------------------- 1 | # 迭代法 2 | 3 | 是一种不断用旧值递推新值的过程,分精确迭代和近视迭代。是用来求方程和方程组近似根的方法。 4 | 5 | 6 | 迭代变量 7 | 迭代关系, 迭代关系选择不合理,会导致迭代失败 8 | 迭代过程控制,也就是迭代什么时候结束,不能无休止进行下去 9 | 10 | -------------------------------------------------------------------------------- /8 Algorithms Analysis/递归.md: -------------------------------------------------------------------------------- 1 | # 递归 2 | 3 | 递归是一种设计和描述算法的有力工具。 也是回溯法和动态规划的基础。 4 | 5 | 递归算法执行过程分 `递推` 和 `回归` 两个阶段 6 | 7 | 在 `递推` 阶段,将大的问题分解成小的问题 8 | 在 `回归` 阶段,获得最简单问题的解后,逐级返回,依次得到稍微复杂情况的解,知道获得最终的结果 9 | 10 | 1) 确定递归公式 , 比如 斐波那契数列 问题中的 `fib(n)=fib(n-1)+fib(n-2)` 11 | 2) 确定边界条件 bad case 12 | 13 | > 自顶向下的递归,自底向上是迭代 14 | 15 | 16 | 递归运行效率较低,因为有函数调用的开销,递归多次也可能造成栈溢出。 17 | 18 | 19 | ### 递归公式 20 | 21 | [快排](../6%20Sort/README.md) 22 | 23 | ```Java 24 | void quicksort(int *a, int left, int right){ 25 | if (left 21 | ``` 22 | 23 | 24 | ## 判断字符串是否是回文 25 | 26 | > 回文,如 abcdcba 27 | 28 | 分析: 2个指针,一头一尾,逐个比较,都相同就是回文 29 | 30 | ``` 31 | /* 32 | * eg acdeedca 33 | * @ret 0 success , -1 fail 34 | */ 35 | int is_huiwen(const char *source){ 36 | if (source == NULL || source == '\0') return -1; 37 | char *head = source; 38 | char *tail = source; 39 | while(*tail != '\0'){ 40 | tail++; 41 | } 42 | tail--; 43 | 44 | while(head < tail){ 45 | if (*head != *tail){ 46 | return -1; 47 | } 48 | head++; 49 | tail--; 50 | } 51 | 52 | return 0; 53 | } 54 | ``` 55 | 56 | 57 | 58 | ## 统计文章里单词出现的次数 59 | 60 | 设计相应的数据结构和算法,尽量高效的统计一片英文文章(总单词数目)里出现的所有英文单词,按照在文章中首次出现的顺序打印输出该单词和它的出现次数。 61 | 62 | ``` 63 | void statistics(char *string) 64 | void statistics(FILE *fd) 65 | ``` 66 | 67 | 68 | 延伸: 69 | 70 | 如果是海量数据里面统计top-k次数的单词呢? 71 | 72 | 73 | 74 | ## 实现字符串转整型的函数 75 | 76 | 也就是实现函数atoi的功能,这道题目考查的就是对各种情况下异常处理。比如: 77 | 78 | 以`213`分析转换成证书的过程。`3+1x10+2x100` ,思路是:每扫描到一个字符,把 `之前得到的数字*10`,再加上当前的数字 79 | 80 | * 0开头,"0213" 81 | * 正/负数,"-432" ,"--422","++23" 82 | * 浮点数,"43.2344" 83 | * 非法,"2123des" 84 | * 存在空格," -32"," +432"," 234","23 432","353 "," + 321" 85 | * NULL/空串,这时候返回值0 86 | * 溢出,"32111111112222222222222222222222222222222" , 与 `INT_MAX `比较 87 | * 如何区分正常的'0'和异常情况下返回的结果"0"? 可以通过一个全局变量 g_status 来标示,值为 kValid/kInvalid。 88 | 89 | 90 | 91 | ``` 92 | int atoi(const char *str){ 93 | 94 | } 95 | ``` 96 | 97 | 详细过程也可以[参考这里](http://blog.csdn.net/v_july_v/article/details/9024123) 98 | 99 | 100 | 101 | 102 | ## 匹配兄弟字符串 103 | 104 | 什么是兄弟字符串? 105 | 106 | 如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。 107 | 108 | 思路:判断各自素数乘积是否相等。更多方法请参见:http://blog.csdn.net/v_JULY_v/article/details/6347454。 109 | 110 | ``` 111 | int isBrother(const char *first,const char *secd) 112 | ``` 113 | 114 | 思路一: 循环匹配 指数级复杂度 115 | 思路二: 利用质数,平方和比较,但这样必须是2个串的长度要一样,需要的空间比较大,最多256个字节。 116 | 117 | 118 | 119 | 示例代码: 120 | 121 | ``` 122 | int isBrother(const char *first,const char *secd){ 123 | 124 | } 125 | ``` 126 | 127 | 128 | 129 | ## 字符串的排列 130 | 131 | 题目:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。输入字符串 abcca,则输出由 a,b,c排列出来的所有字符串,字符出现个数不变 132 | 133 | 134 | 分析:这是一道很好的考查对递归理解的编程题 135 | 136 | 简单的回溯就可以实现了。当然排列的产生也有很多种算法,去看看组合数学,还有逆序生成排列和一些不需要递归生成排列的方法。 137 | 138 | >印象中Knuth的第一卷里面深入讲了排列的生成。这些算法的理解需要一定的数学功底,也需要一定的灵感,有兴趣最好看看。 139 | 140 | 141 | 142 | ## n个字符串联接 143 | 144 | 有n个长为m+1的字符串,如果某个字符串的最后m个字符与某个字符串的前m个字符匹配,则两个字符串可以联接,问这n个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。 145 | 146 | 147 | 148 | 149 | ## 字符串的集合合并 150 | 151 | 给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/1.1 字符串-查找.md: -------------------------------------------------------------------------------- 1 | #字符串-查找 2 | 3 | * 子串查找 4 | 5 | 6 | 7 | ## 找到第一个只出现一次的字符 8 | 9 | 在一个字符串中找到第一个只出现一次的字符。如输入ahbaccdeff,则输出h。 10 | 11 | ``` 12 | char char_first_appear_once(const char *source) 13 | ``` 14 | 15 | 思路一: 蛮力统计, O(n^2)复杂度 16 | 思路二: 使用hash表,2次扫描 17 | * 第一次建立hash表 key为字符,value为出现次数; 18 | * 第二次扫描找到第一个value为1的key,时间复杂度O(n) 19 | 20 | hash表长度 256,字符直接作为key值。需要注意的是 char 的范围是 -128~127,unsigned char 才是0~255 21 | 22 | 23 | 示例代码: 24 | 25 | ``` 26 | char char_first_appear_once(const unsigned char *source){ 27 | int hash[256]={0}; 28 | char *tmp = source; 29 | if (tmp == NULL) return '\0'; 30 | 31 | //第一次建立hash表 key为字符,value为出现次数 32 | while(*tmp != '\0'){ 33 | hash[*tmp]++; 34 | tmp++; 35 | } 36 | 37 | //第二次扫描找到第一个value为1的key 38 | tmp = source; 39 | while(*tmp != '\0'){ 40 | if (hash[*tmp] == 1) return *tmp; 41 | tmp++; 42 | } 43 | return '\0'; 44 | } 45 | ``` 46 | 47 | 48 | 题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 49 | 50 | 51 | 52 | ### 字符串中找出连续最长的数字子串 53 | 54 | 写一个函数,功能: 55 | 56 | 在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串赋值给其中一个函数参数outputstr所指内存。 57 | 58 | 例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789 59 | 60 | 它的原形是: 61 | 62 | ``` 63 | int longest_continuious_number(const char *input,char *output) 64 | ``` 65 | 66 | 应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。 67 | 68 | 69 | 1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历 70 | 2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; 71 | 3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0 72 | 73 | 74 | ``` 75 | int longest_continuious_number(const char *input,char *output){ 76 | int max = 0; 77 | char *start= input; 78 | char *mid = input; 79 | char *end = input; 80 | int tmp = 0; 81 | if (input == NULL || output == NULL) return 0; 82 | 83 | while (*end != '\0'){ 84 | if (*end < '0' || *end < '9'){//字母 85 | if(tmp > max){ 86 | max = tmp; 87 | start = mid; 88 | } 89 | tmp = 0; 90 | }else{//数字 91 | if (tmp == 0){//发现数字 92 | mid = end; 93 | } 94 | tmp++; 95 | } 96 | end++; 97 | } 98 | 99 | //修改已数字结尾的bug 100 | if(tmp > max){ 101 | max = tmp; 102 | start = mid; 103 | } 104 | 105 | //copy 106 | int i=0; 107 | while(i window; 133 | 134 | int left = 0, right = 0; 135 | int res = 0; // 记录结果 136 | while (right < s.size()) { 137 | char c = s[right]; 138 | right++; 139 | // 进行窗口内数据的一系列更新 140 | window[c]++; 141 | // 判断左侧窗口是否要收缩 142 | while (window[c] > 1) { 143 | char d = s[left]; 144 | left++; 145 | // 进行窗口内数据的一系列更新 146 | window[d]--; 147 | } 148 | // 在这里更新答案 149 | res = max(res, right - left); 150 | } 151 | return res; 152 | } 153 | ``` 154 | 155 | 156 | 157 | ## 对称子字符串的最大长度 (最长回文子串) 158 | 159 | 题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。 160 | 161 | 分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 162 | 163 | ``` 164 | int max_symmetrical_char_length(const char *scr); 165 | ``` 166 | 167 | * 思路一:蛮力法,3重循环(类似 求子数组的最大和 fmax(i,j)问题), fmax(i,j)区间i,j是最长的对称字符 168 | * 思路二:遍历所有子串,然后判读是否对称 O(n^2) 169 | * 思路三:有个O(n)复杂度的算法 http://www.cnblogs.com/McQueen1987/p/3559497.html 分析过程如下: 170 | 171 | 172 | ``` 173 | int max_symmetrical_char_length(const char *scr){ 174 | 175 | } 176 | ``` 177 | 178 | 179 | ```Java 180 | public String longestPalindrome(String s) { 181 | String res = ""; 182 | for (int i = 0; i < s.length(); i++) { 183 | // 以 s[i] 为中心的最长回文子串 184 | String s1 = palindrome(s, i, i); 185 | 186 | // 以 s[i] 和 s[i+1] 为中心的最长回文子串 187 | String s2 = palindrome(s, i, i + 1); 188 | 189 | // res = longest(res, s1, s2) 190 | res = res.length() > s1.length() ? res : s1; 191 | res = res.length() > s2.length() ? res : s2; 192 | } 193 | return res; 194 | } 195 | 196 | //实现一个函数来寻找最长回文串 197 | String palindrome(String s, int l, int r) { 198 | // 防止索引越界 199 | while (l >= 0 && r < s.length() 200 | && s.charAt(l) == s.charAt(r)) { 201 | // 向两边展开 202 | l--; r++; 203 | } 204 | // 返回以 s[l] 和 s[r] 为中心的最长回文串 205 | return s.substring(l + 1, r); 206 | } 207 | ``` 208 | 209 | 210 | ## 最小覆盖子串 211 | 212 | (难度 hard) `滑动窗口` 213 | 214 | 给定 字符串 s, t ; 在字符串 s 里找出 包含 t 所有字母的最小子串 215 | eg: s = "ADOBECODEBANC" t = "ABC" 输出: "BANC" 216 | 217 | 218 | 219 | 220 | 221 | 222 | ## 最长公共子串问题 223 | 224 | 请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。 225 | 226 | 例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。 227 | 228 | ``` 229 | int longest_common_subsequence(const char *s1,const char *s2, char *common) 230 | ``` 231 | 232 | 分析:求最长公共子串(Longest Common Subsequence,LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。如"abccade","dgcadde"的最大子串为"cad" 233 | 234 | 235 | 236 | 实例代码: 237 | 238 | ``` 239 | int longest_common_subsequence(const char *s1,const char *s2, char *common){ 240 | 241 | } 242 | ``` 243 | 244 | 245 | 246 | ## 求最大连续递增数字子串 247 | 248 | 如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。 249 | 250 | 251 | ## 请编写能直接实现strstr()函数功能的代码。 252 | 253 | > strstr(str1,str2) 判断str2是否是str1的子串。 254 | 255 | ``` 256 | /* 257 | @ret 有就返回第一次出现子串的地址,否则返回NULL 258 | */ 259 | char *strstr(const char *source, const char *target){ 260 | 261 | } 262 | ``` 263 | 264 | 265 | 266 | ## 子串匹配的个数 267 | 268 | 已知一个字符串,比如asderwsde,寻找其中的一个子字符串比如sde的个数,如果没有返回0,有的话返回子字符串的个数。 269 | 270 | ``` 271 | char *substr_count(const char *src, const char *substr, int *count) 272 | ``` 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/1.2 字符串-删除.md: -------------------------------------------------------------------------------- 1 | # 字符串-删除 2 | 3 | 4 | 5 | ### 删除串中指定的字符 6 | 7 | 删除指定的字符以后,后面的字符都要向前移动一位。这种复杂度是O(N^2);那么有没有O(N)的方法呢? 8 | 9 | 比如 "abcdeccba" 删除字符 "c"。使用2个指针,一前一后,比较前面的指针和删除字符: 10 | 11 | 1. 不相等,两个指针一起跑,且前面的指针值拷贝到后面指针指向的空间 12 | 2. 相等时,快指针向前一步 13 | 14 | ``` 15 | char *delete_occurence_character(char *src , char target){ 16 | char *front = src; 17 | char *rear = src; 18 | while(*front != '\0'){ 19 | if (*front != target){ 20 | *rear = *front; 21 | rear++; 22 | } 23 | front++; 24 | } 25 | *rear = '\0'; 26 | return src; 27 | } 28 | ``` 29 | 30 | 31 | ### 在字符串中删除特定的字符 32 | 33 | 题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”。 34 | 35 | 这是上个题目的升级版本。 36 | 37 | ``` 38 | char *delete_occurence_characterset(char *source,const char *del); 39 | ``` 40 | 41 | 1. 蛮力法。 遍历字符串,每个字符去删除字符串集合中查找,有就删除 42 | 2. 使用上面的方式,一次遍历 43 | 44 | 45 | 46 | ### 删除字符串中的数字并压缩字符串 47 | 48 | 如字符串”abc123de4fg56”处理后变为”abcdefg”。注意空间和效率。(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N)) 49 | 这道题跟上一道题也是一个意思。 50 | 51 | 52 | 示例代码: 53 | 54 | ``` 55 | char *trim_number(char *source){ 56 | char *start = source; 57 | char *end = source; 58 | if (source == NULL) return NULL; 59 | while(*end != '\0'){ 60 | if (*end < '0' || *end > '9' ){ 61 | *start = *end; 62 | start++; 63 | } 64 | end++; 65 | } 66 | *start = '\0'; 67 | return source; 68 | } 69 | ``` 70 | 71 | 72 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/1.3 字符串-修改.md: -------------------------------------------------------------------------------- 1 | # 字符串-修改 2 | 3 | 4 | 5 | ## 翻转句子中单词的顺序 6 | 7 | 题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。 8 | 例如输入“I am a student.”,则输出“student. a am I”。 9 | 10 | ``` 11 | char *revert_by_word(char *source); 12 | ``` 13 | 14 | 思路: 15 | 16 | * 原地逆序,字符串2边的字符逐个交换 , 再按单词逆序; 17 | * 也可以先按单词逆序,再对整个句子逆序; 18 | 19 | 针对不允许临时空间的情况,也就是字符交换不用临时空间,可以使用的方法有: 20 | 21 | 1. 异或操作 22 | 2. 也就是2个整数相互交换一个道理 23 | 24 | ``` 25 | char a = 'a', b = 'b'; 26 | a = a + b; 27 | b = a - b; 28 | a = a - b; 29 | ``` 30 | 31 | 最终示例代码: 32 | 33 | ``` 34 | //反转 35 | void _reverse(char *start,char *end){ 36 | if ((start == NULL) || (end == NULL)) return; 37 | while(start < end){ 38 | char tmp = *start; 39 | *start = *end; 40 | *end = tmp; 41 | 42 | start++, 43 | end--; 44 | } 45 | } 46 | 47 | char *revert_by_word(char *source){ 48 | char *end = source; 49 | char *start = source; 50 | if (source == NULL) return NULL; 51 | 52 | //end指针挪动到尾部 53 | while (*end != '\0') end++; 54 | end--; 55 | 56 | //先全部反转 57 | _reverse(start,end); 58 | 59 | //按单词反转 60 | start=end=source; 61 | while(*start != '\0'){ 62 | if (*start == ' '){ 63 | start++; 64 | end++; 65 | }else if(*end == ' ' || *end == '\0'){ 66 | _reverse(start,end-1); 67 | start = end; 68 | }else{ 69 | end++; 70 | } 71 | } 72 | return source; 73 | } 74 | ``` 75 | 76 | 类似的题目还有: 77 | 78 | 不开辟用于交换数据的临时空间,如何完成字符串的逆序 79 | 用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 80 | 81 | 82 | 83 | ## 替换空格 84 | 85 | 实现一个函数,把每个空格替换成 "%20",如输入“we are happy”,则输出“we%20are%20happy” 86 | 87 | ``` 88 | char *replce_blank(char *source) 89 | ``` 90 | 91 | 主要问题是一个字符替换成3个字符,替换后的字符串比原串长。 92 | 如果想要在原串上直接修改,就不能顺序替换。且原串的空间应该足够大,能容纳替换变长以后的字符串。如果空间不够,就要新建一块空间来保存替换的结果了。这里假设空间足够 93 | 94 | 1. 第一遍扫描,统计空格个数 n, 替换后的字符串长度 = 原长度+2*n 95 | 2. 从后向前扫描字符串,挪动每个字符的位置。注意碰到空格的地方 96 | 97 | 98 | ``` 99 | char *replace_blank(char *source){ 100 | int count = 0; 101 | char *tail = source; 102 | if (source == NULL) return NULL; 103 | while(*tail != '\0'){ 104 | if (*tail == ' ') count++; 105 | tail++; 106 | } 107 | 108 | while(count){ 109 | if(*tail != ' '){ 110 | *(tail+2*count) = *tail; 111 | }else{ 112 | *(tail+2*count) = '0'; 113 | *(tail+2*count-1) = '2'; 114 | *(tail+2*count-2) = '%'; 115 | count--; 116 | } 117 | tail--; 118 | } 119 | 120 | return source; 121 | } 122 | ``` 123 | 124 | 125 | ## 左旋转字符串 126 | 127 | >字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 128 | 129 | 如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 130 | 131 | ``` 132 | char *left_rotate(char *str,int offset){ 133 | 134 | } 135 | ``` 136 | 137 | 思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 138 | 也就是跟上面的问题是同样的问题。 139 | 140 | 141 | 142 | 143 | ## 字符串原地压缩 144 | 145 | 题目描述:“abeeeeegaaaffa" 压缩为 "abe5ag3f2a",请编程实现。 146 | 147 | 这道题需要注意: 148 | 1. 单个字符不压缩 149 | 2. 注意考虑压缩后某个字符个数是多位数(超过10个) 150 | 3. 原地压缩最麻烦的地方就是数据移动 151 | 152 | 153 | 这是使用2个指针,一前一后,如果不相等,都往前移动一位;如果相等,后一位变为数字2,且移动后面的指针一位,任然相等则数字加1,不相等 154 | 155 | 156 | ``` 157 | char *compress(const char *src,char *dest){ 158 | 159 | } 160 | ``` 161 | 162 | 上面的压缩算法可以看到,压缩算法的`效率`验证依赖`给定字符串的特性`,如果'aaaaaaaa....aaa' 这样特征的字符串,使用上面的压缩算法,压缩率接近100%,相反,可能会的0%的压缩率。 163 | 164 | 165 | 166 | 167 | 168 | ## 编写strcpy 函数 169 | 170 | 已知strcpy 函数的原型是: 171 | ``` 172 | char *strcpy(char *strDest, const char *strSrc); 173 | ``` 174 | 其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/1.4 字符串-排序.md: -------------------------------------------------------------------------------- 1 | # 字符串-排序 2 | 3 | 4 | 5 | ## 小写字母排在大写字母的前面 6 | 7 | 有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: 8 | 9 | ``` 10 | void proc(char *str) 11 | ``` 12 | 分析: 13 | 14 | 比如:HaJKPnobAACPc,要小写字母前面且不要求保存顺序,可以是:anobcHJKPAACP 15 | 16 | 1. 小写字母 a~z 的 ASCII码值是 97~122,A~Z的 ASCII码值是 65~90;0~9 的ASCII码是 48~57 17 | 2. 两边向中间扫描,左边大写右边小写就交换;如果都小写,头指针向前知道找到大写;如果都是大写,尾指针向后找小写; 18 | 19 | 20 | 示例代码 21 | 22 | ``` 23 | char *proc(char *str){ 24 | char *start = str; 25 | char *end = str; 26 | if (str == NULL) return NULL; 27 | while(*end != '\0') end++; 28 | end--; 29 | 30 | while(start < end){ 31 | if (*start >= 'A' && *start <= 'Z'){//大写 32 | if (*end >= 'a' && *end <= 'z'){ 33 | char tmp = *start; 34 | *start = *end; 35 | *end = tmp; 36 | 37 | start++; 38 | } 39 | end--; 40 | }else{//小写 41 | if (*end >= 'A' && *end <= 'Z'){ 42 | end--; 43 | } 44 | start++; 45 | } 46 | } 47 | return str; 48 | } 49 | ``` 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/2 链表.md: -------------------------------------------------------------------------------- 1 | # 链表 2 | 3 | 链表常常碰到的问题有: 4 | 5 | 6 | * [链表排序](2.1%20链表-排序.md) 7 | * [链表删除](2.2%20链表-删除.md) : 如删除链表中的p节点,在p节点前面插入节点q, 要求O(1)复杂度 8 | * [2个链表](2.3%20链表-2条.md) 相交,合并 9 | * 链表反转 10 | * 链表中是否有环 11 | 12 | 13 | 链表结点定义如下: 14 | ``` 15 | struct ListNode 16 | { 17 | int m_nKey; 18 | ListNode* m_pNext; 19 | }; 20 | ``` 21 | 22 | 23 | 24 | 常用解题思路 25 | 26 | * 双指针 27 | * 3指针 28 | * 快慢指针 29 | 30 | 31 | 32 | ## 输出链表中倒数第k个节点 33 | 34 | 题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。 35 | 36 | 思路一:2遍遍历,先遍历一遍链表算出 n 的值,然后再遍历链表计算第 n - k 个节点 37 | 思路二:双指针,一个指针先走k步,然后 两个指针一起同步往前走,前面先走的指针到链表尾部吧,后面的指针刚好是第k个节点 38 | 39 | 40 | 类似的题目还有 41 | 42 | `删除第k个节点` 43 | `链表的中间结点` : 使用「快慢指针」的技巧 ,慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 44 | 45 | 46 | 47 | ## 给定单链表,检测是否有环。 48 | 49 | ``` 50 | int isLinkCicle(Link *head); 51 | ``` 52 | 53 | 解题思路 54 | 55 | 1. 暴力 56 | 2. 空间换时间,用一个HashMap 57 | 3. 快慢指针: 使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。 58 | 59 | 60 | 61 | 62 | ## 链表反转 63 | 64 | 65 | 思路一:迭代,三个指针,遍历一遍(0(n)复杂度 66 | 思路一:递归实现,较难理解 67 | 68 | 迭代实现 69 | 70 | ``` 71 | ``` 72 | 73 | 74 | 递归实现 75 | 76 | ``` 77 | ListNode reverse(ListNode head) { 78 | if (head.next == null) return head; 79 | ListNode last = reverse(head.next); 80 | head.next.next = head; 81 | head.next = null; 82 | return last; 83 | } 84 | ``` 85 | 86 | 87 | ## 从尾到头输出链表 88 | 89 | 输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下: 90 | 91 | 思路一: 辅助栈,需要一个栈空间 92 | 思路二: 反转链表,然后遍历 93 | 思路三: 递归实现,将printf语句放在递归调用后面。果然妙极。。 94 | 95 | 96 | 97 | 98 | 99 | ## 复杂链表的复制 100 | 101 | 题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下: 102 | ``` 103 | struct ComplexNode 104 | { 105 | int m_nValue; 106 | ComplexNode* m_pNext; 107 | ComplexNode* m_pSibling; 108 | }; 109 | ``` 110 | 111 | 下图是一个含有5个结点的该类型复杂链表。图中实线箭头表示m_pNext指针,虚线箭头表示m_pSibling指针。为简单起见,指向NULL的指针没有画出。 112 | 请完成函数`ComplexNode* Clone(ComplexNode* pHead)`,以复制一个复杂链表。 113 | 114 | 115 | 这个题目难点在于: 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/2.1 链表-排序.md: -------------------------------------------------------------------------------- 1 | # 链表-排序 2 | 3 | 4 | 5 | 6 | ## 链表排序 7 | 8 | Given a head pointer pointing to a linked list ,please write a function that sort the list 9 | in increasing order. You are not allowed to use temporary array or memory copy (微软面试题) 10 | 11 | ``` 12 | struct 13 | { 14 | int data; 15 | struct S_Node *next; 16 | }Node; 17 | 18 | Node * sort_link_list_increasing_order (Node *pheader): 19 | ``` 20 | 21 | 22 | ## 单链表归并排序 23 | 24 | 啥是归并排序? 25 | 26 | 27 | 28 | 29 | 类似的题有: 30 | 31 | 1 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。 32 | 33 | 如果head1==head2,那么显然相交,直接返回head1。 34 | 否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2,假设len1>=len2, 35 | 那么指针p1由head1开始向后移动len1-len2步,指针p2=head2, 36 | 下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点, 37 | 否则说明两个链表没有交点。 38 | 39 | 40 | 2 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。 41 | 42 | 运用题一,我们可以检查链表中是否有环。如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, 43 | p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始, 44 | 于是运用题二的方法,我们找到它们的第一个交点即为所求。 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/2.2 链表-删除.md: -------------------------------------------------------------------------------- 1 | #链表-删除 2 | 3 | 4 | 5 | ## 在O(1)时间内删除链表结点 6 | 7 | 题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下: 8 | 9 | ``` 10 | struct ListNode{ 11 | int m_nKey; 12 | ListNode* m_pNext; 13 | }; 14 | 15 | //函数的声明如下: 16 | void deleteNode(ListNode* pListHead, ListNode* pToBeDeleted); 17 | 18 | ``` 19 | 20 | 思路: 21 | 保存下一个节点的值tmp,删除下一个节点,当前节点=tmp 22 | 23 | 24 | 25 | 26 | ## 删除链表中的p节点 27 | 28 | 只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。 29 | 30 | 办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。 31 | 32 | 33 | 类似的还有问题:只给定单链表中某个结点p(非空结点),在p前面插入一个结点。办法类似,首先分配一个结点q,将q插入在p后, 34 | 接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。都可以做到0(1)复杂度 35 | 36 | 37 | 38 | 39 | ## 将两链表中data值相同的结点删除 40 | 41 | 42 | 有双向循环链表结点定义为: 43 | 44 | 45 | ``` 46 | struct node{ 47 | int data; 48 | struct node *front,*next; 49 | }; 50 | 51 | ``` 52 | 53 | 有两个双向循环链表A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两链表中data值相同的结点删除。 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/2.3 链表-2条.md: -------------------------------------------------------------------------------- 1 | # 链表-2条 2 | 3 | 链表的结点定义为: 4 | ``` 5 | struct ListNode 6 | { 7 | int m_nKey; 8 | ListNode* m_pNext; 9 | }; 10 | ``` 11 | 12 | 13 | ## 找出两个链表的第一个公共结点 14 | 15 | 题目:两个单向链表,找出它们的第一个公共结点。 16 | 17 | 分析:第一个公共节点,也就是2个链表中的节点的m_pNext 指向的同一个节点。 2遍遍历方法: 18 | 19 | 1. 先遍历2个链表,得到各自的长度,和差sub 20 | 2. 长链表先遍历sub个节点,然后2个节点一起遍历 21 | 3. 第一次同时指向的同一个节点就是这个commonNode 22 | 23 | 24 | 有没有可能一遍遍历就解决问题呢? 25 | 26 | 让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。 27 | 空间复杂度为 O(1),时间复杂度为 O(N), 一遍遍历就搞定 28 | 29 | ``` 30 | ListNode getIntersectionNode(ListNode headA, ListNode headB) { 31 | // p1 指向 A 链表头结点,p2 指向 B 链表头结点 32 | ListNode p1 = headA, p2 = headB; 33 | 34 | while (p1 != p2) { 35 | // p1 走一步,如果走到 A 链表末尾,转到 B 链表 36 | if (p1 == null) p1 = headB; 37 | 38 | else p1 = p1.next; 39 | 40 | // p2 走一步,如果走到 B 链表末尾,转到 A 链表 41 | if (p2 == null) p2 = headA; 42 | else p2 = p2.next; 43 | } 44 | 45 | return p1; 46 | } 47 | ``` 48 | 49 | 50 | 51 | ## 判断俩个链表是否相交 52 | 53 | 给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表均不带环。 54 | 55 | 问题扩展: 56 | 57 | 1.如果链表可能有环列? 58 | 2.如果需要求出俩个链表相交的第一个节点列? 59 | 60 | 61 | 62 | ## 合并2条有序链表 63 | 64 | while 循环每次比较 p1 和 p2 的大小,把较小的节点接到结果链表上 65 | 66 | ``` 67 | ListNode mergeTwoLists(ListNode l1, ListNode l2) { 68 | // 虚拟头结点 69 | ListNode dummy = new ListNode(-1), p = dummy; 70 | ListNode p1 = l1, p2 = l2; 71 | 72 | while (p1 != null && p2 != null) { 73 | // 比较 p1 和 p2 两个指针 74 | // 将值较小的的节点接到 p 指针 75 | if (p1.val > p2.val) { 76 | p.next = p2; 77 | p2 = p2.next; 78 | } else { 79 | p.next = p1; 80 | p1 = p1.next; 81 | } 82 | // p 指针不断前进 83 | p = p.next; 84 | } 85 | 86 | if (p1 != null) { 87 | p.next = p1; 88 | } 89 | 90 | if (p2 != null) { 91 | p.next = p2; 92 | } 93 | 94 | return dummy.next; 95 | } 96 | ``` 97 | 98 | 99 | ## 合并k个有序链表 100 | 101 | 102 | 相比合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? 103 | 104 | 就要用到 优先级队列(二叉堆) 这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点: 105 | 106 | 107 | ``` 108 | ListNode mergeKLists(ListNode[] lists) { 109 | if (lists.length == 0) return null; 110 | // 虚拟头结点 111 | ListNode dummy = new ListNode(-1); 112 | ListNode p = dummy; 113 | // 优先级队列,最小堆 114 | PriorityQueue pq = new PriorityQueue<>( 115 | lists.length, (a, b)->(a.val - b.val)); 116 | // 将 k 个链表的头结点加入最小堆 117 | for (ListNode head : lists) { 118 | if (head != null) 119 | pq.add(head); 120 | } 121 | 122 | while (!pq.isEmpty()) { 123 | // 获取最小节点,接到结果链表中 124 | ListNode node = pq.poll(); 125 | p.next = node; 126 | if (node.next != null) { 127 | pq.add(node.next); 128 | } 129 | // p 指针不断前进 130 | p = p.next; 131 | } 132 | return dummy.next; 133 | } 134 | ``` 135 | 136 | 137 | ## 输出两个非降序链表的并集 138 | 139 | 请修改append函数,利用这个函数实现: 140 | 141 | 两个非降序链表的并集,1->2->3 和 2->3->5 并为 1->2->3->5 。另外只能输出结果,不能修改两个链表的数据。 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/3 堆和栈.md: -------------------------------------------------------------------------------- 1 | # 堆和栈 2 | 3 | 4 | 栈的数据结构 5 | 6 | ``` 7 | typede int Value; 8 | typedef struct Entry{ 9 | Entry *next; 10 | Value value; 11 | }Entry; 12 | 13 | typedef struct Stack{ 14 | Entry *head;/*添加和删除都在这个节点指针上*/ 15 | int length; 16 | int capacity; 17 | }Stack; 18 | 19 | Stack create(unsigned int size); 20 | void push(Stack,Value); 21 | Value pop(Stack); 22 | 23 | ``` 24 | 25 | 队列的数据结构 26 | 27 | ``` 28 | typede int Value; 29 | typedef struct Entry{ 30 | Entry *next; 31 | Value value; 32 | }Entry; 33 | 34 | typedef struct Queue{ 35 | Entry *front;/*删除都在这个节点指针上*/ 36 | Entry *rear;/*添加在这个节点指针上*/ 37 | int length; 38 | }Stack; 39 | 40 | void enqueue(Queue,Value); 41 | void dequeue(Queue); 42 | ``` 43 | 44 | 45 | 这里的题目是有关栈和队列的问题。 46 | 47 | 48 | ## 循环队列中元素个数 49 | 50 | 如果用一个循环数组q[0..m-1]表示队列时,该队列只有一个队列头指针front,不设队列尾指针rear,求这个队列中从队列头到队列尾的元素个数(包含队列头、队列尾) 51 | 52 | 分析: 53 | 54 | 有rear指针时: 55 | 56 | ``` 57 | if (rear>front) 58 | count = rear-front+1 59 | else 60 | count = rear-front+m+1 61 | ``` 62 | 综合: `count = (rear-front+m+1)%m` 63 | 64 | 不用rear指针的话: 65 | 数据结构中加一个 `int count`来计数。 66 | 67 | 68 | 69 | ## 设计包含min函数的栈 70 | 71 | 定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。 72 | 要求函数min、push以及pop的时间复杂度都是O(1)。 73 | 74 | 主要是min函数实现。咋一看,用一个变量来存储最小值,或最小值的下标不就ok了?这在只push的时候有用,想想如果这个最小值pop出去了呢?我们怎么更新现在的最小值? 75 | 76 | 这里用一个辅助栈,空间复杂度是O(n). 77 | 78 | push一个元素a时,该元素a与辅助栈顶f[top]元素比较: 79 | a > f[top],在辅助栈再次中push一遍f[top] (可以把这个省去,节省空间,代价是pop的时候要跟最小栈元素比较,如果pop元素跟最小栈栈顶相等,可能最小值要变了) 80 | a < f(top),把 a push 到 辅助栈中 81 | 82 | 这样辅助栈中的元素是 从上到小 递增的数列。且 辅助栈中的栈顶元素 总是 栈中元素集合的最小值。 83 | 84 | 以上方法需要的空间复杂度是O(n) 85 | 86 | 87 | 88 | ## 栈的push、pop序列 89 | 90 | 题目:输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。 91 | 92 | 比如输入的push序列是[1、2、3、4、5] ,那么[4、5、3、2、1]就有可能是一个pop序列 93 | 94 | 因为可以有如下的push和pop序列: 95 | 【push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop】, 这样得到的pop序列就是【4、5、3、2、1】。 96 | 97 | 但序列【4、3、5、1、2】就不可能是push序列【1、2、3、4、5】的pop序列。 98 | 99 | 100 | 这里只是要判断是不是pop序列,并没有要求所有的pop序列 101 | 需要一个辅助栈(需要一个写好的栈结构辅助,如果是C语言,还要包装一个栈结构) 102 | 103 | 1. 对序列A中的A[0]入栈,比较B[0] 104 | 2. 如果不相等,A[1]入栈 105 | 3. 直到A[i] = B[0] 106 | 4. 栈顶出栈,开始匹配B[1] 107 | 5. 如果A[i-1]!=B[1],那就继续入栈A[i+1] 108 | 109 | 6 .循环往复~ 110 | 7. 如果最后,栈为空,就是一个pop序列;如果栈不为空,或者B数组都没有遍历到尾部,就肯定不是pop序列了 111 | 112 | 113 | 114 | ## 颠倒栈 115 | 116 | 题目:用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5},1在栈顶。颠倒之后的栈为{5, 4, 3, 2, 1},5处在栈顶。 117 | 118 | 思路一:所有元素pop出来,放到一个数组里,然后在从第一个元素开始入栈,空间复杂度需要 O(N) 119 | 思路二: 递归方法。把栈看出2部分 :1 和 {2,3,4,5} , 把栈{2,3,4,5}颠倒过来,然后把1放到底部,那整个栈就颠倒过来了。 120 | 121 | ``` 122 | void reverseStack(){ 123 | stack.pop(); 124 | reverseStack(); 125 | addTopToStackBottom(); 126 | } 127 | ``` 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4 数值问题.md: -------------------------------------------------------------------------------- 1 | # 数值问题 2 | 3 | 这部分都是一些数学几何计算方面的问题。主要由: 4 | 5 | * [加减乘除](4.1%20数值-加减乘除.md) 6 | * 指数(乘方) 7 | * 随机数 8 | * 进制转换:位运算 9 | * 大数问题 10 | * 公倍数 11 | * 素数 12 | * 丑数 13 | 14 | 15 | ## ipv4 转 int 16 | 17 | 比如: 127.0.0.1 , 转为int 为 (01111111 00000000 00000000 00000001) 18 | 19 | 思路: 20 | 21 | ``` 22 | int toInt(){ 23 | 24 | } 25 | ``` 26 | 27 | 那么,int 转 ipv4 如何解呢? 28 | 29 | 30 | 31 | ## 整数的二进制表示中1的个数 32 | 33 | 题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。 34 | 35 | 分析: 36 | 这是一道很基本的考查位运算的面试题。 37 | 解法1:一轮循环移位计数 (移位运算比除法运算效率要高,注意要考虑是负数的情况) 38 | 解法2:位运算 39 | 解法3:num &= num-1 巧妙之处在于,对高位没有影响。不断做 `num &= num-1` 直到num=0。 40 | 41 | 1010 & 1001 = 1000 42 | 1000 * 0111 = 0000 43 | 44 | ``` 45 | int one_appear_count_by_binary(int num){ 46 | int count = 0; 47 | while(num !=0 ){ 48 | num &= num-1; 49 | count++; 50 | } 51 | return count; 52 | } 53 | ``` 54 | 55 | 56 | 57 | ## 把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列 58 | 59 | 分析: 60 | ``` 61 | char *integer_to_hex(long i); //eg: 20 => 14 62 | char *integer_to_bin(long i); //eg: 20 => 10100 63 | ``` 64 | 65 | 注意,转16进制中,要判断tmp[i]是否是有符号的数 66 | ``` 67 | tmp[i] = tmp[i]>=0 ? tmp[i] : tmp[i]+16; 68 | ``` 69 | 70 | 71 | 72 | ## 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句 73 | 74 | 分析: 75 | ``` 76 | #define min(a,b) ((a)>(b)?(a):(b)) 77 | #define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) 78 | ``` 79 | 80 | 这里不能使用比较符号: 81 | 82 | ``` 83 | #define min(a,b) ((a)-(b) & (0x1<<31))?(a):(b) 84 | ``` 85 | 86 | 87 | 88 | ## 整数的素数和分解问题 89 | 90 | > 歌德巴赫猜想说任何一个不小于6的偶数都可以分解为两个奇素数之和。 91 | 对此问题扩展,如果一个整数能够表示成两个或多个素数之和,则得到一个素数和分解式。 92 | 93 | 对于一个给定的整数,输出所有这种素数和分解式。 94 | 注意,对于同构的分解只输出一次(比如5只有一个分解2 + 3,而3 + 2是2 + 3的同构分解式 95 | 96 | 97 | 例如,对于整数8,可以作为如下三种分解: 98 | 99 | ``` 100 | (1) 8 = 2 + 2 + 2 + 2 101 | (2) 8 = 2 + 3 + 3 102 | (3) 8 = 3 + 5 103 | ``` 104 | 105 | 106 | 107 | 108 | ## 输出1到最大的N位数 109 | 110 | 题目:输入数字n,按顺序输出从1最大的n位10进制数。比如输入3,则输出1、2、3一直到最大的3位数即999。 111 | 112 | 分析:这是一道很有意思的题目。看起来很简单,其实里面却有不少的玄机。 113 | 输入4,输出: 1,2,3,。。9999 114 | 输入5,输出: 1,2,3,4,...99999 115 | 116 | 玄机一: 整数溢出 117 | 118 | 119 | 120 | 121 | ## 寻找丑数 122 | 123 | > 我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。 124 | > 分析:这是一道在网络上广为流传的面试题,据说google曾经采用过这道题。 125 | 126 | 求按从小到大的顺序的第1500个丑数。 127 | 128 | 129 | 这里的因子应该不包含本身,因此这个序列应该是这样: 130 | 1,2,3,4,5,6,8,9,10,12,15,16,18,20,28.... 131 | 132 | 133 | 1)所有的偶数都在序列中 134 | 2)3的倍数也在序列中 135 | 3)5的倍数也在系列中 136 | 137 | 138 | 0. 2,3,5最小公倍数是30 139 | 1. [1,30]符合条件有22个 140 | 2. [30,60]符合条件也22个 141 | 142 | 第1500个: `1500/22=68` 余 4,一个周期内的前4个数是2,3,4,5; 最终答案是`68*30+5` 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4.1 数值-加减乘除.md: -------------------------------------------------------------------------------- 1 | # 数值-加减乘除 2 | 3 | 4 | * 等差数列 5 | * 阶乘 6 | 7 | 8 | 9 | ## 求1+2+…+n 10 | 11 | 要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。 12 | 13 | 思路:这是等差数列求和公式, 1+2+3.......+N =(n+1)n/2 14 | 15 | 分析: 16 | 1. 不能使用循环,那就用递归 17 | 2. 递归需要终止递归的条件判断语句,这里也不能用if,想其他办法,可以使用 &&逻辑与 运算符(在n>0条件满足是,才会指向后面的递归语句) 18 | 19 | ``` 20 | int sum(n){ 21 | int sum=0; 22 | (n>0) && sum=n+factorial(n-1) 23 | return sum; 24 | } 25 | ``` 26 | 27 | 28 | 29 | 30 | ## 大数阶乘(factorial) 31 | 32 | > 阶乘 n*(n-1)*(n-2)*...*1 33 | 34 | 主要考虑算出来的结果肯定会大于int表达的范围,这时候怎么处理? 35 | 36 | ``` 37 | int factorial(n){ 38 | int sum=0; 39 | (n>0) && sum=n+factorial(n-1) 40 | return sum; 41 | } 42 | ``` 43 | 44 | 考虑整数溢出情况 45 | 46 | ``` 47 | char[] factorial(int n){ 48 | int sum=0; 49 | return sum; 50 | } 51 | 52 | ``` 53 | 54 | 55 | ## 1024! 末尾有多少个0? 56 | 57 | 分析: 58 | 末尾0的个数取决于2和5的个数 59 | 能被2整除的数比能被5整除的数要多得多,因此只统计被5整除的数的个数 60 | 61 | 62 | 63 | 64 | ## 大整数乘法(或 大整数阶乘) 65 | 66 | 请使用代码计算`1234567891011121314151617181920*2019181716151413121110987654321` 。 67 | 68 | 1. 注意结果可能超出长整形的最大范围 2^64-1 69 | 2. 采用分治算法,将大整数相乘转换为小整数计算 70 | 71 | 规律分析:任意位数的整数相乘,最终都可以转化为2位数相乘 72 | 73 | 74 | 75 | 76 | 77 | 78 | ## 实现两个正整数的除法(和取模) 79 | 80 | 编程实现两个正整数的除法,当然不能用除法操作符。 81 | ``` 82 | int div(const int x, const int y) 83 | ``` 84 | 85 | 1. 循环减被除数,减到不能再减,当除数很大,被除数小时,效率很低 86 | 2. 位运算 87 | 88 | 89 | 90 | 91 | ## 两个数相乘,小数点后位数没有限制,请写一个高精度算法 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4.2 数值-指数.md: -------------------------------------------------------------------------------- 1 | # 4.2 数值-指数 2 | 3 | 4 | > 在a^n中,a叫做底数,n叫做指数。a^n读作“a的n次方”或“a的n次幂“ 5 | 6 | 7 | 8 | ## 数值的整数次方 9 | 10 | 题目:实现函数`double Power(double base, int exponent)`,求base的exponent次方。 11 | 12 | 不需要考虑溢出。 13 | 14 | 15 | 用例: 16 | 17 | ``` 18 | 2^3 = 8 19 | 0^3 = 0 20 | 2^0 = 1 21 | 2^-3 = 1/(2^3) = 0.125 22 | 0^-3 23 | ``` 24 | 25 | 26 | 27 | 分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码: 28 | ``` 29 | double Power(double base, int exponent) 30 | { 31 | double result = 1.0; 32 | for(int i = 1; i <= exponent; ++i) 33 | result *= base; 34 | return result; 35 | } 36 | ``` 37 | 38 | 39 | 上面的代码没有考虑: exponent<=0 40 | 判断一个浮点数是不是等于0时,不是直接写 base == 0 ,而应该判断它们的差的绝对值是不是小于一个很小的范围 41 | 42 | 如果指数大于0,我们还可以使用递归实现:`a^n = a^(n/2)*a^(n/2)` (n为偶数), 通过这个思路也可以实现 43 | ``` 44 | 2^16 = 2^8 * 2^8 45 | 2^8 = 2^4 * 2^4 46 | 2^4 = 2^2 * 2^2 47 | 2^2 = 2^1 * 2^1 48 | 2^1 = 2 49 | ``` 50 | 51 | 52 | 使用递归实现的代码示例: 53 | 54 | ``` 55 | double Power(double base, unsigned int exponent) 56 | { 57 | if (exponent == 0) return 1; 58 | if (exponent == 1) return base; 59 | double result = Power(base, exponent >> 1); 60 | result *= result; 61 | 62 | if (exponent & 1) result = result*base; 63 | 64 | return reslut; 65 | } 66 | ``` 67 | 68 | 69 | ## Sqrt(x) 70 | 71 | 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 72 | 73 | 74 | 示例 1: 75 | ``` 76 | 输入:x = 4 77 | 输出:2 78 | ``` 79 | 示例 2: 80 | ``` 81 | 输入:x = 8 82 | 输出:2 83 | 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 84 | ``` 85 | 86 | 87 | 88 | ## 求根号2的值 89 | 90 | 并且按照我的需要列出指定小数位,比如根号2是1.141 我要列出1位小数就是1.1 2位就是1.14, 1000位就是1.141...... 等。。 91 | 92 | 分析: 93 | 泰勒级数 94 | 牛顿迭代法 95 | 96 | 97 | 98 | 99 | 100 | 101 | ## 判断一个自然数是否是某个数的平方 102 | 103 | 说明:当然不能使用开方运算。 104 | 105 | ``` 106 | square(25) YES 107 | square(35) NO 108 | ``` 109 | 110 | 方法1: 从1开始遍历,显然这种方法很差 111 | 方法2: 除数跟余数比较,除数从2开始,每一轮都跟结果比较;相等就是存在这个数,不相等就把除数++;时间复杂度为(根号n)。在一个数比较大时,效率不够好。如1194877489 =(34567)^2,需要从2开始,一直比较到34566,做3万多次除法和比较运算。跟方法1一样。。 112 | 113 | 方法3:二分查找 O(logn)。比如25: 114 | 115 | a. 先取(0+25)/2=12.5,12.5*12.5>25,因此这个数应该小于12.5 116 | b.(0+12)/2 = 6, 6*6>25 117 | c. (0+6)/2 = 3 118 | d. (3+6)/2 = 4.5 119 | e. (5+6)/2 = 5.5 120 | 121 | ``` 122 | int is_powered(int num) 123 | ``` 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4.3 数值-随机数.md: -------------------------------------------------------------------------------- 1 | # 4.3 数值-随机数 2 | 3 | 4 | 5 | 6 | ## 给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数。 7 | 8 | 9 | 10 | ## 设计一个随机算法 11 | 12 | 给你5个球,每个球被抽到的可能性为30、50、20、40、10,设计一个随机算法,该算法的输出结果为本次执行的结果。输出A,B,C,D,E即可。 13 | 14 | 15 | ## 构造一个随机发生器 16 | 17 | 已知一随机发生器,产生0的概率是p,产生1的概率是1-p,现在要你构造一个发生器, 18 | 使得它构造0和1的概率均为1/2;构造一个发生器,使得它构造1、2、3的概率均为1/3;..., 19 | 构造一个发生器,使得它构造1、2、3、...n的概率均为1/n,要求复杂度最低。 20 | 21 | 22 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4.4 数值-最小公倍数.md: -------------------------------------------------------------------------------- 1 | # 4.4 数值-最小公倍数 2 | 3 | 4 | 5 | 求两个或N个数的最大公约数和最小公倍数? 6 | 7 | > 最大公约数: 12、16的公约数有1、2、4,其中最大的一个是4,4是12与16的最大公约数 8 | > 最小公倍数: 45和30的最小公倍数是多少? 9 | > 定理: (a,b)x[a,b]=ab ; (a,b) 是 a,b 的 最大公约数, [a,b]是a,b 的 最小公倍数 10 | 11 | 12 | 13 | ### 最小公倍数应用场景 14 | 15 | 16 | 甲、乙、丙三人是朋友,他们每隔不同天数到图书馆去一次。甲3天去一次,乙4天去一次,丙5天去一次。有一天,他们三人恰好在图书馆相会,问至少再过多少天他们三人又在图书馆相会? 17 | 18 | 19 | 一块砖长20厘米,宽12厘米,厚6厘米。要堆成正方体至少需要这样的砖头多少块? 20 | 21 | 22 | 甲每秒跑3米,乙每秒跑4米,丙每秒跑2米,三人沿600米的环形跑道从同一地点同时同方向跑步,经过多少时间三人又同时从出发点出发? 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/4.5 数值-素树.md: -------------------------------------------------------------------------------- 1 | # 数值-素树 2 | 3 | > 如果一个数如果只能被 1 和它本身整除,那么这个数就是素数 4 | 5 | 6 | ## 返回区间 [2, n) 中有几个素数 7 | 8 | ``` 9 | int countPrimes(int n) 10 | ``` 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/5 数组数列问题.md: -------------------------------------------------------------------------------- 1 | # 数组数列问题 2 | 3 | 这部分的问题都集中在数据集合上。主要有: 4 | 5 | * 数组排序 6 | * top-k 7 | * 子数组 8 | * 多个数组合并,交集 9 | 10 | 11 | 解决这一类问题时,可以从以下几个方面考虑: 12 | 13 | * 万能的蛮力穷举 14 | * 散列表空间换事件 15 | * 分治法,然后归并 (归并排序) 16 | * 选择合适的数据结构可以显著提高算法效率 (堆排序求top-k) 17 | * 对无序的数组先排序,使用二分 18 | * 贪心算法或动态规划 19 | 20 | 21 | 22 | ## Fibonacci数列 23 | 24 | 题目:定义Fibonacci数列如下: 25 | ``` 26 | 0 n=0 27 | f(n)= 1 n=1 28 | f(n-1)+f(n-2) n=2 29 | ``` 30 | 31 | 输入n,用最快的方法求该数列的第n项。 32 | 33 | 思路一:`递归`,虽然fibonacci数列是`递归`的经典应用,但递归效率很差,会有很多重复的计算,复杂度是成指数递增的,我测试了下计算50的时候已经要300s了。 34 | 35 | ``` 36 | int fib(int N) { 37 | if (N == 1 || N == 2) return 1; 38 | return fib(N - 1) + fib(N - 2); 39 | } 40 | ``` 41 | 42 | 思路二:从下往上计算,复杂度O(N),一个循环就搞定 43 | 44 | ``` 45 | public int fib(int n){ 46 | if (n == 0) return 0; 47 | 48 | //缓存 49 | int[] dp = new int[n + 1]; 50 | 51 | // base case 52 | dp[0] = 0; dp[1] = 1; 53 | 54 | // 状态转移 55 | for (int i = 2; i <= n; i++) { 56 | dp[i] = dp[i - 1] + dp[i - 2]; 57 | } 58 | 59 | return dp[n]; 60 | } 61 | ``` 62 | 63 | 说一个细节优化点,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP table 来存储所有的状态,只要想办法存储之前的两个状态就行了。所以,可以进一步优化,把空间复杂度降为 O(1): 64 | 65 | ``` 66 | int fib(int n) { 67 | if (n < 1) return 0; 68 | if (n == 2 || n == 1) 69 | return 1; 70 | int prev = 1, curr = 1; 71 | for (int i = 3; i <= n; i++) { 72 | int sum = prev + curr; 73 | prev = curr; 74 | curr = sum; 75 | } 76 | return curr; 77 | } 78 | ``` 79 | 80 | 81 | 82 | 83 | ## 递减数列左移后的数组中找数 84 | 85 | 一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5} 86 | 是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。 87 | 88 | 89 | 1. 右移,二分查找。 找到最小的数,右移到第一个位置的时候,右移完成 O(N) 90 | 2. 直接二分查找 91 | 92 | 93 | 94 | 95 | ## 给出一个洗牌算法 96 | 97 | 给出洗牌的一个算法,并将洗好的牌存储在一个整形数组里 98 | 99 | 分析:扑克牌54张`2~10,J,Q,K,A,小王,大王` 100 | 101 | 1)产生随机数, 随机数 rand()%54 ,rand()每次运行都一样,要改为srand(time(NULL)) 102 | 2) 遍历数组, 随机数k属于区间[i,n],然后a[i] 和 随机数 a[k] 对换 103 | 104 | 105 | 106 | ## 重合区间最长的两个区间段 107 | 108 | 在一维坐标轴上有n个区间段,求重合区间最长的两个区间段 109 | 如【-7,21】,【4,23】,【14,100】,【54,76】 110 | 111 | 思路一:两两比较,复杂度 N^2 112 | 思路二:先排序+分而治之 113 | 114 | 115 | 116 | 117 | 118 | 在一个int数组里查找这样的数,它大于等于左侧所有数,小于等于右侧所有数。 119 | 直观想法是用两个数组a、b。a[i]、b[i]分别保存从前到i的最大的数和从后到i的最小的数, 120 | 一个解答:这需要两次遍历,然后再遍历一次原数组, 121 | 将所有data[i]>=a[i-1]&&data[i]<=b[i]的data[i]找出即可。 122 | 给出这个解答后,面试官有要求只能用一个辅助数组,且要求少遍历一次。 123 | 124 | 125 | 126 | 127 | 一排N(最大1M)个正整数+1递增,乱序排列,第一个不是最小的,把它换成-1, 128 | 最小数为a且未知。求第一个被-1替换掉的数原来的值,并分析算法复杂度。 129 | 130 | [4,3,5,2,7,6] 131 | 题目啥意思? 132 | 133 | 134 | 135 | 正整数序列Q中的每个元素都至少能被正整数a和b中的一个整除,现给定a和b,需要计算出Q中的前几项,例如,当a=3,b=5,N=6时,序列为3,5,6,9,10,12 136 | (1)、设计一个函数void generate(int a,int b,int N ,int * Q)计算Q的前几项 137 | (2)、设计测试数据来验证函数程序在各种输入下的正确性。 138 | 139 | 分析: 140 | 这个输出序列是要 递增排列 141 | 思路一: 类似对2各数组merge,取min( A[i],B[j]) .复杂度O(N) 142 | 不过这里要注意,去掉 a, b的公倍数。如 3,5 都有15可以整除 143 | 144 | 145 | 146 | 147 | 148 | 149 | ## 把数组排成最小的数 150 | 151 | 题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。 152 | 例如输入数组{32, 321},则输出这两个能排成的最小数字32132。 153 | 请给出解决问题的算法,并证明该算法。 154 | 155 | 156 | 157 | 158 | ## 旋转数组中的最小元素。 159 | 160 | 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 161 | 输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。 162 | 例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。 163 | 164 | 分析:这道题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小的元素, 165 | 时间复杂度显然是O(N)。但这个思路没有利用输入数组的特性,我们应该能找到更好的解法。 166 | 167 | 168 | 169 | 170 | 171 | ## 约瑟夫环问题 172 | 173 | n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始, 174 | 每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。 175 | 当一个数字删除后,从被删除数字的下一个继续删除第m个数字。 176 | 求出在这个圆圈中剩下的最后一个数字。 177 | 178 | 如 `0,1,2,3,4,5` 删除第2个数字 (n=6,m=2) 179 | 第一次删除:1 180 | 第二次删除: 3 181 | 第三次删除:5 182 | 第四次删除:2 183 | 因此,左后的一个数字就是 4 184 | 185 | 从数学上分析下规律: 186 | 187 | 188 | 189 | 190 | 191 | 192 | ## 求最大重叠区间大小 193 | 194 | 题目描述:请编写程序,找出下面“输入数据及格式”中所描述的输入数据文件中最大重叠区间的大小。 195 | 对一个正整数 n ,如果n在数据文件中某行的两个正整数(假设为A和B)之间,即A<=n<=B或A>=n>=B ,则 n 属于该行; 196 | 如果 n 同时属于行i和j ,则i和j有重叠区间;重叠区间的大小是同时属于行i和j的整数个数。 197 | 198 | 例如,行(10 20)和(12 25)的重叠区间为 [12 20] ,其大小为9,行(20 10)和( 20 30 )的重叠区间大小为 1 。 199 | 200 | 201 | 202 | 203 | ## 四对括号可以有多少种匹配排列方式 204 | 205 | 比如两对括号可以有两种:()()和(()) 206 | 207 | 208 | 209 | 210 | ## 两两之差绝对值最小的值 211 | 212 | 有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。 213 | 如 [-1, 3, 5, 9] 绝对值最小的是2(5-3) 214 | 215 | 最短区间问题 216 | 如果是有重复元素,那最小值就是0了 217 | 218 | 解题思路: 可以将这个问题转化为 ”求最大字段和“ 问题。。。 219 | 220 | 221 | 222 | ## 数值是否连续相邻 223 | 224 | 一个整数数列,元素取值可能是0~65535中的任意一个数,相同数值不会重复出现。0是例外,可以反复出现。请设计一个算法,当你从该数列中随意选取5个数值,判断这5个数值是否连续相邻。 225 | 注意: 226 | - 5个数值允许是乱序的。比如: 8 7 5 0 6 227 | - 0可以通配任意数值。比如:8 7 5 0 6 中的0可以通配成9或者4 228 | - 0可以多次出现。 229 | - 复杂度如果是O(n2)则不得分。 230 | 231 | 这个问题跟”扑克牌顺子“判断问题一样,通过比较0的个数和相邻数字之间间隔总和来判断所有数是否连续。 232 | 233 | 234 | 235 | 236 | 237 | ////////////// 数列 /////////////// 238 | 239 | 240 | 给出两个集合A和B,其中集合A={name}, 241 | 集合B={age、sex、scholarship、address、...}, 242 | 243 | 要求: 244 | 问题1、根据集合A中的name查询出集合B中对应的属性信息; 245 | 问题2、根据集合B中的属性信息(单个属性,如age<20等),查询出集合A中对应的name。 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/5.1 数列-排序.md: -------------------------------------------------------------------------------- 1 | # 5.1 数列-排序 2 | 3 | 4 | ## 使用归并排序对一个 int 类型的数组排序 5 | 6 | 7 | 比如 [1, 6, 2, 2, 2, 3] 8 | 9 | ``` 10 | void sort(int * a, int length) 11 | ``` 12 | 13 | 14 | 15 | ## 用递归的方法判断整数组a[N]是不是升序排列 16 | 17 | 递归 isAscend(n-1) && a[N-1]< a[N] 18 | 19 | 20 | 21 | 22 | ## 求一个数组的最长递减子序列 23 | 24 | 比如`{9,4,3,2,5,4,3,2}`的最长递减子序列为`{9,5,4,3,2}` 25 | 26 | `动态规划` 27 | 28 | 29 | 30 | ## 扑克牌的顺子 31 | 32 | 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。 33 | 2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。 34 | 35 | 对这5个数(大小王看做0) 36 | 1)排序,如快排 37 | 2)统计0的个数 38 | 3)统计相邻元素空缺总数 39 | 40 | ``` 41 | //5个是不是顺子 42 | isShunZi(int *a,int length) 43 | ``` 44 | 45 | 46 | 47 | 48 | ## 分割数组 49 | 50 | 一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它。 51 | 能否只用一个额外数组和少量其它空间实现。 52 | 53 | `快速排序` 54 | 55 | 56 | 57 | 58 | 59 | ## 调整数组顺序使奇数位于偶数前面 60 | 61 | 题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分, 62 | 所有偶数位于数组的后半部分。要求时间复杂度为O(n)。 63 | 64 | 思路:两边向中间扫描,如果第一个指针是偶数,第二个指针是奇数,就交换;如果第一个是偶数,第二个也偶数,第二个指针向前移;反之,第一个指针向后移 65 | 66 | ``` 67 | void reorder(int *data, int length); 68 | ``` 69 | 70 | 71 | 72 | ## 奇偶分离 73 | 74 | 给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。 75 | 要求:空间复杂度O(1),时间复杂度为O(n) 76 | 如 [4,5,2,7,5] => [5,7,5,4,2], 77 | 空间复杂度O(1),得使用 交换排序 78 | 79 | 插入排序思想 80 | 快速排序思想 81 | 82 | 83 | 84 | 1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次. 85 | 每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间, 86 | 能否设计一个算法实现? 87 | 88 | 分析:难点在于 `不用辅助空间`。 89 | 90 | 思路一: `sum(数组元素的总和)-sum(1~1000)` 得到的差即为重复元素,N较大时注意总和溢出 91 | 思路二: 异或操作(位运算) 92 | 93 | 94 | 95 | 96 | 97 | ## 重新排列使负数排在正数前面 98 | 99 | 一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的正负数之间相对顺序 100 | 101 | 比如: input: 1,7,-5,9,-12,15 ans: -5,-12,1,7,9,15 要求时间复杂度O(N),空间O(1)。(此题一直没看到令我满意的答案,一般达不到题目所要求的:时间复杂度O(N),空间O(1),且保证原来正负数之间的相对位置不变)。 102 | 103 | updated:设置一个起始点j, 一个翻转点k,一个终止点L 104 | 从右侧起 105 | 起始点在第一个出现的负数, 翻转点在起始点后第一个出现的正数,终止点在翻转点后出现的第一个负数(或结束) 106 | 如果无翻转点, 则不操作 107 | 如果有翻转点, 则待终止点出现后, 做翻转, 即ab => ba 这样的操作 108 | 翻转后, 负数串一定在左侧, 然后从负数串的右侧开始记录起始点, 继续往下找下一个翻转点 109 | 110 | 例子中的就是 111 | 112 | 1, 7, -5, 9, -12, 15 113 | 第一次翻转: 1, 7, -5, -12,9, 15 => 1, -12, -5, 7, 9, 15 114 | 第二次翻转: -5, -12, 1, 7, 9, 15 115 | 116 | N维翻转空间占用为O(1)复杂度是2N;在有一个负数的情况下, 复杂度最大是2N, ;在有i个负数的情况下, 复杂度最大是2N+2i, 但是不会超过2N+N实际的复杂度在O(3N)以内 117 | 但从最终时间复杂度分析,此方法是否真能达到O(N)的时间复杂度,还待后续考证。感谢John_Lv,MikovChain。2012.02.25。 118 | 119 | 1, 7, -5, -6, 9, -12, 15(后续:此种情况未能处理) 120 | 1 7 -5 -6 -12 9 15 121 | 1 -12 -5 -6 7 9 15 122 | -6 -12 -5 1 7 9 15 123 | 124 | 更多请参考此文,程序员编程艺术第二十七章:重新排列数组(不改变相对顺序&时间O(N)&空间O(1),半年未被KO)http://blog.csdn.net/v_july_v/article/details/7329314。 125 | 126 | 127 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/5.2 数列-nsum问题.md: -------------------------------------------------------------------------------- 1 | #数列-nsum问题 2 | 3 | 4 | ## 数组,找出和为 s 的两个数 5 | 6 | 暴力解法 7 | 8 | ``` 9 | class Solution { 10 | public int[] twoSum(int[] nums, int target) { 11 | int res[] = new int[2]; 12 | for(int i = 0; i < nums.length; i++){ 13 | for(int j = i + 1; j < nums.length; j++){ 14 | if(nums[i] + nums[j] == target){ 15 | res[0] = i; 16 | res[1] = j; 17 | break; 18 | } 19 | } 20 | } 21 | return res; 22 | } 23 | } 24 | ``` 25 | 26 | 使用 hash表,2遍扫描 27 | 28 | ``` Java 29 | class Solution { 30 | public int[] twoSum(int[] nums, int target) { 31 | int res[] = new int[2]; 32 | Map map= new HashMap<>(); 33 | for(int i = 0; i < nums.length; i++){ 34 | map.put(nums[i], i); 35 | } 36 | for(int i = 0; i < nums.length; i++){ 37 | int temp = target - nums[i]; 38 | if(map.containsKey(temp) && map.get(temp) != i){ 39 | res[0] = map.get(temp); 40 | res[1] = i; 41 | } 42 | } 43 | return res; 44 | } 45 | } 46 | 47 | ``` 48 | 49 | 50 | ## 找出和为N+1的2个数 51 | 52 | 一个整数数列,元素取值可能是`1~N`(N是一个较大的正整数)中的任意一个数,相同数值不会重复出现。 53 | 设计一个算法,找出数列中符合条件的数对的个数,满足数对中两数的和等于`N+1`。 54 | 复杂度最好是 `O(n)` ,如果是 `O(n2)`则不得分。 55 | 56 | 57 | 分析:列出所有的数对,如输入15,输出`【1,14】【2,13】【3,12】。。。` 58 | 59 | ``` 60 | int print_sequence_sum(int n) 61 | ``` 62 | 63 | 64 | ## 找出和为m的2个数 65 | 66 | 输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。 67 | 要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。 68 | 例如输入数组【1、2、4、7、11、15=和数字15。由于4+11=15,因此输出4和11。 69 | 70 | 71 | ``` 72 | //返回0找到,返回-1没找到 73 | int findaddends(int *data,int length,int sum,int *a,int *b); 74 | ``` 75 | 76 | 分析: 77 | 数组升序排列,查找可用二分查找,时间复杂度`O(logn)`,这样问题就变成了找其中一个加数的问题,复杂度为`N*logN` 78 | 79 | 巧妙解法:数组2端向中间扫描,复杂度O(N) 80 | 81 | 82 | 83 | 84 | ## 求子数组的最大和(最大字段和) 85 | 86 | 输入一个整形数组,数组里有正数也有负数。 87 | 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 88 | 求所有子数组的和的最大值。要求时间复杂度为O(n)。 89 | 90 | 例如输入的数组为`1, -2, 3, 10, -4, 7, 2, -5` ,和最大的子数组为`3, 10, -4, 7, 2` ,因此输出为该子数组的和18。 91 | 92 | 1. 蛮力法 fmax(i,j) 找出最大的值,3重循环 ,复杂度 0(n^3) 93 | 2. `动态规划` maxendindhere保存当前累加的和,如果<0, 就把maxendinghere清零 , max保存最终的最大和; 如果都是负数 94 | ``` 95 | int maxSumOfVector(int *data,int length){ 96 | 97 | int max=0; 98 | int maxendinghere = 0; 99 | 100 | if(data==NULL || length<=0){ 101 | return 0; 102 | } 103 | for(int i=0;i < length, i++){ 104 | 105 | maxendinghere+=data[i]; 106 | if(maxendinghere<0){ 107 | maxendinghere=0; 108 | continue; 109 | } 110 | 111 | if(maxendinghere>max){ 112 | max=maxendinghere; 113 | } 114 | 115 | } 116 | 117 | return max; 118 | } 119 | ``` 120 | 121 | 122 | 123 | 124 | 125 | ## 和为n连续正数序列 126 | 127 | 题目:输入一个正数n,输出所有和为n连续正数序列。 128 | 129 | 例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。 130 | 131 | ``` 132 | print_continuous_sequence_sum(int n) 133 | ``` 134 | 135 | 思路一:枚举法。从1开始一直加到等于n,再从2开始。。一直到 n/2+1,在每一轮,都要逐步比较。复杂度`O(N^2)` 136 | 思路二: a[small,big] sum[small,big]>N small往前移动,否则,big往前移动。 `O(N)` 复杂度搞定 137 | 138 | 139 | 140 | 141 | ## 求连续子数组和为m的组合 142 | 143 | 数组 [2,3,1,2,4,3]求连续子数组sum=7, 结果[1,2,4] [3,4] 144 | 145 | * 暴力遍历 146 | * 滑动窗口, 左指针,右指针 147 | 148 | ``` 149 | if sum= 0 && index_b >= 0) { 41 | 42 | //2个指针都有值时 43 | if (a[index_a] >= 0 && b[index_b] >= 0) { 44 | if (a[index_a] > b[index_b]) { 45 | a[len_a--] = a[index_a--]; 46 | }else{ 47 | a[len_a--] = b[index_b--]; 48 | } 49 | } 50 | 51 | //a 无值,b有值 ; 把剩下b放好 52 | if (index_a < 0 && index_b >= 0) { 53 | while (index_b >= 0) { 54 | a[len_a--] = b[index_b--]; 55 | } 56 | } 57 | 58 | //a 有值,b无值; 把剩下a放好 59 | if (index_a >=0 && index_b < 0) { 60 | while (index_b >= 0) { 61 | 62 | } 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | 69 | ## 两个序列和之差最小 70 | 71 | 有两个序列a,b,大小都为n,序列元素的值任意整数,无序; 72 | 要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。 73 | 74 | 例如: 75 | ``` 76 | var a=[100,99,98,1,2, 3]; 77 | var b=[1, 2, 3, 4,5,40]; 78 | ``` 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/5.4 数列-查找.md: -------------------------------------------------------------------------------- 1 | # 数列-查找 2 | 3 | 4 | ## 数组中超过出现次数超过一半的数字 5 | 6 | 题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。 7 | 8 | `+-1 计数法` 9 | 10 | 11 | 12 | ## 找数组里面重复的一个数 13 | 14 | 找数组里面重复的一个数,一个含n个元素的整数数组至少存在一个重复数,请编程实现,在O(n)时间内找出其中任意一个重复数。 15 | 16 | 1. hash算法,空间要求多 (注意数组元素要是int类型),数的大小范围和数组长度N都可以是无穷大,这里使用hash算法,空间复杂度是O(n),空间可能无法满足条件。 17 | 2. 先排序,复杂度n(logn);然后遍历一遍就可以知道哪些数重复了。 18 | 2. 高级解法: 将问题转化为 `判断单链表中存在环` 19 | 20 | 21 | 类似问题:找出数组中唯一的重复元素 22 | 23 | 24 | ## 查找只出现一次的元素 25 | 26 | 给一个非空整数数组,比如 [2, 2, 3] ,其余元素均出现2次,找出那个只出现一次的元素。 27 | 28 | 思路: 异或运算,成对儿的数字就会变成 0,落单的数字和 0 做异或还是它本身 29 | 30 | ``` 31 | int singleNumber(vector& nums) { 32 | int res = 0; 33 | for (int n : nums) { 34 | res ^= n; 35 | } 36 | return res; 37 | } 38 | ``` 39 | 40 | 41 | ## 数组中找出某数字出现的次数 42 | 43 | 在排序数组中,找出给定数字的出现次数。比如 [1, 2, 2, 2, 3] 中2的出现次数是3次。 44 | 45 | 分析: 46 | 1. 因为是排序数组,可以使用二分查找 47 | 2. 将二分查找坚持到底,这样在最坏的情况下([2,2,2,2,2,2,2])都有0(lgn)复杂度 48 | 49 | ``` 50 | int binary_search_first(int *a,int length,int key); 51 | int binary_search_lash(int *a,int length,int key); 52 | ``` 53 | 54 | 55 | 56 | 57 | ## 大于K的最小正整数 58 | 59 | 给定一个集合A=[0,1,3,8](该集合中的元素都是在0,9之间的数字,但未必全部包含),指定任意一个正整数K,请用A中的元素组成一个大于K的最小正整数。 60 | 61 | 比如,A=[1,0] K=21 那么输出结构应该为100。 62 | 63 | 64 | 65 | 66 | 67 | ## 查找最小的k个元素(top-k) 68 | 69 | 题目:输入n个整数,输出其中最小的k个。 70 | 例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。 71 | 72 | ``` 73 | topMinK(int *a,int length,int k); 74 | topMaxK(int *a,int length, int k); 75 | ``` 76 | 77 | 78 | 1. 全部排序 复杂度 NlogN , 数据了较大时,内存可能承受不住 79 | 2. 部分排序 维护一个大小为K的数组,由大到小排序,然后遍历所有数据,每个数据跟数组中最小元素比较,如果比最小元素大,就要插入数组了,这里还有寻找插入位置,移动数组元素的cpu消耗。复杂度是N*K 80 | 3. 堆排序 。在这的K较大时(比如这道题目:2亿个整数中求最大的100万之和),上面的算法还是有很多可以改进的地方,如采用二分查找定位插入位置,移动数组元素的计算是躲不过去了。那有没有什么数据结构即能`快速查找,还能快速移动元素`呢?最好是O(1)复杂度。 81 | 82 | 答案就是`二叉堆`。我们可以遍历总量中的每个元素来跟二叉堆中的堆顶元素比较(堆顶元素在`小根堆`中最小值,在`大根堆`中是最大值),这样在0(1)复杂度就可以完成查找操作,揭下来需要的操作就是重新调整推结构,复杂度是O(logk),因此整个操作复杂度是 `O(n*logk)` 83 | 84 | 85 | `top-k 小的时候用 *大根堆* ,top-k 大得时候用 *小根堆*` 86 | 87 | 88 | ## 找第k大的数 89 | 90 | 比如 `1,2,3,4,5,6,7,8 `这 8个数字,第3大的数字是 6 91 | 92 | ``` 93 | int topK(int * a, int length, int k) 94 | ``` 95 | 96 | 97 | ```Java 98 | //冒泡实现 99 | public int findK(int[] nums, int k){ 100 | //base case 101 | if (nums== null || nums.length < k) { 102 | return -1; 103 | } 104 | 105 | for(int i=1; i<=k; i++){ 106 | for(int j=0; j nums[next]){ 109 | int tmp = nums[j]; 110 | nums[j] = nums[next]; 111 | nums[next] = tmp; 112 | } 113 | } 114 | } 115 | 116 | return nums[nums.length-k]; 117 | } 118 | ``` 119 | 120 | 121 | 122 | ```Java 123 | /** 124 | * 小根堆实现 125 | */ 126 | public static int findMaxK(int[] nums, int k) { 127 | PriorityQueue pq = new PriorityQueue<>(k, (a, b) -> (a-b) ); 128 | 129 | for (int i = 0; i 是n个不同的实数的序列,L的递增子序列是这样一个子序列 169 | Lin=< aK1,ak2,…,akm >,其中k1< k2<…< km且aK1< ak2<…< akm。 170 | 求最大的m值。 171 | 172 | 如【5,6,7,3,2,8】 最长子序列 【5,6,7,8】 173 | 174 | `动态规划` 175 | 176 | 177 | 178 | 179 | 180 | ## 在从1到n的正数中1出现的次数 181 | 182 | 题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 183 | 184 | 例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 185 | 分析:这是一道广为流传的google面试题。 186 | ``` 187 | int one_appear_count(int n); 188 | ``` 189 | 190 | 思路1. 遍历`1~n`,统计出现1的个数;n足够大时,效率很低 191 | 思路2. 分析规律 192 | 193 | 194 | 195 | ## 搜索旋转排序数组 196 | 197 | 198 | 整数数组 nums 按升序排列,数组中的值 互不相同 。 199 | 200 | 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 201 | 202 | 给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。 203 | 204 | 205 | 示例 1: 206 | 207 | ``` 208 | 输入:nums = [4,5,6,7,0,1,2], target = 0 209 | 输出:4 210 | ``` 211 | 212 | 示例 2: 213 | ``` 214 | 输入:nums = [4,5,6,7,0,1,2], target = 3 215 | 输出:-1 216 | ``` 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/6 矩阵.md: -------------------------------------------------------------------------------- 1 | # 矩阵 2 | 3 | 矩阵在计算机中表示就是二维数组。这部分内容都是有关二维数组和矩阵相关的题目。 4 | 5 | 6 | ## 顺时针打印矩阵 7 | 8 | 题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 9 | 例如:如果输入如下矩阵: 10 | 11 | ``` 12 | 1 2 3 4 13 | 5 6 7 8 14 | 9 10 11 12 15 | 13 14 15 16 16 | ``` 17 | 则依次打印出数字`1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10` 。 18 | 19 | 分析:包括Autodesk、EMC在内的多家公司在面试或者笔试里采用过这道题。 20 | 难点: 各种边界条件判断,很容易搞错 21 | 22 | 一圈一圈打印,第一圈origin是(0,0),第二圈是(1,1) 23 | 24 | 25 | ``` 26 | void print_matrix(int **matrix,int rows,int cols); 27 | //或 28 | void print_matrix(int *matrix,int rows,int cols); 29 | 30 | ``` 31 | 32 | 示例代码: 33 | 34 | ``` 35 | void print_matrix(int *matrix,int rows,int cols){ 36 | if(matrix==NULL) return ; 37 | if(rows<=0 || cols<=0) return; 38 | 39 | int start = 0; 40 | while(start*2=start;i--){ 61 | printf("\d ",matrix[start*rows+i]); 62 | } 63 | 64 | for(int i=endY-1;i>start;i--){ 65 | printf("\d ",matrix[i*rows+start]); 66 | } 67 | } 68 | 69 | ``` 70 | 71 | 测试代码见 print_matrix.c 72 | 73 | 74 | 75 | ## 从小到大输出矩阵的值 76 | 77 | 78 | 思路1:采用归并进行排序然后进行顺序打印 79 | 思路2:用n个指针指向每一行的第一个数,比较n个指针的数,打印最小的,然后指针后移,若无下一位,则赋值为null,直至所有数都对印完毕 80 | 81 | ``` 82 | void printArry(int *a,int rows,int columns) 83 | { 84 | if(a==null) 85 | return; 86 | 87 | int *arr=new int[rows*(columns+1)];//添加看门狗值,INT_MAX 88 | for(int i=0;iarr[index]) 109 | { 110 | min=arr[index]; 111 | mini=i; 112 | } 113 | } 114 | if(min==INT_MAX) 115 | break;//表示都达到了看门处,则跳出循环。 116 | cout< Google 面试题, 此题采用bfs和拓扑排序均可达到面试官的要求。笔者认为,一般的bfs可以达到hire;记忆化搜索和拓扑排序可以达到strong hire 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/7 二叉树.md: -------------------------------------------------------------------------------- 1 | # 二元树 2 | 3 | 问题涉及有: 4 | 5 | * 遍历 6 | * 翻转 7 | * 子树 8 | 9 | 10 | 写二叉树的算法题,都是基于递归框架的。我们先要搞清楚 root 节点它自己要做什么,然后根据题目要求选择使用前序,中序,后续的递归框架。 11 | 12 | 13 | 二元树的结点定义如下: 14 | ``` 15 | struct SBinaryTreeNode // a node of the binary tree 16 | { 17 | int m_nValue; // value of node 18 | SBinaryTreeNode *m_pLeft; // left child of node 19 | SBinaryTreeNode *m_pRight; // right child of node 20 | }; 21 | ``` 22 | 23 | 二叉树递归框架 24 | 25 | ```Java 26 | 27 | /* 二叉树遍历框架 */ 28 | void traverse(TreeNode root) { 29 | // 前序遍历 30 | traverse(root.left) 31 | // 中序遍历 32 | traverse(root.right) 33 | // 后序遍历 34 | } 35 | 36 | ``` 37 | 38 | 39 | 40 | ## 二叉树翻转 41 | 42 | 输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。例 43 | 44 | 如输入: 45 | ``` 46 | 8 47 | / \ 48 | 6 10 49 | /\ / \ 50 | 5 7 9 11 51 | ``` 52 | 输出: 53 | 54 | ``` 55 | 8 56 | / \ 57 | 10 6 58 | /\ /\ 59 | 11 9 7 5 60 | ``` 61 | 62 | 思路:二叉树翻转 , 把二叉树上的每一个节点的左右子节点进行交换 63 | 64 | ``` 65 | // 将整棵树的节点翻转 66 | TreeNode invertTree(TreeNode root) { 67 | // base case 68 | if (root == null) { 69 | return null; 70 | } 71 | 72 | /**** 前序遍历位置 ****/ 73 | // root 节点需要交换它的左右子节点 74 | TreeNode tmp = root.left; 75 | root.left = root.right; 76 | root.right = tmp; 77 | 78 | // 让左右子节点继续翻转它们的子节点 79 | invertTree(root.left); 80 | invertTree(root.right); 81 | 82 | return root; 83 | } 84 | ``` 85 | 86 | 87 | ## 把二元查找树转变成排序的双向链表 88 | 89 | 难度:中等 90 | 输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求`不能创建任何新的结点`,只调整指针的指向。 91 | 92 | ``` 93 | 10 94 | / \ 95 | 6 14 96 | /\ / \ 97 | 4 8 12 16 98 | ``` 99 | 100 | 转换成双向链表 `4=6=8=10=12=14=16` 101 | 102 | 103 | 分析:题目要求不能创建任何节点,也就是只能调整各个节点的指向 104 | 思路一: 105 | 106 | 107 | 108 | 109 | 110 | ## 二叉树两个结点的最低共同父结点 (最近公共祖先) 111 | 112 | 设计一个算法,找出二叉树上任意两个结点的最近共同父结点。复杂度如果是O(n2)则不得分 113 | 114 | 115 | 输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。 116 | 117 | 分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。 118 | 119 | 120 | ``` 121 | TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 122 | // base case 123 | if (root == null) return null; 124 | if (root == p || root == q) return root; 125 | 126 | TreeNode left = lowestCommonAncestor(root.left, p, q); 127 | TreeNode right = lowestCommonAncestor(root.right, p, q); 128 | // 情况 1 :p, q 在 root 为根的树中 129 | if (left != null && right != null) { 130 | return root; 131 | } 132 | // 情况 2 :p, q 不在 在 root 为根的树中 133 | if (left == null && right == null) { 134 | return null; 135 | } 136 | // 情况 3 : p, q 只有1个在root 为根的树中 137 | return left == null ? right : left; 138 | } 139 | ``` 140 | 141 | 142 | 143 | 144 | ## 求一个二叉树中任意两个节点间的最大距离 145 | 146 | 两个节点的距离的定义是 这两个节点间边的个数,比如某个孩子节点和父节点间的距离是1,和相邻兄弟节点间的距离是2,优化时间空间复杂度。 147 | 148 | 149 | 150 | 151 | 152 | ## 在二元树中找出和为某一值的所有路径 153 | 154 | 题目:输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。 155 | 156 | 例如 输入整数22和如下二元树 157 | ``` 158 | 10 159 | / \ 160 | 5 12 161 | /\ 162 | 4 7 163 | ``` 164 | 则打印出两条路径:`10, 12` 和 `10, 5, 7` 。 165 | 166 | 167 | 1. 累加当前节点,累加和大于给定值 168 | 2. 不为叶节点,左子树入栈 169 | 170 | 171 | 172 | ## 一棵排序二叉树,令 f=(最大值+最小值)/2,设计一个算法,找出距离f值最近、大于f值的结点。 173 | 复杂度如果是O(n2)则不得分。 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/7.1 二叉树-遍历.md: -------------------------------------------------------------------------------- 1 | # 二叉树-遍历 2 | 3 | 4 | > 快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 5 | 6 | 7 | ## 2种方法实现二叉树的前序遍历。 8 | 9 | 递归和非递归 10 | 11 | 12 | 13 | 14 | ## 按层打印二叉树 15 | 16 | 输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。 17 | 例如输入 18 | 19 | ``` 20 | 8 21 | /\ 22 | 6 10 23 | / \ /\ 24 | 5 7 9 11 25 | ``` 26 | 27 | 输出`8 6 10 5 7 9 11` 28 | 29 | 30 | 31 | 32 | ## 二元树的深度 33 | 34 | 题目:输入一棵二元树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 35 | 例如:输入二元树: 36 | ``` 37 | 38 | 10 39 | / \ 40 | 6 14 41 | / / \ 42 | 4 12 16 43 | ``` 44 | 45 | 输出该树的深度3。 46 | 47 | 48 | 49 | 分析:这道题本质上还是考查二元树的遍历。对于一颗完全二叉树,要求给所有节点加上一个pNext指针,指向同一层的相邻节点;如果当前节点已经是该层的最后一个节点,则将pNext指针指向NULL;给出程序实现,并分析时间复杂度和空间复杂度。 50 | 51 | 52 | ## 二元树的最小深度 53 | 54 | 55 | 例如:输入二元树: 56 | ``` 57 | 58 | 10 59 | / \ 60 | 6 14 61 | / \ 62 | 12 16 63 | ``` 64 | 65 | 输出该树的最小深度2。 66 | 67 | 68 | 69 | ## 完全二叉树的节点个数 70 | 71 | 72 | 73 | 74 | 75 | 76 | ## 判断整数序列是不是二元查找树的后序遍历结果 77 | 78 | 题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。 79 | 如果是返回true,否则返回false。 80 | 81 | 例如输入`5、7、6、9、11、10、8`,由于这一整数序列是如下树的后序遍历结果: 82 | ``` 83 | 8 84 | / \ 85 | 6 10 86 | /\ / \ 87 | 5 7 9 11 88 | ``` 89 | 因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/7.2 二叉树-建树.md: -------------------------------------------------------------------------------- 1 | # 二叉树-建树 2 | 3 | 4 | ## 把一个有序整数数组放到二叉树中? 5 | 6 | 分析:本题考察二叉搜索树的建树方法,简单的递归结构。 7 | 8 | 关于树的算法设计一定要联想到递归,因为树本身就是递归的定义。而,学会把递归改称非递归也是一种必要的技术。 9 | 毕竟,递归会造成栈溢出,关于系统底层的程序中不到非不得以最好不要用。但是对某些数学问题,就一定要学会用递归去解决。 10 | 11 | 12 | 13 | 14 | ## 恢复树结构 15 | 16 | 有一棵树(树上结点为字符串或者整数),请写代码将树的结构和数据写到一个文件中,并能通过读取该文件恢复树结构。 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/7.5 二叉搜索树.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树 2 | 3 | 关于二叉查找树特性[参考这里](../4%20Tree/2-二叉查找树/二叉查找树.md) 4 | 5 | ## 验证二叉搜索树 6 | 7 | 8 | ## 二叉搜索树中的节点搜索 9 | 10 | 11 | ## 二叉搜索树中的插入操作 12 | 13 | 14 | ## 删除二叉搜索树中的节点 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/7.9 树.md: -------------------------------------------------------------------------------- 1 | # 树 2 | 3 | 4 | ## 给一棵树 求最大宽度 5 | 6 | 7 | 8 | 9 | ## 树的广度遍历深度遍历 10 | 11 | 12 | 13 | 14 | ## 多叉树的层序遍历 15 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/8 图.md: -------------------------------------------------------------------------------- 1 | # 图 2 | 3 | 深度优先遍历(DFS)和广度优先遍历(BFS) 深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中 4 | 5 | 6 | ## 深度优先遍历 7 | 8 | 深度优先遍历(DFS) 9 | 10 | 11 | ## 广度优先遍历 12 | 13 | 14 | 广度优先遍历(BFS) 15 | 16 | 17 | 18 | ## 华为面试题:一类似于蜂窝的结构的图,进行搜索最短路径(要求5分钟) 19 | 20 | 21 | 22 | ## 求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边, 23 | 有向图不再连通,描述算法。 24 | 25 | 26 | 27 | 28 | ## 平面上N个点,每两个点都确定一条直线, 29 | 求出斜率最大的那条直线所通过的两个点(斜率不存在的情况不考虑)。时间效率越高越好。 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/9 智力思维训练.md: -------------------------------------------------------------------------------- 1 | # 智力题 2 | 3 | 这部分内容的题目侧重思维发散。 4 | 5 | 6 | 1.有两个房间,一间房里有三盏灯,另一间房有控制着三盏灯的三个开关,这两个房间是 分割开的,从一间里不能看到另一间的情况。 7 | 现在要求受训者分别进这两房间一次,然后判断出这三盏灯分别是由哪个开关控制的。 8 | 有什么办法呢? 9 | 10 | 11 | 2.你让一些人为你工作了七天,你要用一根金条作为报酬。金条被分成七小块,每天给出一块。如果你只能将金条切割两次,你怎样分给这些工人? 12 | 13 | 14 | 15 | 16 | 有4张红色的牌和4张蓝色的牌,主持人先拿任意两张,再分别在A、B、C三人额头上贴任意两张牌, 17 | A、B、C三人都可以看见其余两人额头上的牌,看完后让他们猜自己额头上是什么颜色的牌, 18 | A说不知道,B说不知道,C说不知道,然后A说知道了。 19 | 请教如何推理,A是怎么知道的。 20 | 如果用程序,又怎么实现呢? 21 | 22 | 23 | 有A、B、C、D四个人,要在夜里过一座桥。他们通过这座桥分别需要耗时1、2、5、10分钟,只有一支手电,并且同时最多只能两个人一起过桥。请问,如何安排,能够在17分钟内这四个人都过桥? 24 | 25 | 26 | 27 | 有12个小球,外形相同,其中一个小球的质量与其他11个不同,给一个天平,问如何用3次把这个小球找出来,并且求出这个小球是比其他的轻还是重 28 | 29 | 30 | 31 | 32 | “十袋白色粉末,其中有一袋溶于水两分钟以后会变蓝,现在只有四个杯子,无限多的水,要求是在最短的时间内找出这袋特殊的粉末” 33 | 34 | 一二三四号粉末放第1个杯子,二三四号粉末分别放在第234个杯子里,五六七号粉末放到2号杯子里,六七号粉末分别放到34号杯子里,八九号粉末放到第3个杯子里,九十号粉末放到第4个杯子里,OK了” 35 | 36 | 37 | 38 | 39 | 27.跳台阶问题 40 | 题目:一个台阶总共有n级,如果一次可以跳1级,也可以跳2级。 41 | 求总共有多少总跳法,并分析算法的时间复杂度。 42 | 43 | 这道题最近经常出现,包括MicroStrategy等比较重视算法的公司都 44 | 曾先后选用过个这道题作为面试题或者笔试题。 45 | 46 | 47 | 48 | 49 | 1.设计一个魔方(六面)的程序。 50 | 51 | 52 | 53 | 54 | 1.用天平(只能比较,不能称重)从一堆小球中找出其中唯一一个较轻的,使用x次天平,最多可以从y个小球中找出较轻的那个,求y与x的关系式 55 | 56 | 57 | 58 | 26、有一根27厘米的细木杆,在第3厘米、7厘米、11厘米、17厘米、23厘米这五个位置上各有一只蚂蚁。 59 | 木杆很细,不能同时通过一只蚂蚁。开始时,蚂蚁的头朝左还是朝右是任意的,它们只会朝前走或调头,但不会后退。 60 | 当任意两只蚂蚁碰头时,两只蚂蚁会同时调头朝反方向走。假设蚂蚁们每秒钟可以走一厘米的距离。 61 | 62 | 编写程序,求所有蚂蚁都离开木杆的最小时间和最大时间。 63 | 64 | 65 | 66 | 67 | 20、13个球一个天平,现知道只有一个和其它的重量不同,问怎样称才能用三次就找到那个球?(http://zhidao.baidu.com/question/66024735.html 68 | )。 69 | 70 | 71 | 72 | 73 | 74 | 75 | 1.烧一根不均匀的绳,从头烧到尾总共需要1个小时。 76 | 现在有若干条材质相同的绳子,问如何用烧绳的方法来计时一个小时十五分钟呢? 77 | 2.你有一桶果冻,其中有黄色、绿色、红色三种,闭上眼睛抓取同种颜色的两个。 78 | 抓取多少个就可以确定你肯定有两个同一颜色的果冻?(5秒-1分钟) 79 | 3.如果你有无穷多的水,一个3公升的提捅,一个5公升的提捅,两只提捅形状上下都不均匀, 80 | 问你如何才能准确称出4公升的水?(40秒-3分钟) 81 | 一个岔路口分别通向诚实国和说谎国。 82 | 来了两个人,已知一个是诚实国的,另一个是说谎国的。 83 | 诚实国永远说实话,说谎国永远说谎话。现在你要去说谎国, 84 | 但不知道应该走哪条路,需要问这两个人。请问应该怎么问?(20秒-2分钟) 85 | 86 | 87 | 88 | 100.第4组微软面试题,挑战思维极限 89 | 1.12个球一个天平,现知道只有一个和其它的重量不同,问怎样称才能用三次就找到那个球。 90 | 13个呢?(注意此题并未说明那个球的重量是轻是重,所以需要仔细考虑)(5分钟-1小时) 91 | 2.在9个点上画10条直线,要求每条直线上至少有三个点?(3分钟-20分钟) 92 | 3.在一天的24小时之中,时钟的时针、分针和秒针完全重合在一起的时候有几次? 93 | 都分别是什么时间?你怎样算出来的?(5分钟-15分钟) 94 | 95 | 96 | 97 | 98 | 99 | 91. 100 | 1.一道著名的毒酒问题 101 | 有1000桶酒,其中1桶有毒。而一旦吃了,毒性会在1周后发作。 102 | 现在我们用小老鼠做实验,要在1周内找出那桶毒酒,问最少需要多少老鼠。 103 | 2.有趣的石头问题 104 | 有一堆1万个石头和1万个木头,对于每个石头都有1个木头和它重量一样, 105 | 把配对的石头和木头找出来。 106 | 107 | 108 | 92. 109 | 1.多人排成一个队列,我们认为从低到高是正确的序列,但是总有部分人不遵守秩序。 110 | 如果说,前面的人比后面的人高(两人身高一样认为是合适的), 111 | 那么我们就认为这两个人是一对“捣乱分子”,比如说,现在存在一个序列: 112 | 176, 178, 180, 170, 171 113 | 这些捣乱分子对为 114 | <176, 170>, <176, 171>, <178, 170>, <178, 171>, <180, 170>, <180, 171>, 115 | 那么,现在给出一个整型序列,请找出这些捣乱分子对的个数(仅给出捣乱分子对的数目即可,不用具体的对) 116 | 要求: 117 | 输入: 118 | 为一个文件(in),文件的每一行为一个序列。序列全为数字,数字间用”,”分隔。 119 | 输出: 120 | 为一个文件(out),每行为一个数字,表示捣乱分子的对数。 121 | 详细说明自己的解题思路,说明自己实现的一些关键点。 122 | 并给出实现的代码 ,并分析时间复杂度。 123 | 限制: 124 | 输入每行的最大数字个数为100000个,数字最长为6位。程序无内存使用限制。 125 | 126 | 127 | 29、有A、B、C、D四个人,要在夜里过一座桥。他们通过这座桥分别需要耗时1、2、5、10分钟,只有一支手电,并且同时最多只能两个人一起过桥。请问,如何安排,能够在17分钟内这四个人都过桥? 128 | 129 | 30、有12个小球,外形相同,其中一个小球的质量与其他11个不同, 130 | 给一个天平,问如何用3次把这个小球找出来,并且求出这个小球是比其他的轻还是重 131 | 132 | 133 | 134 | 创新工场面试题:abcde五人打渔,打完睡觉,a先醒来,扔掉1条鱼,把剩下的分成5分,拿一份走了;b再醒来,也扔掉1条,把剩下的分成5份,拿一份走了;然后cde都按上面的方法取鱼。问他们一共打了多少条鱼,写程序和算法实现。提示:共打了多少条鱼的结果有很多。但求最少打的鱼的结果是3121条鱼(应该找这5个人问问,用什么工具打了这么多条鱼)。(http://blog.csdn.net/nokiaguy/article/details/6800209)。 135 | 我们有很多瓶无色的液体,其中有一瓶是毒药,其它都是蒸馏水,实验的小白鼠喝了以后会在5分钟后死亡,而喝到蒸馏水的小白鼠则一切正常。现在有5只小白鼠,请问一下,我们用这五只小白鼠,5分钟的时间,能够检测多少瓶液体的成分? 136 | 淘宝2012笔试(研发类):http://topic.csdn.net/u/20110922/10/e4f3641a-1f31-4d35-80da-7268605d2d51.html(一参考答案)。 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 华为面试2:1分2分5分的硬币,组成1角,共有多少种组合。 145 | 146 | 147 | 43、Smith夫妇召开宴会,并邀请其他4对夫妇参加宴会。在宴会上,他们彼此握手, 148 | 并且满足没有一个人同自己握手,没有两个人握手一次以上,并且夫妻之间不握手。 149 | 然后Mr. Smith问其它客人握手的次数,每个人的答案是不一样的。 150 | 151 | 求Mrs Smith握手的次数 152 | 153 | 44、有6种不同颜色的球,分别记为1,2,3,4,5,6,每种球有无数个。现在取5个球,求在一下 154 | 的条件下: 155 | 1、5种不同颜色, 156 | 2、4种不同颜色的球, 157 | 3、3种不同颜色的球, 158 | 4、2种不同颜色的球, 159 | 它们的概率。 160 | 161 | 45、有一次数学比赛,共有A,B和C三道题目。所有人都至少解答出一道题目,总共有25人。 162 | 在没有答出A的人中,答出B的人数是答出C的人数的两倍;单单答出A的人,比其他答出A的人 163 | 总数多1;在所有只有答出一道题目的人当中,答出B和C的人数刚好是一半。 164 | 求只答出B的人数。 165 | 166 | 167 | 168 | 169 | 12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种? 170 | 这个笔试题,很YD,因为把某个递归关系隐藏得很深. 171 | 172 | 173 | 47、金币概率问题(威盛笔试题) 174 | 175 | 题目:10个房间里放着随机数量的金币。每个房间只能进入一次,并只能在一个房间中拿金币。 176 | 一个人采取如下策略:前四个房间只看不拿。随后的房间只要看到比前四个房间都多的金币数, 177 | 就拿。否则就拿最后一个房间的金币。? 178 | 179 | 编程计算这种策略拿到最多金币的概率。 180 | 181 | 182 | 183 | 184 | 185 | 186 | 22、有5个海盗,按照等级从5到1排列,最大的海盗有权提议他们如何分享100枚金币。 187 | 但其他人要对此表决,如果多数反对,那他就会被杀死。 188 | 他应该提出怎样的方案,既让自己拿到尽可能多的金币又不会被杀死? 189 | (提示:有一个海盗能拿到98%的金币) 190 | 191 | 192 | 193 | 194 | 五只猴子分桃。半夜,第一只猴子先起来,它把桃分成了相等的五堆,多出一只。于是,它吃掉了一个,拿走了一堆; 第二只猴子起来一看,只有四堆桃。于是把四堆合在一起,分成相等的五堆,又多出一个。于是,它也吃掉了一个,拿走了一堆;.....其他几只猴子也都是 这样分的。问:这堆桃至少有多少个?(朋友说,这是小学奥数题)。 195 | 参考答案:先给这堆桃子加上4个,设此时共有X个桃子,最后剩下a个桃子.这样: 196 | 第一只猴子分完后还剩:(1-1/5)X=(4/5)X; 197 | 第二只猴子分完后还剩:(1-1/5)2X; 198 | 第三只猴子分完后还剩:(1-1/5)3X; 199 | 第四只猴子分完后还剩:(1-1/5)4X; 200 | 第五只猴子分完后还剩:(1-1/5)5X=(1024/3125)X; 201 | 得:a=(1024/3125)X; 202 | 要使a为整数,X最小取3125. 203 | 减去加上的4个,所以,这堆桃子最少有3121个。 204 | 205 | 已知有个rand7()的函数,返回1到7随机自然数,让利用这个rand7()构造rand10() 随机1~10。 206 | (参考答案:这题主要考的是对概率的理解。程序关键是要算出rand10,1到10,十个数字出现的考虑都为10%.根据排列组合,连续算两次rand7出现的组合数是7*7=49,这49种组合每一种出现考虑是相同的。怎么从49平均概率的转换为1到10呢?方法是: 207 | 1.rand7执行两次,出来的数为a1=rand7()-1,a2=rand7()-1. 208 | 2.如果`a1*7+a2<40,b=(a1*7+a2)/4+1`;如果`a1*7+a2>=40`, 重复第一步。参考代码如下所示: 209 | 210 | 211 | ``` 212 | 213 | int rand7() 214 | { 215 | return rand()%7+1; 216 | } 217 | 218 | int rand10() 219 | { 220 | int a71,a72,a10; 221 | do 222 | { 223 | a71= rand7()-1; 224 | a72 = rand7()-1; 225 | a10 = a71 *7 + a72; 226 | } while (a10>= 40); 227 | return (a71*7+a72)/4+1; 228 | } 229 | ``` 230 | 231 | 232 | 233 | 234 | 2)一串首尾相连的珠子(m个),有N种颜色(N<=10), 235 | 设计一个算法,取出其中一段,要求包含所有N中颜色,并使长度最短。 236 | 并分析时间复杂度与空间复杂度。 237 | 238 | 239 | 240 | 241 | 242 | 41.求固晶机的晶元查找程序 243 | 晶元盘由数目不详的大小一样的晶元组成,晶元并不一定全布满晶元盘, 244 | 245 | 照相机每次这能匹配一个晶元,如匹配过,则拾取该晶元, 246 | 若匹配不过,照相机则按测好的晶元间距移到下一个位置。 247 | 求遍历晶元盘的算法 求思路。 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/91 系统设计.md: -------------------------------------------------------------------------------- 1 | # 系统设计 2 | 3 | ## 要求设计一个DNS的Cache结构,要求能够满足每秒5000以上的查询,满足IP数据的快速插入,查询的速度要快。 4 | 5 | 6 | 7 | ## 设计一个系统处理词语搭配问题 8 | 9 | 设计一个系统处理词语搭配问题,比如说 中国 和人民可以搭配, 10 | 则中国人民 人民中国都有效。要求: 11 | 12 | * 系统每秒的查询数量可能上千次; 13 | * 词语的数量级为10W; 14 | * 每个词至多可以与1W个词搭配 15 | 16 | 当用户输入中国人民的时候,要求返回与这个搭配词组相关的信息。 17 | 18 | 19 | 20 | 21 | ## 任务调度 22 | 23 | 系统有很多任务,任务之间有依赖,比如B依赖于A,则A执行完后B才能执行 24 | (1)不考虑系统并行性,设计一个函数`(Task *Ptask,int Task_num)`不考虑并行度,最快的方法完成所有任务。 25 | (2)考虑并行度,怎么设计 26 | 27 | ``` 28 | typedef struct{ 29 | int ID; 30 | int * child; 31 | int child_num; 32 | }Task; 33 | ``` 34 | 35 | 提供的函数: 36 | ``` 37 | bool doTask(int taskID);无阻塞的运行一个任务; 38 | int waitTask(int timeout);返回运行完成的任务id,如果没有则返回-1; 39 | bool killTask(int taskID);杀死进程 40 | ``` 41 | 42 | 43 | ## 设计一种内存管理算法 44 | 45 | 46 | 相关的问题: 47 | 48 | * 请编写实现malloc()内存分配函数功能一样的代码 49 | * 用C语言实现函数memmove()函数 50 | 51 | ``` 52 | void * memmove(void *dest, const void *src, size_t n) 53 | ``` 54 | 55 | memmove()函数的功能是拷贝src所指的内存内容前n个字节到dest所指的地址上。 56 | 57 | 分析:由于可以把任何类型的指针赋给void类型的指针,这个函数主要是实现各种数据类型的拷贝。 58 | 59 | 60 | 61 | 62 | ## A向B发邮件,B收到后读取并发送收到,但是中间可能丢失了该邮件,怎么设计一种最节省的方法,来处理丢失问题。 63 | 64 | 65 | 66 | ## 设计一种算法求出算法复杂度 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/97 其他.md: -------------------------------------------------------------------------------- 1 | # 其他 2 | 3 | 4 | 5 | ## 两个圆相交 6 | 7 | 两个圆相交,交点是A1,A2。现在过A1点做一直线与两个圆分别相交另外一点B1,B2。B1B2可以绕着A1点旋转。问在什么情况下,B1B2最长 8 | 9 | 10 | 11 | 12 | ## 输入四个点的坐标,求证四个点是不是一个矩形 13 | 14 | 关键点: 15 | 1.相邻两边斜率之积等于-1, 16 | 2.矩形边与坐标系平行的情况下,斜率无穷大不能用积判断。 17 | 3.输入四点可能不按顺序,需要对四点排序。 18 | 19 | 20 | 21 | 22 | ## 排列组合问题 23 | 24 | 25 | ## 1,2,3,4,5 五个不同的数字,打印不同的排列。这就是一个无向图的遍历,把每个数字看成一个节点。 26 | 27 | 28 | ## 用1、2、2、3、4、5这六个数字,写一个main函数,打印出所有不同的排列, 29 | 30 | 如:512234、412345 等,要求:"4"不能在第三位,"3"与"5"不能相连. 31 | 32 | 这是对上一题增加难度。但是需要 33 | 1. 去掉 3,5之间的联通 34 | 2. 2重复,过滤重复结果 treeset 35 | 3. 4不能在3位 36 | 37 | 38 | 39 | 40 | 41 | 42 | ## 圆形和正方形是否相交 43 | 44 | 用最简单, 最快速的方法计算出下面这个圆形是否和正方形相交 45 | 3D坐标系 原点(0.0,0.0,0.0) 46 | 圆形: 47 | 半径r = 3.0 48 | 圆心o = (*.*, 0.0, *.*) 49 | 50 | 正方形: 51 | 4个角坐标; 52 | 1:(*.*, 0.0, *.*) 53 | 2:(*.*, 0.0, *.*) 54 | 3:(*.*, 0.0, *.*) 55 | 4:(*.*, 0.0, *.*) 56 | 57 | 分析: 58 | 59 | 2个形状不相交: 60 | 61 | 62 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/1 string/char_first_appear_once.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | char char_first_appear_once(const unsigned char *source){ 4 | int hash[256]={0}; 5 | char *tmp = source; 6 | if (tmp == NULL) return '\0'; 7 | while(*tmp != '\0'){ 8 | hash[*tmp]++; 9 | tmp++; 10 | } 11 | 12 | tmp = source; 13 | while(*tmp != '\0'){ 14 | if (hash[*tmp] == 1) return *tmp; 15 | tmp++; 16 | } 17 | return '\0'; 18 | } 19 | 20 | 21 | int main(int argc, char const *argv[]) 22 | { 23 | char *test = "77ah-ba-ccdeff"; 24 | char c = char_first_appear_once(test); 25 | printf("c=%c\n", c);/* c = 'h'*/ 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/1 string/proc.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | char *proc(char *str){ 4 | char *start = str; 5 | char *end = str; 6 | if (str == NULL) return NULL; 7 | while(*end != '\0') end++; 8 | end--; 9 | 10 | while(start < end){ 11 | if (*start >= 'A' && *start <= 'Z'){//大写 12 | if (*end >= 'a' && *end <= 'z'){ 13 | char tmp = *start; 14 | *start = *end; 15 | *end = tmp; 16 | 17 | start++; 18 | } 19 | end--; 20 | }else{//小写 21 | if (*end >= 'A' && *end <= 'Z'){ 22 | end--; 23 | } 24 | start++; 25 | } 26 | } 27 | return str; 28 | } 29 | 30 | int main(int argc, char const *argv[]) 31 | { 32 | char test[] = "HaJKPnobAACPc";//"char *test" always make "28767 bus error" 33 | printf("%s\n", proc(test)); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/1 string/replce_blank.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | char *replace_blank(char *source){ 4 | int count = 0; 5 | char *tail = source; 6 | if (source == NULL) return NULL; 7 | while(*tail != '\0'){ 8 | if (*tail == ' ') count++; 9 | tail++; 10 | } 11 | 12 | while(count){ 13 | if(*tail != ' '){ 14 | *(tail+2*count) = *tail; 15 | }else{ 16 | *(tail+2*count) = '0'; 17 | *(tail+2*count-1) = '2'; 18 | *(tail+2*count-2) = '%'; 19 | count--; 20 | } 21 | tail--; 22 | } 23 | 24 | return source; 25 | } 26 | 27 | int main(int argc, char const *argv[]) 28 | { 29 | char str[100]="we are happy"; 30 | printf("ret=%s\n",replace_blank(str)); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/1 string/revert_by_word.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | void _reverse(char *start,char *end){ 4 | if ((start == NULL) || (end == NULL)) return; 5 | while(start < end){ 6 | char tmp = *start; 7 | *start = *end; 8 | *end = tmp; 9 | 10 | start++, 11 | end--; 12 | } 13 | } 14 | 15 | char *_revert_by_word(char *source){ 16 | char *end = source; 17 | char *start = source; 18 | while(*start != '\0'){ 19 | if (*start == ' '){ 20 | start++; 21 | end++; 22 | }else if(*end == ' ' || *end == '\0'){ 23 | _reverse(start,end-1); 24 | start = end; 25 | }else{ 26 | end++; 27 | } 28 | } 29 | return source; 30 | } 31 | 32 | char *revert_by_word(char *source){ 33 | char *end = source; 34 | char *start = source; 35 | if (source == NULL) return NULL; 36 | while (*end != '\0') end++; 37 | end--; 38 | 39 | /*整个句子逆序*/ 40 | _reverse(start,end); 41 | 42 | /*按单词逆序.tneduts a ma I */ 43 | _revert_by_word(source); 44 | 45 | return source; 46 | } 47 | 48 | //为啥运行时 bus error? 49 | int main(int argc, char const *argv[]) 50 | { 51 | char *test = "how are you ?"; 52 | printf("%s\n",_revert_by_word(test)); 53 | 54 | test = "I am a student."; 55 | printf("%s\n",revert_by_word(test)); 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/10.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "stdio.h" 4 | 5 | void generate(int a,int b,int N ,int * Q){ 6 | 7 | int p=1,q=1; 8 | int tmpA,tmpB; 9 | 10 | for (int i = 0; i < N; ++i) 11 | { 12 | tmpA = a*p; 13 | tmpB = b*q; 14 | 15 | if (tmpA>tmpB) 16 | { 17 | Q[i]=tmpB; 18 | q++; 19 | 20 | } 21 | else if(tmpA 2 | #include 3 | 4 | #define PrintInt(expr) printf("%s : %d\n",#expr,(expr)) 5 | 6 | int main() 7 | { 8 | int y = 100; 9 | int *p; 10 | p = malloc(sizeof(int)); 11 | *p = 10; 12 | y = y / *p; 13 | PrintInt(y); 14 | return 0; 15 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/21.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int day,month,year; 5 | printf("Enter the date (dd-mm-yyyy) format including -'s:"); 6 | scanf("%d-%d-%d",&day,&month,&year); 7 | printf("The date you have entered is %d-%d-%d\n",day,month,year); 8 | return 0; 9 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/22.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int n; 5 | printf("Enter a number:\n"); 6 | scanf("%d",&n); // & 7 | 8 | printf("You entered %d \n",n); 9 | return 0; 10 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/23.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int cnt = 5, a; 5 | 6 | do { 7 | a /= cnt; 8 | } while (cnt --); 9 | 10 | printf ("%d\n", a); 11 | return 0; 12 | } 13 | 14 | /// cnt==1 时,会发生除0错误 15 | 16 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/24.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int i = 6; 5 | if( ((++i < 7) && ( i++/6)) || (++i <= 9)) 6 | ; 7 | printf("%d\n",i); 8 | return 0; 9 | } 10 | 11 | // && 优先级大于 || -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/25.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define SIZE 15 4 | int main() 5 | { 6 | int *a, i; 7 | 8 | a = malloc(SIZE*sizeof(int)); 9 | 10 | for (i=0; i 4 | int main() 5 | { 6 | char dummy[80]; 7 | printf("Enter a string:\n"); 8 | scanf("%[^a]",dummy); 9 | printf("%s\n",dummy); 10 | return 0; 11 | } 12 | 13 | /** 14 | 15 | 输入一个串,以字符'a'结尾 16 | */ 17 | 18 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/27.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int a=3, b = 5; 5 | 6 | printf(&a["Ya!Hello! how is this? %s\n"], &b["junk/super"]); 7 | 8 | //printf(3["helloworld \n"]); 9 | 10 | printf("%c\n", 0["this"] ); 11 | 12 | printf(&a["WHAT%c%c%c %c%c %c !\n"], 1["this"], 13 | 2["beauty"],0["tool"],0["is"],3["sensitive"],4["CCCCCC"]); 14 | return 0; 15 | } 16 | 17 | /* 18 | 19 | 20 | 21 | */ -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/28.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | int main() 4 | { 5 | int* ptr1,*ptr2; 6 | ptr1 = malloc(sizeof(int)); 7 | ptr2 = ptr1; 8 | *ptr2 = 10; 9 | return 0; 10 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4 numer/Power.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | double Power(double base, int exponent) 4 | { 5 | if (exponent == 0) return 1; 6 | if (exponent == 1) return base; 7 | double result = Power(base, exponent >> 1); 8 | result *= result; 9 | 10 | if (exponent & 1) result = result*base; 11 | 12 | return result; 13 | } 14 | 15 | int main(int argc, char const *argv[]) 16 | { 17 | printf("%f\n", Power(2,4)); 18 | printf("%f\n", Power(2.1,4)); 19 | printf("%f\n", Power(0,4)); 20 | printf("%f\n", Power(2,0)); 21 | printf("%f\n", Power(2,-3)); //负数就挂了 22 | return 0; 23 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4 numer/integer_to_bin.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | #include "stdlib.h" 4 | 5 | char *integer_to_bin(long integer){ 6 | 7 | int bit_num = sizeof(long)*8; 8 | char *buffer= (char *)malloc(bit_num+1); 9 | if (buffer==NULL) 10 | { 11 | return "malloc error"; 12 | } 13 | buffer[bit_num]='\0'; 14 | 15 | for (int i = 0; i < bit_num; ++i) 16 | { 17 | buffer[i]= integer<>(bit_num-1); 18 | buffer[i]= buffer[i]==0?'0':'1'; 19 | 20 | } 21 | return buffer; 22 | 23 | } 24 | 25 | char *integer_to_hex(long integer){ 26 | 27 | int bit_num=sizeof(long)*8/4; 28 | char *buffer=(char *)malloc(bit_num+3); 29 | buffer[0]='0'; 30 | buffer[1]='x'; 31 | buffer[bit_num+2]='\0'; 32 | char *tmp=&buffer[2]; 33 | 34 | for (int i = 0; i < bit_num; i++) 35 | { 36 | tmp[i]= integer<<(i*4)>>(bit_num*4-4); 37 | tmp[i]= tmp[i]>=0?tmp[i]:tmp[i]+16; 38 | tmp[i]= tmp[i]<10?(tmp[i]+48):(tmp[i]-10+'A'); 39 | } 40 | 41 | return buffer; 42 | } 43 | 44 | 45 | /* 46 | 47 | A 65 48 | 0 48 49 | 50 | */ 51 | 52 | 53 | 54 | int main(){ 55 | 56 | 57 | printf("b(11) = %s\n", integer_to_bin(11)); 58 | printf("b(1023) = %s\n",integer_to_bin(1023) ); 59 | printf("b(23564) = %s\n",integer_to_bin(23564) ); 60 | 61 | 62 | printf("h(11) = %s\n", integer_to_hex(11)); 63 | printf("h(1023) = %s\n",integer_to_hex(1023) ); 64 | printf("h(23564) = %s\n",integer_to_hex(23564) ); 65 | printf("b(11) = %x\n", 11); 66 | printf("b(1023) = %x\n",1023 ); 67 | printf("b(23564) = %x\n",23564 ); 68 | 69 | 70 | return 0; 71 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4 numer/isSquare.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | 4 | int isSquare(unsigned integer){ 5 | 6 | if (integer==1 || integer==0) 7 | { 8 | return integer; 9 | } 10 | 11 | int divider=2,result=integer/divider; 12 | 13 | while(result>divider){ 14 | 15 | divider++; 16 | result = integer/divider; 17 | 18 | } 19 | 20 | if (result!=divider) 21 | { 22 | return -1; 23 | } 24 | 25 | return result; 26 | } 27 | 28 | 29 | int main(int argc, char const *argv[]) 30 | { 31 | // 243,1849(43),289(17), 64(8) 32 | 33 | int a[] = {1849,144,243,289,64,1194877489}; 34 | for (int i = 0; i < sizeof(a)/sizeof(int); ++i) 35 | { 36 | int ret = isSquare(a[i]); 37 | 38 | if (ret==-1) 39 | { 40 | printf("%d 不是某个数的平方\n",a[i]); 41 | }else{ 42 | 43 | printf("%d 是 %d 的平方\n", a[i], ret); 44 | } 45 | 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4 numer/one_appear_count_by_binary.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | int one_appear_count_by_binary(int num){ 4 | int count = 0; 5 | while(num !=0 ){ 6 | num &= num-1; 7 | count++; 8 | } 9 | return count; 10 | } 11 | 12 | int main(int argc, char const *argv[]) 13 | { 14 | int test = 10; 15 | printf("%d\n", one_appear_count_by_binary(test)); 16 | printf("%d\n", one_appear_count_by_binary(32+1)); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4 numer/string_to_integer.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | #include "string.h" 4 | 5 | int string_to_integer(char *s,int length){ 6 | 7 | if (length>1) 8 | { 9 | return s[0]=='-'?string_to_integer(s,length-1)*10-(s[length-1]-'0'):string_to_integer(s,length-1)*10+s[length-1]-'0'; 10 | }else{ 11 | return s[0]=='-'?-1/10:s[0]-'0'; 12 | 13 | } 14 | } 15 | 16 | /* 问题: 17 | 1. s是空字符串 18 | 2. s传参为非数字字符 19 | 3. 超出整数所能表示范围 20 | */ 21 | 22 | 23 | int main(){ 24 | 25 | char *s = "4323"; 26 | 27 | printf("integer == %d\n",string_to_integer(s,strlen(s)) ); 28 | printf("integer == %d\n",string_to_integer("12342",5)); 29 | printf("integer == %d\n",string_to_integer("-11",3) ); 30 | printf("integer == %d\n",string_to_integer("-5",2) ); 31 | printf("integer == %d\n", string_to_integer("3",1)); 32 | 33 | printf("integer == %d\n", string_to_integer("abcd",4)); 34 | printf("integer == %d\n", string_to_integer("a",1)); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/4-1.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | int main(){ 4 | 5 | printf("%ld",236432123443*33453098); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/5 array/delete_occurence_character.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | char *delete_occurence_character(char *src , char target){ 4 | char *front = src; 5 | char *rear = src; 6 | while(*front != '\0'){ 7 | if (*front != target){ 8 | *rear = *front; 9 | rear++; 10 | } 11 | front++; 12 | } 13 | *rear = '\0'; 14 | return src; 15 | } 16 | 17 | int main(int argc, char const *argv[]) 18 | { 19 | char test[] = "abcdeccba"; 20 | printf("%s\n", delete_occurence_character(test,'c')); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/5 array/factorial.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | 4 | 5 | int factorial(int n){ 6 | 7 | int ret=0; 8 | (n==0) || (ret=n+factorial(n-1)); 9 | return ret; 10 | } 11 | 12 | 13 | int main(){ 14 | 15 | int n=10; 16 | printf("factorial(10)= %d\n",factorial(10)); 17 | printf("factorial(5)= %d\n",factorial(5)); 18 | printf("factorial(1)= %d\n",factorial(1)); 19 | printf("factorial(0)= %d\n",factorial(0)); 20 | 21 | return 0; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/5 array/fibonacci.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" 3 | #include "time.h" 4 | 5 | 6 | 7 | int fibonacci(int n){ 8 | 9 | int result[2] = {0,1}; 10 | if (n<2) 11 | { 12 | return result[n]; 13 | } 14 | 15 | return fibonacci(n-1)+fibonacci(n-2); 16 | } 17 | 18 | 19 | // 循环 20 | int fibonacci2(int n){ 21 | 22 | int result[2] = {0,1}; 23 | 24 | if ( n<2 ) 25 | { 26 | return result[n]; 27 | } 28 | 29 | int fibOne=0; 30 | int fibTwo=1; 31 | int fibN; 32 | for (int i = 2; i < n; ++i) 33 | { 34 | fibN = fibOne + fibTwo; 35 | fibOne = fibTwo; 36 | fibTwo = fibN; 37 | 38 | } 39 | 40 | return fibN; 41 | } 42 | 43 | 44 | 45 | int main(){ 46 | 47 | int start,end; 48 | long result; 49 | 50 | start = clock(); 51 | result=fibonacci2(40); 52 | end = clock(); 53 | printf("%ld %.6fs",result,(double)(end-start)/CLOCKS_PER_SEC); 54 | 55 | return 0; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/5 array/longest_continuious_number.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | int longest_continuious_number(const char *input,char *output){ 4 | int max = 0; 5 | char *start= input; 6 | char *mid = input; 7 | char *end = input; 8 | int tmp = 0; 9 | if (input == NULL || output == NULL) return 0; 10 | 11 | while (*end != '\0'){ 12 | if (*end < '0' || *end > '9'){//字母 13 | if(tmp > max){ 14 | max = tmp; 15 | start = mid; 16 | } 17 | tmp = 0; 18 | }else{//数字 19 | if (tmp == 0){//发现数字 20 | mid = end; 21 | } 22 | tmp++; 23 | } 24 | end++; 25 | } 26 | 27 | //修改已数字结尾的bug 28 | if(tmp > max){ 29 | max = tmp; 30 | start = mid; 31 | } 32 | 33 | //copy 34 | int i=0; 35 | while(in){ 20 | sum-=small; 21 | small++; 22 | if (sum==n) 23 | { 24 | printf("%d, %d\n",small,big); 25 | } 26 | 27 | } 28 | 29 | big++; 30 | sum+=big; 31 | 32 | } 33 | 34 | 35 | 36 | } 37 | 38 | 39 | 40 | int main(int argc, char const *argv[]) 41 | { 42 | 43 | 44 | print_continuous_sequence_sum(115); 45 | 46 | return 0; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/5.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "stdio.h" 4 | 5 | 6 | /* 7 | 8 | 1 是第一个元素 9 | 序列中元素被2或3或5整除 10 | 11 | */ 12 | unsigned long value_in_sequence(unsigned index){ 13 | 14 | 15 | 16 | 17 | } 18 | 19 | 20 | int main(){ 21 | 22 | 23 | 24 | return 0; 25 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/6 matrix/print_matrix.c: -------------------------------------------------------------------------------- 1 | #include "stdio.h" 2 | 3 | // a[i*rows+j] 4 | void print_matrix_incircle(int *matrix,int rows,int cols,int start){ 5 | int endX = cols-start-1; 6 | int endY = rows-start-1; 7 | 8 | //然后依次打印 上,右,下,左 9 | for(int i=start;i<=endX;i++){ 10 | printf("%d ",matrix[start*rows+i]); 11 | } 12 | 13 | for(int i=start+1;i<=endY;i++){ 14 | printf("%d ",matrix[i*rows+endX]); 15 | } 16 | 17 | for(int i=endX-1;i>=start;i--){ 18 | printf("%d ",matrix[endY*rows+i]); 19 | } 20 | 21 | for(int i=endY-1;i>start;i--){ 22 | printf("%d ",matrix[i*rows+start]); 23 | } 24 | } 25 | 26 | void print_matrix(int *matrix,int rows,int cols){ 27 | if(matrix==NULL) return ; 28 | if(rows<=0 || cols<=0) return; 29 | 30 | int start = 0; 31 | while(start*2=key)//往左找 15 | { 16 | high = mid; 17 | }else{ 18 | 19 | low = mid+1; 20 | } 21 | 22 | } 23 | return high; 24 | 25 | } 26 | 27 | int binary_search_last(int *a,int length,int key){ 28 | 29 | int low = 0; 30 | int high = length -1; 31 | int mid = 0; 32 | 33 | while(low 4 | 5 | #define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0])) 6 | int array[] = {23,34,12,17,204,99,16}; 7 | 8 | int main() 9 | { 10 | int d; 11 | int n = TOTAL_ELEMENTS-2; 12 | for(d=-1;d <= (TOTAL_ELEMENTS-2);d++) 13 | printf("%d\n",array[d+1]); 14 | 15 | return 0; 16 | } 17 | 18 | 19 | /* 20 | 21 | d 是有符号数,sizeof()计算为无符号数,比较时d会转为无符号数,变成一个大数 22 | 23 | */ -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c10.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int i=43; 5 | printf("%d\n",printf("%d",printf("%d",i))); 6 | return 0; 7 | } 8 | 9 | 10 | // printf 返回值是打印数据的长度 4321 11 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c11-2.c: -------------------------------------------------------------------------------- 1 | #include 2 | void foobar1(void) 3 | { 4 | printf("In foobar1\n"); 5 | } 6 | 7 | void foobar2() 8 | { 9 | printf("In foobar2\n"); 10 | } 11 | 12 | int main() 13 | { 14 | char ch = 'a'; 15 | foobar1(); 16 | foobar2(); 17 | return 0; 18 | } 19 | 20 | // compile error -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c11.c: -------------------------------------------------------------------------------- 1 | #include 2 | void foobar1(void) 3 | { 4 | printf("In foobar1\n"); 5 | } 6 | 7 | void foobar2() 8 | { 9 | printf("In foobar2\n"); 10 | } 11 | 12 | int main() 13 | { 14 | char ch = 'a'; 15 | foobar1(); 16 | foobar2(33, ch); 17 | return 0; 18 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c12.c: -------------------------------------------------------------------------------- 1 | // FROM:http://www.gowrikumar.com/c/ 2 | #include 3 | int main() 4 | { 5 | float a = 12.5; 6 | printf("%d\n", a); 7 | //printf("%d\n", *(int *)&a); 8 | return 0; 9 | } 10 | 11 | // 浮点数 转 整形 ,用强制转换(int)a 12 | 13 | /* 14 | 浮点数的存储 15 | 16 | */ 17 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c13-1.c: -------------------------------------------------------------------------------- 1 | int arr[80]; -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c13-2.c: -------------------------------------------------------------------------------- 1 | #include "c13-1.c" 2 | 3 | 4 | int main() 5 | { 6 | return 0; 7 | } 8 | 9 | 10 | /** 11 | 类型并不匹配,所以导致这里的arr并没有指向c13-1.c中声明的arr[80],应该修改为 extern int arr[] 12 | 13 | int *arr 和 int arr[80] 区别? 14 | 15 | 16 | 17 | 18 | */ 19 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c14.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int a=1; 5 | switch(a) 6 | { int b=20; 7 | case 1: printf("b is %d\n",b); 8 | break; 9 | default:printf("b is %d\n",b); 10 | break; 11 | } 12 | return 0; 13 | } 14 | 15 | 16 | // In my computer ,output:b is 32767 switch中只会从case开始执行,b会随机输出 -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c15.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #define SIZE 10 4 | void size(int arr[SIZE]) 5 | { 6 | printf("size of array is:%d\n",sizeof(arr)); 7 | } 8 | 9 | int main() 10 | { 11 | int arr[SIZE]; 12 | size(arr); 13 | return 0; 14 | } 15 | 16 | 17 | // 32位机器 4, 64位机器 8 -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c16.c: -------------------------------------------------------------------------------- 1 | // FROM:http://www.gowrikumar.com/c/ 2 | 3 | #include 4 | #include 5 | void Error(char* s) 6 | { 7 | printf(s); 8 | return; 9 | } 10 | 11 | int main() 12 | { 13 | int *p; 14 | p = malloc(sizeof(int)); 15 | if(p == NULL) 16 | { 17 | Error("Could not allocate the memory\n"); 18 | Error("Quitting....\n"); 19 | exit(1); 20 | } 21 | else 22 | { 23 | /*some stuff to use p*/ 24 | Error("Could not allocate the memory\n"); 25 | Error("Quitting....\n"); 26 | Error(5); 27 | } 28 | return 0; 29 | } 30 | 31 | /* 32 | 33 | 潜在的问题是: 34 | 35 | Error函数中,如果输入的参数不是字符串,比如传一个整数5,5被转成一个内存地址,printf这时候访问就会出问题了。 36 | $1 = 0x5 37 | 38 | */ -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c17.c: -------------------------------------------------------------------------------- 1 | // FROM:http://www.gowrikumar.com/c/ 2 | 3 | #include 4 | int main() 5 | { 6 | 7 | char c; 8 | scanf("%c",&c); 9 | printf("%c\n",c); 10 | 11 | scanf(" %c",&c); 12 | printf("%c\n",c); 13 | 14 | return 0; 15 | } 16 | 17 | /* 18 | 19 | scanf("%c",&c) 和 scanf(" %c",&c) 区别 20 | 21 | 22 | 使用第二个scanf("%c",&c) 时,系统会将前一个scanf()输入的回车符号读入改变量。 23 | 这里为什么加一个“空格”就可以? 24 | scanf带“空格”后,会从输入缓冲区中skip 空白符(空格、tab,换行符),读取一个字符 25 | 26 | */ 27 | 28 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c18.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int i; 5 | i = 10; 6 | printf("i : %d\n",i); 7 | printf("sizeof(i++) is: %d\n",sizeof(i++)); 8 | printf("i : %d\n",i); 9 | return 0; 10 | } 11 | 12 | // sizeof()不会对传入的表达式计算 ,所以后面的输入还是i:10 13 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c19.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define SIZEOF(arr) (sizeof(arr)/sizeof(arr[0])) 5 | 6 | #define PrintInt(expr) printf("%s:%d\n",#expr,(expr)) 7 | int main() 8 | { 9 | /* The powers of 10 */ 10 | int pot[] = { 11 | 0001, 12 | 0010, 13 | 0100, 14 | 1000 15 | }; 16 | int i; 17 | 18 | for(i=0;i 2 | 3 | void OS_Solaris_print() 4 | { 5 | printf("Solaris - Sun Microsystems\n"); 6 | } 7 | 8 | void OS_Windows_print() 9 | { 10 | printf("Windows - Microsoft\n"); 11 | 12 | } 13 | void OS_HP_UX_print() 14 | { 15 | printf("HP-UX - Hewlett Packard\n"); 16 | } 17 | 18 | int main() 19 | { 20 | int num; 21 | printf("Enter the number (1-3):\n"); 22 | scanf("%d",&num); 23 | switch(num) 24 | { 25 | case 1: 26 | OS_Solaris_print(); 27 | break; 28 | case 2: 29 | OS_Windows_print(); 30 | break; 31 | case 3: 32 | OS_HP_UX_print(); 33 | break; 34 | default: 35 | printf("Hmm! only 1-3 :-)\n"); 36 | break; 37 | } 38 | 39 | return 0; 40 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c20.c: -------------------------------------------------------------------------------- 1 | int main() 2 | { 3 | char ch1; 4 | char ch2; 5 | ch1 = getchar(); 6 | ch2 = getchar(); 7 | printf("%d %d", ch1, ch2); 8 | return 0; 9 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c3.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | enum {false,true}; 4 | 5 | int main() 6 | { 7 | int i=1; 8 | do 9 | { 10 | printf("%d\n",i); 11 | i++; 12 | if(i < 15) 13 | continue; 14 | }while(false); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c4.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | int main() 4 | { 5 | while(1) 6 | { 7 | fprintf(stdout,"hello-out"); 8 | fprintf(stderr,"hello-err"); 9 | sleep(1); 10 | } 11 | return 0; 12 | } 13 | 14 | 15 | /* 16 | 17 | stdout 会缓冲输出 18 | 19 | \n 20 | fflush() 21 | 22 | setbuf(stdout,NULL) 23 | 24 | */ -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c5.c: -------------------------------------------------------------------------------- 1 | 2 | // FROM:http://www.gowrikumar.com/c/ 3 | #include 4 | #define f(a,b) a##b 5 | #define g(a) #a 6 | #define h(a) g(a) 7 | 8 | int main() 9 | { 10 | printf("%s\n",h(f(1,2))); // 11 | printf("%s\n",g(f(1,2))); // f(1,2) 12 | return 0; 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c6.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int a=10; 5 | switch(a) 6 | { 7 | case '1': 8 | printf("ONE\n"); 9 | break; 10 | case '2': 11 | printf("TWO\n"); 12 | break; 13 | a: 14 | printf("NONE\n"); 15 | } 16 | return 0; 17 | } 18 | 19 | 20 | //什么也不输出, “default”这里随便什么都没有编译错误 -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c7.c: -------------------------------------------------------------------------------- 1 | #include 2 | int main() 3 | { 4 | int* p; 5 | p = (int*)malloc(sizeof(int)); 6 | *p = 10; 7 | return 0; 8 | } -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c8.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int main() 5 | { 6 | float f=0.0f; 7 | int i; 8 | 9 | for(i=0;i<10;i++) 10 | f = f + 0.1f; 11 | 12 | if(f == 1.0f) 13 | printf("f is 1.0 \n"); 14 | else 15 | printf("f is NOT 1.0\n"); 16 | 17 | return 0; 18 | } 19 | 20 | // 浮点数判相等 21 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/c9.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | int main() 5 | { 6 | int a = (1,2); 7 | printf("a : %d\n",a); 8 | return 0; 9 | } 10 | 11 | // ,运算符优先级 最低 ,括号不能少 12 | // 13 | 14 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/codes/most_visit_ip.c: -------------------------------------------------------------------------------- 1 | 2 | #include "stdio.h" // fwrite 3 | #include "unistd.h" // write 4 | #include "stdlib.h" // itoa 5 | #include "string.h" 6 | 7 | #define test_file_path2 "./ip2.txt" 8 | #define test_file_path "./ip.txt" 9 | #define ip_count 100000000 //随机1亿个IP 10 | #define tmp_file_count 32 11 | #define mem_count 128*1024*1028 //128MB个IP空间 12 | 13 | int hash(unsigned i){ 14 | return i>>27; 15 | } 16 | 17 | int main(){ 18 | 19 | 20 | // 模拟创建一个文件,产生测试用的IP 21 | // FILE *fd; 22 | // fd = fopen(tmp_file_path,"a+"); 23 | // for (int i = 0; i < ip_count;++i) 24 | // { 25 | // //char c[] = "this is sentence!"; 26 | // unsigned random_ip = (unsigned)rand(); 27 | // char tmp[50]; 28 | // sprintf(tmp,"%d",random_ip); 29 | // //printf("random ip = %s\n",tmp); 30 | // fwrite(tmp,strlen(tmp),1,fd); 31 | // fputc('\n',fd);//加入换行符号 32 | // } 33 | 34 | 35 | //创建临时文件 36 | FILE *fd[tmp_file_count]; 37 | for (int i = 0; i < tmp_file_count; ++i) 38 | { 39 | char file_path[128]; 40 | //s[0]=(char)(i+48); 41 | //char *file_path = strcat("./ipslice/ipslice",s) ; // 这样写出现 bus error!!! 42 | sprintf(file_path,"./ipslice/ipslice%d",i); 43 | fd[i] = fopen(file_path ,"a+" ); 44 | if ( !fd[i]) 45 | { 46 | printf("file %d open fail!! \n" ,i); 47 | } 48 | } 49 | 50 | //开始读测试数据IP,按IP,映射到32个文件中 51 | FILE *testfd = fopen(test_file_path2,"r"); 52 | if (!testfd) 53 | { 54 | printf("file open fail !!\n"); 55 | exit(-1); 56 | } 57 | unsigned tmp_data; 58 | while( getline() ){ //能读到数据 // fread(&tmp_data,sizeof(unsigned),1,testfd) , C语言中没有getline() 这样的函数 59 | 60 | printf("%d\n",(unsigned)tmp_data ); 61 | int key = hash((unsigned)tmp_data);// key值即为对应文件fd 62 | 63 | int size = fwrite(&tmp_data,sizeof(unsigned),1,fd[key]); 64 | printf("写入数据size=%d ,key=%d\n",size, key ); 65 | 66 | //跳过换行符 67 | fseek(testfd,1,SEEK_CUR); 68 | } 69 | 70 | 71 | // 依次读入每个文件并统计, hash_map 统计每个区间段的最大IP 72 | int hash_map[mem_count]; 73 | int max_ip; 74 | int max_times; 75 | 76 | for (int i = 0; i < tmp_file_count; ++i) 77 | { 78 | 79 | // hash统计 80 | while(){} 81 | 82 | //hash 表里找出最大的,变量一遍 83 | for (int i = 0; i < mem_count; ++i) 84 | { 85 | /* code */ 86 | } 87 | 88 | } 89 | 90 | 91 | 92 | // 释放 93 | for (int i = 0; i < tmp_file_count; ++i) 94 | { 95 | fclose(fd[i]); 96 | } 97 | 98 | 99 | return 0; 100 | } 101 | 102 | 103 | 104 | /** 105 | 关于warning 106 | implicit declaration of function 'itoa' is invalid in C99 107 | 108 | itoa() 函数并不是一个 standard functions ANSI C standard 109 | 110 | 111 | inet_ntop() // 整数转换成 .分 ip地址 字符串 112 | inet_nton() // 113 | 114 | inet_aton() // 115 | 116 | struct in_addr 117 | 118 | */ 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /9 Algorithms Job Interview/编程之美/README.md: -------------------------------------------------------------------------------- 1 | #《编程之美》 2 | 3 | 书中的内容分为4个部分: 4 | 5 | 1. 游戏之乐:游戏中的一些问题 6 | 2. 数字之魅: 数字和字符的处理能力 7 | 3. 结构之法: 对字符串,链表,队列,树的操作 8 | 4. 数学之趣: 一些数学问题 9 | 10 | 《剑指offer》中已经出现的题目先不写了 11 | 12 | # 游戏之乐 13 | 14 | 15 | 16 | 17 | # 数字之魅 18 | 19 | 20 | #### 二进制数中1的个数 21 | 22 | 23 | #### 阶乘 24 | 25 | 26 | #### 寻找发帖"水王" 27 | 28 | 29 | #### 1的数目 30 | 31 | 32 | #### 寻找最大的k个数 33 | 34 | 35 | #### 精确表达浮点数 36 | 37 | 38 | #### 最大公约数 39 | 40 | 41 | #### 找符合条件的整数 42 | 43 | 44 | #### Fibonacci数列 45 | 46 | 47 | #### 寻找数组中的最大值和最小指 48 | 49 | 50 | #### 寻找最近点对 51 | 52 | #### 快速寻找满足条件的两个数 53 | 54 | 55 | #### 子数组的最大乘积 56 | 57 | 58 | #### 求数组的子数组之和的最大值 59 | 60 | 61 | #### 子数组之和的最大值 62 | 63 | 64 | #### 数组中最长递归子序列 65 | 66 | 67 | #### 数组循环移位 68 | 69 | 70 | 71 | #### 数组分割 72 | 73 | 74 | #### 区间重合判断 75 | 76 | 77 | 78 | # 结构之法 79 | 80 | 81 | #### 字符串移位包含的问题 82 | 83 | 84 | #### 电话号码应对英语单词 85 | 86 | 87 | #### 计算字符串的相似度 88 | 89 | 90 | #### 从无头单链表中删除节点 91 | 92 | 93 | 94 | 95 | #### 最短摘要生成 96 | 97 | 98 | #### 判断2个链表是否相交 99 | 100 | 101 | #### 队列中取最大值 102 | 103 | 104 | #### 求二叉树中节点的最大距离 105 | 106 | 107 | #### 重建二叉树 108 | 109 | 110 | #### 程序改错 111 | 112 | 113 | 114 | # 数学之趣 115 | 116 | 117 | #### 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /91 Algorithms In Big Data/Bitmap.md: -------------------------------------------------------------------------------- 1 | # Bitmap 2 | 3 | 也就是用1个(或几个)bit位来标记某个元素对应的value(如果是1bitmap,就只能是元素是否存在;如果是x-bitmap,还可以是元素出现的次数等信息)。使用bit位来存储信息,在需要的存储空间方面可以大大节省。应用场景有: 4 | 5 | 6 | 1. 判断某个元素是否存在 7 | 2. 排序(如果是1-bitmap,就只能对无重复的数排序) 8 | 9 | 10 | 比如,某文件中有若干8位数字的电话号码,要求统计一共有多少个不同的电话号码? 11 | 12 | 分析:8位最多99 999 999, 如果1Byte表示1个号码是否存在,需要95MB空间,但是如果1bit表示1个号码是否存在,则只需要 95/8=12MB 的空间。这时,数字 `k(0~99 999 999)`与bit位的对应关系是: 13 | 14 | 15 | ``` 16 | #define SIZE 15*1024*1024 17 | char a[SIZE]; 18 | memset(a,0,SIZE); 19 | 20 | // a[k/8]这个字节中的 `k%8` 位命中,置为1 21 | // 这里要注意 big-endian 和 little-endian的问题 ,假设这里是big-endian 22 | a[k/8] = a[k/8] | (0x01 << (k%8)) 23 | 24 | ``` 25 | -------------------------------------------------------------------------------- /91 Algorithms In Big Data/Hash映射,分而治之.md: -------------------------------------------------------------------------------- 1 | # Hash映射,分而治之 2 | 3 | 这里的`Hash映射`是指通过一种映射散列的方式,将海量数据均匀分布在对应的内存或更小的文件中 4 | 5 | 使用hash映射有个最重要的特点是: `hash值相同的两个串不一定一样,但是两个一样的字符串hash值一定相等`。哈希函数如下: 6 | 7 | ``` 8 | int hash = 0; 9 | for (int i=0;i=2) ,有一些特征: 37 | 38 | 1. 根节点至少有2个子节点 39 | 2. 所有的叶节点具有相同的深度 h,也就是树高 40 | 3. 每个叶子节点至少包含一个key和2个指针,最多2d-1个key和2d个指针,叶节点的指针都是null。每个节点的关键字个数在【d-1,2d-1】之间 41 | 4. 每个非叶子节点,key和指针互相间隔,节点两端是指针,因此节点中指针个数=key的个数+1 42 | 5. 每个指针要么是null,要么指向另一个节点 43 | 44 | 如果某个指针在节点node最左边且不为null,则其指向节点的所有key小于v(key1),其中v(key1)为node的第一个key的值。 45 | 如果某个指针在节点node最右边且不为null,则其指向节点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。 46 | 如果某个指针在节点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向节点的所有key小于v(keyi+1)且大于v(keyi)。 47 | 48 | 49 | 使用数据结构表示如下: 50 | 51 | ``` 52 | typedef struct Item{ 53 | int key; 54 | Data data; 55 | } 56 | 57 | #define m 3 //B树的阶 58 | 59 | typedef struct BTNode{ 60 | int degree; //B树的度 61 | int keynums; //每个节点key的个数 62 | Item items[m]; 63 | struct BTNode *p[m]; 64 | }BTNode,* BTree; 65 | 66 | typedef struct{ 67 | BTNode *pt; //指向找到的节点 68 | int i; // 节点中关键字的序号 (0,m-1) 69 | int tag; //1:查找成功,0:查找失败 70 | }Result; 71 | 72 | Status btree_insert(root,target); 73 | Status btree_delete(root,target); 74 | Result btree_find(root,target); 75 | 76 | ``` 77 | 78 | ### 建立索引 79 | 80 | 当为一张空表创建索引时,数据库系统将为你分配一个索引页,该索引页在你插入数据前一直是空的。此页此时既是根结点,也是叶结点。每当你往表中插入一行数据,数据库系统即向此根结点中插入一行索引记录。 81 | 82 | 插入和删除新的数据记录都会破坏B-Tree的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持B-Tree性质 83 | 84 | ### 查找操作 85 | 86 | 从root节点出发,对每个节点,找到等于target的key,则查找成功;或者找到大于target的最小k[i], 到 k[i] 左指针指向的子节点继续查找,直到页节点,如果找不到,说明关键字target不在B树中。 87 | 88 | 分析下时间复杂度: 89 | 90 | 对于一个度为d的B-Tree,每个节点的索引key个数是d-1, 索引key个数为N,树高h上限是: 91 | 92 | 2d^h-1=N ==> h=logd^((N+1) /2) ??? 93 | 94 | 因此,检索一个key,查找节点的个数的复杂度是O(logd^N) 95 | 96 | ``` 97 | 比如d=2,N=1,000,000 (1百万),h差不多20个 98 | d=3,N=1,000,000 (1百万) ,h差不多13个(3^11=1,594,323) 99 | d=4,N=1,000,000 (1百万) ,h差不多10个 100 | d=5,N=1,000,000 (1百万) ,h差不多9个 (5^9 = 1,953,125) 101 | d=6,N=1,000,000 (1百万) ,h差不多8个(6^8 = 1,679,616) 102 | d=7,N=1,000,000 (1百万) ,h差不多8个 103 | d=8,N=1,000,000 (1百万) ,h差不多7个 104 | d=9,N=1,000,000 (1百万) ,h差不多7个 105 | d=10,N=1,000,000 (1百万) ,h差不多6个 106 | d=100时,h差不多3个 107 | ``` 108 | 109 | 数据库系统在设计时,通常将一个节点的大小设为一个页大小(通常4k),这样保证一个节点在物理上也存储在一个页里,加上计算机存储分配都是按页对其,这样保证一个节点只需要一次I/O. 110 | 111 | 实际应用中,d都是比较大,通常超过100,因此1百万的数据通常最多访问3个节点,也就是3次I/O, 因此使用B树作为索引结构查询效率非常高。 112 | 113 | 114 | ### 插入数据 115 | 116 | 插入数据时,需要更新索引,索引中也要添加一条记录。索引中添加一条记录的过程是: 117 | 118 | 沿着搜索的路径从root一直到叶节点 119 | 120 | 每个节点的关键字个数在【d-1,2d-1】之间,当节点的关键字个数是2t-1时,再加入target就违反了B树定义,需要对该节点进行分裂:已中间节点为界,分成2个包含 `d-1` 个关键字的子节点(另外还有一个分界关键字,`2*(d-1)+1=2d-1)`,同时把该分界关键字提升到该叶子的父节点中,如果这导致父节点关键字个数超过 `2d-1`, 就继续向上分裂,直到根节点。 121 | 122 | 如下演示动画,往度d=2的B树中插入:` 6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4` 123 | 124 | ![](./btree_insert.gif) 125 | 126 | 127 | ### B树和B+树的区别 128 | 129 | B树和B+树的区别在于: 130 | 131 | 1. B+树的非叶子节点只包含导航信息,不包含实际记录的信息,这可以保证一个固定大小节点可以放入更多个关键字,也就是更大的度d,从而树高h可以更小,从而相比B树有更优秀的查询效率 132 | 2. 所有的叶子节点和相邻的节点使用链表方式相连,便于区间查找和遍历 133 | 134 | 135 | -------------------------------------------------------------------------------- /91 Algorithms In Big Data/README.md: -------------------------------------------------------------------------------- 1 | # 海量数据处理 2 | 3 | 所谓海量数据,就是数据量太大,要么在短时间内无法计算出结果,要么数据太大,无法一次性装入内存。 4 | 5 | 针对时间,我们可以使用巧妙的算法搭配合适的数据结构,如bitmap/堆/trie树等 6 | 针对空间,就一个办法,大而化小,分而治之。常采用hash映射 7 | 8 | 9 | * [Hash映射,分而治之](Hash映射,分而治之.md) 10 | * [Bitmap](Bitmap.md) 11 | * [Bloom filter(布隆过滤器)](Bloomfilter.md) 12 | * [双层桶划分](双层桶划分.md) 13 | * [Trie树](../4%20Tree/4-字典树Trie/README.md) 14 | * [数据库索引](Inverted%20Index/数据库索引.md) 15 | * 倒排索引(Inverted Index) 16 | * [外排序](../6%20Sort/外排序.md) 17 | * [simhash算法](simhash算法.md) 18 | * 分布处理之Mapreduce 19 | 20 | 21 | ### 估算 22 | 23 | 在处理海量问题之前,我们往往要先估算下数据量,能否一次性载入内存?如果不能,应该用什么方式拆分成小块以后映射进内存?每次拆分的大小多少合适?以及在不同方案下,大概需要的内存空间和计算时间。 24 | 25 | 比如,我们来了解下以下常见问题`时间` 和 `空间` 估算 : 26 | 27 | ``` 28 | 8位的电话号码,最多有99 999 999个 29 | IP地址 30 | 1G内存,2^32 ,差不多40亿,40亿Byte*8 = 320亿 bit 31 | 32 | ``` 33 | 34 | 35 | 海量处理问题常用的分析解决问题的思路是: 36 | 37 | * 分而治之/Hash映射 + hash统计/trie树/红黑树/二叉搜索树 + 堆排序/快速排序/归并排序 38 | * 双层桶划分 39 | * Bloom filter 、Bitmap 40 | * Trie树/数据库/倒排索引 41 | * 外排序 42 | * 分布处理之 Hadoop/Mapreduce 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /91 Algorithms In Big Data/mapreduce/Hash映射,分而治之.md: -------------------------------------------------------------------------------- 1 | # Hash映射,分而治之 2 | 3 | 这里的`Hash映射`是指通过一种映射散列的方式,将海量数据均匀分布在对应的内存或更小的文件中 4 | 5 | 使用hash映射有个最重要的特点是: `hash值相同的两个串不一定一样,但是两个一样的字符串hash值一定相等`。哈希函数如下: 6 | 7 | ``` 8 | int hash = 0; 9 | for (int i=0;i