二叉树(C语言)

1、二叉树的结构

        二叉树是由一个储存数据的变量和两个指向子树的指针构成的,定义在一个结构体中。其一般形式为:

typedef struct BiTree
{
	char data;
	struct BiTree *lchild;
	struct BiTree *rchild;
}BiTree,*BiNode;

        BiNode即结构体变量的指针,BiTree是结构体变量。在创建二叉树时,为每个结点动态分配内存时要用到这两个标识符,例如: BiNode root=(BiNode)malloc(sizeof(BiTree))。

2、二叉树的建立

        二叉树一般使用递归的方式建立,递归调用函数来创建自己的左右子树。

BiNode create()//先序建立二叉树 
{
	BiNode T;
	char a;
	scanf("%c",&a);
	if(a=='.') return NULL;//当输入.时表示这个结点为空,而且没有左右子树。
	else
	{
		T=(BiNode)malloc(sizeof(BiTree));//动态分配内存
		T->data=a;
		T->lchild=create();//创建左子树 
		T->rchild=create();//创建右子树 
	}
	return T;
}

          若想中序或后序创建,则只需改变函数中        T->data=a;        T->lchild=create();        T->rchild=create(); 这三条语句的顺序,先给T->data=a在先的话是先序,在中间的话是中序,在最后的话是后序。

3、二叉树的遍历

        二叉树一般有4种遍历方法,即先序、中序、后序、层序。

        先序的遍历顺序是根节点->左子树->右子树。

        中序的遍历顺序是左子树->根节点->右子树。

        后序的遍历顺序是右子树->根节点->左子树。

        层序的遍历顺序是按层顺次遍历。

        先序、中序、后序的代码基本相同

void pre(BiNode root)//先序遍历二叉树 
{
	if(root)
	{
		printf("%c ",root->data);
		pre(root->lchild);
		pre(root->rchild);
	}
}

void mid(BiNode root)//中序遍历二叉树 
{
	if(root)
	{
		mid(root->lchild);
		printf("%c ",root->data);
		mid(root->rchild);
	}
}

void post(BiNode root)//后序遍历二叉树
{
	if(root)
	{
		post(root->lchild);
		post(root->rchild);
		printf("%c ",root->data);
	}
}

        例如这个简单的二叉树:

图1

        当我们先序建立二叉树时,应输入:ABDF..GH..I..E..C..,这三种遍历的结果为:

        而层序遍历较为复杂,可以用队列来实现,队列的特点是先进先出。我们先构造一个队列,先将根节点入队列,之后每个出队列的结点都要判断其左右子树是否存在,若存在则入队列。代码如下:

void level(BiNode root)//二叉树的层次遍历,运用队列,每层的结点挨个先进先出。 
{
	BiNode queue[20],pTemp;
	int cur=0,pre=0;//cur表示当前入队列的结店,pre表示当前出队列的结点
	queue[cur++]=root;
	while(cur!=pre)
	{
		pTemp=queue[pre++];
		printf("%c ",pTemp->data);
		if(pTemp->lchild!=NULL) queue[cur++]=pTemp->lchild;
		if(pTemp->rchild!=NULL) queue[cur++]=pTemp->rchild;
	}
}

        层序遍历上面的二叉树的运行结果为:

4、特殊二叉树

  1、二叉排序树

    1.1 特点及构造方法

        二叉排序树的特点是:若一个结点的左子树非空,则左子树的值一定小于结点的值;若一个节点的右子树非空,则右子树的值一定大于结点的值。

        例如这颗简单二叉排序树:

  

        其创建过程也不复杂,对于每个数据,都判断它与根节点的大小关系,若大于根节点,则作为根节点的右子树,若小于根节点,则作为根节点的左子树。则二叉排序树的中序遍历一定是严格递增的。代码如下:

BiNode BSTInsert(BiNode T,int key)
{
	if(!T)//若为空,则创建这个结点 
	{
		T=(BiNode)malloc(sizeof(BiTree));
		T->data=key;
		T->lchild=T->rchild=NULL;
	}
	else 
	{
		if(T->data<key)
			T->rchild=BSTInsert(T->rchild,key);
		else if(T->data>key)
			T->lchild=BSTInsert(T->lchild,key);
	}
	return T;
}

BiNode Insert(BiNode T,int data[],int n)
{
	int i;
	for(i=0;i<n;i++)
		T=BSTInsert(T,data[i]);
	return T;
}
int main()
{
	int num[9]={8,3,10,13,14,1,6,7,4};
	BiNode T=NULL;
	T=Insert(T,num,9);
	printf("\n中序遍历:");
	mid(T);
	return 0;
}

        将一个数组{8,3,10,13,14,1,6,7,4}作为数据构造一棵二叉排序树,可以得到上图中的结果,对他进行中序遍历的结果为:

        得到了正确的结果。也可以在二叉排序树构造成功后直接使用BSTInsert函数来插入元素,例如在上面的例子里插入5:

int main()
{
	int num[9]={8,3,10,13,14,1,6,7,4};
	BiNode T=NULL;
	T=Insert(T,num,9);
	printf("中序遍历:");
	mid(T);
	BSTInsert(T,5);
	printf("\n插入后:");
	mid(T);
	return 0;
}

        运行结果为:

    1.2 二叉排序树的查找

        根据二叉树的特点,我们可以轻松的找出二叉排序树中的最小、最大元素和特定元素。

        寻找最小、最大元素的方法是相同的。根据二叉树的特点,若左右子树不为空,则根节点的值一定大于左子树的值、小于右子树的值,那么这棵树的根节点的最左子树和最右子树即为最小、最大元素。代码如下:

BiNode Searchmin(BiNode T)//寻找二叉排序树中的最小元素 
{
	if(T==NULL)
		return NULL;
	if(T->lchild==NULL)
		return T;
	else return Searchmin(T->lchild);//不断搜索他的左子树 
}

BiNode Searchmax(BiNode T)//寻找二叉排序树中的最大元素 
{
	if(T==NULL)
		return NULL;
	if(T->rchild==NULL)
		return T;
	else return Searchmax(T->rchild);//不断搜索他的右子树 
}

        而查找特定元素也利用了排序树的特点。将结点的值与待查值做比较,若结点值大,则说明待查值在结点的左子树;若结点值小,则说明待查值在结点的右子树,然后递归或不递归地查询结点的左子树或右子树即可。递归查询的代码如下:

BiNode Search_1(BiNode T,int key)//递归查找特定元素 
{
	if(T==NULL)
		return NULL;
	if(key>T->data)
		return Search_1(T->rchild,key);//查找右子树 
	else if(key<T->data)
		return Search_1(T->lchild,key);//查找左子树 
	else return T;//找到则返回指针 
}

        在此基础上,我们可以省掉递归过程,用while循环来实现结点的遍历。非递归查找代码如下:

BiNode Search_2(BiNode T,int key)//非递归查找特定元素 
{
	BiNode p=T;
	while(p)
	{
		if(p->data==key)//找到就返回结点
			return p;
		else p=(p->data>key)?p->lchild:p->rchild;//判断下一个要查找的是左子树还是右子树。 
	}
	return NULL;//找不到则返回空
}

    1.3 删除二叉排序树的一个特定结点

        对于一般的二叉树来说,删去树中的一个结点是没有意义的,因为它将使以被删除的结点为根的子树变成森林,破坏了整棵树的结构。但是,对于二叉排序树,删去树上的一个结点相当于删去有序序列中的一个记录,只要在删除某个结点后不改变二叉排序树的特性即可。

        删除节点p的一般过程是:

        1、在二叉排序树中找到要删除的那个结点p。

        2、若p有左子树,则找到左子树的最右子树r,用r来代替p,将r的左子树作为r父亲的右子树。

        3、若p没有左子树,则用p的右子树r代替p。

        第一步我们可以用一个函数,先根据二叉排序树的特点找到要删除的结点。找到后,用另一个函数来实现第二步或第三步。

        明确思路后,应考虑如何完成用r代替p这一操作,若移动r和p的指针,不仅麻烦,而且移来移去容易出错。仔细思考一下,我们删除的实质上是r,并不是p,则更好的办法是将r的值赋给p,然后删除掉r结点。代码如下:

BiNode Delete(BiNode T,int key)//删除二叉排序树中的特定元素
{
	if(T->lchild)//如果存在左子树 
	{
		BiNode p=T->lchild;
		BiNode parent=T->lchild; 
		while(p->rchild!=NULL)//找到左子树的最右子树 
		{
			parent=p;
			p=p->rchild;
		}
		T->data=p->data;//将要删除的结点的值覆盖掉 
		if(parent!=p)//如果找到的最右子树不是T的左子树,则把最他的左子树作为他的父结点的右子树 
			parent->rchild=p->lchild;
		else T->lchild=p->lchild;//否则T的左子树指向p的左子树 
		free(p);//清空p的内存 
		return T;
	}
	else//如果不存在左子树,则直接返回自己的右子树。 
	{
		BiNode p=T->rchild;
		free(T);
		return p;
	}
}

BiNode DeleteBST(BiNode T,int key)//二叉排序树的删除 
{
	if(T)
	{
		if(T->data==key)//若找到要删除的结点,则执行删除操作 
			T=Delete(T,key);
		else if(T->data>key)
			T->lchild=DeleteBST(T->lchild,key);
		else T->rchild=DeleteBST(T->rchild,key);
	}
	return T;
}

        用{8,3,10,13,14,1,6,7,4}来测试一下这段代码:

        完全符合预期。

猜你喜欢

转载自blog.csdn.net/Q_M_X_D_D_/article/details/82708180