目录
CreateBiTree(&T, definition); //按定义构造二叉树
Find(T, e); //查找值为ee的结点,成功返回结点的地址,失败返回NULL。
int Delete(BSTree *TT,ElemType *ee) // 在(二叉排列树)树TT中删除值为ee的结点
序:
树和二叉树 是两种数据结构。
数据结构的核心算法:遍历算法
6.1 树的定义和基本术语
1.定义:
树是n(n>=0)个结点的有限集。
(1) 有且仅有一个特定的称为根的结点;
(2) 当n>1时,其它结点可分成m个互不相交的有限集T1,T2,…,Tm,其中每一个集合也构成一棵树,并且称为根的子树。
2. 特点:
(1)树的根结点没有前驱结点,其他结点有且仅有一个前驱结点;
(2)树中的任何一个结点,可以有零个或多个后继结点。
3. 术语:
(1)结点(node):
(2)结点的度(degree):
(3)树的度:
(4)叶子或终(结)端结点:
(5)非终结(端)结点:
(6)父(双)亲结点(parent):
(7)儿(孩)子结点(child):
(8)兄弟结点(sibling):
(9) 路径: (10)祖先: (11)子孙(后代): (12)结点的层数(level):
(13)树的深度(depth)或高度:
(14)有序树和无序树:
(15)森林(forest):是m(m≥0)棵互不相交树的集合。
4. 树的表示方法
(1)图
(2)嵌套集合
(3)广义表形式:(A(B(D),C))
(4)凹入表示法:
ADT Tree
{
数据对象D:D是具有相同特征的数据元素的集合。
数据关系R:若D为空集,则称为空树;
若D含有一个数据元素,则R为空集,
否则R={H},H是如下二元关系:
(1)在D中存在惟一的为根的数据元素root,它在关系H下无前驱;
(2)若D-{root} ≠空集∅,…………
基本操作:
查找类:
插入类:
删除类:
} ADT Tree
6.2 二叉树
6.2.1 二叉树的定义
1.定义:
二叉树(Binary Tree)是n(n≥0)个结点的集合,这个集合可以是空集,或者是由一个根结点和两棵称为左子树和右子树的互不相交的二叉树组成。
2.特点:
(1)可以是空树,即不含任何结点;
(2)每个结点至多只有二棵子树(即二叉树中不存在度大于2的结点);
(3)二叉树是有序树,子树有左右之分,次序不能任意颠倒;允许某些结点只有右子树,或者只有左子树。
(4).二叉树的五种基本形态:
(5).二叉树与树的区别
①每个结点最多只有两棵子树,并有左右之分;
②与度数为2的有序树也不同。
ADT BinaryTree
{
数据对象D:D是具有相同特征的数据元素的集合。
数据关系R:若D=∅,称为BinaryTree为空二叉树;
若D≠∅,则R={H},H是如下二元关系:
(1)在D中存在惟一的为根的数据元素root,
它在关系H下无前驱;
(2)若D-{root} ≠∅,………………………
基本操作:
} ADT BinaryTree
6.2.2 二叉树的性质
性质1 在二叉树的第i(i≥1)层上至多有2^i-1^个结点。
性质2 深度为k的二叉树最多有2^k^-1个结点。
性质3 对任意二叉树T,如果其终端结点数为n0(度数为0的结点数),n1,n2分别表示度数为1,2的结点个数,则n0=n2+1。
证明:从两个方面考虑 ①节点总数 ②总度数
①节点总数:设n为二叉树T的结点总数,则有: n=n0+n1+n2
②总度数:总度数 n-1=n1+2*n2
求得:n0=n2+1
满二叉树:
如果一个二叉树的叶子结点都在最后一层上,且不存在度数为1的结点,则称该二叉树为满二叉树。
设高为K,则有2^k^-1个结点。
特点:
(1)对给定的高度,他有( 最多 )结点;
(2)不存在度数为( 1 )的结点;
(3)每个分支都有两棵高度( 相同 )的子树;
(4)叶子结点都在( 最后一层上 )。
完全二叉树:(树的顺序存储结构的基础!!!)
如果存在一棵二叉树,对树中的结点自上而下、自左而右连续编号,若编号为i的结点与满二叉树中编号为i的结点的位置相同,则称此二叉树为完全二叉树。(相当于是满二叉树从最后开始向前删除,且不能跳着删)
特点:
a.叶子结点只可能在( 层数最大的两层上 )出现;
b.对任一结点,若有右子树,则( 必有 )左子树。
性质4 具有n个结点的完全二叉树的深度为【log2n】+1或者( 【log2(n+1)】)。//下取整
性质5 对于一个具有n个结点的完全二叉树,其结点按层序编号(从第1层到第【log2n 】 +1层(最后一层),每层从左到右),则对任一结点ki(1≤i≤n),有
(1)已知编号i,求双亲的编号?(向下取整)
(2)已知编号i,求左孩子的编号?答:当1≤i≤n/2时,其左孩子节点为2i.
(3)已知编号i,求右孩子的编号?答:当1≤i≤(n-1)/2时其右孩子节点为2i+1.
(4)已知编号i,求左兄弟的编号?答:当i为奇数且1<i≤n时,其左兄弟节点为i-1.
(5)已知编号i,求右兄弟的编号?答:当i为偶数且1<i<n时,其右兄弟节点为i+1.
6.2.3 二叉树的存储结构
1、顺序存储结构
1.利用完全二叉树的性质,可以把一个完全二叉树的结点存入向量b[n]中,b[0]存根结点。
#define MAX_TREE_SIZE 100 // 二叉树的最大结点数
typedef TElemType SqBiTree[MAX_TREE_SIZE]; // 定义数组数据类型SqBiTree
SqBiTree bt; // 与TElemType bt[100] 相同。
结点数组
注意:实际应用中可采用:1号单元存储根结点,0号单元存节点的个数。
下列哪个命题是正确的?
2、链式存储结构
(1)二叉链表:
在每个结点中设置两个链域一个指向左孩子,一个指向右孩子。 若没有左孩子或没有右孩子,则相应链域为空。
typedef struct BiTNode
{
TElemType data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
注意:对于树的链式存储,我们一般知道的是根的地址,例如:已知二叉链表树T。
(2)三叉链表
在每个结点中设置三个链域一个指向左孩子,一个指向右孩子,一个指向父结点。
若没有左孩子或没有右孩子,则相应链域为空,父结点也是如此。
typedef struct TriTNode
{
TElemType data;
struct TriTNode *lchild,*rchild,*parent;
}TriTNode, *TriTree;
6.3、遍历二叉树和线索二叉树(下)
6.3.1、遍历二叉树
顺着某一条搜索路径巡访二叉树 中的结点,使得每个结点均被访问一 次,而且仅被访问一次。
“访问”的含义很多,如:输出结点的信息、查找、记数等等。
先序(先根)遍历
若二叉树为空,则返回;
若不空:
访问根结点;
先序遍历左子树;
先序遍历右子树。
先序遍历中第一个结点和最后一个结点各有什么特征?
第一个是根节点,最后一个根据树的形状而定。
算法:
void Preorder (BiTree T)
{
if (T)
{
//可以用cout实现
visit(T->data); // 访问结点
Preorder(T->lchild); // 遍历左子树
Preorder(T->rchild);// 遍历右子树
}
}
程序:
void PreTraverse(BT p)
{
if(NULL!=p)//递归结束条件,T为空
{
printf("%c ",p->data );//先访问根节点
if(NULL!=p->plchild )PreTraverse(p->plchild );//再先序访问左子树,层数+1 p->plchild可以代表整个左子树
if(NULL!=p->prchild )PreTraverse(p->prchild );//在先序访问右子树,层数+1
}
}
中序(中根)遍历
若二叉树为空,则退出;
若不空:
中序遍历左子树;
访问根结点;
中序遍历右子树。
中序遍历中第一个结点和最后一个结点各有什么特征? 根结点处于什么位置?
第一个是最左边的节点;最后一个是最右面的节点; 根结点位于左子树节点和右子树节点之间。
算法:
// 中序遍历二叉树
void Inorder (BiTree T)
{
if (T)
{
Inorder(T->lchild); // 遍历左子树
visit(T->data); // 访问结点
Inorder(T->rchild);// 遍历右子树
}
}
//非递归-课件
void NINORDER(BiTree BT)
{
STACK S;
BiTree T=BT,e;
InitStack(S);//初始化一个空的栈
while(T) //树不为空
{
PUSH(S, T);//压栈
T=T->lchild;//左孩子
}//此时到了最左结点
while(!IsEmpty(S))//栈不为空
{
POP(S,e);visit(e);//出栈,打印
e=e->rchild; //右孩子
while(!IsEmpty(e)) //找到右子树上的最左节点
{
PUSH(S,e);//入栈
e=e->lchild;//左孩子
}//此时e是右子树上的最左节点,进入下一个循环(再出栈,打印)
}
}
程序:
void InTraverse(BT p)
{
if(NULL!=p)
{
if(NULL!=p->plchild )InTraverse(p->plchild );//先中序访问左子树,层数+1 p->plchild可以代表整个左子树
printf("%c ",p->data );//再访问根节点
if(NULL!=p->prchild )InTraverse(p->prchild );//再中序访问右子树,层数+1
}
}
//非递归
void InOrderTraverse(BiTree &T)
{
SqStack S;//栈,先入后出
InitStack(S);//初始化栈
BiTree p = T;//根节点
BiTree q = new BiTNode;
while(p || S.base != S.top)
{
if(p)//条件不满足时,GetTop(S,p)得到的指针p是空的
{//中序遍历,所以我们首先要找到树的最左边结点
Push(S,p);//该节点入栈
p = p->lchild;//左孩子
}
else//左节点已经全部入栈,到达最左边了,p->lchild是空,故我们需要把这个进栈的空指针给Pop掉
{
Pop(S,q);//删除栈顶结点并赋给q,q是最左边结点,左子树为空
cout << q->data << " ";//打印
p = q->rchild;//将右结点进栈,重新进行这个操作,即先找最左边结点..
}
}
}
后序(后根)遍历
若二叉树为空,则退出;
若不空:
后序遍历左子树;
后序遍历右子树;
访问根结点。
后序遍历中第一个结点和最后一个结点各有什么特征?
后序遍历中第一个结点视树形而定;最后一个结点是根节点。
// 后序遍历二叉树
void Postorder (BiTree T)
{
if (T)
{
Postorder(T->lchild); // 遍历左子树
Postorder(T->rchild);// 遍历右子树
visit(T->data); // 访问结点
}
}
层次遍历:
若二叉树为空,返回:
否则:
1、设置一个队列,置空:// 先入先出
2、根结点入队:
3、while(队列不空)
{出队,访问出队结点;
出队结点的左右子树进队; }
Status LevelOrderTraverse(BiTree T)
{
Queue Q;
InitQueue(Q);
if (T) Enqueue(Q,T); //根结点入队
while(!QueueEmpty(Q))//while(队列不空)
{
DeQueue(Q,&E);visit(E);//出队,访问出队结点;
if (E->lchild) EnQueue(Q,E->lchild);
if (E->rchild) EnQueue(Q,E->rchild);
//出队结点的左右子树进队;
}
}
//非递归:
//循环队列
typedef struct
{
Elemtype *base;
int front,rear;
}SqQueue;
void SeOrderTraverse(BiTree &T)
{
SqQueue SQ;
InitQueue(SQ);//初始化队列,先入先出
BiTree p = T;//根节点
if(T)//不空
{
enQueue(SQ,p);//入队
while(SQ.front != SQ.rear)//队列不空
{
deQueue(SQ,p);//第一个元素出队
cout << p->data << " ";//访问该元素
// //出队结点的左右子树进队;
if(p->lchild)//左孩子入队
enQueue(SQ,p->lchild);
if(p->rchild)//右孩子入队
enQueue(SQ,p->rchild);
}
}
DestroyQueue(SQ);//销毁队列
}
讨论1:给出一种遍历次序是否能唯一确定一棵二叉树?
答:不能。
讨论2:给出先序和后序是否能唯一确定一棵二叉树?
不能:
讨论3; 给出中序和后序(或先序)是否能唯一确定一棵二叉树?
能:
题1:
已知一棵二叉树先序遍历次序为
1,2,4,8,9,5,10,11,3,6,12,7
中序遍历次序为
8,4,9,2,10,5,11,1,12,6,3,7
画出该二叉树:
分治算法思想:
把一个大问题分解成两个或 多个小问题, 小问题和大问 题具有同样的 性质。一般采 用递归算法.
算法:(二叉链表)
typedef struct BiTNode
{
TElemType data;
struct BiTNode *lchild, *rchild;
} BiTNode, *BiTree;
InitBiTree(&T); // 构造空二叉树T
Status initTree(BTree *T) //注意& 和 * 是C语言,C++用引用&
{ //将二叉树的根结点置为空则初始化成功
*T=NULL;
printf("初始化成功!\n");
return OK;
}
DestroyBiTree(&T); //销毁树T-后序
//递归算法-课件
void DestroyBiTree ( BiTree T )
{
if ( T)
{
DestroyBitree ( T->lchild );
DestroyBitree ( T->rchild );
free(T);
T=NULL;//在通过free函数释放空间后,最好将指针立即置空,这样可以防止后面的程序对指针的误操作。
}
}
Status destroyBTree(BTree *T)
{
if(*T)
{ //若当前结点存在
if((*T)->leftChild)
{ //则判断是否有左孩子,若有则先销毁左孩子
destroyBTree(&(*T)->leftChild); //递归销毁左孩子
}
if((*T)->rightChild)
{ //无左孩子则判断是否有右孩子,若有则销毁右孩子
destroyBTree(&(*T)->rightChild); //递归销毁右孩子
}
free(*T); //若都没有则释放当前结点
*T=NULL; //将结点置空
/*
在通过free函数释放空间后,最好将指针立即置空,这样可以防止后面的程序对指针的误操作。
释放空间后,指针的值没有改变,无法直接通过指针本身判断空间是否已经释放,
将指针置空有助于检测一个指针指向的空间是否已经释放。*/
}
return OK;
}
CreateBiTree(&T, definition); //按定义构造二叉树
算法:
void CreateBtree(BiTree &T)
{
getchar(ch);
if ( ch == ‘#’ )
T = NULL ;
else
{
T =(BiTNode *)malloc(sizeof(BiTNode));
T →data = ch ;
CreateBtree ( T →lchild ) ;
CreateBtree ( T →rchild ) ;
}
}
//递归-先序 逐个输入
void CreateBiTree(BiTreee* T)
{
char c;
scanf("%c",&c);
if (c == '#')
*T = NULL;
else
{
*T = (BiTNode *)malloc(sizeof(BiTNode));//创建根结点
(*T)->data = c;//向根结点中输入数据
CreateBiTree(&((*T)->lchild));//递归地创建左子树
CreateBiTree(&((*T)->rchild));//递归地创建右子树
}
}
//依次输入:
//在创建二叉树的过程中,程序总是按照:创建根结点-创建左子树-创建右子树顺序进行
/*
注意:给定先序遍历不能唯一确定一棵二叉树。
要想给定先序遍历确定一棵二叉树,必须把 空子树位置补上。
如由图所示二叉树,输入:
AB#E##CF###
其中:#表示空
*/
// 用数组arr中的序列构建二叉排序树TT。
// 以下两种写法都可以。
//void CreateBST(BSTree *TT,int arr[],int len)
void CreateBST(BSTree *TT,int *arr,int len)
{
(*TT)=NULL;
int ii=0;
for (ii=0;ii<len;ii++)
{
Insert(TT,&arr[ii]); // 把数组中的元素逐个插入树中。
// Insert1(TT,&arr[ii]); // 把数组中的元素逐个插入树中。
}
}
//insert
// 在树TT中插入关键字为ee的新结点(递归实现),返回值:0-树中已存在关键字为ee的结点;1-成功。
int Insert(BSTree *TT,ElemType *ee)
{
if ((*TT)==NULL)// 如果当前子树为空,创建子树的根结点。
{
(*TT)=(BSTree)malloc(sizeof(BSTNode));//创建子树的根结点。
memcpy( &(*TT)->data , ee , sizeof(ElemType) );// 考虑数据元素ee为结构体的情况,采用memcpy而不是直接赋值。
(*TT)->lchild = (*TT)->rchild=NULL;// 当前子树的左右孩子指针置空。
return 1; // 返回成功。
}
if (*ee==(*TT)->data)
return 0; // 如果ee已存在,返回失败。
if (*ee < (*TT)->data )
Insert(&(*TT)->lchild,ee);// 递归向左插入。
else
Insert(&(*TT)->rchild,ee); // 递归向右插入。
}
// 在树TT中插入关键字为ee的新结点(非递归实现),返回值:0-树中已存在关键字为ee的结点;1-成功。
int Insert1(BSTree *TT,ElemType *ee)
{
// 如果树为空,创建树的根结点。
if ((*TT)==NULL)
{
(*TT)=(BSTree)malloc(sizeof(BSTNode));
memcpy(&(*TT)->data,ee,sizeof(ElemType)); // 考虑数据元素ee为结构体的情况,采用memcpy而不是直接赋值。
(*TT)->lchild=(*TT)->rchild=NULL; // 树的左右孩子指针置空。
return 1; // 返回成功。
}
BSTNode *pss=NULL; // 记录双亲结点的地址。
BSTNode *ss=(*TT); // 用于查找的临时指针。
int rl=0; // 记录ss是双亲结点的左子树还是右子树,0-左子树;1-右子树。
while (ss!=NULL)
{
if (*ee==ss->data) return 0; // 如果ee已存在,返回失败。
pss=ss; // 记录双亲结点的地址。
if (*ee<ss->data)
{
ss=ss->lchild;
rl=0;
} // 继续向左走。
else
{
ss=ss->rchild;
rl=1;
} // 继续向右走。
}
// 分配新结点。
ss=(BSTree)malloc(sizeof(BSTNode));
memcpy(&ss->data,ee,sizeof(ElemType)); // 考虑数据元素ee为结构体的情况,采用memcpy而不是直接赋值。
ss->lchild=ss->rchild=NULL; // 当前子树的左右孩子指针置空。
// 让双亲结点的左或右指针指向新分配的结点。
if (rl==0) pss->lchild=ss;
else pss->rchild=ss;
return 1; // 返回成功。
}
Root(T); //返回树T的根的值
char getRoot(BTree T)
{
if(isEmpty(T))//判断二叉树是否为空
{
printf("根结点不存在,无法获取\n");
return ERROR;
}
return T->data; //若二叉树不为空,则返回当前结点的数据域
}
Value(T, e); //返回T中某节点e的值
Find(T, e); //查找值为ee的结点,成功返回结点的地址,失败返回NULL。
// 在树TT中查找值为ee的结点(递归实现),成功返回结点的地址,失败返回NULL。
BSTNode *Find(BSTree TT,ElemType *ee)
{
if (TT==NULL) return NULL; // 查找失败。
if (*ee==TT->data) return TT; // 查找成功。
if (*ee<TT->data) return Find(TT->lchild,ee); // 递归向左查找。
else return Find(TT->rchild,ee); // 递归向右查找。
}
// 在树TT中查找值为ee的结点(非递归实现),成功返回结点的地址,失败返回NULL。
BSTNode *Find1(BSTree TT,ElemType *ee)
{
BSTNode *ss=TT; // 用于查找的临时指针,也可以直接用TT。
while (ss!=NULL)
{
if (*ee==ss->data) return ss; // 成功找到。
if (*ee<ss->data) ss=ss->lchild; // 继续向左走。
else ss=ss->rchild; // 继续向右走。
}
return NULL;
// 以下代码更精简,可以取代以上的代码。
while ( (TT!=NULL) && (*ee!=TT->data) )
{
if (*ee<TT->data) TT=TT->lchild; // 继续向左走。
else TT=TT->rchild; // 继续向右走。
}
return TT;
}
BiTreeEmpty(T); //判断树T是否为空
Status isEmpty(BTree T)
{ //若二叉树的根结点为空,则此树为空
if(T==NULL)
return TRUE;
else
return FALSE;
}
BiTreeDepth(T); //求树T的深度
递归算法:
如果t=NULL,高度为零,
否则,
求左子树的高度,
求右子树的高度,
树的高度为比较高的那棵子树的高度加1。
int getDepth(BTree *T)
{
int deep = 0; //深度计数
if(*T!=NULL)
{ //如果当前结点不为空
int leftdeep = getDepth(&(*T)->leftChild); //则先判断其左子树的深度
int rightdeep = getDepth(&(*T)->rightChild); //然后判断右子树的深度
deep = leftdeep >= rightdeep ? leftdeep+1 : rightdeep+1 ; //二叉树的深度对应左右子树中最大的一个 再加上当前的深度1
}
return deep; //将深度值返回
}
int TreeDepth(BSTree TT)
{
if (TT==NULL) return 0;
int ll=TreeDepth(TT->lchild); // 求左子树的高度。
int rr=TreeDepth(TT->rchild); // 求右子树的高度。
return ll>rr ? ll+1: rr+1; // 取左、右子树的较大者再加上根结点的高度。
}
NodeCount(BiTree T)//结点个数
计算二叉树结点个数的递归算法
int c=0;
void count ( BiTree T )
{
if ( T!= NULL )
{
count(T->lchild);
c++;
count(T->rchild);
}
}
NodeCount(BiTree T)//叶子结点个数
算法:
int c=0;
void count(BiTree T)
{
if(T!=NULL)
{
count(T->lchild);
if(T->lchild==NULL&&T->rchild==NULL)
c++;
count(T->rchild);
}
}
程序:
int NodeCount(BiTree T)
{
if(T == NULL)
return 0;
if(!T->lchild && !T->rchild) //叶子结点
return 1;
else
return NodeCount(T->lchild) + NodeCount(T->rchild);
}
int Delete(BSTree *TT,ElemType *ee) // 在(二叉排列树)树TT中删除值为ee的结点
// 在树TT中删除值为ee的结点,成功返回0,结点不存在返回1。
int Delete(BSTree *TT,ElemType *ee) //删除
{
if ((*TT)==NULL) return 1; // 树为空,返回1。
// 1)如果树只有根结点,并且待删除的结点就是根结点。
if ( ((*TT)->data==*ee) && ((*TT)->lchild==NULL) && ((*TT)->rchild==NULL) )
{
free(*TT);
(*TT)=NULL;//置空,防止误访问
return 0;
}
BSTNode *pss=NULL;// 记录双亲结点的地址。
BSTNode *ss=(*TT);// 用于查找的临时指针。
int rl=0;// 记录ss是双亲结点的左子树还是右子树,0-左子树;1-右子树。
// 查找待删除的结点。
while (ss!=NULL)
{
if (*ee==ss->data)
break; // 成功找到。
pss=ss; // 记录双亲结点的地址。
if (*ee<ss->data)
{
ss=ss->lchild;
rl=0;
} // 继续向左走。
else
{
ss=ss->rchild;
rl=1;
} // 继续向右走。
}
// 结点不存在,返回1。
if (ss==NULL)
return 1;
// 2)如果待删除的结点ss是叶结点,直接删除,不会破坏二叉排序树的性质。
if ( (ss->lchild==NULL) && (ss->rchild==NULL) )
{
free(ss); // 释放结点。
// 修改双亲结点pss的左或右指针指向NULL。
if (rl==0)
pss->lchild=NULL;
else
pss->rchild=NULL;
return 0; // 返回成功。
}
// 3)如果待删除的结点ss只有左子树或右子树,则让子树代替自己。
if ( (ss->lchild==NULL) || (ss->rchild==NULL) )
{
if (ss->lchild!=NULL) // 有左子树。
{
// 修改双亲结点pss的左或右指针指向ss的左子树。
if (rl==0)
pss->lchild=ss->lchild;
else
pss->rchild=ss->lchild;
}
else // 有右子树。
{
// 修改双亲结点pss的左或右指针指向ss的右子树。
if (rl==0)
pss->lchild=ss->rchild;
else
pss->rchild=ss->rchild;
}
return 0; // 返回成功。
}
// 4)如果待删除的结点ss有左子树和右子树,让左子树最右侧的结点代替自己(赋值过去),然后再删除左子树最右侧的结点。
// 也可以让右子树最左侧的结点代替自己,然后删除右子树最左侧的结点。
BSTNode *pss1=ss; // 记录双亲结点的地址。
BSTNode *ss1=ss->lchild; // 用于查找的临时指针。
int rl1=0;// 记录ss1是双亲结点的左子树还是右子树,0-左子树;1-右子树。
// 左子树向右走到尽头。
while (ss1->rchild!=NULL)
{
rl1=1;
pss1=ss1;
ss1=ss1->rchild;
}
// 把左子树最右侧的结点值复制到结点ss中。
memcpy(&ss->data,&ss1->data,sizeof(ElemType));
// 左子树最右侧的结点ss1必定无右子树。
// 修改双亲结点pss1的左或右指针指向ss1的左子树,ss1的左子树可以为空。
if (rl1==0)
pss1->lchild=ss1->lchild;
else
pss1->rchild=ss1->lchild;
free(ss1); // 释放ss1。
return 0;
}