一個被廣泛使用的面試題: 給定一個二叉搜索樹,請找出其中的第K個大的結點。
PS:我第一次在面試的時候被問到這個問題而且讓我直接在白紙上寫的時候,直接蒙圈了,因為沒有刷題准備,所以就會有傷害。知恥而后勇,於是我回家花了兩個半小時(在不參考任何書本和網路上的源碼的前提下),從構建BST開始,到實現中序遍歷,最后用遞歸方法寫出bst_findKthNode()並用gdb調試成功。 不過,使用遞歸實現這個實在是比較low,所以這個周末我決定用非遞歸方法實現。
先貼一下我的遞歸實現 (個人覺得比較low, 雖然實現了,但是不滿意)
1 /* 2 * Find the Kth Node in BST, K = 1, 2, ... 3 */ 4 int 5 bst_findKthNode(bst_node_t *root, key_t *key, unsigned int k) 6 { 7 if (root == NULL) 8 return -1; 9 10 if (root->left != NULL && k > 0) 11 k = bst_findKthNode(root->left, key, k); 12 13 if (--k == 0) { 14 *key = root->key; 15 return 0; 16 } 17 18 if (root->right != NULL && k > 0) 19 k = bst_findKthNode(root->right, key, k); 20 21 return k; 22 }
下面的代碼是我寫的非遞歸實現。
1 /* 2 * Find the Kth Node in BST, K = 1, 2, ... 3 */ 4 bst_node_t * 5 bst_findKthNode(bst_node_t *root, unsigned int k) 6 { 7 bst_node_t *kp = NULL; 8 9 if (root == NULL) 10 return NULL; 11 12 (void) stack_init(STACK_SIZE); 13 14 while (root != NULL || !stack_isEmpty()) { 15 if (root != NULL) { 16 push((uintptr_t)root); 17 root = root->left; 18 continue; 19 } 20 21 pop((uintptr_t *)(&root)); 22 if (--k == 0) { 23 kp = root; 24 break; 25 } 26 27 root = root->right; 28 } 29 30 stack_fini(); 31 32 return kp; 33 }
使用Meld進行diff后的截圖,
注意: 題目請參見《劍指Offer》(何海濤著)面試題63: 二叉搜索樹的第k個結點, 其cpp答案在這里。
最后,貼出完整的代碼和測試運行結果。
o libstack.h 和 libstack.c (參見 將遞歸函數非遞歸化的一般方法(cont) 一文)
o libbst.h
1 #ifndef _LIBBST_H 2 #define _LIBBST_H 3 4 #ifdef __cplusplus 5 extern "C" { 6 #endif 7 8 #define STACK_SIZE 16 9 10 typedef int key_t; 11 12 typedef struct bst_node_s { 13 key_t key; 14 struct bst_node_s *left; 15 struct bst_node_s *right; 16 } bst_node_t; 17 18 int bst_init(bst_node_t **root, key_t a[], size_t n); 19 void bst_fini(bst_node_t *root); 20 void bst_walk(bst_node_t *root); 21 bst_node_t *bst_findKthNode(bst_node_t *root, unsigned int k); 22 23 #ifdef __cplusplus 24 } 25 #endif 26 27 #endif /* _LIBBST_H */
o libbst.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "libbst.h" 4 #include "libstack.h" 5 6 static int bst_add_node(bst_node_t **root, key_t key); 7 8 int 9 bst_init(bst_node_t **root, key_t a[], size_t n) 10 { 11 *root = NULL; 12 for (int i = 0; i < n; i++) { 13 if (bst_add_node(root, a[i]) != 0) 14 return -1; 15 } 16 17 return 0; 18 } 19 20 #define UMEM_FREE_PATTERN 0xdeadbeefdeadbeefULL 21 static inline void 22 BST_DESTROY_NODE(bst_node_t *p) 23 { 24 p->left = NULL; 25 p->right = NULL; 26 *(unsigned long long *)p = UMEM_FREE_PATTERN; 27 } 28 29 void 30 bst_fini(bst_node_t *root) 31 { 32 if (root == NULL) 33 return; 34 35 bst_fini(root->left); 36 bst_fini(root->right); 37 38 BST_DESTROY_NODE(root); 39 free(root); 40 } 41 42 static int 43 bst_add_node(bst_node_t **root, key_t key) 44 { 45 bst_node_t *leaf = NULL; 46 leaf = (bst_node_t *)malloc(sizeof (bst_node_t)); 47 if (leaf == NULL) { 48 fprintf(stderr, "failed to malloc\n"); 49 return -1; 50 } 51 52 /* init leaf node */ 53 leaf->key = key; 54 leaf->left = NULL; 55 leaf->right = NULL; 56 57 /* add leaf node to root */ 58 if (*root == NULL) { /* root node does not exit */ 59 *root = leaf; 60 } else { 61 bst_node_t **pp = NULL; 62 while (1) { 63 if (leaf->key < (*root)->key) 64 pp = &((*root)->left); 65 else 66 pp = &((*root)->right); 67 68 if (*pp == NULL) { 69 *pp = leaf; 70 break; 71 } 72 73 root = pp; 74 } 75 } 76 77 return 0; 78 } 79 80 void 81 bst_walk(bst_node_t *root) 82 { 83 if (root == NULL) 84 return; 85 86 (void) stack_init(STACK_SIZE); 87 88 while (root != NULL || !stack_isEmpty()) { 89 if (root != NULL) { 90 push((uintptr_t)root); 91 root = root->left; 92 continue; 93 } 94 95 pop((uintptr_t *)(&root)); 96 printf("%d\n", root->key); 97 98 root = root->right; 99 } 100 101 stack_fini(); 102 } 103 104 /* 105 * Find the Kth Node in BST, K = 1, 2, ... 106 */ 107 bst_node_t * 108 bst_findKthNode(bst_node_t *root, unsigned int k) 109 { 110 bst_node_t *kp = NULL; 111 112 if (root == NULL) 113 return NULL; 114 115 (void) stack_init(STACK_SIZE); 116 117 while (root != NULL || !stack_isEmpty()) { 118 if (root != NULL) { 119 push((uintptr_t)root); 120 root = root->left; 121 continue; 122 } 123 124 pop((uintptr_t *)(&root)); 125 if (--k == 0) { 126 kp = root; 127 break; 128 } 129 130 root = root->right; 131 } 132 133 stack_fini(); 134 135 return kp; 136 }
o foo.c (簡單測試)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include "libbst.h" 4 5 int 6 main(int argc, char *argv[]) 7 { 8 if (argc != 2) { 9 fprintf(stderr, "Usage: %s <Kth>\n", argv[0]); 10 return -1; 11 } 12 13 int a[] = {30, 10, 40, 20, 50, 80, 70, 60, 90}; 14 int n = sizeof (a) / sizeof (int); 15 16 bst_node_t *root = NULL; 17 bst_init(&root, a, n); 18 19 bst_walk(root); 20 21 unsigned int k = atoi(argv[1]); 22 bst_node_t *p = NULL; 23 if ((p = bst_findKthNode(root, k)) == NULL) { 24 printf("\nOops, the %dth node not found\n", k); 25 goto done; 26 } 27 printf("\nWell, the %dth node found, its key is %d\n", k, p->key); 28 29 done: 30 bst_fini(root); 31 32 return 0; 33 }
o Makefile
1 CC = gcc 2 CFLAGS = -g -Wall -std=gnu99 -m32 3 INCS = 4 5 TARGET = foo 6 7 all: ${TARGET} 8 9 foo: foo.o libstack.o libbst.o 10 ${CC} ${CFLAGS} -o $@ $^ 11 12 foo.o: foo.c 13 ${CC} ${CFLAGS} -c $< ${INCS} 14 15 libstack.o: libstack.c libstack.h 16 ${CC} ${CFLAGS} -c $< 17 18 libbst.o: libbst.c libbst.h 19 ${CC} ${CFLAGS} -c $< 20 21 clean: 22 rm -f *.o 23 clobber: clean 24 rm -f ${TARGET}
o 編譯並測試運行
$ make gcc -g -Wall -std=gnu99 -m32 -c foo.c gcc -g -Wall -std=gnu99 -m32 -c libstack.c gcc -g -Wall -std=gnu99 -m32 -c libbst.c gcc -g -Wall -std=gnu99 -m32 -o foo foo.o libstack.o libbst.o $ ./foo 6 10 20 30 40 50 60 70 80 90 Well, the 6th node found, its key is 60 $ ./foo 16 | egrep 'Oops,' Oops, the 16th node not found $
擴展題目: "尋找兩個數組的中位數"。 題目描述如下:
有兩個數組, 第一個數組a里的元素按照升序排列, e.g. int a[] = {10, 30, 40, 70, 80, 90};
第二個數組b里的元素按照降序排列, e.g. int b[] = {60, 50, 30, 20, 10};
請尋找數組a和b的合集的中位數,e.g. 50。
解決方案:
- 使用數組a構建一個無重復key的BST
- 將數組b里的元素加入BST (若某個元素已經在BST中存在,不予加入)
- 設BST中的所有結點總數為N (a) 若N為偶數, 查找第K, K+1個元素 (K=N/2) 並求其平均值; (b) 若N為奇數, 查找第K+1個元素(K=N/2)。
關於此題目的詳細描述和解決方案請參見 《劍指Offer》(何海濤著)面試題64: 數據流中的中位數。