一、树
1.1 树的定义
树是n (n ≥ 0)个节点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:
- 1)有且仅有一个特定的称为根的结点。
- 2)当n >1时,其余节点可分为m (m > 0)个互不相交的有限集T1,T2,…, Tm,其中每个集合本身又是一棵树,并且称为根的子树。也就是,任意一个孩子部分也要是一个树。
树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:
- 1)树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
- 2)树中所有结点可以有零个或多个后继。
1.2 树的结构
树结点的数据结构
typedef char BiElemType;
typedef struct BiTNode
{
BiElemType c;
struct BiTNode * lchild;
struct BiTNode *rchild;
}BiTNode,*BiTree;
二、二叉树
2.1 二叉树的定义
二叉树是另一种树形结构,其特点是每个结点至多只有两棵子树(即二叉树中不存在度大于2的结点),并且二叉树的子树有左右之分,其次序不能任意颠倒。度,我们在这里可以理解为个数,即从顶点伸出几个结点就是几个度
与树相似,二叉树也以递归的形式定义。二叉树是n (n ≥0)个结点的有限集合:
①或者为空二叉树,即n = 0。
②或者由一个根结点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树。
2.2 二叉树的结构
满二叉树:
完全二叉树:每一层必须都放满,才可以放下一层,而且下一层放的时候必须是从左到右放。
2.3 二叉树的顺序存储
2.4 二叉树的链式存储
2.4 二叉树的遍历
2.4.1 前/先序遍历(先打印根)为深度优先遍历
//前序遍历,递归实现
void preOrder(BiTree p)
{
if(p!=NULL)
{
putchar(p->c);//等价于visit函数,先打印访问的结点,
preOrder(p->lchild);
preOrder(p->rchild);
}
}
2.4.2 中序遍历(中间打印根)为广度优先遍历
//中序遍历,递归实现
void InOrder(BiTree p)
{
if(p!=NULL)
{
InOrder(p->lchild);
putchar(p->c);
Inorder(p->rchild);
}
}
中序遍历,非递归,里面用到的函数是已经提前定义好的,如有需要请点击此链接:栈与队列
//中序遍历非递归
void InOrder2(BiTree T)
{
SqStack S;
InitStack(S);BiTree p=T;
while(p||!StackEmpty(S))//逻辑或||
{
if(p)
{ //不断压栈的过程找到了最左边的孩子
//当一个节点不为空,压栈,并取左孩子
Push(S,P);
p=p->lchild;
}
else{//弹出栈中元素,并打印,获取打印元素的右结点
Pop(S,p);putchar(s->c);
p=p->rchild;
}
}
}
2.4.3 后序遍历(最后打印根)
//后序遍历,递归实现
void PostOrder(BiTree p)
{
if(p!=NULL)
{
PostOrder(p->lchild);
PostOrder(p->rchild);
putchar(p->c);
}
}
那么结果怎样写呢?
根据每个子节点都要挨着父节点的道理,且每次只看一个子树,来写结果。
2.4.4 层次遍历/层序遍历/广度优先遍历
需要使用到辅助队列,里面的部分函数已经提前定义,如有需要,请点击以下连接:栈与队列
void LevelOrder(BiTree T)
{
LinkQueue Q;//辅助队列
InitQueue(Q);//初始化队列
BiTree p;
EnQueue(Q,T);//树根入队
while(!IsEmpty(Q))
{
DeQueue(Q,p);//出队当前结点并打印
putchar(p->c);
if(p->lchild!=NULL)//入队左孩子
{
EnQueue(Q,p->lchild);
}
if(p->rchild!=NULL)//入队右孩子
{
EnQueue(Q,p->rchild);
}
}
}
2.5 二叉树的遍历的部分代码
//树结点的结构体
typedef char BiElemType;
typedef struct BiTNode
{
BiElemType c;//c可以理解为data
struct BiTNode * lchild;
struct BiTNode *rchild;
}BiTNode,*BiTree;
//建立链表的结构体
typedef struct tag{
BiTree p;//树的某一个结点的地址值
struct tag *pnext;
}tag_t,*ptag_t;
//前序遍历,递归实现
void preOrder(BiTree p)
{
if(p!=NULL)
{
putchar(p->c);//等价于visit函数,先打印访问的结点,
preOrder(p->lchild);
preOrder(p->rchild);
}
}
//中序遍历
void InOrder(BiTree p)
{
if(p!=NULL)
{
InOrder(p->lchild);
putchar(p->c);
Inorder(p->rchild);
}
}
//后序遍历
void PostOrder(BiTree p)
{
if(p!=NULL)
{
PostOrder(p->lchild);
PostOrder(p->rchild);
putchar(p->c);
}
}
//中序遍历非递归
void InOrder2(BiTree T)
{
SqStack S;
InitStack(S);BiTree p=T;
while(p||!StackEmpty(S))
{
if(p)
{
Push(S,P);
p=p->lchild;
}
else{
Pop(S,p);putchar(s->c);
p=p->rchild;
}
}
}
//层序遍历
void LevelOrder(BiTree T)
{
LinkQueue Q;//辅助队列
InitQueue(Q);//初始化队列
BiTree p;
EnQueue(Q,T);//树根入队
while(!IsEmpty(Q))
{
DeQueue(Q,p);
putchar(p->c);
if(p->lchild!=NULL)
{
EnQueue(Q,p->lchild);
}
if(p->rchild!=NULL)
{
EnQueue(Q,p->rchild);
}
}
}
int main()
{
BiTree pnew;
BiTree tree=NULL;//树根
ptag_t phead=NULL,ptail=NULL,listpnew=NULL,pcur=NULL;//phead 就是队列头,ptail就是队列尾
while(scanf("%c",&c)!=EOF)
{
if(c=="\n")
{
break;
}
pnew=(BiTree)calloc(1,sizeof(BiTNode));//calloc申请空间并对空间进行初始化,赋值为0
//calloc也是申请空间的函数,申请空间的大小是括号里面左边X右边
pnew->c=c;//数据放进去
listpnew=(ptag_t)calloc(1,sizeof(tag_t));//为队列结点申请空间
listpnew->p=pnew;
if(tree==NULL)//如果树根为空
{
tree=pnew;//树根
phead=listpnew;//队列头
ptail=listpnew;//队列尾
pcur=listpnew;
continue;
}
else{
ptail->pnext=listpnew;//新节点放入链表,通过尾插法
ptail=listpnew;//ptail指向队列尾部
}
//pcur始终指向要插入的结点的位置
if(pcur->p->lchild==NULL)//如何把新结点放入树
{
pcur->p->lchild=pnew;//把新结点放到要插入结点的左边
}
else if(pcur->p->rchild==NULL)
{
pcur->p->rchild=pnew;//把新结点放到要插入结点的右边
pcur=pcur->pnext;//左右都放了结点后,pcur指向下一个
}
}
printf("————————————前/先序遍历————————————\n");//先打印当前结点,打印左孩子,打印右孩子
preOrder(tree);
printf("————————————中序遍历————————————\n");
InOrder(tree);
printf("————————————后序遍历————————————\n");
PostOrder(tree);
printf("————————————中序遍历,非递归————————————\n");
InOrder2(tree);
}
二、线索二叉树(大题不会出)
当一个结点的左孩子指针或右孩子指针是空着的,线索二叉树就是不让结点的左右孩子指针空着。
以中序线索二叉树的建立为例。附设指针pre指向刚刚访问过的结点,指针p指向正在访问的结点,即pre指向p的前驱。在中序遍历的过程中,检查p的左指针是否为空,若为空就将它指向pre;检查pre的右指针是否为空,若为空就将它指向p
2.1 线索二叉树定义结构
typedef char ElemType;
typedef struct ThreadNode{
ElemType data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;//相对于二叉树多的
}ThreadNode,*ThreadTree;
以上图的代码如下:
void InThread(ThreadTree &p,ThreadTree &pre)
{
if(p!=NULL)
{
InThread(p->lchild,pre);//递归找树的左孩子
if(p->lchild==NULL)
{
p->lchild=pre;
p->ltag=1;
}
if(pre!=NULL&&pre->rchild==NULL)
{
//pre结点右孩子为NULL,就让其指向后继结点
pre->rchild=p;
pre->rtag=1;
}
pre=p;
InThread(p->rchild,pre)
}
}
解释以下:左图经过中序遍历得到的结果为BDAEC。但是B为第一个结点,是没有前驱节点的。D是没有左右孩子结点,所以前驱结点时B,后继节点是A;E的前驱结点是A,后继结点是C;C没有后继结点,写NULL。
创建线索二叉树
//创建线索二叉树
void CreateInThread(ThreadTree T)
{
ThreadTree pre=NULL;//使用辅助指针pre
if(T!NULL)
{
InThread(T,pre);
pre->rchild=NULL;
pre->rtag=1;
}
}
手工建线索树,总计5个结点;提前知道有几个结点,才可用这种方法
void BuildThreadTree(ThreadTree &T)
{
ThreadTree arr[5];
int i;
for(i=0;i<5;i++)
{
arr[i]=(ThreadTree)malloc(sizeof(ThreadNode));
memset(arr[i],0,sizeof(ThreadNode));
arr[i]->data="A"+i;
}
arr[0]->lchild=arr[1];
arr[0]->rchild=arr[2];
arr[1]->rchild=arr[3];
arr[1]->lchild=arr[4];
T=arr[0];
}
三、二叉排序树(二叉查找/搜索树)
二叉排序树(也称二叉查找树)或者是一棵空树,或者是具有下列特性的二叉树:
1)若左子树非空,则左子树上所有结点的值均小于根结点的值。
2)若右子树非空,则右子树上所有结点的值均大于根结点的值。
3)左、右子树也分别是一棵二叉排序树。
特点:左孩子必须小于中间结点,右孩子必须大于中间结点。
部分代码函数请看上面
3.1 二叉排序树的创建
//创建二叉排序树
void Creat_BST(BiTree &T,KeyType str[],int n)
{
T=NULL;
int i=0;
while(i<n)
{
BST_Insert(T,str[i]);//把某一个结点放入二叉查找树中
i++;
}
}
3.2 二叉排序树的删除
//二叉排序树的删除
void DeleteNode(BiTree &root,KeyType x)
{
if(root==NULL)
{
return;
}
if(root->key>x)//树根如果大于要删除的值
{
DeleteNode(root->lchild,x);
}
else if(root->key<x)
{
DeleteNode(root->rchild,x);
}
else{//查到了删除结点
if(root->lchild==NULL)//左子树为空,右子树直接顶上去
{
BiTree tempNode =root;//给一个临时结点,目的是一会儿要free
root=root->rchild;
free(tempNode);
}
else if(root->rchild==NULL)//如果右子树为空,左子树直接顶上去
{
BiTree tempNode =root;//临时指针
root=root->lchild;
free(tempNode);
}
else//左右子树都不为空
{//一般的策略是左子树的最大数据或右子树的最小数据 代替要删除的结点(这里采用查找左子树最大数据来代替)
//注意,左子树的最大数据指的是要删除结点的左子树下面的最大数据,这时部分左右子树,直接找最大数据
BiTree tempNode =root->lchild;
if(tempNode->rchild!=NULL)
{
tempNode=tempNode->rchild;
}
root->key=tempNode->key;
DeleteNode(root->lchild,tempNode->key)
}
}
}
3.3 二叉排序树的查找
//二叉查找树的查找
BSTNode *BST_Search(BiTree T,KeyType key,BiTree &p)
{
p=NULL;//存储要找的结点的父亲
while(T!=NULL&&key!=T->key)
{
p=T;
if(key<T->key) T=T->lchild;//比当前节点小,就左边找
else T=T->rchild;//比当前节点大,就右边找
}
return T;
}
3.4 二叉排序树的插入
//二叉查找树的插入
int BST_Insert(BiTree &T,KeyType k)
{
if(NULL==T)//T用来表示树根
{
T=(BiTree)malloc(sizeof(BSTNode));//为新节点申请空间
T->key=k;//k是要插入的元素
T->lchild=T->rchild=NULL;
return 1;//代表插入成功
}
else if(k==T->key)
return 0;//发现相同元素,就不插入
else if(k<T->key)//如果插入的元素节点k小于当前节点的值
return BST_Insert(T->lchild,k);//函数调用结束后,左孩子和原来的父亲会关联起来
else
return BST_Insert(T->rchild,k);
}
3.5 二叉排序树的中序遍历
//中序遍历
void InOrder(BiTree T)
{
if(T!=NULL)
{
InOrder(T->lchild);
printf("%3d",T->key);
InOrder(T->rchild);
}
}