程序会从根节点开始提问,其左右子树为疑犯名字或另一个问题。先看数据结构:node
typedef struct node { char *question; struct node *no; struct node *yes; } node;
一个递归结构,内容很简单,一个char*指针,两个node指针。python
节点的建立函数:数据结构
node *create(char *question) { node *n = malloc(sizeof(node)); n->question = strdup(question); n->no = NULL; n->yes = NULL; return n; }
先分配空间,而后拷贝question字符串常量,两个node指针置空。函数
节点的销毁函数:this
void release(node *n) { if (n) { if (n->no) release(n->no); if (n->yes) release(n->yes); if (n->question) free(n->question); free(n); } }
从输入节点开始遍历,递归销毁其子树,释放n->question字符串,最后释放n。spa
一个通用的判断输入y/n的函数:
指针
int yes_no(char *question) { char answer[3]; printf("%s? (y/n):", question); fgets(answer, 3, stdin); if (answer[sizeof(answer) - 1] == '\n') answer[sizeof(answer) - 1] = 0; return answer[0] == 'y'; }
若是输入y返回1,输入其余字符返回0。code
以上代码比较简单,二叉树的建立主要在主要在主函数里。递归
这段代码比较长,分开贴:homebrew
char question[80]; char suspect[20]; node *start_node = create("Does suspect have a mustache"); start_node->no = create("Loretta Barnsworth"); start_node->yes = create("Vinny the Spoon"); node *current;
建立接收用户输入的question和suspect字符串。
建立根节点,并初始化一个问题,“疑犯有胡子么”。
将根节点的两个子树初始化为“Loretta Barnworth”和“Vinny the Spoon”。
定义一个current指针,用作活动指针,链接二叉树。
外层循环:
do { }while(yes_no("Run again"));
至少执行一次,若是用户在yes_no函数输入y,则重复执行。
循环体:
current = start_node; while (1) { ...... }
将current指向起始节点,并执行while(1)循环的内容,一个if else条件判断,第一个if:
if (yes_no(current->question)) { if (current->yes) current = current->yes; else { printf("SUSPECT IDENTIFIED\n"); break; } }
yes_no打印问题(或疑犯名)并要求用户输入,若是用户选择y,则进入子条件判断:当前节点current->yes是否存在,若是存在,将current=current->next,会进入下一次循环,若是不存在current->yes,则表示当前节点就是疑犯节点(疑犯节点没有子树),输出“SUSPECT IDENTIFIED”,跳出本层循环。
下一个条件,若是用户在yes_no时输入了n(本文中不输入y一概认为输入n)
else if (current->no) { current = current->no; }
判断该节点是否存在no子树,若是存在,将current=current->no,进入下一次循环。
最后一个条件,在yes_no()时输入了n,可是又没有no子树,表示咱们已存在的数据中,并无咱们想定位的疑犯信息,那么会进入新建步骤:
else { /*Make the yes-node the new suspect name*/ printf("Who's the suspect? "); fgets(suspect, 20, stdin); if (suspect[sizeof(suspect) - 1] == '\n') suspect[sizeof(suspect) - 1] = 0; node *yes_node = create(suspect); current->yes = yes_node; /*Make the no-node a copy of this question*/ node *no_node = create(current->question); current->no = no_node; /*The replace this question with the new question*/ printf("Give me a question that is TRUE for %s but not for %s ", suspect, current->question); fgets(question, 80, stdin); if (question[sizeof(question) - 1] == '\n') question[sizeof(question) - 1] = 0; /*while replacing,the old block for 'question' must be freed*/ current->question = strdup(question); break; }
程序输出:“Who's the suspect?”
输入疑犯名字,而后进行如下几步操做:
建立一个新节点(疑犯节点)yes_node
将当前节点(疑犯节点)current->yes=yes_node
建立一个新节点(疑犯节点),与当前节点问题(其实是疑犯名)相同,no_node,并将当前节点的current->no=no_node;注意,此时有两个节点的question(疑犯名)相同,当前节点和以当前节点疑犯名命名的新节点。
输入一个新问题,对你给出的新疑犯是true,对当前节点的老疑犯为false
将当前节点的current->question(疑犯名)指向你新输入的question的拷贝,strdup(question)。
程序最后release(start_node),销毁整个二叉树。
第一部分,二叉树的动态建立到此为止,接下来是用valgrind检查内存泄漏。
用valgrind检查内存泄漏:
安装valgrind:brew install valgrind 就这么简单,强烈安利homebrew。
使用valgrind检查:输入命令 valgrind --leak-check=full ./spies
➜ spies valgrind --leak-check=full ./spies ==1165== Memcheck, a memory error detector ==1165== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al. ==1165== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info ==1165== Command: ./spies ==1165== Does suspect have a mustache? (y/n):n Loretta Barnsworth? (y/n):n Who's the suspect? Johnny ==1165== Conditional jump or move depends on uninitialised value(s) ==1165== at 0x100000D62: main (in ./spies) ==1165== Give me a question that is TRUE for Johnny but not for Loretta Barnsworth He burns ==1165== Conditional jump or move depends on uninitialised value(s) ==1165== at 0x100000E01: main (in ./spies) ==1165== Run again? (y/n):n
注意必定要对初始化后的子树作一些改动才能复现。
结果:
==1165== ==1165== HEAP SUMMARY: ==1165== in use at exit: 42,915 bytes in 420 blocks ==1165== total heap usage: 530 allocs, 110 frees, 50,093 bytes allocated ==1165== ==1165== 19 bytes in 1 blocks are definitely lost in loss record 8 of 82 ==1165== at 0x100008EBB: malloc (in /usr/local/Cellar/valgrind/3.11.0/lib/valgrind/vgpreload_memcheck-amd64-darwin.so) ==1165== by 0x1001ECDDE: strdup (in /usr/lib/system/libsystem_c.dylib) ==1165== by 0x100000B57: create (in ./spies) ==1165== by 0x100000C61: main (in ./spies) ==1165== ==1165== LEAK SUMMARY: ==1165== definitely lost: 19 bytes in 1 blocks ==1165== indirectly lost: 0 bytes in 0 blocks ==1165== possibly lost: 0 bytes in 0 blocks ==1165== still reachable: 4,096 bytes in 1 blocks ==1165== suppressed: 38,800 bytes in 418 blocks ==1165== Reachable blocks (those to which a pointer was found) are not shown. ==1165== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==1165== ==1165== For counts of detected and suppressed errors, rerun with: -v ==1165== Use --track-origins=yes to see where uninitialised values come from ==1165== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 16 from 16)
致使内存泄漏的函数给咱们列举出来了,书中给的例子里结果中有行号,但是用的版本没有,只有检查这些函数在程序中的调用及其上下文
按照书中的解释,第一次不对二叉树做改动的状况下,并无出现内存泄漏,所以create函数没有问题,这也是一种定位方法,控制可变量。
问题出在current->question = strdup(question);由于在这步以前,current->question是不为空的,它已经指向了某块堆内存,如今给它新赋值,那块内存就找不到了。因此咱们要在current->question = strdup(question)以前,先free(current->question)。