/* Program: dict.c (Report comments/bugs to chikh@yuntech.edu.tw) Function: 擴充書binarySearchTree.c範例程式,實作字典功能,要求的規格詳見課程網頁的第六次作業公告 Notes: 輸入可為 w={"Mary":10, "Tom":3, "Charlie":5, "Bob":6, "Dickens":20, 4:2, "z":0, "Za":12, "aZ":8} del w["Dickens"] w["Mary"]=1 w["Tom"]+=2 w[4]? w? */ #include #include #include /* for strcmp() */ /* 定義dict結構 */ typedef struct dict { char key[20]; int value; struct dict *llink; /* 左子鏈結 */ struct dict *rlink; /* 右子鏈結 */ } dict; //void insert(); /* 新增函數 */ void prune(); /* 刪除函數 */ void modify(char, char [], int); /* 修改函數 */ void show(char, char []); /* 輸出函數 */ void process(char [], int); /* 將資料加入二元搜尋樹 */ void drop(char []); /* 將資料從二元搜尋樹中移除 */ dict *replace(dict *); /* 尋找替代節點並移動至適當位置 */ void connect(dict *, char); /* 調整鏈結 */ void inorder(dict *); /* 資料以中序法輸出 */ void deleteAll(dict *); /* 刪除所有節點 */ dict *search(char []); /* 搜尋節點 */ dict *search_re_r(dict *); /* 搜尋右子樹中的替代節點 */ dict *search_re_l(dict *); /* 搜尋左子樹中的替代節點 */ dict *search_p(dict *); /* 搜尋父節點 */ dict *root, *ptr; /* root為根節點,為全域變數 */ int main() { char *token, key[12], line[128]; const char *s = " ={},"; /* 分隔符記的字元 */ int value; char *rest, option; printf("\n輸入存取字典的陳述,逕按結束程式\n"); while (1) { printf("\n指令==> "); fgets(line,128,stdin); //gets(line); if (line[0]=='\n') break; /* 輸入空字串則跳離此迴圈 */ if (strstr(line,"{")) { sscanf(line,"w = {%[^}]}",line); rest = line; while (token = strtok_r(rest,s,&rest)) { /* strtok_r()的用法可參見 http://bit.ly/3VcvbbZ */ if (strstr(token,":")) { /* token含有":" */ sscanf(token,"%[^:]:%d",key,&value); //printf("Token #%d: 鍵=%s; 值=%d\n",++i,key,value); process(key,value); /* 把key,value插入樹中 */ } } } else if (strstr(line,"del")) { if (sscanf(line,"del w[%[^]]]",key)) drop(key); else { printf("無法判讀將刪除哪一個元素,將刪除整個字典? (y/n) => "); scanf("%c",&option); fflush(stdin); if (option == 'y' || option == 'Y') { deleteAll(root); root = NULL; } } //printf("刪除運算:辨識鍵=%s\n",key); } else if (strstr(line,"+=")) { /* line含有"+=" */ sscanf(line,"w[%[^]]] +=%d",key,&value); //printf("遞增運算:辨識鍵=%s;運算元=%d\n",key,value); modify('+',key,value); } else if (strstr(line,"=")) { /* line含有"=" */ sscanf(line,"w[%[^]]] = %d",key,&value); //printf("指派運算:辨識鍵=%s; 運算元=%d\n",key,value); modify('s',key,value); } else if (sscanf(line,"w[%[^]]]",key)==1) //printf("讀值運算:辨識鍵=%s\n",key); show('@',key); else if (strstr(line,"?")) //printf("顯示整個字典內容\n"); show('*',NULL); else printf("語法無法辨認,sorry!\n"); } /* char option; while (1) { puts(""); puts("********************"); puts(" <1> insert"); puts(" <2> delete"); puts(" <3> modify"); puts(" <4> show"); puts(" <5> quit"); puts("********************"); printf("Enter your choice: "); option = getchar(); fflush(stdin); printf("\n"); switch(option) { case '1': insert(); break; case '2': prune(); break; case '3': modify(); break; case '4': show(); break; case '5': exit(0); default : puts("選項錯誤!"); } } */ return 0; } /* 新增函數,新增一筆新的資料 */ /* void insert() { char key[20]; int value; puts("=====INSERT DATA====="); printf("姓名: "); scanf("%s", key); printf("分數: "); scanf("%d", &value); fflush(stdin); process(key, value); } */ /* 刪除函數,將資料從二元搜尋樹中刪除 */ void prune(char key[]) /* 本函數原名為delete_f(),這裡改名令程式看起來乾脆一些 */ { //char key[20]; if (root == NULL) { puts("字典內無任何資料"); return; } //puts("=====DELETE DATA====="); //printf("請輸入欲刪除的姓名: "); //scanf("%s", key); //fflush(stdin); drop(key); } /* 修改資料,修改學生成績 */ void modify(char mode, char key[], int value) { dict *node; //char key[20]; //if (root == NULL) { /* 判斷根節點是否為NULL,若為空,根本沒得玩,於是直接返回,不會有後續 */ //puts("二元搜尋樹是空的!"); //return; //} //puts("=====MODIFY DATA===== "); //printf("請輸入欲修改的姓名: "); //scanf("%s", key); //fflush(stdin); if ((node=search(key)) == NULL) //printf("%s不在二元搜尋樹中!\n", key); process(key,value); else { /* 列出原資料狀況 */ //printf("姓名: %s\n", node->key); //printf("分數: %d\n\n", node->value); //printf("請輸入新的分數: "); //scanf("%d", &node->value); //fflush(stdin); if (mode == '+') node->value += value; else if (mode == 's') node->value = value; value = node->value; } printf("新設w[%s]=%d\n",key,value); } /* 輸出函數,將資料輸出至螢幕 */ void show(char mode, char key[]) { dict *node; if (mode == '@') { if ((node=search(key)) == NULL) printf("w[%s]不存在\n",key); else printf("字典元素w[%s]=%d\n",key,node->value); } else { if (root == NULL) { /* 判斷根節點是否為NULL */ puts("字典無任何內容"); return; } //puts("=====SHOW DATA====="); printf("字典變數內部儲存的元素(key:value)如下\n"); inorder(root); /* 以中序法輸出資料 */ } } /* 處理二元搜尋樹,將新增資料加入二元搜尋樹 */ void process(char key[], int value) { dict *node, *prev; if ((node=search(key)) != NULL) { /* 若資料已存在,則顯示錯誤,因二元搜尋樹要求每個節點的鍵值不重複 */ //printf("%s已存在!\n", key); node->value = value; return; } ptr = (dict *)malloc(sizeof(dict)); /* ptr指向即將加入的節點 */ strcpy(ptr->key, key); /* 把傳入此函數的name引數(字串)複製到ptr->name欄位中 */ ptr->value = value; /* 把傳入此函數的score引數複製到ptr->score欄位中 */ ptr->llink = ptr->rlink = NULL; /* ptr節點的左右子樹先設為空 */ if (root == NULL) /* 若root為NULL,代表樹尚未建立,因此以ptr節點作為創始二元樹的根節點 */ root = ptr; else { /* 根節點不為NULL,代表樹已存在,則需找到適合ptr節點插入樹中的位置 */ node = root; /* 從根節點開始,透過底下迴圈持續搜尋資料的插入點 */ while (node != NULL) { /* node為當下檢視的樹節點,只要不為空,代表尚未底定,須持續尋找 */ prev = node; /* prev記住node現今所指節點;prev將成為ptr的父節點 */ if (strcmp(ptr->key, node->key) < 0) /* 若ptr->name小於node->name,意味著ptr應安插在node的左子樹,因此改令node為左子節點,繼續往下找尋 */ node = node->llink; else /* ptr->name大於node->name,意味著ptr應安插在node的右子樹,故改令node為右子節點,繼續往下找尋 */ node = node->rlink; } if (strcmp(ptr->key, prev->key) < 0) /* 若ptr->name小於prev->name,則把ptr節點設為prev的左子節點,ptr節點以樹葉的形式加入二元樹 */ prev->llink = ptr; else /* ptr->name大於prev->name,則把ptr節點設為prev的右子節點,ptr節點以樹葉的形式加入二元樹 */ prev->rlink = ptr; } } /* 將資料從二元搜尋樹中移除 */ void drop(char key[]) /* 本函數原名為removing(),這裡改名令程式看起來乾脆一些 */ { dict *del_node; /* del_node記住將被移除的節點位址 */ if ((del_node=search(key)) == NULL) /* 以傳入的name引數當作比對字串,找尋樹中哪一個節點的name欄位等於該字串;若找不到資料則顯示錯誤 */ { printf("w[%s]不存在\n", key); return; } if (del_node->llink != NULL || del_node->rlink != NULL) /* 若del_node節點不為樹葉,則呼叫replace()函數置換該節點內容,內嵌"抓交替"行為 */ del_node = replace(del_node); /* 注意傳入replace()的del_node與回傳出來的del_node將不同,回傳出來的是被抓交替的節點位址;replace()內呼叫connecting()調整被抓替的節點遺留的左/右子樹連結方式 */ else /* 若del_node節點為樹葉,則繼續判別是否為根節點或一般內部節點 */ if (del_node == root) /* 若del_node為根節點,則把root指標重置為空 */ root = NULL; else /* del_node為一般內部節點,則直接呼叫connecting()把該節點移除 */ connect(del_node, 'n'); free(del_node); /* 釋放被抓交替的節點空間 */ printf("刪除w[%s]元素\n",key); } /* 尋找刪除非樹葉的替代節點 */ dict *replace(dict *node) { dict *re_node; /* 替代節點(被抓交替的節點) */ /* 以傳入的node為出發點,呼叫search_re_r()搜尋右子樹替代節點re_node;若右子樹找不到替代節點,再呼叫search_re_l()搜尋左子樹中的替代節點 */ if ((re_node=search_re_r(node->rlink)) == NULL) re_node = search_re_l(node->llink); if (re_node->rlink != NULL) /* 若替代節點re_node有右子樹,則把該右子樹往上掛為re_node的父節點的左子樹 */ connect(re_node, 'r'); else if (re_node->llink != NULL) /* 替代節點re_node有左子樹,則把該左子樹往上掛為re_node的父節點的右子樹 */ connect(re_node, 'l'); else /* re_node指向的替代節點為樹葉 */ connect(re_node, 'n'); /* 'n'或應改為'm',以利與後面的程式邏輯一致 */ strcpy(node->key, re_node->key);/* node所指的節點改以re_node節點內容覆蓋掉,完成置換 */ node->value = re_node->value; return re_node; /* 回傳被抓交替的節點位址 */ } /* 調整二元搜尋樹的鏈結,link為'r'表示處理右鏈結,為'l'表處理左鏈結,為'm'則將鏈結設為NULL */ void connect(dict *node, char link) /* 這裡傳入的node引數為即將被取代的節點位址 */ { dict *parent; parent = search_p(node); /* 搜尋將被取代的節點的父節點parent */ if (strcmp(node->key, parent->key) < 0) /* 被取代的節點位於parent的左子樹 */ if (link == 'r') /* 若link為'r',則把node的右子樹改掛為parent的左子樹,相當於被取代的節點讓出位置 */ parent->llink = node->rlink; else /* link為'm',則node為樹葉,此時只需把parent的左子樹設為空 */ parent->llink = NULL; else /* 被取代的節點位於parent的右子樹 */ if (link == 'l') /* 若link為'l',則把node的左子樹改掛為parent的右子樹,相當於令node讓位 */ parent->rlink = node->llink; else /* link為'm',則node為樹葉,此時只需把parent的右子樹設為空 */ parent->rlink = NULL; } /* 以中序追蹤法秀出每個節點紀錄的姓名與成績,採遞迴方式 */ void inorder(dict *node) { if (node != NULL) { inorder(node->llink); printf("%s:%d\n", node->key, node->value); inorder(node->rlink); } } void deleteAll(dict *node) /* 以後序走訪的方式刪除節點 */ { if (node != NULL) { deleteAll(node->llink); deleteAll(node->rlink); printf("刪除'w[%s]=%d'節點\n",node->key,node->value); free(node); } } /* 搜尋target所在節點 */ dict *search(char target[]) { dict *node; node = root; while (node != NULL) { if (strcmp(target, node->key) == 0) /* 若target與node所指的節點內存的字串相同,則回傳node所指節點位址 */ return node; else if (strcmp(target, node->key) < 0) /* 若target小於目前節點,則往左搜尋 */ node = node->llink; else /* target大於目前節點,往右搜尋 */ node = node->rlink; } return node; } /* 搜尋右子樹替代節點 */ dict *search_re_r(dict *node) { dict *re_node; re_node = node; while (re_node != NULL && re_node->llink != NULL) /* 尋找node右子樹底部最左節點,此為最接近的節點,由re_node指向 */ re_node = re_node->llink; return re_node; /* 找得到的re_node回傳出去 */ } /* 搜尋左子樹替代節點 */ dict *search_re_l(dict *node) { dict *re_node; re_node = node; while (re_node != NULL && re_node->rlink != NULL) /* 尋找node左子樹底部最右節點 */ re_node = re_node->rlink; return re_node; /* 找得到的re_node回傳出去 */ } /* 搜尋node的父節點 */ dict *search_p(dict *node) { dict *parent; parent = root; while (parent != NULL) { if (strcmp(node->key, parent->key) < 0) { /* 這裡strcmp()<0,意味node位於parent節點的左子樹 */ if (strcmp(node->key, parent->llink->key) == 0) /* 若parent的左子節點紀錄的name即為我們關心的節點node所載字串 */ return parent; /* 即把parent回傳出來 */ else /* 否則,原parent的左子節點設為下一個目標,繼續往下找尋 */ parent = parent->llink; } else { /* node位於parent節點的右子樹 */ if (strcmp(node->key, parent->rlink->key) == 0) /* 若parent的右子節點紀錄的name即為我們關心的節點所載字串 */ return parent; /* 即把parent回傳出來 */ else /* 否則,原parent的右子節點設為下一個目標,繼續往下找尋 */ parent = parent->rlink; } } return NULL; }