C -- 二叉树(含递归以及非递归算法实现)

二叉查找树

什么是二叉查找树?

  • 是树的一种,即度为2的树。简单点说就是,每个节点至多只有两个子节点(也就是说存在0、1、2个结点);
  • 描述的是一种一对多的关系,可由一个根节点衍生出一棵左子树,一棵右子树,每一棵子树又可以看成一一个根节点发散的树;

节点

据此我们可以定义树的基本单元为一个节点包含:Data域和指针域(两个指针,一个指向节点,一个指向右节点)

typedef struct _node{
	ElemType item;	//抽象高级数据类型,可用结构自定义
	struct _node * left;
	struct _node * right;
}Node;

二叉树

有了定义的基本二叉节点类型,我们可以开始定义我们的二叉树类型,树中包含:二叉树根节点指针和当前树节点数量size(增加这个size的目的是为了后续的操作可以更方便);

typedef struct _BinaryTree{
	Node * root;
	int size;
}BinaryTree;

接口以及实现

一、初始化

//将树初始化为空树
void InitializeTree(BinaryTree * bt)
{
	bt->root = NULL;
	size = 0;
}

二、是否为空

//判断是否是一棵空树
bool TreeIsEmpty(const BinaryTree * bt)
{
	return size == 0;
}

三、是否已满

//假设我们已经定义了树的节点上限为MAXSIZE
#define MAXSIZE 20
bool TreeIsFull(const BinaryTree * bt)
{
	return size == MAXSIZE;
}

四、返回当前树节点数量

int TreeCount(const BinaryTree * bt)
{
	return bt->size;
}

五、添加节点(非递归)

static CopyToNode(Node * pnew, const ElemType * pt);
static bool Toleft(const ElemType m1, const ElemType m2);
static bool Toright(const ElemType m1, const ElemType m2);

//该函数返回是否添加成功
bool AddToTree(BinaryTree * bt, const ElemType * pt)
{
	if(bt->size == MAXSIZE)	//已满
		return false;
	Node * pnew = (Node *) malloc(sizeof(Node));
	if(pnew == NULL)	//内存分配失败
		return false;
	pnew->left = pnew->right = NULL;
	CopyToNode(pnew,pt);	//根据元素类型需要创建的辅助函数
	
	if(bt->root == NULL)	//添加第一个根节点
		bt->root = pnew;
	else{		//添加后续	
		Node * p = root;
		while(p)	//找到正确叶子节点的空子节点(区分左右)
			if(Toleft(p->item, *pt))
				p = p->left;
			else if(Toright(p->item),*pt)
				p = p->right;
			else
				return false;	//假定树中不能存在相同元素
		p = pnew;	//链接
	}
	bt->size++;
	
	return true;
}

六、删除节点(递归法)

删除节点考虑,考虑删除的节点可能有四种情况

  • 叶子节点,此节点度为0;这种情况较容易,可直接释放当前节点,将父节点设为NULL即可;
  • 仅含左节点,将父节点指向左子节点,释放当前节点即可;
  • 仅含右节点,将父节点指向右子节点,释放当前节点即可;
  • 含左右节点,释放当前节点,将父节点指向左子树,沿着左子树向下查找一个空的右子节点位置,将右子树连接上去即可;

从分析来看我们是需要知道待删除节点的父节点的,此处省略查找父节点的函数,假设我们有一个函数可返回相关信息,我们将其存放在一个结构中定义为Pair类型的一个look变量,包含父节点parent成员以及当前要删除的节点child。

//seekitem()函数返回的结构类型,局部数据类型
typedef struct pair
{
	Node * parent;
	Node * child;
}Pair;

static DeleteNode(Node ** pt)
{
	Node * temp;
	if((*pt)->left == NULL)
	{
		temp = (*pt);
		(*pt) = (*pt)->right;
		free(temp);
	}else if((*pt)->right == NULL)
	{
		temp = (*pt);
		(*pt) = (*pt)->left;
		free(temp);
	}else{
		for(temp = (*pt)->left;temp->right;temp = temp->right)
			continue;
		temp->right = (*pt)->right;
		temp = (*pt);
		(*pt) = (*pt)->left;
		free(temp);
	}
}

bool DelInTree(BinaryTree * bt, const ElemType * pt)
{
	Pair look;
	look = SeekElem(bt,pt);
	if(look.child == NULL)
		return false;	//未找到
	
	if(look.parent == NULL)
		DeleteNode(&bt->root);
	else if(look.parent->left == look.child)
		DeleteNode(&look.parent->left);
	else
		DeleteNode(&look.parent->right);
	bt->size--;
	
	return true;
}

七、清空树(递归)

清空树涉及到遍历树的相关方法,其实其他查找之类的也要用到遍历,此处我们仅写一个遍历删除树的方法,其他操作可以类似的遍历。

遍历二叉树包括三种方式:

  • 先序遍历,先根,根->左子->右子
  • 后序遍历,后根,左子->右子->根
  • 中序遍历,中根,左子->根->右子

下面采用中序遍历的删除方式:

static void DeleteAllNode(Node * root)
{
	Node * p;
	if(root != NULL)
	{
		//暂存右子树
		p = root->right;
		//删除左子树
		DeleteAllNode(root->left);
		//释放根节点
		free(root);
		//删除右子树
		DeleteAllNode(pright);
	}
}

void ClearTree(BinaryTree * bt)
{
	if(bt)
		DeleteAllNode(bt->root);
	bt->root = NULL;
	bt->size = 0;
}

小结

关于二叉树的添加、删除、查找就到这,基于这些基础操作我们可以定义更多的相关操作,添加时需要做一些判断,删除时也需要判断树是否为空是否找到之类,查找也涉及到树的遍历,树的遍历有三种方式:先序遍历、中序遍历、后序遍历。由于树本身的定义就是递归的,除了用非递归的方式写相关函数,也可以用递归的方式写,递归遍历的时候我们只需要知道根节点就可以了,每一次递归调用传入下一个位置,又将下个位置视为根节点,直到某个返回条件。

发布了18 篇原创文章 · 获赞 4 · 访问量 842

猜你喜欢

转载自blog.csdn.net/GuoningningPro/article/details/103646658