(一)树的基本概念
定义:
节点的度:节点的子树个数
树的度:节点度的最大的度
高度:就是深度
深度:根节点的深度为1,下面每一层深度加1
分支节点:度不为0的节点
叶节点:度为0的节点
森林:0个或者多个不相交的树
有序树:各子树的顺序有关
无序树:各子树的顺序无关
性质:
树的节点的个数 = 树的所有节点度的个数求和 + 1;
层数为n的树的节点总个数:最多的情况为:满m叉树的情况
以及树的第n层的节点个数 最多的情况为:m的(n-1)次方
(二)二叉树
1.二叉树的定义及其主要特征
二叉树:每个节点的度最多为2
主要特征:
1.第i层上的节点个数最多为 2的(i-1)次方
2.n层二叉树 节点个数最多为2的i次方-1
3.二叉树的度为2的节点 + 1 = 度为0的节点
2.二叉树的顺序存储结构
完全二叉树的情况,可以采用数组,根节点下标为1,节点为i的左子树下标为2*i,右子树下标为2*i+1,父节点为 i/2 (向下取整)。
3.二叉树的链式存储结构
typedef strcut BitNode{
int x;
struct BitNode* lchild, * rchild;
}BiTNode,*BitTree;
4.二叉树的遍历
1.先序遍历
void PreOrderTraverse(BitTree root)
{
visit(root);
if(root->lchild) PreOrderTraverse(root->lchild);
if(root->rchild) PreOrderTraverse(root->rchild);
}
void PreOrderTraverse(BitTree root)
{
stack<BiTNode*> S;
S.push(root);
BitNode *p = root;
while(p || !S.empty()) {
while(p) {
visit(p);
S.push(p);
p = p->lchild;
}
p = S.top();
S.pop();
p = p->rchild;
}
}
2.中序遍历
void InOrderTraverse(BitTree root)
{
if(root->lchild) InOrderTraverse(root->lchild);
visit(root);
if(root->rchild) InOrderTraverse(root->rchild);
}
void InOrderTraverse(BitTree root)
{
Stack<int> S;
BitNode* p = root;
while(p) {
S.push(p);
p = p->lchild;
}
p = S.top();
S.pop();
visit(p);
p = p->rchild;
}
3.后序遍历
void PostOrderTraverse(BitTree root)
{
if(root->lchild) PostOrderTraverse(root->lchild);
if(root->rchild) PostOrderTraverse(root->rchild);
visit(root);
}
void PostOrderTraverse(BitTree root)
{
stack<BitTree> S;
BitNode* p = root;
while(p || !S.empty())
{
while(p) {
S.push(p);
p = p->lchild;
}
p = S.top();
if(p->rchild&&p->rchild != r)
{
p = p->rchild;
}
else {
visit(p);
S.pop();
r = p;
p = null;
}
}
}
4.层次遍历
void LevelTraverse(BitTree root)
{
Queue<BitNode*> Q;
Q.push(root);
BitNode* temp;
while(!Q.empty())
{
temp = Q.front();
visit(temp);
Q.pop();
if(temp->lchild) Q.push(temp->lchild);
if(temp->rchild) Q.push(temp->rchild);
}
}
4.线索二叉树的基本概念和构造
(三)树、森林
1.树的存储结构
1.孩子链表表示法,采用顺序结构+链式结构。
优点:方便找到某一个节点的所有孩子节点
缺点:要找到某一个孩子节点父节点,需要遍历每个节点后跟的链式结构
typedef struct childNode
{
int no;
struct childNode* next;
};
typedef struct Node
{
int value;
childNode* firstchild;
};
typedef struct CTree
{
Node[MAX_SIZE] ;
int n,r;
}CTree;
2.双亲表示法
优点:方便找到父节点
缺点:不方便找到某一个节点的子节点
typedef struct {
int value;
int parent;
}Node;
typedef struct {
Node nodes[MAX_SIZE];
int n;
}PTree;
3.孩子兄弟表示法(也称为二叉树表示法)
优点:方便找到孩子节点
缺点:不方便找到某一个节点的父节点,可以加上一个parent节点进行改进
typedef struct CSNode {
int data;
struct CSNode* firstchild,*nextsibling;
}CSNode,*CSTree
2树、森林与二叉树的转换
1.树与二叉树的转换:
采用孩子-兄弟表示法,节点的左lchild表示第一个孩子,rchild实际是该节点的第一个右兄弟。
2.森林与二叉树的转换:
由于每个树转换成二叉树的根节点,是没有rchild的,因此将不同的树的根节点连接到前一个树的根节点的rchild上。将二叉树转换成森林时,切分掉每个根节点的rchild变成树,再用树与二叉树的转换。
3.树和森林的遍历
1.对树的遍历:
先根遍历:先遍历根,在遍历子树
后根遍历:先遍历子树,再遍历根
2.对森林的遍历:
先序遍历森林:对每个树进行先序遍历,等价于对森林转换成的二叉树进行先序遍历
中序遍历森林:对每个数进行后序遍历,等价于对森林转换成的二叉树进行中序遍历
(四)树和二叉树的应用
1.二叉搜索树:
折半查找会画二叉搜索树。一串数值按照大小顺序排好,根节点为中数,根节点的左节点为中数 左边的中数,根节点的右节点为中数 右边的中数,这样下来直观的效果是左子树的上的所有值都小于根节点,右子树上的所有值都大于根节点。若采用中序遍历,得到的是有序的序列。
二叉搜索树的插入:插入的只能是叶子结点,首先根据插入值与原来的值的左右子树值大小进行判断,不断搜索到叶子结点,发现没有匹配到,则接下来根据该值与该叶子节点的大小,将该值插入到该叶子结点的左子树或者右子树上。(需要两个指针,一个指针指向当前遍历的指针,另一个指针为该节点的父节点指针)
二叉搜索树的删除:1.删除叶子结点,有一个指针记录该叶子结点的父节点,直接删除掉父节点的lchild或者rchild。
2.删除度为1的节点,该度为1的节点指针记为p,其父节点记为pa,若p是pa的右孩子,则将p的子树当做pa的右孩子;若p是pa的左孩子,则将p的子树当做pa的左孩子
3.删除度为2的节点,该度为2的节点指针记为r,第一种方法是从r的所有左子树中选择一个值最大的节点(该节点肯定没有右子树),覆盖掉节点r,然后按照“删除度为1或0的节点”的方法删除该节点;第二种方法是从r的所有右子树中选择一个值最小的节点,覆盖掉节点r,然后按照“删除度为1或者0的节点”的方法删除掉该节点。
2.平衡二叉树
平衡二叉树的调整:
万能方法:写出中序遍历,然后按照中序遍历画出二叉搜索树。
一般方法,分为四种情况RR型、LL型、RL型、LR型
3.霍夫曼树和霍夫曼编码
给定每个叶子结点的权重,要求到所有叶子结点的路径长度和最小。
应用场景:霍夫曼编码。
4.并查集
主要是在图中的应用,相互连通的节点共用一个节点作为根节点,这些节点都看做该根节点的孩子。
应用场景:
1.使用邻接矩阵看无向连通图的连接性。合并两个节点,不断为其root重新赋值,若最后所有的节点的root为一个值,那么说明连通;否则有几个root就有几个连通分量。
2.最小生成树的克鲁斯卡尔算法:为防止有环产生,不在root相同的两个节点之间加边(不合并root相同的两个节点)