在二叉搜索树(BST)中查找第K个大的结点之非递归实现
一个被广泛使用的面试题: 给定一个二叉搜索树,请找出其中的第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: 数据流中的中位数。