2013.5.12搜狐实习生笔试题(编码题部分)
1.利用堆排序对数组进行排序。
堆结构是把数组看成完全二叉树,除最后一层,每一层都是满的。堆分最大堆和最小堆,其中最大堆是指每一个子树中,根要比其两个孩子的值大,最小堆反之;数组元素升序需要用最大堆,降序用最小堆;这里使用最大堆。
实现堆排序需要三个主要函数:建立最大堆,维持最大堆性质,和堆排序函数。
建立最大堆(BuildMaxHeap):自底向上地维持最大堆性质,heapsize/2为最底下一层的父结点,即需要从结点下标为heapsize/2 ~ 0的结点调用维持最大堆性质的函数(MaxHeapify)。
维持最大堆性质(MaxHeapify):将堆中某个元素调整到合适的位置(下沉);使之满足最大堆性质。
堆排序函数(HeapSort):将堆顶(该堆的最大值)和堆底交换,并将heapsize减1,再调用函数使目前的堆维持最大堆性质。
code:
1 void Swap(int *A, int i, int j) 2 { 3 int tmp = A[i]; 4 A[i] = A[j]; 5 A[j] = tmp; 6 } 7 //维持最大堆性质 8 void MaxHeapify(int *A, int i, int heapsize) 9 { 10 //递归版本 11 //int nLeft = 2 * i + 1; 12 //int nRight = 2 * i + 2; 13 //int nLargrIndex; 14 //if (A[nLeft] < A[nRight]) 15 //{ 16 // nLargrIndex = nRight; 17 //} 18 //else 19 //{ 20 // nLargrIndex = nLeft; 21 //} 22 //if (A[i] < A[nLargrIndex]) 23 //{ 24 // //swap(A, i , nLargeIndex) 25 // Swap(A, i, nLargrIndex); 26 // MaxHeapify(A, nLargrIndex, heapsize); 27 //} 28 //非递归版本 29 int child; int tmp; 30 for (tmp = A[i]; heapsize > 2 * i + 1; i = child) 31 { 32 child = 2 * i + 1; 33 //记录孩子中较大者下标 34 if (child != heapsize - 1 && A[child + 1] > A[child]) 35 { 36 child++; 37 } 38 //若当前的父亲比孩子小,则交换 39 if (tmp < A[child]) 40 { 41 A[i] = A[child]; 42 } 43 else 44 break; 45 } 46 A[i] = tmp;//把原父节点放到合适的位置上 47 } 48 49 void BuildMaxHeap(int *A, int heapsize) 50 { 51 //从堆底的父节点开始向上建立堆,就是让每一个子堆具有最大堆性质,自底向上的 52 for(int i = heapsize / 2; i >= 0; i--) 53 { 54 MaxHeapify(A, i, heapsize); 55 } 56 } 57 58 void HeapSort(int *A, int heapsize) 59 { 60 BuildMaxHeap(A, heapsize); 61 for (int i = heapsize - 1; i > 0; i--) 62 { 63 //交换堆顶和堆最后一个元素,堆个数减1 64 Swap(A, 0, i); 65 heapsize--; 66 //保持最大堆性质 67 MaxHeapify(A, 0, heapsize); 68 } 69 70 }
2.已知rand7()生成1~7的随机数,写出rand10()。
思想:利用进制的方法生成进制位上的元素,选择输出指定的范围。ps:直接扩充的方法产生的每个数概率不相等,中间的数概率会高些。
code:
1 //产生范围1~10 2 int rand10() 3 { 4 int result; 5 while (1) 6 { //利用7进制产生result,其范围是0-48 7 result = 7 * (rand7() - 1) + rand7() - 1; 8 //若范围在1~10,则break并返回 9 if (result > 0 && result < 11) 10 { 11 break; 12 } 13 } 14 return result; 15 16 }
3.设计算法,实现一个对数组左移k个元素的函数,要求时间复杂度为O(n)。
此题在Programming Pearls Column2有相应介绍。主要思想是利用等式(arbr)r = ba,r表示reverse,例如序列{1,2,3,4,5,6,7},现在数组左移3位得到的结果为{4,5,6,7,1,2,3};利用前面介绍的等式,把原序列中的前3位,和剩下一部分分别反转,于是得到{3,2,1,7,6,5,4},再整体反转一次得到{4,5,6,7,1,2,3}即为最终结果。反转的时间复杂度为O(n),故满足题意,而且不需要额外的内存空间。
code:
1 //对数组A 指定范围反转元素 2 template <class T> 3 void Reverse(T *A, int nStart, int nEnd) 4 { 5 int i; 6 for (i = nStart; i < nStart + (1 + nEnd - nStart) / 2; ++i) 7 { 8 T tmp = A[i]; 9 A[i] = A[nEnd + nStart - i]; 10 A[nEnd + nStart - i] = tmp; 11 } 12 } 13 template <class T> 14 void LeftMoveKthElements(T *A, int k, int n) 15 { 16 //@Param n: Number of elements in array A 17 //@Param k: left move k elements 18 Reverse(A, 0, k - 1); 19 Reverse(A, k, n - 1); 20 Reverse(A, 0, n - 1); 21 }
实例:
4.用最快的算法打印1~n的素数。
思路:用筛选法和位运算。利用一个unsigned int的位数可代表32个数,于是n个元素只需要1+ n/32个unsigned int就够了。
筛选法:遍历2~sqrt(n),把2~sqrt(n)的倍数 对应的位设为0(异或操作^)。 输出时把2~n对应位数非0的数输出即可。
1 void printPrime(unsigned int n) 2 { 3 int nSize = (int)sqrt((double)n); 4 //申请空间n个元素所需要的空间(按bit算) 5 unsigned int *nArray = (unsigned int*)malloc((1 + n / 32) * sizeof(unsigned int)); 6 if (nArray == NULL) 7 { 8 throw ("malloc failed!"); 9 } 10 for (int i = 0; i < 1 + n / 32; ++i) 11 { 12 nArray[i] = 0xFFFFFFFF;//initialization 13 } 14 for (int i = 2; i < nSize; ++i) 15 { 16 for (int j = 2 * i; j <= n; j += i) 17 { 18 //判断某位是否为0,非零则设为0,注意下面的判断语句的值不一定是1,而是某位上的1对应的数 19 if ( (nArray[j >> 5] & (1 << (j % 32))) != 0 ) 20 { 21 nArray[j >> 5] ^= 1 << (j % 32); 22 } 23 } 24 } 25 int j = 0; 26 for (int i = 2; i <= n; ++i) 27 { 28 //判断某位是否为0,非零则是素数,输出 29 if ( (nArray[i >> 5] & (1 << (i % 32)) ) != 0 ) 30 { 31 printf("%d ",i); 32 ++j; 33 if (j % 10 == 0) 34 { 35 printf("\n"); 36 } 37 } 38 39 } 40 free(nArray); 41 nArray = NULL; 42 }
下面是打印1~1000的素数