搜索结果
Powered by: Simple-Jekyll-Search
面试做算法题时才头疼起来....热热也许还能吃
作为有较高难度且使用相对广泛的一种数据结构,树结构一直是备受算法题青睐的数据结构之一。这一篇是一个简单的树结构的笔记。
不过本篇相对于常规的课本内容,会将更多注意力放在代码实现上。我会用尽可能多的编程语言实现,请期待吧。
这是一个典型的树结构
符号约定:
- $n$:结点数
- $n_i$:度为$i$的结点数,$n_0$即为叶子结点个数
例题:例题1
二叉树可以说是在树类数据结构中最常用、最常考的类型
考虑最多情况:1,2,4,8….等比数列求和而已,套公式得出的就是这个。
证明:
- 设$n_1$为度为$1$的结点数,易知:$n=n_0+n_1+n_2$①
- 设$B$为分支数,则有$n=B+1$(考虑边数,每个结点都有“来自其父节点的边”,除了根节点,所以结点数是分支数+1)
- 而又因为分支由$n_1,n_2$射出,所以$B=n_1+2\times n_2$
- 两式可得$n=n_1+2n_2+1$②
- 根据①,②得$n_0=n_2+1$
也就是除了最下面一层外,其余层都是满的二叉树,并且即使是最下面一层,也是从左到右排列的。
证明:$2^{k-1}-1< n \leq 2^k-1$,两边同取对数就能得到了
证明略
在这种存储结构中,最重要的问题是存储结构中的序号如何与结点的位置对应起来
因为上面刚说过的,完全二叉树天生层次编号的话有规律,就直接按照层次编号存储就好
如果是按照上述存储结构,放弃list[0]
直接从list[1]
开始,那么完全二叉树的顺序存储完全遵循上述层序编号的规律。对于结点$i(i \geq 1)$,其左子结点为list[2*i]
,右子结点为list[2*i+1]
,父结点为list[i//2]
。
普通二叉树
如果硬是要仿照完全二叉树的结构来存储,也不是不行,但对于普通二叉树来说会造成空间的巨大浪费。
所以对于普通二叉树,通常采用链式存储。
注意,本小节以及衍生的遍历方法均为重点。
在树的链式存储中,我们通常是直接存储树的各个结点,每个结点存储其数据元素以及指向其他结点的指针。
通常结点至少要包含“指针域”和“数据域”。指针域即指向
lChild
和rChild
的指针,数据域即该结点的数据,这种存储结构被称为二叉链表。而如果还包括指向父结点的指针,则被称为三叉链表。
C
Version// 二叉链表
typedef struct BiTNode {
TElemType data;
struct BiTNode *lChild = NULL;
struct BiTNode *rChild = NULL;
} BiTNode, *BiTree;
// 三叉链表
typedef struct BiTNode {
TElemType data;
struct BiTNode *lChild = NULL;
struct BiTNode *rChild = NULL;
struct BiTNode *parent = NULL;
} BiTNode, *TriTree;
Python
Version# 二叉链表
class BiTNode:
def __init__(self, data):
self.data = data
self.lChild = None
self.rChild = None
# 三叉链表
class BiTNode:
def __init__(self, data):
self.data = data
self.lChild = None
self.rChild = None
self.parent = None
Java
Version// 二叉链表
class BiTNode {
public BiTNode (TElemType data) {
this.data = data;
this.lChild = null;
this.rChild = null;
}
}
// 三叉链表
class BiTNode {
public BiTNode (TElemType data) {
this.data = data;
this.lChild = null;
this.rChild = null;
this.parent = null;
}
}
遍历二叉树其实是一种递归算法。假如我们遍历的操作只是打印值,那么根据向左走、执行操作、向右走(其实还有一个隐式的回溯)这几个操作的先后顺序,分为前(先)序遍历、中序遍历、后序遍历三种结果。
三种动作的优先级为执行操作(打印)>向左走>向右走。执行结果为
A B D E F G C
三种动作的优先级为向左走>执行操作(打印)>向右走。执行结果为
D B F E G A C
三种动作的优先级为向左走>向右走>执行操作(打印)。执行结果为
D F G E B C A
三种遍历方法的路径其实都是这个,只不过打印的策略有所不同罢了
C
Versionvoid PreOrder(BiTree T)
{
if (T != NULL)
{
printf("%c ", T->data);
PreOrder(T->lChild);
PreOrder(T->rChild);
}
}
void InOrder(BiTree T)
{
if (T != NULL)
{
InOrder(T->lChild);
printf("%c ", T->data);
InOrder(T->rChild);
}
}
void PostOrder(BiTree T)
{
if (T != NULL)
{
PostOrder(T->lChild);
PostOrder(T->rChild);
printf("%c ", T->data);
}
}
Python
Versiondef PreOrder(T: BiTNode):
if T is not None:
print(T.data, end=" ")
PreOrder(T.lChild)
PreOrder(T.rChild)
def InOrder(T: BiTNode):
if T is not None:
InOrder(T.lChild)
print(T.data, end=" ")
InOrder(T.rChild)
def PostOrder(T: BiTNode):
if T is not None:
PostOrder(T.lChild)
PostOrder(T.rChild)
print(T.data, end=" ")
在一棵度数为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T的叶结点个数是()
答案:82
这道题乍一想似乎没有思路,但只要想清楚边和结点的关系就好做了。对于任意一个结点,一个度对应一个子结点,所以除了度为0也就是叶结点外,度应该和其余结点数一致。若以$n_i$表示度为$i$的结点数,从结点数的角度可得$n_0+n_1+n_2+n_3+n_4=n_0+41$;从分支的角度可得$n_1+2\times n_2+3\times n_3+4\times n_4=122$。应满足$n_0+41-1=122$(因为根结点作为一个结点,但却没有父结点指向它的度,也就是说根结点只有结点没有度,所以这里要-1,得到$n_0=82$)