《C Primer Plus》第十七章二叉查找树tree.c程序详解

个人认为这个程序是这本书最难的,所以花的时间很多:

//接口头文件.h
#ifndef _TREE_H_
#define _TREE_H_

/*二叉树ADT抽象定义:
  类型名: 二叉查找树
  类型属性: 二叉树要么是空节点的集合(空树),要么是由一个根节点的节点集合
             每个节点都有两个子树,叫做左子树和右子树
			 每个子树本身也是二叉树,也有可能是空树
			 二叉查找树是一个有序的二叉树,每个节点都包含一个项
			 左子树的所有项都在根节点项的前面,右子树的所有项都在根节点的后面
  类型操作: 初始化树为空
             确定树是否为空
			 确定树是否已满
			 确定树的项数
			 在树中添加一个项
			 在树中删除一个项
			 在树中查找一个项
			 在树中访问一个项
			 清空树
*/


//建立接口第一步:描述如何表示数据

#define SLEN 20
typedef struct item {
	char petname[SLEN];//宠物名
	char petkind[SLEN];//宠物种类
} Item;//将item结构重命名为Item

#define MAXITEMS 10//最大项数
typedef struct trnode {
	Item item;//包含一个item结构
	struct trnode * left;//指向左子树的指针
	struct trnode * right;//指向右子树的指针
} Trnode;//将trnode结构重命名为Trnode

typedef struct tree {
	Trnode * root;//指向根的指针
	int size;//树的项数
} Tree;//将tree结构重命名为Tree,tree结构就是一个树


//建立接口第二步:描述实现ADT操作的函数

/*操作:把树初始化为空
  前提条件:ptree指向一个树
  后置条件:树被初始化为空
*/
void InitializeTree(Tree * ptree);

/*操作:确定树是否已满
  前提条件:ptree指向一个已初始化的树
  后置条件:如果树已满,函数返回true,否则函数返回false
*/
bool TreeIsFull(const Tree * ptree);

/*操作:确定树是否为空
  前提条件:ptree指向一个已初始化的树
  后置条件:如果树为空,函数返回true,否则函数返回false
*/
bool TreeIsEmpty(const Tree * ptree);

/*操作:确定树的项数
  前提条件:ptree指向一个已初始化的树
  后置条件:返回树的项数
*/
int TreeItemCount(const Tree * ptree);

/*操作:在树中添加一个项
  前提条件:ptree指向一个已初始化的树,pi是待添加项的地址
  后置条件:如果可以添加,该函数将在树中添加一个项,并返回true,否则返回false
*/
bool AddItem(const Item * pi, Tree * ptree);

/*操作:在树中查找一个项
  前提条件:ptree指向一个已初始化的树,pi指向一个项
  后置条件:如果在树中找到该项,函数返回true,否则函数返回false
*/
bool InTree(const Item * pi, const Tree * ptree);

/*操作:在树中删除一个项
  前提条件:ptree指向一个已初始化的树,pi是删除项的地址
  后置条件:如果从树中成功删除一个项,函数返回true,否则函数返回false
*/
bool DeleteItem(const Item * pi, Tree * ptree);

/*操作:把函数应用于树中的每一项
  前提条件:ptree指向一个已初始化的树,pfun指向一个函数,该函数接受一个Item类型的参数,并无返回值
  后置条件:pfun指向的这个函数为树中的每一项指向一次
*/
void Traverse(const Tree * ptree, void (*pfun) (Item item));

/*操作:删除树中的所有内容
  前提条件:ptree指向一个已初始化的树
  后置条件:树为空
*/
void DeleteAll(Tree * ptree);




#endif



//实现接口.c
#include<stdio.h>//包含fprintf函数的头文件
#include<stdlib.h>//包含malloc函数、free函数和exit函数的头文件
#include<string.h>//包含strcmp函数的头文件
#include "接口头文件.h"//包含外部定义头文件

typedef struct pair {
	Trnode * parent;//指向父节点的指针
	Trnode * child;//指向子节点的指针
} Pair;//将pair结构重命名为Pair


//内部链接函数,仅本文件可用
static Trnode * MakeNode(const Item * pi);
static bool ToLeft(const Item * i1, const Item * i2);
static bool ToRight(const Item * i1, const Item * i2);
static void AddNode(Trnode * new_node, Trnode * root);
static Pair SeekItem(const Item * pi, const Tree * ptree);
static void DeleteNode(Trnode ** ptr);
static void InOrder(const Trnode * root, void (*pfun) (Item item));
static void DeleteAllNode(Trnode * root);




//接口函数,供外部调用
void InitializeTree(Tree * ptree)//传入指向树的指针
{
	ptree -> root = NULL;//将指向根的指针初始化为NULL
	ptree -> size = 0;//将树的项数初始化为0
}



bool TreeIsFull(const Tree * ptree)
{
	return ptree -> size == MAXITEMS;//当树的项数等于最大值时返回true
}



bool TreeIsEmpty(const Tree * ptree)
{
	return ptree -> size == 0;//当树的项数等于0时返回true
}



int TreeItemCount(const Tree * ptree)
{
	return ptree -> size;//返回树的项数
}



bool AddItem(const Item * pi, Tree * ptree)//指向item结构的指针和指向树的指针
{
	Trnode * new_node;//定义一个trnode结构变量,该结构包含一个item结构和两个分别指向左子节点和右子节点的指针
	
	if(TreeIsFull(ptree))//当树的项数已满时无法添加新项
	{
		fprintf(stderr, "Tree is full\n");//报告错误
		return false;
	}
	
	if(SeekItem(pi, ptree).child != NULL)//如果树中已经存在此项时
	{
		fprintf(stderr, "Attempted to add duplicate item\n");//报告错误
		return false;//返回false,因为树中不能包含重复的项
	}
	
	new_node = MakeNode(pi);//为新项分配内存
	
	if(new_node == NULL)//如果分配内存失败,报告错误,返回false
	{
		fprintf(stderr, "Couldn't create node\n");
		return false;
	}
	
	ptree -> size++;//项数+1
	
	if(ptree -> root == NULL)//如果根节点为空,将新项作为根节点
		ptree -> root = new_node;
	else
		AddNode(new_node, ptree -> root);//否则,将新项添加到合适的位置
	
	return true;
}



static Pair SeekItem(const Item * pi, const Tree * ptree)//pi指向待查找项,ptree指向二叉树
{
	Pair look;//定义一个包含指向父节点和子节点的指针的结构
	look.parent = NULL;//将父节点赋值为NULL
	look.child = ptree -> root;//子节点指向树的根节点
	
	if(look.child == NULL)//如果子节点为NULL,说明树的根节点为空,返回false
		return look;
	
	while(look.child != NULL)//循环直到节点后无子节点
	{
		if(ToLeft(pi, &(look.child -> item)))//比较child所指节点的item成员和待查找的item结构,如果待查找项在该节点左边
		{
			look.parent = look.child;//将子节点的指针赋给父节点
			look.child = look.child -> left;//子节点指向其左子树
		}
		else if(ToRight(pi, &(look.child -> item)))//比较child所指节点的item成员和待查找的item结构,如果待查找项在该节点右边
		{
			look.parent = look.child;//将子节点的指针赋给父节点
			look.child = look.child -> right;//子节点的指针指向其右子树
		}
		else//如果待查找项和child所指节点的item成员相同,跳出循环,此时look.parent指向待查找项所在节点的父节点,look.child指向待查找项所在节点
			break;
	}
	return look;//返回look结构
}



static bool ToLeft(const Item * i1, const Item * i2)
{
	int comp1;
	
	if((comp1 = strcmp(i1 -> petname, i2 -> petname)) < 0)//根据strcmp函数的机制,如果字符串1在字符串2前面,函数返回负数,否则返回正数,也就是说当子节点应排在
		//父节点左边时,ToLeft函数返回true
		return true;
	
	else if(comp1 == 0 && strcmp(i1 -> petkind, i2 -> petkind) < 0)//当名字相同时,比较种类,当i1的种类名应排在i2种类名之前时,返回true
		return true;
	
	else
		return false;//其余情况,返回false
}



static bool ToRight(const Item * i1, const Item * i2)
{
	int comp1;
	
	if((comp1 = strcmp(i1 -> petname, i2 -> petname)) > 0)//当字符串1在字符串2后面时,strcmp函数返回正数,即子节点应排在父节点右边时,ToRight函数返回true
		return true;
	
	else if(comp1 == 0 && strcmp(i1 -> petkind, i2 -> petkind) > 0)//当名字相同时,比较种类,当i1的种类名应排在i2种类名之后时,返回true
		return true;
	
	else
		return false;//其余情况,返回false
}



static Trnode * MakeNode(const Item * pi)//函数一般采用传递指向结构的指针,而不是传递结构,因为传递结构需要对原结构进行拷贝,占用内存,影响效率
{
	Trnode * new_node;
	new_node = (Trnode *) malloc(sizeof(Trnode));//请求系统分配一个trnode结构的内存
	
	if(new_node != NULL)//如果分配内存成功
	{
		new_node -> item = *pi;//将pi所指向的item结构的数据赋给trnode结构成员item
		new_node -> left = NULL;//将trnode结构成员left和right赋值为NULL,表示其后没有子节点
		new_node -> right = NULL;
	}
	
	return new_node;//返回该trnode结构的地址
}



static void AddNode(Trnode * new_node, Trnode * root)//传入两个指向trnode结构的指针,new_node指向待添加的项,root指向二叉树中本来的项(即一个节点)
{
	if(ToLeft(&new_node -> item, &root -> item))//将new_node所指结构的item成员与root所指节点的item成员作比较,如果返回为true,new_node所指结构应属于root所指节点的左子树
	{
		if(root -> left == NULL)//如果root所指节点没有左子树,那么就将new_node所指结构作为root所指节点的左子节点
			root -> left = new_node;
		else//如果root所指节点有左子节点
			AddNode(new_node, root -> left);//递归调用,传入new_node和指向root所指节点的左子节点的指针,直到最后root -> left == NULL或者root -> right == NULL
	}
	else if(ToRight(&new_node -> item, &root -> item))//将new_node所指结构的item成员与root所指节点的item成员作比较,如果返回为true,new_node所指结构应属于root所指节点的右子树
	{
		if(root -> right == NULL)//如果root所指节点没有右子树,那么就将new_node所指结构作为root所指节点的右子节点
			root -> right = new_node;
		else//如果root所指节点有右子节点
			AddNode(new_node, root -> right);//递归调用,传入new_node和指向root所指节点的右子节点的指针,直到最后root -> left == NULL或者root -> right == NULL
	}
	else//当以上两种情况都不符合时,说明new_node所指结构的item成员和root所指节点的item成员相同,程序报告错误,并退出
	{
		fprintf(stderr, "location error in Addnode()\n");
		exit(1);
	}
}



bool InTree(const Item * pi, const Tree * ptree)
{
	return (SeekItem(pi, ptree).child == NULL)? false : true;//查找树中是否包含此项,如果包含,SeekItem(pi, ptree).child != NULL,返回true,否则== NULL,返回false
}



bool DeleteItem(const Item * pi, Tree * ptree)
{
	Pair look;
	look = SeekItem(pi, ptree);//先在树中查找待删除的项
	
	if(look.child == NULL)//没有找到,返回false
		return false;
	
	if(look.parent == NULL)//如果look.parent == NULL,说明项在根节点,删除根节点
		DeleteNode(&ptree -> root);
	
	else if(look.parent -> left == look.child)//如果待删除的项是其父节点的左子节点
		DeleteNode(&look.parent -> left);//传入的是待删除节点的父节点的left指针的地址,简单来说就是传入指向待删除节点的指针的地址
	
	else
		DeleteNode(&look.parent -> right);//如果待删除的项是其父节点的右子节点
	
	ptree -> size--;//项数-1
	
	return true;
}



static void DeleteNode(Trnode ** ptr)//传入的是指向指针(该指针指向一个trnode结构,即一个节点,该节点为待删除节点)的指针
{
	Trnode * temp;//定义一个指向trnode结构的指针
	
	if((*ptr) -> left == NULL)//如果待删除节点没有左子节点
	{
		temp = *ptr;//将待删除节点的地址保存到临时指针temp中
		*ptr = (*ptr) -> right;//将待删除节点的rignt赋给指向待删除节点的指针,现在*ptr指向待删除节点的右子节点
		free(temp);//释放待删除节点的内存
	}
	
	else if((*ptr) -> right == NULL)//如果待删除节点没有右子节点
	{
		temp = *ptr;//将待删除节点的地址保存到临时指针temp中
		*ptr = (*ptr) -> left;//将待删除节点的left成员赋给指向待删除节点的指针,现在*ptr指向待删除节点的左子节点
		free(temp);//释放待删除节点的内存
	}
	//当待删除节点既没有左子节点,又没有右子节点时,首先判断其没有左子节点,然后将*ptr指向待删除节点的右子节点,由于待删除节点的right为NULL
	//所以指向待删除节点的指针变为NULL

	else//当待删除节点左右两个子节点都有时
	{
		for(temp = (*ptr) -> left; temp -> right != NULL; temp = temp -> right)//初始化条件为temp指向待删除节点的左子节点,更新循环时每循环一次temp就指向其所指节点的
			//右子节点,测试条件为temp所指节点后无右子节点
			continue;
		
		//当退出for循环时,temp指向的是待删除节点的左子树中最右侧的子节点
		
		temp -> right = (*ptr) -> right;//将待删除节点的右子树移动到temp节点的右子节点(注意是将整个右子树移动)
		temp = *ptr;//将待删除节点的数据保存到temp中
		*ptr = (*ptr) -> left;//将待删除节点的左子节点移动到待删除节点的位置,也就是说原本指向待删除节点的指针现在指向待删除节点的左子节点
		free(temp);//释放待删除节点的内存
	}
}



void Traverse(const Tree * ptree, void (*pfun) (Item item))
{
	if(ptree != NULL)//树不是空树
		InOrder(ptree -> root, pfun);//传入指向根节点的指针,按照顺序打印节点信息
}



static void InOrder(const Trnode * root, void (*pfun) (Item item))//传入指向节点的指针,以及一个函数指针,该函数接受一个item结构,无返回
{
	if(root != NULL)//指向节点的指针不为NULL,即节点存在,不为空
	{
		InOrder(root -> left, pfun);//递归1
		(*pfun)(root -> item);
		InOrder(root -> right, pfun);//递归2
	//上面三条语句包含2个递归调用,其原理为递归1语句逐步递进,直到最左端的左子节点,其后无左子节点,条件不符合,逐步向根节点回归,回归归过程中(*pfun)(root -> item)
	//打印每个节点的信息,而递归2语句在递归1回归过程中递进,判断每个节点后是否有右子节点,如果有,递归1递进,判断该右子节点其后是否有左子节点,如果没有,
	//递归1回归,打印右子节点的信息,就这样逐步返回到根节点,同样,根节点的右子树也是这样操作,先打印右子树的所有左子节点,再打印右子节点
	}
}



void DeleteAll(Tree * ptree)
{
	if(ptree != NULL)//树不是空树
		DeleteAllNode(ptree -> root);//按顺序删除节点
	ptree -> root = NULL;//将指向树根节点的指针赋值为NULL,表明是空树
	ptree -> size = 0;//树的项数变为0
}



static void DeleteAllNode(Trnode * root)//传入指向节点的指针
{
	Trnode * pright;//指向trnode结构的指针
	
	if(root != NULL)//节点不为空
	{
		pright = root -> right;
		DeleteAllNode(root -> left);
		free(root);
		DeleteAllNode(pright);//这段代码和InOrder函数中的递归调用类似,也是从最底端的节点开始释放,坚持先左节点后右节点的原则
	}
}




//使用接口.c
#include<stdio.h>
#include<string.h>//包含strchr函数的头文件
#include<ctype.h>//包含toupper函数和tolower函数的头文件
#include "接口头文件.h"//包含外部定义头文件

char menu(void);//显示菜单函数
void addpet(Tree * pt);//添加宠物函数
void droppet(Tree * pt);//删除宠物函数
void showpets(const Tree * pt);//显示所有宠物函数
void findpet(const Tree * pt);//查找宠物函数

void printitem(Item item);//打印函数

void uppercase(char * str);//将字符全部改为大写
char * s_gets(char * st, int n);

int main(void)
{
	Tree pets;//定义一个树
	char choice;
	InitializeTree(&pets);//初始化树
	
	while((choice = menu()) != 'q')//输入不为q
	{
		switch (choice)
		{
		case 'a':
			addpet(&pets);//添加宠物
			break;
		case 'l':
			showpets(&pets);//显示所有宠物
			break;
		case 'f':
			findpet(&pets);//查找宠物
			break;
		case 'n':
			printf("%d pets in club\n", TreeItemCount(&pets));//打印树的项数
			break;
		case 'd':
			droppet(&pets);//删除宠物
			break;
		default:
			puts("Switching error");
		}
	}
	
	DeleteAll(&pets);//清空树
	
	puts("Bye");
	return 0;
}

char menu(void)
{
	char ch;
	puts("Nerfville Pet Club Membership Program");
	puts("Enter the letter corresponding to your choice:");
	puts("a) add a pet          l) show list of pets");
	puts("n) number of pets     f) find pets");
	puts("d) delete a pet       q) quit");
	
	while((ch = getchar()) != EOF)
	{
		while(getchar() != '\n')//丢弃剩余输入
			continue;
		
		ch = tolower(ch);//将输入字符置换为小写
		
		if(strchr("alfndq", ch) == NULL)//查找输入字符是否为alfndq中的一个,如果不是
			puts("Please enter an a, l, r, f, n, d, or q");
		else
			break;//否则跳出
	}
	if(ch == EOF)//如果读取到文件结尾,表示程序退出
		ch = 'q';
	
	return ch;
}

void addpet(Tree * pt)
{
	Item temp;
	
	if(TreeIsFull(pt))//查看树是否已满
		puts("No room in the club");
	
	else
	{
		puts("Please enter name of pet");
		s_gets(temp.petname, SLEN);//输入宠物名
		
		puts("Please enter pet kind");
		s_gets(temp.petkind, SLEN);//输入宠物种类
		
		uppercase(temp.petname);
		uppercase(temp.petkind);//将宠物名和宠物种类转换为大写
		
		AddItem(&temp, pt);//将新增的宠物添加到树中
	}
}

void showpets(const Tree * pt)
{
	if(TreeIsEmpty(pt))//如果树为空
		puts("No entries!");
	else
		Traverse(pt, printitem);//打印所有宠物信息
}

void printitem(Item item)
{
	printf("Pet: %-19s Kind: %-19s\n", item.petname, item.petkind);
}

void findpet(const Tree * pt)
{
	Item temp;
	if(TreeIsEmpty(pt))//如果树为空
	{
		puts("No entries!");
		return;//返回
	}
	puts("Please enter name of pet you wish to find");
	s_gets(temp.petname, SLEN);//输入待查找宠物名
	
	puts("Please enter pet kind");
	s_gets(temp.petkind, SLEN);//输入待查找宠物种类
	
	uppercase(temp.petname);
	uppercase(temp.petkind);//将输入转换为大写
	
	printf("%s the %s ", temp.petname, temp.petkind);
	
	if(InTree(&temp, pt))//如果在树中
		printf("is a member\n");
	else//不在树中
		printf("is not a member\n");
}

void droppet(Tree * pt)
{
	Item temp;
	if(TreeIsEmpty(pt))//如果树为空
	{
		puts("No entries!");
		return;
	}
	puts("Please enter name of pet you wish to delete");
	s_gets(temp.petname, SLEN);//输入待查找宠物名
	
	puts("Please enter pet kind");
	s_gets(temp.petkind, SLEN);//输入待查找宠物种类
	
	uppercase(temp.petname);
	uppercase(temp.petkind);//将输入转换为大写
	
	printf("%s the %s ", temp.petname, temp.petkind);
	
	if(DeleteItem(&temp, pt))//如果删除成功
		printf("is dropped from the club\n");
	else//如果删除失败
		printf("is not a member\n");
}

void uppercase(char * str)
{
	while(*str)//指针指向字符串第一个字符
	{
		*str = toupper(*str);//将字符转换为大写
		str++;//指针指向下一字符
	}
}

char * s_gets(char * st, int n)
{
	char * ret_val;
	char * find;
	ret_val = fgets(st, n, stdin);//读取到文件结尾或者读取失败时,返回NULL
	if(ret_val)
	{
		find = strchr(st, '\n');
		if(find)
			*find = '\0';//将换行符替换为空字符
		else
			while(getchar() != '\n')//丢弃剩余输入
				continue;
	}
	return ret_val;
}

其中,具体的递归调用的原理可查看我之前发的文章,如有错误,还请指正,谢谢!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值