数据结构(树和二叉树)

一、树

6.1.1 树的知识

1.树的定义

(1)树是由n(n\geq 0)个结点组成的有限集合,当n=0时,该树为空树。

(2)非空树特征

1)有且仅有一个称为根结点的结点。

2)当n\geq 1时,除根结点以外的其余结点可以划分为m(m> 0)个互不相交的有限子集,每个子集又是一棵树称为根结点的子树,

3)树的定义是递归的。

4)树是一种非线性结构,每个结点可以有0个或多个后继,但有且仅有一个前驱。

5)若非空树中,子树出现相交构成回路的情况,则不属于树形结构

图示:

2.树的基本术语

(1)图示部分

(2)其余部分

1) 路径和路径长度

从根结点或子树的根结点到某结点的途径,其中经过的边数称为路径长度。

2)有序树(左)和无序树(右)

 3)森林

m(m\geq 0)棵互不相交的树的集合,当m=1时森林变为树,m=0时为空森林。

3.树的存储结构

(1)多叉链表

注: 多叉链表使用链式存储结构,包含一个数据域和若干指针域。若有n个结点,树的度为k则一共需要nk个指针域,其中有n-1个非空指针域。

(2)双亲表示法

注:树中的每个结点有且仅有一个双亲结点,使用顺序存储,树中的每个结点对应一个二元组(data,parent).

(3)孩子链表

注:将每个孩子结点排列,看成是一个一个的线性表,然后使用单链表进行存储。其采用的是链式和顺序存储结构的结合线性表存储孩子结点数据信息称为结点表(只存储下标),每个结点建立一个孩子链表。

(4)孩子兄弟表示法

注:三域表示,除去数据域外,其余两个指针域的指针分别指向该结点的第一个孩子结点和右兄弟结点。

二、二叉树

6.1.2 二叉树的知识

1.二叉树的定义

(1)二叉树是由n(n\geq 0)个结点组成的有限集合,该树可以为空树,也可以由一个根结点和两棵互不相交的被称为左子树和右子树的二叉树组成

(2)二叉树的特征

1)每个结点的度只可能是0,1,或者2.

2)二叉树是有序的,即使某结点只有一棵子树要区分是左子树函数右子树

3)满二叉树

满二叉树只有度为0或者2的结点,且叶子结点只可能出现在最底层,编号是自上而下,从左到右。

图示

4)完全二叉树

在满二叉树中从最后一个结点开始连续的去掉任意个结点即位一棵完全二叉树,如果有度为1的结点,则只可能有一个且该结点只能做孩子,叶子结点值可能出现在最底两层。

图示

 

 2.二叉树的性质

(1)非空二叉树中,i层最多有2^{i-1}个结点(i\geq 1)

(2)高度为h的二叉树中结点总数最多为2^{h}-1

(3)设某二叉树的叶子结点数为n_{0}度为2的节点数为n_{2}n_{0}=n_{2}+1

(4)具有n个结点的完全二叉树的高度为\left \lfloor log_{2} n\right \rfloor+1

(5)对有n个结点的完全二叉树编号后,第i个结点(1\leq i\leq n) 的编号有以下性质:

1)i> 1,则i的双亲编号为\left \lfloor i/2 \right \rfloor

2)2i<n,左孩子编号为2i否则左孩子不存在

3)2i+1<n,则i的右孩子编号为2i+1,否则i无右孩子

3.二叉树的存储结构

(1)顺序存储结构

一组连续的存储单元来存放二叉树的结点

 

对于非完全二叉树,只需要添加虚结点使其形成逻辑上的完全二叉树,对于非完全二叉树来说,顺序存储空间浪费不多,但当二叉树的深度越深时就会产生较大的空间浪费。

(2)链式存储

三域组成:左孩子lchild,双亲结点数据域data,右孩子rchild。

 

 

 注:若某二叉树有n个结点,则有2n个指针域。其中有n-1个指针域不为空,n+1个指针域为空。

 4.二叉树的基本操作

(1)二叉树的遍历

 遍历时有三种方式分别是先序遍历(DLR),中序遍历(LDR)以及后序遍历(LRD),其中D,L,R分别表示根结点,左子树,右子树。

1)先序遍历:从根结点开始访问,之后先访问左子树,然后再遍历左子树的左子树,当左子树的左子树为空时,访问左子树的右子树。等左子树所有结点全部访问完成之后,开始访问根结点的右子树,然后再访问右子树的左子树,当左子树为空时,遍历右子树。直到二叉树的所有结点按照此类规则全部访问完为止。

先序遍历如上图所示的二叉树结果为:abdecf。

算法对应(只展示部分,结尾处有完整的代码和举例)

void PreOrder(BiTree T) {
    if (T) {
        cout << T->data << " ";
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

2)中序遍历:从二叉树左子树的最深的一棵左子树开始,每一棵子树都遵循 LDR的访问规则。即先访问子树的左结点,其次访问子树的根结点,最后访问子树的右结点。直到二叉树的所有结点按照此类规则全部访问完为止。

中序遍历如上图所示的二叉树结果为:dbeafc。 

算法对应(只展示部分,结尾处有完整的代码和举例)

void InOrder(BiTree T) {
    if (T) {
        InOrder(T->lchild);
        cout << T->data << " ";
        InOrder(T->rchild);
    }
}

3)后序遍历:从二叉树左子树的最深的一棵左子树开始,每一棵子树都遵循 LRD的访问规则。即先访问子树的左结点,其次访问子树的右结点,最后访问子树的根结点,直到二叉树的所有结点按照此类规则全部访问完为止。

 

后序遍历如上图所示的二叉树结果为:debfca。 

 算法对应(只展示部分,结尾处有完整的代码和举例)

void PostOrder(BiTree T) {
    if (T) {
        PostOrder(T->lchild);
        PostOrder(T->rchild);
        cout << T->data << " ";
    }
}

(2)二叉树的建立 

由以上所述的单个遍历均不能建立二叉树,其原因是因为存在了空的结点,只需要使用特殊字符对空的结点填入即可。

void CreateBiTree(BiTree& T) {
    char ch;
    cin >> ch;
    if (ch == "#") T = NULL;
    else {
        T = new BiTNode();
        T->data = ch;
        CreateBiTree(T->lchild);
        CreateBiTree(T->rchild);
    }
}

(3)二叉树的销毁

利用后序遍历将“访问”转换成“释放空间”

void DestroyBiTree(BiTree &T) {
    if (T) {
        DestroyBiTree(T->lchild);
        DestroyBiTree(T->rchild);
        delete T;
        T = NULL;
    }
}

三、树,森林,二叉树的转换

6.1.3转换 

1.树与二叉树

 (1)树转二叉树(兄弟相连留长子

1)连线:兄弟结点全部连线

2)去线:树中的每个结点,只保留它与第一个孩子结点的连线,删去其与其他孩子之间的连线。

3)调整:

4)图示

 

注:转换得到的二叉树的根结点的右孩子必然为空 

 (2)二叉树转树(左孩右右连双亲,去掉原来右孩线)

1)连线:某结点是其双亲结点的左孩子,则把该结点的右孩子,右孩子的右孩子....与该结点的双亲结点用线相连

2)去线:删除元二叉树中所有的双亲结点与右孩子的连线。

3)调整:

4)图示

 

(3)树的遍历

树的遍历中,树的先序遍历与二叉树的先序遍历一致,但树的遍历中不存在树的中序遍历。

树的后序遍历对应二叉树中序遍历。

2.森林和二叉树的转换 

(1)森林转二叉树(森变二叉根相连)

1)将森林的每棵树都转换成二叉树

2)第一棵二叉树不动从第二棵二叉树开始依次将后一棵二叉树的根结点作为前一棵二叉树的右孩子,最后将所有二叉树相连即可。

3)图示

(2)二叉树转森林

1)二叉树转化为森林的方法与二叉树转化为树的方法一致

2)图示

(3)森林的遍历

森林的遍历分为先序和中序遍历,其先序和中序遍历都与二叉树的先序和中序遍历一致。

四、总结

1.树和二叉树的唯一区别是,当二叉树只存在唯一一棵子树时需要严格区分是左子树函数右子树

2.在二叉树中,度为0的结点数永远比度为2的结点数多1,并且满二叉树不存在度为1的结点数。

3.树的基本术语同样使用于二叉树

4,.其余大多已包含在上述中,如有需求请查看目录寻找。

5.二叉树的遍历操作以及全代码:

#include <stdio.h>  
#include <stdlib.h>  
  
// 定义二叉树结点结构体  
typedef struct Node {  
    int data;               // 结点数据  
    struct Node* left;       // 左子树结点指针  
    struct Node* right;      // 右子树结点指针  
} Node;  
  
// 创建新结点  
Node* createNode(int data) {  
    Node* newNode = (Node*)malloc(sizeof(Node));  
    newNode->data = data;  
    newNode->left = NULL;  
    newNode->right = NULL;  
    return newNode;  
}  
  
// 构建二叉树  
Node* createTree() {  
    Node* root = createNode(1);   // 创建根结点  
    root->left = createNode(2);   // 创建左子树结点  
    root->right = createNode(3);  // 创建右子树结点  
    root->left->left = createNode(4);   // 创建左子树的左子树结点  
    root->left->right = createNode(5);   // 创建左子树的右子树结点  
    return root;                       // 返回根结点指针  
}  
  
// 先序遍历二叉树(输出结点值)  
void PreOrderTraversal(Node* root) {  
    if (root != NULL) {  
        printf("%d ", root->data);   // 输出根结点值  
        PreOrderTraversal(root->left);   // 遍历左子树  
        PreOrderTraversal(root->right);  // 遍历右子树  
    }  
}  
  
// 中序遍历二叉树(输出结点值)  
void InOrderTraversal(Node* root) {  
    if (root != NULL) {  
        InOrderTraversal(root->left);   // 遍历左子树 
        printf("%d ", root->data);   // 输出根结点值   
        InOrderTraversal(root->right);  // 遍历右子树  
    }  
}  

// 后序遍历二叉树(输出结点值)  
void PostOrderTraversal(Node* root) {  
    if (root != NULL) {  
        PostOrderTraversal(root->left);   // 遍历左子树  
        PostOrderTraversal(root->right);  // 遍历右子树
        printf("%d ", root->data);   // 输出根结点值  
    }  
}  


int main() {  
    Node* root = createTree();   // 构建二叉树  
    printf("Pre-order traversal of binary tree: ");  
    PreOrderTraversal(root);   // 先序遍历输出结点值  
    printf("\n");
    printf("In-order traversal of binary tree: ");  
    InOrderTraversal(root);   // 先序遍历输出结点值  
    printf("\n");
    printf("Post-order traversal of binary tree: ");  
    PostOrderTraversal(root);   // 先序遍历输出结点值  
    printf("\n");  
    return 0;  
}

以上便是树与二叉的基本要点以及图示,但该张还未完结,后续会进行补充和一些知识的拓展以及我的一些见解来帮助大家更好的理解。 

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值