看了一下数据结构中树的相关知识,树这一章还是很重要的,直接说知识点吧
一、基本概念
树:n(n >= 0)个结点的有限集合
结点的度:结点拥有子树的个数
树的度:树中所有度的最大值 (注意是 最大值)
树的深度:树中所有节点层次的最大值
叶子结点:度为0的节点
满二叉树:在深度为K的满二叉树中,1至k-1层每个结点均有两个孩子。顾名思义,也就是除过叶子结点,其他的节点都有左右孩子。
满二叉树的连续编号:从根节点开始,按层从上到下,层内从左到右,逐个对每个结点进行编号1,2,3...,n。
完全二叉树:深度为K,节点数为n (n <= 2^k-1)的二叉树,当且仅当其n个结点与满二叉树中连续编号为1至n的结点位置一一对应时,称为完全二叉树。
满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。
我觉得就这些概念需要注意一下,其他还有许多概念,根据字面意思大家就知道是指什么,这里就不多说了。
二、二叉树的遍历
中序遍历和后序遍历只是那三个的调用顺序不同,根据各自的定义,放在合适的位置就好。递归方法实现确实很简单,缺点是运行效率低,消耗时间和空间资源比较多,所以还是重点说一下非递归的这三种遍历。
其实在大多数的递归问题用非递归解决时,都用到了栈,在二叉树的访问过程中,在第一次访问该结点时,应该保留该结点的信息,保证下次经过时可以使用该结点的信息。就用中序遍历来讲,如果没有保存父母结点的信息当从左子树返回时,就没有办法找到其父母结点的信息。所以,在进入左子树之前保存结点的信息是必要的。先序遍历也是如此。但是后序遍历有些不同,从左子树返回后,要读取栈顶结点的信息,但是不能弹出,因为要先访问右子树再访问父母结点,所以结点信息要继续保留,方便访问完右子树后访问该结点。
1.先序遍历
(1)基本思想
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.访问当前结点,当前结点进栈,进入其左子树,直到当前结点为空。
B.如果栈不为空,则栈顶元素退栈,并进入其右子树。
(2)主要代码
2.中序遍历
(1)基本思想:
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.当前结点入栈,进入其左子树,重复操作,直至当前结点为空。
B.如果栈不为空,栈顶结点退栈,访问栈顶结点,并进入其右子树。
其实与先序遍历不同的就是,先序遍历是第一次访问就访问,而中序遍历是左子树访问完之后再访问。
(2)核心代码
3.后序遍历
后序遍历比较复杂,首先,如何判断是左子树返回的还是右子树返回的,这关系到栈顶的上层结点时否应该出栈。
可以用tag来标记,也可以换一种思路,判断刚返回的是不是当前栈顶的右孩子,如果是右孩子或者当前栈顶的右孩子为空,则需要出栈,访问出栈的p结点,并将p赋给q,q用来记录刚刚被访问过的结点。然后将p赋为空,赋空是防止再次访问该棵树。如果不是,说明其右孩子未被访问,则访问其右孩子。
(1)基本思想
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.当前结点进栈,并进入其左子树,重复该操作直至当前结点为空。
B.若栈不为空,判断栈顶结点p的右孩子是否为空,右子树是否刚被访问。是,则退栈,访问p结点,p赋给q,p置为空。不是则进入其右子树.
(2)核心代码
对于三种遍历算法就总结这么多,其实基本思想知道后,代码写起来很容易,总之还是思想重要啊。一定要懂 了思想,再开始写代码。
一、基本概念
树:n(n >= 0)个结点的有限集合
结点的度:结点拥有子树的个数
树的度:树中所有度的最大值 (注意是 最大值)
树的深度:树中所有节点层次的最大值
叶子结点:度为0的节点
满二叉树:在深度为K的满二叉树中,1至k-1层每个结点均有两个孩子。顾名思义,也就是除过叶子结点,其他的节点都有左右孩子。
满二叉树的连续编号:从根节点开始,按层从上到下,层内从左到右,逐个对每个结点进行编号1,2,3...,n。
完全二叉树:深度为K,节点数为n (n <= 2^k-1)的二叉树,当且仅当其n个结点与满二叉树中连续编号为1至n的结点位置一一对应时,称为完全二叉树。
满二叉树一定是完全二叉树,但是完全二叉树不一定是满二叉树。
我觉得就这些概念需要注意一下,其他还有许多概念,根据字面意思大家就知道是指什么,这里就不多说了。
二、二叉树的遍历
二叉树的遍历有两种方法,一种是递归,另一种是非递归。递归的特别简单,就是在访问顺序上不同,还是先说一下递归吧,就拿先序遍历的来看。
void PreOrder(BiTree root)
{
if(root)
{
Visit(root->data);
PreOrder(root->LChild);
PreOrder(root->RChild);
}
}
中序遍历和后序遍历只是那三个的调用顺序不同,根据各自的定义,放在合适的位置就好。递归方法实现确实很简单,缺点是运行效率低,消耗时间和空间资源比较多,所以还是重点说一下非递归的这三种遍历。
其实在大多数的递归问题用非递归解决时,都用到了栈,在二叉树的访问过程中,在第一次访问该结点时,应该保留该结点的信息,保证下次经过时可以使用该结点的信息。就用中序遍历来讲,如果没有保存父母结点的信息当从左子树返回时,就没有办法找到其父母结点的信息。所以,在进入左子树之前保存结点的信息是必要的。先序遍历也是如此。但是后序遍历有些不同,从左子树返回后,要读取栈顶结点的信息,但是不能弹出,因为要先访问右子树再访问父母结点,所以结点信息要继续保留,方便访问完右子树后访问该结点。
1.先序遍历
(1)基本思想
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.访问当前结点,当前结点进栈,进入其左子树,直到当前结点为空。
B.如果栈不为空,则栈顶元素退栈,并进入其右子树。
(2)主要代码
void PerOrder(BiTree root)
{
SeqStack *s;
BiTree p;
InitStack(s);
p = root;
while(p != NULL || !Empty(s))
{
while(p != NULL)
{
visit(p->data);
push(s,p);
p = p->LChild;
}
if(!Empty(s))
{
pop(s,&p);
p = p->RChild;
}
}
}
2.中序遍历
(1)基本思想:
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.当前结点入栈,进入其左子树,重复操作,直至当前结点为空。
B.如果栈不为空,栈顶结点退栈,访问栈顶结点,并进入其右子树。
其实与先序遍历不同的就是,先序遍历是第一次访问就访问,而中序遍历是左子树访问完之后再访问。
(2)核心代码
void InOrder(BiTree root)
{
SeqStack *s;
BiTree p;
InitStack(s);
p = root;
while(p != NULL || !Empty(s))
{
while(p != NULL)
{
push(s,p);
p = p->LChild;
}
if(!Empty(s))
{
pop(s,&p);
visit(p->data);
p = p->RChild;
}
}
}
3.后序遍历
后序遍历比较复杂,首先,如何判断是左子树返回的还是右子树返回的,这关系到栈顶的上层结点时否应该出栈。
可以用tag来标记,也可以换一种思路,判断刚返回的是不是当前栈顶的右孩子,如果是右孩子或者当前栈顶的右孩子为空,则需要出栈,访问出栈的p结点,并将p赋给q,q用来记录刚刚被访问过的结点。然后将p赋为空,赋空是防止再次访问该棵树。如果不是,说明其右孩子未被访问,则访问其右孩子。
(1)基本思想
从根结点开始,重复下面两步操作,条件为 当前结点不为空 或者 栈不为空。
A.当前结点进栈,并进入其左子树,重复该操作直至当前结点为空。
B.若栈不为空,判断栈顶结点p的右孩子是否为空,右子树是否刚被访问。是,则退栈,访问p结点,p赋给q,p置为空。不是则进入其右子树.
(2)核心代码
void PostOrder(BiTree root)
{
SeqStack *s;
BiTree p,q;
InitStack(s);
p = root;
q = NULL;
while(p != NULL || !Empty(s))
{
while(p != NULL)
{
push(s,p);
p = p->LChild;
}
if(!Empty(s))
{
Top(s,&p);
if((p->RChild == NULL) || (p->RChild == q))
{
pop(s,&p);
visit(p);
q = p;
p = NULL;
}
else
{
p = p->RChild;
}
}
}
}
对于三种遍历算法就总结这么多,其实基本思想知道后,代码写起来很容易,总之还是思想重要啊。一定要懂 了思想,再开始写代码。