树主要是应用题偏多。(算法题只考过了两次,都挺简单的,就是一个遍历;算法题在下一篇)
思维导图
基本概念
养成习惯:区分度和度之和:
结点个数=总度+1
度:指一棵树中,每一个结点度的最大值
链式
链式存储如果考多叉树,只有可能是孩子表示法那一块。
因此,大题只可能是二叉树那块。
遍历
递归遍历很简单。
void preOrder(Node *p)
{
if(p!=NULL)
{
visit(p);
preOrder(p->lchild);
preOrder(p->rchild);
}
}
// 前序、中序、后续遍历,都不难
⭐非递归遍历。
中序和前序是基本一样的,都是1️⃣一路向左入栈左结点,入栈到最后,发现某个点的左孩子空了之后,不能再入栈了之后;2️⃣弹出栈顶,访问:2️⃣.1️⃣如果有左孩子,执行1️⃣;2️⃣.2️⃣如果没有右孩子,执行2️⃣。
void inOrder(BTNode *root)
{
Stack <BTNode>;
InitStack(S);
BTNode p = root;
while(p || isEmpty(S))
{
if(p)
{
push(S,p);
p = p -> lchild;
}
else{
pop(S,p);
visit(p); // 中序遍历,先 -> 根 -> 右
p = p -> rchild;
}
}
}
void preOrder(BTNode *root)
{
Stack <BTNode>;
InitStack(S);
BTNode p = root;
while(p || isEmpty(S))
{
if(p)
{
visit(p); // 先序遍历,根 -> 先 -> 右
push(S,p);
p = p -> lchild;
}
else{
pop(S,p);
p = p -> rchild;
}
}
}
后序遍历需要对某一个子树的根进行一个标志处理。因为后序是左右根。某一个根的某一个子树结束搜索之后,会不知道是哪一个子树退出了栈中。因此,需要额外的处理。
void postOrder(BTNode *root)
{
Stack <BTNode>;
InitStack(S);
BTNode p = root;
while(p || isEmpty(S))
{
if(p)
{
push(S,p);
p = p -> lchild;
}
else{
getTop(S,p);
if(p-> rchild && p->rchild != r)
p = p -> rchild;
else{
pop(S,p)
visit(p);
r = p;
p = NULL;
}
}
}
}
⭐几个需要说明的点:
- 后序遍历中,遍历到某一个点后,其栈中的元素均是该点的祖先
- 后序遍历在非递归遍历时,除了结束的时刻,并不会出现某一时刻的栈空的情况。栈空有且仅有结束时,根结点退栈。
构造
👍🏼给定一个中序序列和一个先序序列,构造该树?
Tree preInCreate(int A[], int B[], int l1, int l2, int h1, int h2)
{// A(l1, h1)先序; B(l2, h2)中序
Node *root = (Node*)malloc(sizeof(Node));
root->data = A[l1];
// 循环,划分
for(i = l2; B[i]!= root->data; i++);
llen = i - l2;
rlen = h2 - i;
// 递归建立左子树
if(llen) // 有左子树的话
root->lchild = preInCreate(A,B,l1+1, l1+llen, l2, l2+llen-1);
else root->lchild = NULL;
// 递归建立右子树
if(rlen) // 有右子树的话
root->rchild = preInCreate(A,B,h1-rlen+1, h1, i+1, h2);
else root->lchild = NULL;
return root;
}
线索二叉树
本质:遍历。
利用空指针域,指向前驱或者是后继。
构造
算法程序:
有pre 有p。先递归左子树;再检查p的左指针是否为空,如果空,则附上;再看pre的右子树是否为空,如果是空,则连到p;再递归右子树。
如果先序、后序就调整一下。【不可能考更深的东西了!】
Node *pre;
void Inthread(Node *p) //中序
{
Inthread(p->lchild);
if(!p->lchild)// 没有左孩子,连上
{
p -> lchild = pre;
p -> ltag = 1;
}
if(! pre -> rchild){
pre -> rchild = p;
pre -> rtag = 1;
}
pre = p;
Inthread(p->rchild);
}
遍历
事实上,线索二叉树可以从两个方向进行遍历:从前到后、从后到前。
从前到后时,我们需要找到后继。
当前结点p后继的存放位置无非就是两个:
- 当前结点p的rtag== 1。此时,(右孩子== 右线索)就是后继。
- 当前结点p的rtag==0。此时,以右孩子为根的子树中,我们需要找到中序遍历的第一个结点。(其实就是右子树的最左下角。)
将其封装为两个函数:
Node *find_next(Node *p)
{
if (p->rtag == 1)
return p -> rchild;
else
return find_first_node(p->rchild);
}
Node *find_first_node(Node p)
{
while(p->ltag == 0) p = p -> lchild;
return p;
}
于是,我们可以得到从前到后的遍历:
void Inorder_thread(Node *T)
{
for(Node *p = find_first_node(T); p!=NULL; p = find_next(p))
visit(p);
}
手动转换线索
其实,就是写出一遍序列,然后画出图,手动连。关键是知道,箭头的方向永远是指向下一个。
并且,能指向NULL的线索,一定是最前一个或者最后一个。
后序线索树的坑爹情况
后序线索树的遍历可能会出现断了的情况,而中序和先序不会。
例如:
这样一个后序线索树,d→b→c→a。但是b→c的线索,却是无的。(因为右孩子接后继结点,而有孩子已经连了d了)。