程序會從根節點開始提問,其左右子樹爲疑犯名字或另一個問題。先看數據結構: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)。