笔试算法题(03):最小第K个数 & 判定BST后序序列
出题:输入N个整数,要求输出其中最小的K个数;
分析:
- 快速排序和最小堆都可以解决最小(大)K个数的问题(时间复杂度为O(NlogN));另外可以建立大小为K的最大堆,将前K个数不断插入最大堆,对于之后的N-K个数,依次与堆顶元素进行比较,如果新元素更小则删除当前堆顶元素,并更新最大堆;如果新元素大则跳过;
- 第一种实现:快速排序框架,插入排序处理小子文件,随机函数选取split点,双向扫描;
- 第二种实现:最小堆框架,基于fixUp的堆构建策略(数组整体更新,bottom up),通过删除前K个堆顶元素获取最小K个数;
- 第三种实现:最大堆框架,基于fixDown的堆构建策略(0起点插入式,top down),首先构建K个数的最大堆,然后新元素与堆顶元素进行比较,小于则替换现有堆顶元素,更新最大堆;
解题:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <malloc.h> 4 #include <string.h> 5 #include <time.h> 6 7 void ShowMinK(int *array, int k) { 8 printf("\nMin k: \n"); 9 for(int m=0; m<=k;m++) { 10 printf("%d, ",array[m]); 11 } 12 } 13 14 void merge(int *array, int i, int split, int j) { 15 /** 16 * 此版本采用的是双向遍历,left和right指针同时从左右向中间遍历 17 * 其他还有单向遍历和三相遍历,此处不作解释 18 * */ 19 /** 20 * 首先将split元素交换到j作为参考元素 21 * */ 22 int temp=array[split]; 23 array[split]=array[j]; 24 array[j]=temp; 25 26 int left=i; int right=j-1; 27 28 while(true) { 29 /** 30 * 两个指针同时相向遍历,当指针相遇(交错)或者满足与array[j] 31 * 的特定关系,则暂时停止。 32 * */ 33 while(left<right && array[left]<=array[j]) { 34 left++; 35 } 36 while(right>left && array[right]>array[j]) { 37 right--; 38 } 39 40 if(left>=right) break; 41 42 temp=array[left]; 43 array[left]=array[right]; 44 array[right]=temp; 45 left++; 46 right--; 47 } 48 /** 49 * 将split元素从j位置交换回分界位置 50 * */ 51 temp=array[left]; 52 array[left]=array[j]; 53 array[j]=temp; 54 } 55 56 void InsertSort(int *array, int i, int j) { 57 /** 58 * 此版本仅使用最基本的插入排序思想,左边的序列都已经排好序 59 * 每一次循环都是讲array[m]插入左边序列 60 * 其他版本可以将找到最大值并放到最左边,这样可以省去每次 61 * 循环中对左边范围的检查 62 * */ 63 int temp,k; 64 for(int m=i+1;m<=j;m++) { 65 temp=array[m]; 66 k=m-1; 67 while(k>=i) { 68 if(array[k]>temp) { 69 /** 70 * 当array[k]大于temp,则将其向前复制 71 * */ 72 array[k+1]=array[k]; 73 } else { 74 /** 75 * 当array[k]小于等于temp,则最终将m插入 76 * array[k+1],此次插入结束 77 * */ 78 break; 79 } 80 k--; 81 } 82 /** 83 * 注意不同的跳出条件,是从break跳出的还是k>=i跳出的 84 * */ 85 array[k+1]=temp; 86 } 87 } 88 89 void MinKQuickSort(int *array, int i, int j, int k) { 90 /** 91 * 当数组长度小于7的时候,直接的插入排序将会优于更小的子递归 92 * */ 93 94 if(j-i+1 < 7) { 95 InsertSort(array, i, j); 96 ShowMinK(array, k); 97 return; 98 } 99 100 /** 101 * 使用随机函数rand()选择split的策略,避免最坏情况 102 * time()或者getpid()可以作为srand函数的参数 103 * */ 104 if(i>j) return; 105 if(i==j && i==k){ 106 ShowMinK(array,k); 107 return; 108 } 109 srand((int)time(0)); 110 int split=rand()%(j-i+1) + i; 111 merge(array, i, split, j); 112 113 if(split > k) { 114 /** 115 * 当前的split大于k,所以前k小的数应该在左边部分,k已经是相对i 116 * 的计数,所以需要减去i 117 * */ 118 MinKQuickSort(array, i, split-1, k); 119 } else if(split < k) { 120 /** 121 * 122 * 当前的split小于k,所以前k小 的数应该在右边部分,k是相对i的 123 * 计数,现在需要相对split+1,所以需要更改 124 * */ 125 MinKQuickSort(array, split+1, j, k); 126 } else if(split == k) { 127 ShowMinK(array, k); 128 } 129 } 130 /** 131 * 132 * */ 133 void MinFixUp(int *array, int length, int i) { 134 int start=i; 135 int temp,father; 136 while(start>1 && array[start]<array[start/2]) { 137 138 temp=array[father]; 139 array[father]=array[start]; 140 array[start]=temp; 141 start/=2;; 142 } 143 } 144 145 void MinFixDown(int *array, int length, int i) { 146 int s; 147 int temp, father; 148 while(2*i<=length) { 149 s=2*i; 150 if(s+1<=length && array[s] > array[s+1]) { 151 s++; 152 } 153 if(array[i]<=array[s]) break; 154 155 temp=array[i]; 156 array[i]=array[s]; 157 array[s]=temp; 158 i=s; 159 } 160 } 161 162 /** 163 * 从最后一个拥有子节点的内部节点开始逆向遍历到根节点 164 * */ 165 void BuildMinHeap(int *array, int length) { 166 167 for(int i=length/2;i>=1;i=i-1) { 168 MinFixDown(array, length, i); 169 } 170 } 171 172 /** 173 * 从array[1]开始存储元素,从而简化代码 174 * 首先构建最小堆,然后删除堆顶元素,缩小堆大小 175 * 更新堆结构,重复K次 176 * */ 177 void MinKMiniHeap(int *array, int length, int k) { 178 BuildMinHeap(array, length); 179 int capability=length; 180 for(int i=1;i<=k;i++) { 181 printf("%d, ",array[1]); 182 array[1]=array[capability]; 183 capability--; 184 MinFixDown(array, capability, 1); 185 } 186 } 187 188 void MaxFixUp(int *array, int length, int i) {} 189 190 void MaxFixDown(int *array, int length, int i) {} 191 192 void BuildMaxHeap(int *array, int k) {} 193 194 void MinKMaxHeap(int *array, int length, int k) { 195 BuildMaxHeap(array, k); 196 for(int i=k+1;i<=length;i++) { 197 if(array[1]>array[i]) { 198 array[1]=array[i]; 199 MaxFixDown(array, k, 1); 200 } 201 } 202 203 int capability=k; 204 for(int i=1;i<=k;i++) { 205 printf("%d, ",array[1]); 206 array[1]=array[capability]; 207 capability--; 208 MaxFixDown(array, capability, 1); 209 } 210 211 }
出题:输入一个整数数组,判断该数组是否给定二元查找树(Binary Search Tree)的部分后序(Post Order)遍历结果(给定二元查找树,判定整数数组是否在其中);
分析:使用int *position作为对比位置记录
解题:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 struct Node { 2 int value; 3 Node *left; 4 Node *right; 5 }; 6 /** 7 * Binary Search Tree可能不是满树,所以有右子树不一定就有左子树 8 * 仅当子树中完全找到整个序列才返回true,否则返回false,使用position 9 * 记录当前的比较位置 10 * */ 11 bool PostOrderCompare(Node *cur, int *array, int length, int *position) { 12 bool isLeft=false, isRight=false; 13 if(cur->left != NULL) { 14 /** 15 * 左子树存在时候的后序遍历 16 * */ 17 isLeft=PostOrderCompare(cur->left, array, length, position); 18 if(!isLeft && cur->right != NULL) { 19 isRight=PostOrderCompare(cur->right, array, length, position); 20 } 21 } else if(cur->right != NULL) { 22 /** 23 * 仅右子树存在时候的后序遍历 24 * */ 25 isRight=PostOrderCompare(cur->right, array, length, position); 26 } 27 28 if(isLeft || isRight) return true; 29 /** 30 * 左右子树都还没有满足条件,处理当前节点 31 * */ 32 printf("\ncurrent node: %d\n", cur->value); 33 printf("\ncurrent position value: %d\n", array[*position]); 34 if(cur->value == array[*position]) { 35 if(*position +1 == length) return true; 36 else { 37 /** 38 * 仅当当前值匹配的时候position才更新, 否则直接返回 39 * */ 40 *position = *position+1; 41 } 42 } 43 return false; 44 }