C語言系列 : 以二元搜尋樹實現成績管理程式

透由二元搜尋樹及鏈結佇列,來實現成績管理程式的新增,刪除,讀檔,存檔功能。

 

二元搜尋樹(Binary Search Tree)

二元搜尋樹具有幾個特性:

  1. 在左子樹(left subtree)的所有鍵值均小於樹根的鍵值。
  2. 在右子樹(right subtree)的所有鍵值均大於樹根的鍵值。
  3. 左子樹跟右子樹也是二元搜尋樹。
  4. 每個鍵值必須為不同。

以本程式的資料當作範例,可供讀者參閱二元搜尋樹,其中沒有連結處為NULL,亦即空集點。

 

從此圖中我們可以得知,左子樹鍵值會小於樹根,而右子樹鍵值會大於樹根,同時不能存在相同鍵值的狀況。

在本文中不會過多著墨在介紹二元搜尋樹,僅討論關於如果刪除二元搜尋樹中的某一節點呢?

 

二元搜尋樹的節點刪除

筆者因為在準備出國,因此沒有太多時間去準備,所以直接以畫圖的方式更容易描述步驟,而更細節的部份可以參閱筆者的程式碼或資料結構書籍。

完整程式碼:

//the score record program used by linked list, binary search tree and structure.
//have functions: insert,load data,save data,show
#include "pch.h"
#include <stdio.h>
#include <stdlib.h>
#include <string>

void insert(char*, int); /* 新增函數 */
void load(char);   /* 載入函數 */
void save(void);   /* 存檔函數 */
void show(void);   /* 顯示函數 */
void delet(struct student*,char*,int); /*刪除函數*/
void replace(struct student*, struct student*);/*取代函數*/
void introdu(void);/* 介紹函數 */
void inorder(struct student*); 
//資料以中序追蹤(inorder):依序拜訪left subtree, root and right subtree.
void preorder(struct student*,FILE *);
//以前序追蹤(preorder)儲存資料到檔案:依序拜訪 root, left subtree and right subtree.


struct student {
	struct student *Llink; //left subtree
	char name[20];         //node data
	int  score;			   //node data
	bool right;			   //is the node right subtree of paeent node?
	struct student *Rlink; //right subtree
};

struct student *head;
struct student *null = (struct student*)malloc(sizeof(struct student));

//當某節點的Llink與Rlink皆指向 *null時,代表此節點為終點節點。

int main()
{
	null = NULL;
	char key, loadQ, name[20] = {};
	int score;
	introdu();
	printf("Do you want to load the data to program?(Y/N)");
	scanf("%s", &loadQ);
	load(loadQ);
	while (1) {
		printf("Please enter a command:");
		scanf("%s",&key);
		switch (key) {
		case '1':
			printf("\n*******Insert the student score*******\n");
			printf("Please enter the student name:");
			scanf("%s",&name);
			printf("Please enter the student score:");
			scanf("%d", &score);
			insert(name,score);
			printf("\n**************************************\n");
			break;
		case '2':
			printf("\n*******Delete the student score*******\n");
			printf("Please enter the student name:");
			scanf("%s", &name);
			printf("Please enter the student score:");
			scanf("%d", &score);
			delet(head,name,score);
			printf("\n**************************************\n");
			break;
		case '3':
			show();
			break;
		case '4':
			save();
			break;
		default:
			return 0;
			break;
		}

	}


}

void insert(char* name,int score) {
	struct student *node = (struct student*)malloc(sizeof(struct student));
	struct student *point = (struct student*)malloc(sizeof(struct student));
	point->score = score;
	strcpy(point->name,name);
	point->Rlink = NULL;
	point->Llink = NULL;

	if (head == NULL) {
		head = point;
	}
	else {
		node = head;
		while (1) {
			if (point->score > node->score) {
				if (node->Rlink != NULL) {
					node = node->Rlink;//移動到節點的右子樹
				}
				else {
					node->Rlink = point;
					point->right = true;//為父節點下的右子樹
					//printf("Node:%s %d\n", node->name, node->score);
					//printf("Right:%s %d\n", point->name, point->score);
					break;
				}
			}
			else if (point->score < node->score) {
				if (node->Llink != NULL) {
					node = node->Llink;//移動到節點的左子樹
				}
				else {
					node->Llink = point;
					point->right = false;//為父節點下的左子樹
					//printf("Node:%s %d\n", node->name, node->score);
					//printf("Light:%s %d\n", point->name, point->score);
					break;
				}
			}
			else {
				printf("ERROR!!!/n");
				break;
			}
		}
	}

}

void load(char Q) {
	if (Q == 'Y'||Q == 'y') {
		char name[20] = {};
		int score = 0;
		FILE *fp;
		if ((fp = fopen("final test.bin", "rb+")) != NULL) {
			printf("\n*******Load the student score from Data.*******\n");
			while (fscanf(fp,"%s %d", name, &score)!=EOF) {
				insert(name,score);//insert
			}
			printf("\n***********************************************\n");
		}
		fclose(fp);
	}
}

void save(void) {
	FILE *fp;
	if ((fp = fopen("final test.bin", "wb+")) != NULL) {
		preorder(head,fp);
	}
	else {
		printf("ERROR:CANNOT SAVE THE DATA.\n");
	}
	fclose(fp);
}

void show(void) {
	printf("use inorder to traversal:\n");
	printf("**********************************\n");
	inorder(head);
	printf("**********************************\n\n");
}

void introdu() {//介紹功能
	printf("The program can save the student final exam scores. \n");
	printf("(Use by linked list, binary search tree and structure.) \n");
	printf("You can enter the number which choose the function:\n");
	printf("---------------------------------------\n");
	printf("    1.Inset the new student score \n");
	printf("    2.Delete student scores \n");
	printf("    3.Display all student scores \n");
	printf("    4.Save the data\n");
	printf("    5.Exit\n");
	printf("---------------------------------------\n");
}

void delet(struct student* node, char* target_name, int target_score) {
	struct student *previous=node;
	if (head == NULL) {
		printf("No score record!\n");
	}
	else {
		while (node != NULL) {
			if (target_score > node->score) {
				previous = node;
				node = node->Rlink;
			}
			else if (target_score < node->score) {
				previous = node;
				node = node->Llink;
			}
			else {
				if (strcmp(node->name,target_name)==0) {//成績與名字一致
					printf("Find the student name is %s, and score is %d.\n", node->name, node->score);
					if (node->Llink == NULL && node->Rlink == NULL) {//若此節點為樹葉節點
						if (node->right) {
							previous->Rlink = NULL;
							free(node);
						}
						else {
							previous->Llink = NULL;
							free(node);
						}
					}
					else replace(previous, node); //此節點非樹葉節點,需要找替代節點
					break;
				}
				else {
					printf("Cannot find student name from record!\n");
					break;
				}
			}
		}
	}
}

void replace(struct student* previous_target_node, struct student* target_node) {
	//previous_target_node is the parent node from target_node.
	//And pre_replace_node is also the parent node from replace_node.
	struct student *replace_node,*pre_replace_node;
	if (target_node->Rlink != NULL) {//搜尋右子樹最小替代節點
		pre_replace_node = target_node;
		replace_node = target_node->Rlink;
		//printf("Node %s:%d\n", replace_node->name, replace_node->score);
		while (replace_node->Llink != NULL) { //找右子樹中最小的節點
			pre_replace_node = replace_node;
			replace_node = replace_node->Llink;
		}
		 pre_replace_node->Llink = replace_node->Rlink;
		//將替代節點's父節點的Llink連結至替代節點's左子樹,若無則為NULL
		if (target_node->Rlink != replace_node)replace_node->Rlink = target_node->Rlink;
		if (target_node->Llink != replace_node)replace_node->Llink = target_node->Llink;
		//將替代節點與target同步相同的左右子樹,同時避免自己的子樹為自己的狀況發生

		if (target_node != head) {
			if (target_node->right) {
				previous_target_node->Rlink = replace_node;
				replace_node->right = true; 
			}
			else {
				previous_target_node->Llink = replace_node;
				replace_node->right = false;
			}
		}
		else head = replace_node;
		//若target不為root,則將target's父節點的右子樹連結到替代節點
		//若為,則替代節點直接取代root
	}
	else { //搜尋左子樹最大替代節點
		pre_replace_node = target_node;
		replace_node = target_node->Llink;
		//printf("Node %s:%d else\n", replace_node->name, replace_node->score);
		while (replace_node->Rlink != NULL) { //找左子樹中最大的節點
			pre_replace_node = replace_node;
			replace_node = replace_node->Rlink;
		}
		pre_replace_node->Rlink = replace_node->Llink;
		//將替代節點's父節點的Rlink連結至替代節點's左子樹,若無則為NULL
		if (target_node->Rlink != replace_node)replace_node->Rlink = target_node->Rlink;
		if (target_node->Llink != replace_node)replace_node->Llink = target_node->Llink;
		//將替代節點與target同步相同的左右子樹,同時避免自己的子樹為自己的狀況發生
		if (target_node != head) {
			if (target_node->right) {
				previous_target_node->Rlink = replace_node;
				replace_node->right = true;
			}
			else {
				previous_target_node->Llink = replace_node;
				replace_node->right = false; 
			}
		}
		else head = replace_node;
		//若target不為root,則將target's父節點的左子樹連結到替代節點
		//若為,則替代節點直接取代root
	}
	free(target_node);
}

void inorder(struct student* node) {
	if (node != NULL) {
		inorder(node->Llink);
		printf("this node %s:%d", node->name, node->score);
		if (node == head) {
			printf(" is root.\n");
		}
		else {
			if (node->right == true) {
				printf(" is right subtree.\n");
			}
			else {
				printf(" is left subtree.\n");
			}
		}
		inorder(node->Rlink);
	}
}//資料以中序追蹤(inorder):依序拜訪left subtree, root and right subtree.

void preorder(struct student* node,FILE *fp) {
	if (node != NULL){
		fprintf(fp,"%s %d\n", node->name, node->score);
		preorder(node->Llink, fp);
		preorder(node->Rlink, fp);
	}
}//資料以前序追蹤(preorder)的方式儲存:依序拜訪 root, left subtree and right subtree.

執行結果:

_______________________________________________

我們透過閱讀,拼湊出真實世界的面貌,
並在反覆的探索及思維中,打破由自我無知與偏見所建立的籓籬。