算法、数据结构
1、不用第三个变量交换两个变量
1 加减法 2 a += b 3 b = a - b 4 a = a - b 5 6 乘除法 7 a *= b 8 b = a/b 9 a = a/b 10 11 位运算: 12 a ^= b 13 b ^= a 14 a ^= b
2、线性结构有哪些
3、手写排序
1 void quick_sort(int arry[], int left, int right) { 2 if(left >= right) return ; 3 int i = left, j = right; 4 int tmp = arry[left]; 5 do { 6 while(arry[j] > tmp && i < j) { 7 j--; 8 } 9 if(i < j) { 10 arry[i] = arry[j]; 11 i++; 12 } 13 while(arry[i] < tmp && i < j) { 14 i++; 15 } 16 if(i < j) { 17 arry[j] = arry[i]; 18 j--; 19 } 20 } while(i != j); 21 arry[i] = tmp; 22 quick_sort(arry, left, i - 1); 23 quick_sort(arry, i + 1, right); 24 }
partition算法的应用
1 void solve(int arry[], int left, int right) { 2 if(left >= right) return ; 3 int i = left, j = right; 4 int tmp = arry[left]; 5 while(i < j) { 6 while(i < j && arry[j] >= tmp) { 7 j--; 8 } 9 if(i < j) { 10 arry[i] = arry[j]; 11 i++; 12 } 13 while(i < j && arry[i] < tmp) { 14 i++; 15 } 16 if(i < j) { 17 arry[j] = arry[i]; 18 j--; 19 } 20 } 21 arry[i] = tmp; 22 if(i >= tot - 1) { 23 solve(arry, left, i - 1); 24 } else { 25 solve(arry, left, i - 1); 26 solve(arry, i + 1, right); 27 } 28 }
1 void solve(int arry[], int left, int right, int index) { 2 if(left >= right) return ; 3 int i = left, j = right; 4 int tmp = arry[left]; 5 while(i < j) { 6 while(i < j && arry[j] > tmp) { 7 j--; 8 } 9 if(i < j) { 10 arry[i] = arry[j]; 11 i++; 12 } 13 while(i < j && arry[i] <= tmp) { 14 i++; 15 } 16 if(i < j) { 17 arry[j] = arry[i]; 18 j--; 19 } 20 } 21 arry[i] = tmp; 22 if(i == index) { 23 return ; 24 } 25 if(i > index) { 26 solve(arry, left, i - 1, index); 27 } else { 28 solve(arry, i + 1, right, index); 29 }
堆排
1 void Heap_just(int a[], int root, int heap_size) { 2 if(root < heap_size) { 3 int Min = root; 4 int l_son = root << 1 | 1; 5 int r_son = (root << 1) + 2; 6 if(l_son < heap_size && a[Min] > a[l_son]) Min = l_son; 7 if(r_son < heap_size && a[Min] > a[r_son]) Min = r_son; 8 if(Min == root) return ; 9 a[root] ^= a[Min]; 10 a[Min] ^= a[root]; 11 a[root] ^= a[Min]; 12 Heap_just(a, Min, heap_size); 13 } 14 } 15 16 void build_heap(int a[], int n) { 17 for(int i = n / 2; i >= 0; i--) { 18 Heap_just(a, i, n); 19 } 20 } 21 22 void Heap_sort(int a[], int n) { 23 build_heap(a, n); 24 for(int i = n - 1; i > 0; i--) { 25 a[i] ^= a[0]; 26 a[0] ^= a[i]; 27 a[i] ^= a[0]; 28 Heap_just(a, 0, i); 29 } 30 }
1 #include <cstdio> 2 3 const int maxn = 1005; 4 5 void swap(int &a, int &b) { 6 a ^= b; 7 b ^= a; 8 a ^= b; 9 } 10 11 class MaxHeap { 12 public: 13 int a[maxn]; 14 int size; 15 void init() { 16 size = 0; 17 } 18 void pushDown(int rt) { 19 int Max = rt; 20 int left = rt << 1 | 1; 21 int right = (rt + 1) << 1; 22 if(left < size && a[Max] < a[left]) { 23 Max = left; 24 } 25 if(right < size && a[Max] < a[right]) { 26 Max = right; 27 } 28 if(Max != rt) { 29 swap(a[Max], a[rt]); 30 pushDown(Max); 31 } 32 } 33 34 void pushUp(int son) { 35 if(son <= 0) return ; 36 int rt = (son - 1) >> 1; 37 if(a[son] > a[rt]) { 38 swap(a[son], a[rt]); 39 pushUp(rt); 40 } 41 } 42 43 void insert(int x) { 44 a[size++] = x; 45 pushUp(size - 1); 46 } 47 }; 48 49 class MinHeap { 50 public: 51 int a[maxn]; 52 int size; 53 void init() { 54 size = 0; 55 } 56 57 void pushDown(int rt) { 58 int Min = rt; 59 int left = rt << 1 | 1; 60 int right = (rt + 1) << 1; 61 if(left < size && a[Min] > a[left]) { 62 Min = left; 63 } 64 if(right < size && a[Min] > a[right]) { 65 Min = right; 66 } 67 if(Min != rt) { 68 swap(a[Min], a[rt]); 69 pushDown(Min); 70 } 71 } 72 73 void pushUp(int son) { 74 if(son <= 0) return ; 75 int rt = (son - 1) >> 1; 76 if(a[son] < a[rt]) { 77 swap(a[son], a[rt]); 78 pushUp(rt); 79 } 80 } 81 82 void insert(int x) { 83 a[size++] = x; 84 pushUp(size - 1); 85 } 86 }; 87 88 MaxHeap maxheap; 89 MinHeap minheap; 90 91 void insert(int x) { 92 if(maxheap.size <= minheap.size) { 93 if(minheap.size == 0) { 94 maxheap.insert(x); 95 } else { 96 if(x <= minheap.a[0]) { 97 maxheap.insert(x); 98 } else { 99 swap(x, minheap.a[0]); 100 minheap.pushDown(0); 101 maxheap.insert(x); 102 } 103 } 104 } else { 105 if(x >= maxheap.a[0]) { 106 minheap.insert(x); 107 } else { 108 swap(maxheap.a[0], x); 109 maxheap.pushDown(0); 110 minheap.insert(x); 111 } 112 } 113 } 114 115 int getMiddle() { 116 if(maxheap.size == minheap.size) { 117 return (maxheap.a[0] + minheap.a[0]) >> 1; 118 } 119 return maxheap.size > minheap.size ? maxheap.a[0] : minheap.a[0]; 120 } 121 122 int main() { 123 int n, x; 124 while(EOF != scanf("%d",&n) ) { 125 maxheap.init(); 126 minheap.init(); 127 for(int i = 0; i < n; i++) { 128 scanf("%d",&x); 129 printf("x = %d\n", x); 130 insert(x); 131 printf("middle = %d\n", getMiddle()); 132 } 133 } 134 }
1 void arry_add(int arry[], int left, int mid, int right) { 2 if(left >= right) return ; 3 int i = left, j = mid + 1, k = 0; 4 while(i <= mid && j <= right) { 5 if(arry[i] <= arry[j]) { 6 tmp[k++] = arry[i++]; 7 } else { 8 tmp[k++] = arry[j++]; 9 cnt += (mid - i + 1); 10 } 11 } 12 while(i <= mid) { 13 tmp[k++] = arry[i++]; 14 } 15 while(j <= right) { 16 tmp[k++] = arry[j++]; 17 } 18 for(i = 0; i < k; i++) { 19 arry[i + left] = tmp[i]; 20 } 21 } 22 void merge_sort(int arry[], int left, int right) { 23 if(left >= right) return ; 24 int mid = (left + right) >> 1; 25 merge_sort(arry, left, mid); 26 merge_sort(arry, mid + 1, right); 27 arry_add(arry, left, mid, right); 28 }
1 void swap(int &a, int &b) { 2 a ^= b; b ^= a; a ^= b; 3 } 4 5 void swap_arry(int arry[], int left, int right) { 6 while(left < right) { 7 swap(arry[left++], arry[right--]); 8 } 9 } 10 11 int cnt; 12 void arry_add(int arry[], int left, int mid, int right) { 13 if(left >= right) return ; 14 int i = left, j = mid + 1; 15 while(i < j && j <= right) { 16 while(i < j && arry[i] <= arry[j]) { 17 i++; 18 } 19 int old_j = j; 20 while(i < j && j <= right && arry[j] <= arry[i]) { 21 j++; 22 } 23 if(i < j) { 24 cnt += (j - old_j) * (old_j - i); 25 swap_arry(arry, i, old_j - 1); 26 swap_arry(arry, old_j, j - 1); 27 swap_arry(arry, i, j - 1); 28 } 29 } 30 } 31 void merge_sort(int arry[], int left, int right) { 32 if(left >= right) return ; 33 int mid = (left + right) >> 1; 34 merge_sort(arry, left, mid); 35 merge_sort(arry, mid + 1, right); 36 arry_add(arry, left, mid, right); 37 }
1 void Binary_Insert_sort(int arry[], int first, int end) { 2 for(int i = first + 1; i <= end; i++) { 3 int low = first, high = i - 1; 4 while(low <= high) { 5 int mid = (low + high) >> 1; 6 if(arry[mid] > arry[i]) { 7 high = mid - 1; 8 } else { 9 low = mid + 1; 10 } 11 } 12 int key = arry[i]; 13 for(int j = i; j > high + 1; j--) { 14 arry[j] = arry[j - 1]; 15 } 16 arry[high + 1] = key; 17 } 18 }
1 void shell_sort(int arry[], int left, int right) { 2 int n = right - left; 3 int gep = 1; 4 while(gep <= n) { 5 gep = gep << 1 | 1; 6 } 7 while(gep >= 1) { 8 for(int i = left + gep; i <= right; i++) { 9 int temp = arry[i]; 10 int j = i - gep; 11 while(j >= left && arry[j] > temp) { 12 arry[j + gep] = arry[j]; 13 j -= gep; 14 } 15 arry[j + gep] = temp; 16 } 17 gep = (gep - 1) / 2; 18 } 19 }
4、o(1)删除链表某个节点
1 这是一道广为流传的Google面试题,考察我们对链表的操作和时间复杂度的了解,咋一看这道题还想不出什么较好的解法,但人家把题出在这,肯定是有解法的。一般单链表删除某个节点,需要知道删除节点的前一个节点,则需要O(n)的遍历时间,显然常规思路是不行的。在仔细看题目,换一种思路,既然不能在O(1)得到删除节点的前一个元素,但我们可以轻松得到后一个元素,这样,我们何不把后一个元素赋值给待删除节点,这样也就相当于是删除了当前元素。可见,该方法可行,但如果待删除节点为最后一个节点,则不能按照以上思路,没有办法,只能按照常规方法遍历,时间复杂度为O(n),是不是不符合题目要求呢?可能很多人在这就会怀疑自己的思考,从而放弃这种思路,最后可能放弃这道题,这就是这道面试题有意思的地方,虽看简单,但是考察了大家的分析判断能力,是否拥有强大的心理,充分自信。其实我们分析一下,仍然是满足题目要求的,如果删除节点为前面的n-1个节点,则时间复杂度为O(1),只有删除节点为最后一个时,时间复杂度才为O(n),所以平均的时间复杂度为:(O(1) * (n-1) + O(n))/n = O(1);仍然为O(1).下面见代码:
5、红黑树
1 性质1. 节点是红色或黑色。 2 3 性质2. 根是黑色。 4 5 性质3. 所有叶子都是黑色(叶子是NIL节点)。 6 7 性质4. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) 8 9 性质5. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。 10 11 12 4、5可以推出 最长路不会超过最短路的两倍
1 RBT 红黑树 2 AVL是严格平衡树,因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多; 3 红黑是弱平衡的,用非严格的平衡来换取增删节点时候旋转次数的降低; 4 所以简单说,搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应该选择RB树。 5 红黑树上每个结点内含五个域,color,key,left,right,p。如果相应的指针域没有,则设为NIL。 6 一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树: 7 1)每个结点要么是红的,要么是黑的。 8 2)根结点是黑的。 9 3)每个叶结点,即空结点(NIL)是黑的。 10 4)如果一个结点是红的,那么它的俩个儿子都是黑的。 11 5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。 12 下图所示,即是一颗红黑树: 13 14 15 16 红黑树的基本操作(一) 左旋和右旋 17 红黑树的基本操作是 添加 、 删除 。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。 18 旋转包括两种: 左旋 和 右旋 。下面分别对它们进行介绍。 19 1. 左旋 20 21 对x进行左旋,意味着"将x变成一个左节点"。 22 左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的。 23 LEFT-ROTATE(T, x) 24 01 y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作 25 02 right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子 26 03 p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x 27 04 p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲” 28 05 if p[x] = nil[T] 29 06 then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点 30 07 else if x = left[p[x]] 31 08 then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子” 32 09 else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子” 33 10 left[y] ← x // 将 “x” 设为 “y的左孩子” 34 11 p[x] ← y // 将 “x的父节点” 设为 “y” 35 理解左旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。 36 37 2. 右旋 38 39 对x进行左旋,意味着"将x变成一个左节点"。 40 右旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点y进行右旋”是如何进行的。 41 RIGHT-ROTATE(T, y) 42 01 x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作 43 02 left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子 44 03 p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y 45 04 p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲” 46 05 if p[y] = nil[T] 47 06 then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点 48 07 else if y = right[p[y]] 49 08 then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子” 50 09 else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子” 51 10 right[x] ← y // 将 “y” 设为 “x的右孩子” 52 11 p[y] ← x // 将 “y的父节点” 设为 “x” 53 理解右旋之后,看看下面一个更鲜明的例子。你可以先不看右边的结果,自己尝试一下。 54 55 旋转总结 : 56 (01) 左旋 和 右旋 是相对的两个概念,原理类似。理解一个也就理解了另一个。 57 (02) 下面谈谈如何区分 左旋 和 右旋。 在实际应用中,若没有彻底理解 左旋 和 右旋,可能会将它们混淆。下面谈谈我对如何区分 左旋 和 右旋 的理解。 58 3. 区分 左旋 和 右旋 59 仔细观察上面"左旋"和"右旋"的示意图。我们能清晰的发现,它们是对称的。无论是左旋还是右旋,被旋转的树,在旋转前是二叉查找树,并且旋转之后仍然是一颗二叉查找树。 60 61 左旋示例图 (以x为节点进行左旋): 62 z 63 x / 64 / \ --(左旋)--> x 65 y z / 66 y 67 对x进行左旋,意味着,将“x的右孩子”设为“x的父亲节点”;即,将 x变成了一个左节点(x成了为z的左孩子)!。 因此, 左旋中的“左”,意味着“被旋转的节点将变成一个左节点” 。 68 右旋示例图 (以x为节点进行右旋): 69 y 70 x \ 71 / \ --(右旋)--> x 72 y z \ 73 z 74 对x进行右旋,意味着,将“x的左孩子”设为“x的父亲节点”;即,将 x变成了一个右节点(x成了为y的右孩子)! 因此, 右旋中的“右”,意味着“被旋转的节点将变成一个右节点” 。 75 红黑树的基本操作(二) 添加 76 将一个节点插入到红黑树中,需要执行哪些步骤呢?首先,将红黑树当作一颗二叉查找树,将节点插入;然后,将节点着色为红色;最后,通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。详细描述如下: 77 第一步: 将红黑树当作一颗二叉查找树,将节点插入。 78 红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。此外,无论是左旋还是右旋,若旋转之前这棵树是二叉查找树,旋转之后它一定还是二叉查找树。这也就意味着,任何的旋转和重新着色操作,都不会改变它仍然是一颗二叉查找树的事实。 79 好吧?那接下来,我们就来想方设法的旋转以及重新着色,使这颗树重新成为红黑树! 80 第二步:将插入的节点着色为"红色"。 81 为什么着色成红色,而不是黑色呢?为什么呢?在回答之前,我们需要重新温习一下红黑树的特性: 82 (1) 每个节点或者是黑色,或者是红色。 83 (2) 根节点是黑色。 84 (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!] 85 (4) 如果一个节点是红色的,则它的子节点必须是黑色的。 86 (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 87 将插入的节点着色为红色,不会违背"特性(5)"!少违背一条特性,就意味着我们需要处理的情况越少。接下来,就要努力的让这棵树满足其它性质即可;满足了的话,它就又是一颗红黑树了。o(∩∩)o...哈哈 88 第三步: 通过一系列的旋转或着色等操作,使之重新成为一颗红黑树。 89 第二步中,将插入节点着色为"红色"之后,不会违背"特性(5)"。那它到底会违背哪些特性呢? 90 对于"特性(1)",显然不会违背了。因为我们已经将它涂成红色了。 91 对于"特性(2)",显然也不会违背。在第一步中,我们是将红黑树当作二叉查找树,然后执行的插入操作。而根据二叉查找数的特点,插入操作不会改变根节点。所以,根节点仍然是黑色。 92 对于"特性(3)",显然不会违背了。这里的叶子节点是指的空叶子节点,插入非空节点并不会对它们造成影响。 93 对于"特性(4)",是有可能违背的! 94 那接下来,想办法使之"满足特性(4)",就可以将树重新构造成红黑树了。 95 下面看看代码到底是怎样实现这三步的。 96 添加操作的伪代码《算法导论》 97 RB-INSERT(T, z) 98 01 y ← nil[T] // 新建节点“y”,将y设为空节点。 99 02 x ← root[T] // 设“红黑树T”的根节点为“x” 100 03 while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y” 101 04 do y ← x 102 05 if key[z] < key[x] 103 06 then x ← left[x] 104 07 else x ← right[x] 105 08 p[z] ← y // 设置 “z的父亲” 为 “y” 106 09 if y = nil[T] 107 10 then root[T] ← z // 情况1:若y是空节点,则将z设为根 108 11 else if key[z] < key[y] 109 12 then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子” 110 13 else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子” 111 14 left[z] ← nil[T] // z的左孩子设为空 112 15 right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。 113 16 color[z] ← RED // 将z着色为“红色” 114 17 RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树 115 结合伪代码以及为代码上面的说明,先理解RB-INSERT。理解了RB-INSERT之后,我们接着对 RB-INSERT-FIXUP的伪代码进行说明。 116 添加修正操作的伪代码《算法导论》 117 RB-INSERT-FIXUP(T, z) 118 01 while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。 119 02 do if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。 120 03 then y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)” 121 04 if color[y] = RED // Case 1条件:叔叔是红色 122 05 then color[p[z]] ← BLACK ▹ Case 1 // (01) 将“父节点”设为黑色。 123 06 color[y] ← BLACK ▹ Case 1 // (02) 将“叔叔节点”设为黑色。 124 07 color[p[p[z]]] ← RED ▹ Case 1 // (03) 将“祖父节点”设为“红色”。 125 08 z ← p[p[z]] ▹ Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点) 126 09 else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子 127 10 then z ← p[z] ▹ Case 2 // (01) 将“父节点”作为“新的当前节点”。 128 11 LEFT-ROTATE(T, z) ▹ Case 2 // (02) 以“新的当前节点”为支点进行左旋。 129 12 color[p[z]] ← BLACK ▹ Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。 130 13 color[p[p[z]]] ← RED ▹ Case 3 // (02) 将“祖父节点”设为“红色”。 131 14 RIGHT-ROTATE(T, p[p[z]]) ▹ Case 3 // (03) 以“祖父节点”为支点进行右旋。 132 15 else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。 133 16 color[root[T]] ← BLACK 134 根据被插入节点的父节点的情况,可以将"当节点z被着色为红色节点,并插入二叉树"划分为三种情况来处理。 135 ① 情况说明:被插入的节点是根节点。 136 处理方法:直接把此节点涂为黑色。 137 ② 情况说明:被插入的节点的父节点是黑色。 138 处理方法:什么也不需要做。节点被插入后,仍然是红黑树。 139 ③ 情况说明:被插入的节点的父节点是红色。 140 处理方法:那么,该情况与红黑树的“特性(5)”相冲突。这种情况下,被插入节点是一定存在非空祖父节点的;进一步的讲,被插入节点也一定存在叔叔节点(即使叔叔节点为空,我们也视之为存在,空节点本身就是黑色节点)。理解这点之后,我们依据"叔叔节点的情况",将这种情况进一步划分为3种情况(Case)。 141 现象说明 处理策略 142 Case 1 当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。 (01) 将“父节点”设为黑色。 143 (02) 将“叔叔节点”设为黑色。 144 (03) 将“祖父节点”设为“红色”。 145 (04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。 146 Case 2 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子 (01) 将“父节点”作为“新的当前节点”。 (02) 以“新的当前节点”为支点进行左旋。 147 Case 3 当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子 (01) 将“父节点”设为“黑色”。 148 (02) 将“祖父节点”设为“红色”。 149 (03) 以“祖父节点”为支点进行右旋。 150 上面三种情况(Case)处理问题的核心思路都是:将红色的节点移到根节点;然后,将根节点设为黑色。下面对它们详细进行介绍。 151 1. (Case 1)叔叔是红色 152 1.1 现象说明 153 当前节点(即,被插入节点)的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。 154 1.2 处理策略 155 (01) 将“父节点”设为黑色。 156 (02) 将“叔叔节点”设为黑色。 157 (03) 将“祖父节点”设为“红色”。 158 (04) 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。 159 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 160 “当前节点”和“父节点”都是红色,违背“特性(4)”。所以,将“父节点”设置“黑色”以解决这个问题。 161 但是,将“父节点”由“红色”变成“黑色”之后,违背了“特性(5)”:因为,包含“父节点”的分支的黑色节点的总数增加了1。 解决这个问题的办法是:将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”。关于这里,说明几点:第一,为什么“祖父节点”之前是黑色?这个应该很容易想明白,因为在变换操作之前,该树是红黑树,“父节点”是红色,那么“祖父节点”一定是黑色。 第二,为什么将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;能解决“包含‘父节点’的分支的黑色节点的总数增加了1”的问题。这个道理也很简单。“包含‘父节点’的分支的黑色节点的总数增加了1” 同时也意味着 “包含‘祖父节点’的分支的黑色节点的总数增加了1”,既然这样,我们通过将“祖父节点”由“黑色”变成“红色”以解决“包含‘祖父节点’的分支的黑色节点的总数增加了1”的问题; 但是,这样处理之后又会引起另一个问题“包含‘叔叔’节点的分支的黑色节点的总数减少了1”,现在我们已知“叔叔节点”是“红色”,将“叔叔节点”设为“黑色”就能解决这个问题。 所以,将“祖父节点”由“黑色”变成红色,同时,将“叔叔节点”由“红色”变成“黑色”;就解决了该问题。 162 按照上面的步骤处理之后:当前节点、父节点、叔叔节点之间都不会违背红黑树特性,但祖父节点却不一定。若此时,祖父节点是根节点,直接将祖父节点设为“黑色”,那就完全解决这个问题了;若祖父节点不是根节点,那我们需要将“祖父节点”设为“新的当前节点”,接着对“新的当前节点”进行分析。 163 1.3 示意图 164 165 2. (Case 2)叔叔是黑色,且当前节点是右孩子 166 2.1 现象说明 167 当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子 168 2.2 处理策略 169 (01) 将“父节点”作为“新的当前节点”。 170 (02) 以“新的当前节点”为支点进行左旋。 171 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 172 首先,将“父节点”作为“新的当前节点”;接着,以“新的当前节点”为支点进行左旋。 为了便于理解,我们先说明第(02)步,再说明第(01)步;为了便于说明,我们设置“父节点”的代号为F(Father),“当前节点”的代号为S(Son)。 173 为什么要“以F为支点进行左旋”呢?根据已知条件可知:S是F的右孩子。而之前我们说过,我们处理红黑树的核心思想:将红色的节点移到根节点;然后,将根节点设为黑色。既然是“将红色的节点移到根节点”,那就是说要不断的将破坏红黑树特性的红色节点上移(即向根方向移动)。 而S又是一个右孩子,因此,我们可以通过“左旋”来将S上移! 174 按照上面的步骤(以F为支点进行左旋)处理之后:若S变成了根节点,那么直接将其设为“黑色”,就完全解决问题了;若S不是根节点,那我们需要执行步骤(01),即“将F设为‘新的当前节点’”。那为什么不继续以S为新的当前节点继续处理,而需要以F为新的当前节点来进行处理呢?这是因为“左旋”之后,F变成了S的“子节点”,即S变成了F的父节点;而我们处理问题的时候,需要从下至上(由叶到根)方向进行处理;也就是说,必须先解决“孩子”的问题,再解决“父亲”的问题;所以,我们执行步骤(01):将“父节点”作为“新的当前节点”。 175 2.2 示意图 176 177 3. (Case 3)叔叔是黑色,且当前节点是左孩子 178 3.1 现象说明 179 当前节点(即,被插入节点)的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子 180 3.2 处理策略 181 (01) 将“父节点”设为“黑色”。 182 (02) 将“祖父节点”设为“红色”。 183 (03) 以“祖父节点”为支点进行右旋。 184 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 185 为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“叔叔节点”为U(Uncle),“父节点”为F(Father),祖父节点为G(Grand-Father)。 186 S和F都是红色,违背了红黑树的“特性(4)”,我们可以将F由“红色”变为“黑色”,就解决了“违背‘特性(4)’”的问题;但却引起了其它问题:违背特性(5),因为将F由红色改为黑色之后,所有经过F的分支的黑色节点的个数增加了1。那我们如何解决“所有经过F的分支的黑色节点的个数增加了1”的问题呢? 我们可以通过“将G由黑色变成红色”,同时“以G为支点进行右旋”来解决。 187 2.3 示意图 188 189 提示:上面的进行Case 3处理之后,再将节点"120"当作当前节点,就变成了Case 2的情况。 190 红黑树的基本操作(三) 删除 191 将红黑树内的某一个节点删除。需要执行的操作依次是:首先,将红黑树当作一颗二叉查找树,将该节点从二叉查找树中删除;然后,通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。详细描述如下: 192 第一步:将红黑树当作一颗二叉查找树,将节点删除。 193 这和"删除常规二叉查找树中删除节点的方法是一样的"。分3种情况: 194 ① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。 195 ② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。 196 ③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。 197 第二步:通过"旋转和重新着色"等一系列来修正该树,使之重新成为一棵红黑树。 198 因为"第一步"中删除节点之后,可能会违背红黑树的特性。所以需要通过"旋转和重新着色"来修正该树,使之重新成为一棵红黑树。 199 删除操作的伪代码《算法导论》 200 RB-DELETE(T, z) 201 01 if left[z] = nil[T] or right[z] = nil[T] 202 02 then y ← z // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”; 203 03 else y ← TREE-SUCCESSOR(z) // 否则,将“z的后继节点”赋值给 “y”。 204 04 if left[y] ≠ nil[T] 205 05 then x ← left[y] // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”; 206 06 else x ← right[y] // 否则,“y的右孩子” 赋值给 “x”。 207 07 p[x] ← p[y] // 将“y的父节点” 设置为 “x的父节点” 208 08 if p[y] = nil[T] 209 09 then root[T] ← x // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。 210 10 else if y = left[p[y]] 211 11 then left[p[y]] ← x // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子” 212 12 else right[p[y]] ← x // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子” 213 13 if y ≠ z 214 14 then key[z] ← key[y] // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!! 215 15 copy y's satellite data into z 216 16 if color[y] = BLACK 217 17 then RB-DELETE-FIXUP(T, x) // 若“y为黑节点”,则调用 218 18 return y 219 结合伪代码以及为代码上面的说明,先理解RB-DELETE。理解了RB-DELETE之后,接着对 RB-DELETE-FIXUP的伪代码进行说明 220 RB-DELETE-FIXUP(T, x) 221 01 while x ≠ root[T] and color[x] = BLACK 222 02 do if x = left[p[x]] 223 03 then w ← right[p[x]] // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子) 224 04 if color[w] = RED // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。 225 05 then color[w] ← BLACK ▹ Case 1 // (01) 将x的兄弟节点设为“黑色”。 226 06 color[p[x]] ← RED ▹ Case 1 // (02) 将x的父节点设为“红色”。 227 07 LEFT-ROTATE(T, p[x]) ▹ Case 1 // (03) 对x的父节点进行左旋。 228 08 w ← right[p[x]] ▹ Case 1 // (04) 左旋后,重新设置x的兄弟节点。 229 09 if color[left[w]] = BLACK and color[right[w]] = BLACK // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。 230 10 then color[w] ← RED ▹ Case 2 // (01) 将x的兄弟节点设为“红色”。 231 11 x ← p[x] ▹ Case 2 // (02) 设置“x的父节点”为“新的x节点”。 232 12 else if color[right[w]] = BLACK // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。 233 13 then color[left[w]] ← BLACK ▹ Case 3 // (01) 将x兄弟节点的左孩子设为“黑色”。 234 14 color[w] ← RED ▹ Case 3 // (02) 将x兄弟节点设为“红色”。 235 15 RIGHT-ROTATE(T, w) ▹ Case 3 // (03) 对x的兄弟节点进行右旋。 236 16 w ← right[p[x]] ▹ Case 3 // (04) 右旋后,重新设置x的兄弟节点。 237 17 color[w] ← color[p[x]] ▹ Case 4 // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。 238 18 color[p[x]] ← BLACK ▹ Case 4 // (02) 将x父节点设为“黑色”。 239 19 color[right[w]] ← BLACK ▹ Case 4 // (03) 将x兄弟节点的右子节设为“黑色”。 240 20 LEFT-ROTATE(T, p[x]) ▹ Case 4 // (04) 对x的父节点进行左旋。 241 21 x ← root[T] ▹ Case 4 // (05) 设置“x”为“根节点”。 242 22 else (same as then clause with "right" and "left" exchanged) // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。 243 23 color[x] ← BLACK 244 下面对删除函数进行分析。在分析之前,我们再次温习一下红黑树的几个特性: 245 (1) 每个节点或者是黑色,或者是红色。 246 (2) 根节点是黑色。 247 (3) 每个叶子节点是黑色。 [注意:这里叶子节点,是指为空的叶子节点!] 248 (4) 如果一个节点是红色的,则它的子节点必须是黑色的。 249 (5) 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 250 前面我们将"删除红黑树中的节点"大致分为两步,在第一步中"将红黑树当作一颗二叉查找树,将节点删除"后,可能违反"特性(2)、(4)、(5)"三个特性。第二步需要解决上面的三个问题,进而保持红黑树的全部特性。 251 为了便于分析,我们假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。为什么呢? 252 通过RB-DELETE算法,我们知道:删除节点y之后,x占据了原来节点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设"x包含一个额外的黑色",就正好弥补了"删除y所丢失的黑色节点",也就不会违反"特性(5)"。 因此,假设"x包含一个额外的黑色"(x原本的颜色还存在),这样就不会违反"特性(5)"。 253 现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是"红+黑"或"黑+黑",它违反了"特性(1)"。 254 现在,我们面临的问题,由解决"违反了特性(2)、(4)、(5)三个特性"转换成了"解决违反特性(1)、(2)、(4)三个特性"。RB-DELETE-FIXUP需要做的就是通过算法恢复红黑树的特性(1)、(2)、(4)。RB-DELETE-FIXUP的思想是:将x所包含的额外的黑色不断沿树上移(向根方向移动),直到出现下面的姿态: 255 a) x指向一个"红+黑"节点。此时,将x设为一个"黑"节点即可。 256 b) x指向根。此时,将x设为一个"黑"节点即可。 257 c) 非前面两种姿态。 258 将上面的姿态,可以概括为3种情况。 259 ① 情况说明:x是“红+黑”节点。 260 处理方法:直接把x设为黑色,结束。此时红黑树性质全部恢复。 261 ② 情况说明:x是“黑+黑”节点,且x是根。 262 处理方法:什么都不做,结束。此时红黑树性质全部恢复。 263 ③ 情况说明:x是“黑+黑”节点,且x不是根。 264 处理方法:这种情况又可以划分为4种子情况。这4种子情况如下表所示: 265 现象说明 处理策略 266 Case 1 x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。 (01) 将x的兄弟节点设为“黑色”。 267 (02) 将x的父节点设为“红色”。 268 (03) 对x的父节点进行左旋。 269 (04) 左旋后,重新设置x的兄弟节点。 270 Case 2 x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。 (01) 将x的兄弟节点设为“红色”。 (02) 设置“x的父节点”为“新的x节点”。 271 Case 3 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。 (01) 将x兄弟节点的左孩子设为“黑色”。 272 (02) 将x兄弟节点设为“红色”。 273 (03) 对x的兄弟节点进行右旋。 274 (04) 右旋后,重新设置x的兄弟节点。 275 Case 4 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。 (01) 将x父节点颜色 赋值给 x的兄弟节点。 276 (02) 将x父节点设为“黑色”。 277 (03) 将x兄弟节点的右子节设为“黑色”。 278 (04) 对x的父节点进行左旋。 279 (05) 设置“x”为“根节点”。 280 1. (Case 1)x是"黑+黑"节点,x的兄弟节点是红色 281 1.1 现象说明 282 x是"黑+黑"节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。 283 1.2 处理策略 284 (01) 将x的兄弟节点设为“黑色”。 285 (02) 将x的父节点设为“红色”。 286 (03) 对x的父节点进行左旋。 287 (04) 左旋后,重新设置x的兄弟节点。 288 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 289 这样做的目的是将“Case 1”转换为“Case 2”、“Case 3”或“Case 4”,从而进行进一步的处理。对x的父节点进行左旋;左旋后,为了保持红黑树特性,就需要在左旋前“将x的兄弟节点设为黑色”,同时“将x的父节点设为红色”;左旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。 290 1.3 示意图 291 292 2. (Case 2) x是"黑+黑"节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色 293 2.1 现象说明 294 x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。 295 2.2 处理策略 296 (01) 将x的兄弟节点设为“红色”。 297 (02) 设置“x的父节点”为“新的x节点”。 298 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 299 这个情况的处理思想:是将“x中多余的一个黑色属性上移(往根方向移动)”。 x是“黑+黑”节点,我们将x由“黑+黑”节点 变成 “黑”节点,多余的一个“黑”属性移到x的父节点中,即x的父节点多出了一个黑属性(若x的父节点原先是“黑”,则此时变成了“黑+黑”;若x的父节点原先时“红”,则此时变成了“红+黑”)。 此时,需要注意的是:所有经过x的分支中黑节点个数没变化;但是,所有经过x的兄弟节点的分支中黑色节点的个数增加了1(因为x的父节点多了一个黑色属性)!为了解决这个问题,我们需要将“所有经过x的兄弟节点的分支中黑色节点的个数减1”即可,那么就可以通过“将x的兄弟节点由黑色变成红色”来实现。 300 经过上面的步骤(将x的兄弟节点设为红色),多余的一个颜色属性(黑色)已经跑到x的父节点中。我们需要将x的父节点设为“新的x节点”进行处理。若“新的x节点”是“黑+红”,直接将“新的x节点”设为黑色,即可完全解决该问题;若“新的x节点”是“黑+黑”,则需要对“新的x节点”进行进一步处理。 301 2.3 示意图 302 303 3. (Case 3)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的 304 3.1 现象说明 305 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。 306 3.2 处理策略 307 (01) 将x兄弟节点的左孩子设为“黑色”。 308 (02) 将x兄弟节点设为“红色”。 309 (03) 对x的兄弟节点进行右旋。 310 (04) 右旋后,重新设置x的兄弟节点。 311 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 312 我们处理“Case 3”的目的是为了将“Case 3”进行转换,转换成“Case 4”,从而进行进一步的处理。转换的方式是对x的兄弟节点进行右旋;为了保证右旋后,它仍然是红黑树,就需要在右旋前“将x的兄弟节点的左孩子设为黑色”,同时“将x的兄弟节点设为红色”;右旋后,由于x的兄弟节点发生了变化,需要更新x的兄弟节点,从而进行后续处理。 313 3.3 示意图 314 315 4. (Case 4)x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色 316 4.1 现象说明 317 x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的,x的兄弟节点的左孩子任意颜色。 318 4.2 处理策略 319 (01) 将x父节点颜色 赋值给 x的兄弟节点。 320 (02) 将x父节点设为“黑色”。 321 (03) 将x兄弟节点的右子节设为“黑色”。 322 (04) 对x的父节点进行左旋。 323 (05) 设置“x”为“根节点”。 324 下面谈谈为什么要这样处理。 (建议理解的时候,通过下面的图进行对比) 325 我们处理“Case 4”的目的是:去掉x中额外的黑色,将x变成单独的黑色。处理的方式是“:进行颜色修改,然后对x的父节点进行左旋。下面,我们来分析是如何实现的。 326 为了便于说明,我们设置“当前节点”为S(Original Son),“兄弟节点”为B(Brother),“兄弟节点的左孩子”为BLS(Brother's Left Son),“兄弟节点的右孩子”为BRS(Brother's Right Son),“父节点”为F(Father)。 327 我们要对F进行左旋。但在左旋前,我们需要调换F和B的颜色,并设置BRS为黑色。为什么需要这里处理呢?因为左旋后,F和BLS是父子关系,而我们已知BL是红色,如果F是红色,则违背了“特性(4)”;为了解决这一问题,我们将“F设置为黑色”。 但是,F设置为黑色之后,为了保证满足“特性(5)”,即为了保证左旋之后: 328 第一,“同时经过根节点和S的分支的黑色节点个数不变”。 329 若满足“第一”,只需要S丢弃它多余的颜色即可。因为S的颜色是“黑+黑”,而左旋后“同时经过根节点和S的分支的黑色节点个数”增加了1;现在,只需将S由“黑+黑”变成单独的“黑”节点,即可满足“第一”。 330 第二,“同时经过根节点和BLS的分支的黑色节点数不变”。 331 若满足“第二”,只需要将“F的原始颜色”赋值给B即可。之前,我们已经将“F设置为黑色”(即,将B的颜色"黑色",赋值给了F)。至此,我们算是调换了F和B的颜色。 332 第三,“同时经过根节点和BRS的分支的黑色节点数不变”。 333 在“第二”已经满足的情况下,若要满足“第三”,只需要将BRS设置为“黑色”即可。 334 经过,上面的处理之后。红黑树的特性全部得到的满足!接着,我们将x设为根节点,就可以跳出while循环(参考伪代码);即完成了全部处理。 335 至此,我们就完成了Case 4的处理。理解Case 4的核心,是了解如何“去掉当前节点额外的黑色”。 336 4.3 示意图 337 338 OK!至此,红黑树的理论知识差不多讲完了。后续再更新红黑树的实现代码!
6、高h的avl树最少多少节点?
h(1) = 1 h(2) = 2 h(n) = h(i- 1) + h(i - 2) + 1
求rbt比某个元素小的有多少个
高h的rbt最少多少个节点 分h为奇数还是偶数
rbt相比avl树的优点
7、为什么hasetable的桶数是一个素数
假设桶数为一个合数 m = x * y
在最坏的情况下 我所有想要hash的数都是x 的倍数 n = A * x
那么 n % m = (A * x) % (x * y) = (A * x) - B*(x * y) = (A - B*y) * x
所以取得的结果不是随机的 而是都是x的倍数 hash成这样利用率 和效率受损 而且开的小了的话很危险的
8、求日访问量最多的ip
1、以前10位分桶 求每桶的最大值 然后整体来求
2、如果重复率特别高 出去重复的 内存可以很轻松的装下 可以用字典树 每个叶子节点记录个数
9、一堆串 求醉热门十个
1.字典树
2.hashe_table
3.求topk 堆 patation
10、有十个文件 每个1G 按词频排序
1。由于每个文件都很大 所以 将每个单词 hash成一个数字 然后取余 10 重新分到十个文件中 这样一来 相同的词一定在同一个文件中
2.对每个文件hash_table 求个数 然后 找个两个G的处理机 排序 排序方法 归并 快排 堆排 怎么排都行
3.按照归并排序的思想对这十个文件进行整体排序
11、40亿个整数 问某个数有没有出现过
1.bitmat
2.分块
3.布隆过滤器
12、2.5亿个整数 找出不重复的数字
1。数字集中 直接统计个数
2.分散的话 bit_map 两个字节表示一个数 第一位是是否出现过 第二位 是否是一个 还可以分桶
13、1-n中1出现的次数
abcde 拿c位来说
若c == 0 c位上出现1的次数 (ab - 1) * 100
若c==1 (ab - 1) * 100 + de
若c>1 (ab+1) * 100
14、45123中最小的数字
二分
15、将一个链表逆序
1 #include <iostream> 2 using namespace std; 3 4 struct Node { 5 int data; 6 Node *next; 7 Node() { 8 data = 0; 9 next = NULL: 10 } 11 }; 12 13 Node * Add(Node *head1, Node *head2) { 14 Node *head = NULL; 15 if(head1 == NULL) { 16 return head2; 17 } 18 if(head2 == NULL) { 19 return head1; 20 } 21 if(head1 -> data < head2 -> data) { 22 head = head1; 23 head -> next = Add(head1->next, head2); 24 } else { 25 head = head2; 26 head -> next = Add(head1, head2 -> next); 27 } 28 return head; 29 }
16、找链表中间节点
17、判断两个链表是否相交 判断最后一个节点就可以了
18、判断一个链表是否有环 判断入口
两个指针 一个走1 一个走2 相交一定有环
19、删除链表某个节点
20、在p之前插入某个节点
21、删除倒数第k个节点
23、二叉搜索树转双向链表
1 #include <iostream> 2 using namespace std; 3 4 template<class T> 5 void Change(Node<T> *root, Node<T> *&last) { 6 if(root == NULL) return ; 7 Change(root -> left, last); 8 root -> left = last; 9 if(last != NULL) { 10 last -> right = root; 11 } 12 last = root; 13 Change(root -> right, last); 14 }
24、手写全排列
1 #include <iostream> 2 using namespace std; 3 4 void dfs(int a[], int now, int n) { 5 if(now >= n) { 6 for(int i = 0; i <= now; i++) { 7 printf("%d ", a[i]); 8 } puts(""); 9 return ; 10 } 11 for(int i = 0; i <= now; i++) { 12 int b[maxn]; 13 Insert(a, i, b); 14 dfs(b, now + 1, n); 15 } 16 } 17 18 void dfs2(int a[], int left, int righ, int n) { 19 if(right <= left) { 20 for(int i = 0; i < n; i++) { 21 printf("%d ", a[i]); 22 } puts(""); 23 return ; 24 } 25 for(int i = left; i <= right; i++) { 26 swap(a[i], a[right]); 27 dfs2(a, left, right - 1, n); 28 } 29 }
25、求最近公共祖先
离线tarjan
在线rmq优化
26、筛素数
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 7 const int maxn = 105; 8 int flag[maxn], prm[maxn]; 9 10 int get(int n) { 11 int k = 0; 12 memset(flag, 1, sizeof(flag)); 13 for(int i = 2; i <= n; i++) { 14 if(flag[i]) { 15 prm[k++] = i; 16 } 17 for(int j = 0; j < k && prm[j] * i <= n; j++) { 18 flag[prm[j] * i] = 0; 19 if(i % prm[j] == 0) 20 break; 21 } 22 } 23 return k; 24 } 25 26 int main() { 27 int k = get(100); 28 for(int i = 0; i < k; i++) { 29 printf("%d ", prm[i]); 30 } puts(""); 31 }
27、x & (x - 1) 二进制最后一个1抹去
28、位运算实现四则运算
29、 2队列实现栈 1队列实现栈 相反
30、设计一个有min功能的栈
31、求超过一半的数字
1.patation
2.对于一个数字 相同 ++ 不同-- 0的时候另选数字
32、判断两个字符串有没有不同的小写字母
二进制
33、最大子序列和
34、设计一个能求中位数的数据结构 两个堆
35、对于10G个数 求中位数 桶
36、n个数相邻实轴差的最大值 M>=(max - min) / n
37、判断一棵树是否为平衡二叉树
1 #include <iostream> 2 using namespace std; 3 4 void get_H(Node *root) { 5 if(root == NULL) { 6 root -> high = 0; 7 return ; 8 } 9 get_H(root -> left); 10 get_H(root -> right); 11 root-> high = max(root->left->high, root -> right -> high) + 1; 12 } 13 14 bool is_B(Node *root) { 15 if(root == NULL) { 16 return true; 17 } 18 if(fabs(root -> left -> high - root -> right -> high) <= 1) { 19 return is_B(root -> left) && is_B(root -> right); 20 } else { 21 return false; 22 } 23 }
38、除了两个数之外其余数都出现了两次 求这两个数
^ 然后选1的 再分成两类 分别^
39、输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
二分或者前后往中间压缩 得到必然是成绩最小的 不用证明
40、求和为S的连续数列有多少种
ans == s j++
ans < s j++
ans > s i++
40、N!末尾有多少0
N/5+N/25+n/125……
41、最长子序列 最大子矩阵
int i = 10
printf("%d %d %d",i++,++i,++i);
12 12 11
42 、
char *p[] = {"AAA","BBB","CCC","DDD"}
char **cp[] = {cp + 3, cp + 2, cp + 1, cp}
char ***cpp = cp;
**++cpp ++cpp cpp 指向了cp数组的下一个元素也就是cp + 2 然后解引用 及cp+2 的地址 再解引用 就是cp+2实际内容 CCC
*--*++cpp ++ cpp又指向了下一个元素 解 cp + 1 -- cp中第‘2’个元素变成了cp 又解 *cp 也就是AAA
*cpp[-2] 中括号也就是 **(cpp -2)
cpp[-1][-1] + 1 也就是*((*(cpp - 1) - 1) + 1) EW
43、判断一棵树是否为平衡二叉树
1 #include <iostream> 2 using namespace std; 3 4 int GD(Node *node) { 5 if(!node) return 0; 6 return max(GD(node->left), GD(node->right)) + 1; 7 } 8 9 bool ch(Node *node) { 10 if(!node) return true; 11 return abs(GD(node->left) - GD(node->right)) <= 1 && ch(node -> left) && ch(node->rtight); 12 }
44、求最长公共子串
if(s[i] == t[j]) dp[i][j] = dpi - 1][j - 1] + 1
else dp[i][j] = 0
求最长公共子序列
dp[i][j] = dp[i - 1][j - 1]
if(s[i] == t[j]) dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1);
dp[i][j] = max(dp[i][j], dp[i - 1][j]);
dp[i][j] = max(dp[i][j], dp[i][j - 1])
编辑距离:
if(s[i] == t[j]) dp[i][j] = dp[i - 1][j - 1]
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + 1
1