数据结构-树

1.树的定义

树是n(n>=0)个结点的有限集。当n = 0时,称为空树。在任意一棵非空树中应满足:

        1)有且仅有一个特定的称为根的结点。
        2)当n>1时,其余节点可分为m(m>0)个互不相交的有限集T1,T2,…,Tm,其中每个集合本身又是一棵树,并且称为根的子树。
        显然,树的定义是递归的,即在树的定义中又用到了自身,树是一种递归的数据结构。树作为一种逻辑结构,同时也是一种分层结构,具有以下两个特点:

1)树的根结点没有前驱,除根结点外的所有结点有且只有一个前驱。
2)树中所有结点可以有零个或多个后继。
因此n个结点的树中有n-1条边。

2.基本术语

631e09741729bdbb8fae0ce6a531e4c2.png

1)祖先:如结点G,根A到结点G的唯一路径上的任意结点称为G的祖先,如,A,B,D;

子孙:同理,结点G也可称为A,B,D的子孙;

双亲:如结点G,路径上最接近结点G的结点D称为G的双亲;

孩子:G则可称为D的孩子;

兄弟:有相同双亲的结点称为兄弟,如G,H,I具有共同的双亲D,即G,H,I称为兄弟;

堂兄弟:双亲在同一层的结点称为堂兄弟,如D,E,F的双亲B和C在同一层,即D,E,F称为堂兄弟;

2)树中一个结点的孩子的个数称为该结点的度,如A的度为2,B的度为1,D的度为3;

树的度:树中结点的最大度数,如上图中树的度为3;

3)分支结点(又称非终端结点):度大于0的结点;

叶子结点(终端结点):度为0的结点,即没有子女的结点;

在分支结点中,每个结点的分支数就是该结点的度;

4)结点的层次,从树根开始定义,根在第一层,它的子结点为第二层,如上图A在第一层,B,C在第二层,D,E,F在第三层,G,H,I,J在第四层,

结点的深度:从根节点开始自顶向下逐层累加;

结点的高度:从叶结点开始自底向上逐层累加;

树的高度:树中结点的最大层数;

5)有序数:树中结点的各子树从左到右是有次序的,不能交换;反之则为无序树;

6)路径:树中两个结点之间的路径是由这两个结点之间所经过的结点序列构成;

路径长度:路径上所经过的边的个数;

7)森林:森林是m(m>=0)棵互不相交的树的集合;

3.树的性质

1)树中结点数等于所有结点的度数和加1,设度为0的结点数为n0,度为1的结点数为n1,……,度为i的结点数为ni,结点数 n = 0*n0+1*n1+...+i*ni;

2)   度为m的树中第i层上至多有gif.latex?m%5E%7Bi-1%7D个结点(i>=1)

3)   高度为h的m叉树至多有%28m-1%29个结点(等比数列前n项和)

4)具有n个结点的m叉树的最小高度为 gif.latex?log_%7Bm%7D%28n%28m-1%29+1%29 向上取整;

T1.树的路径长度是从树根到每个结点的路径长度的总和

根到每个结点的路径长度的最大值应该是树的高度减1;

T2.在一棵度为4的树T中,若有20个度为4的结点,10个度为3的结点,1个度为2的结点,10个度为1的结点,则树T中叶结点的个数为(   82  );

解:根据树的性质1)可知树T的结点数  n  =  4*20+3*10+2*1+1*10+1 = 123;

分支结点的个数(即度不为0的结点的个数)  m = 20 + 10 + 1 +10 = 41;

叶结点的个数为 n - m = 82;

4.二叉树的定义

二叉树是一种有序树,若将其左右子树颠倒,则变成另一棵不同的二叉树,其特点是每个结点至多只有两棵子树(即二叉树的度不超过2,可以为1或者0)

二叉树和度为2的有序树的区别:

① 度为2的树至少有3个结点,而二叉树可以为空;

② 度为2的有序树的孩子的左右次序是相对于另一孩子而言的,若某个结点只有一个孩子,则这个孩子就无需区分其左右次序,而二叉树无论其孩子数是否为2,均需确定其左右次序,即二叉树的结点次序是确定的,而不是相对的;

几种特殊的二叉树:

1)满二叉树:一棵高度为h,且含有gif.latex?2%5E%7Bh%7D-1个结点的二叉树,称为满二叉树,如下图:

edf12962574d5111b851b971c1d9cc7c.png

 对于编号为 i 的结点,若有双亲,则其双亲为 i/2( 向下取整),若有左孩子,则左孩子为2i,

若有右孩子,则右孩子为 2i+1 ;(注意先决条件为若有,也可能没有)

T3.结点按完全二叉树层序编号的二叉树中,第 i 个结点的左孩子的编号为2i (√      ×)

错误 , 第 i 个结点不一定有左孩子;

2)完全二叉树:高为h,有n个结点的二叉树,当且仅当其每个结点都与高度为h的满二叉树中编号为1-n的结点一一对应时,称为完全二叉树,如下图:

     0f274c9958e24dd38d6ef68513eb8f61.png

① 若i<=n/2,则结点i为分支结点,否则是叶子结点;

② 叶子结点只可能在层次最大的两层上出现。对于最大层次中的叶子结点,都依次排列在该层最左边的位置上;

T4.已知一棵完全二叉树的第六层(设根为第一层)有8个叶结点,则该完全二叉树的结点的个数为多少? 答:39  或  111

解:两种可能   

第一种 第六层为最后一层,则结点数为 gif.latex?2%5E%7B5%7D-1&plus;8%20%3D%2039

第二种 第六层为倒数第二层 第六层有 2^5(32)个结点 ,8个叶子结点,则24个分支结点,因此第七层有24*2=48,则总结点个数为 2^6-1+48 = 111个结点

③ 若有度为1的结点,则只可能有一个(即,完全二叉树最多只有一个度为1 的结点),且该结点只有左孩子而无右孩子;

T5.一棵有124个叶子结点的完全二叉树,最多有(248)个结点

解:完全二叉树具有度为2的结点,度为1的结点,度为0的结点,其中度为1的结点有1个或者0个(看下一条性质)

第一种可能,设该完全二叉树有n2个度为2的结点,1个度为1的结点,总结点为n

由树的性质1)可知n = 1 + 2*n2 + 1;又 n = 124 + 1 + n2  ; 解得n2 = 123

n = 248;

第二种可能,设该完全二叉树有n2个度为2的结点,0个度为1的结点,总结点为n

n = 2*n2 + 1 ;  n = 124 + n2;   解得  n = 247;

因此,最多有248个结点

④ 按层序编号后,一旦出现某结点(编号为i)为叶子结点或只有左孩子,则编号大于i的结点均为叶子结点;

⑤ 若n为奇数,则每个分支结点都与左孩子和右孩子;若n为偶数,则编号最大的分支结点(编号为n/2)只有左孩子,没有右孩子,其余分支结点左右孩子都有;

3)二叉排序树:左子树上所有的关键字均小于根结点的关键字;右子树上的所有结点的关键字均大于根结点的关键字;右子树和左子树又各是一棵二叉排序树。

2a4e93e91ad0d43785cc4ee3adde0439.png

 4)平衡二叉树:树上任一结点的左子树和右子树的深度之差不超过1。

5.二叉树的性质

1)非空二叉树上的叶子结点树等于度为2的结点数+1,即gif.latex?n_%7B0%7D%20%3D%20n_%7B2%7D%20&plus;%201

若结点的数量为n,则边的数量为n-1 ;

2)非空二叉树上第k层上最多有gif.latex?2%5E%7Bk-1%7D个结点(k>=1)

3)高为h的二叉树至多有gif.latex?2%5E%7Bh%7D-1个结点(h>=1)

4)对于完全二叉树按从上到下,从左到右的顺序依次编号1,2,3,...,n(注意是从1开始),则有以下关系:

① 当i>1时,结点i的双亲编号为i/2(向下取整),即 i 偶数 双亲 i/2,左孩子 , i 奇数 双亲 (i-1)/2  右孩子

② 当2i<=n时,结点i的左孩子结点为2i,否则无左孩子;

③ 当2i+1<=n时,结点i的右孩子编号为2i+1,否则没有右孩子;

④ 结点i所在层次为gif.latex?log_%7B2%7Di%20&plus;%201 (向下取整)

5)具有n(n>=0)个结点的完全二叉树的高度为gif.latex?log_%7B2%7Dn&plus;1(向下取整),易求;

6.二叉树的存储结构

 1.顺序存储结构

二叉树的顺序存储是指用一组地址连续的存储单元依次自上而下,自左向右存储所有二叉树上的结点元素,如下图:

       45a788db07a44c9d959992fec4f27c5a.png

 依据二叉树的性质,完全二叉树和满二叉树采用顺序存储较为合适,树中结点的序号可以唯一的反映结点之间的逻辑关系,这样既能最大可能地节省存储空间,又能利用数组元素的下标值确定结点在二叉树中的位置,以及结点之间的关系;但是最坏情况下,高为h的且只有h个结点的单支树,却要占据近gif.latex?2%5E%7Bh%7D-1个存储单元。

上图这种存储结构建议从数组下标为1开始,若从0开始,则需要重新考虑性质4

2.链式存储结构

由于顺序存储利用效率较低,因此二叉树一般采用链式存储结构;

二叉链表至少包含3个域:数据域,左指针域和右指针域

b6e4f5bfa4724e01a457a3c0e82999ad.png

 

6cd5abaa805e6d52d0847ad4bc70b63e.png

//二叉树链式存储结构
typedef struct BiTNode{
    ElemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

在含有n个结点的二叉链表中,含有n+1个空链域(也叫空指针)

 

T6.设二叉树有2n个结点,且m<n,则不可能存在(C)的结点

A . n个度为0        B.2m个度为0        C.2m个度为1        D.2m个度为2

解:设度为0的结点数为n0,度为1的结点数为n1,度为2的结点数为n2

2n = n1 + 2 * n2 + 1;观察发现 n1应为奇数故 C选项有2m个度为1 的结点错误;

T7.一棵高度为h的满m叉树有如下性质:根结点所在层次为第一层,第h层上的结点都是叶结点,其余各层上每个结点都有m棵非空子树,若按层次自顶向下,同一层自左向右,顺序从1开始,对全部结点进行编号,试问:

(1)各层的结点个数是多少?

(2)编号为 i 的结点的双亲结点(若存在)的编号是多少?

(3)编号为 i 的结点的第k个孩子结点(若存在)的编号是多少?

(4)编号为 i 的结点有有右兄弟的条件是什么?其右兄弟结点的编号是多少?

6ebd567813ef48228531bc51c4a062e2.jpg

3c5b78f5843745dca39c5cf48b977598.jpg

 

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: (Tree)是一种非线性的数据结构,它由n(n>=0)个结点构成,其中一个结点为根结点,其余结点可分为m(m>=0)个互不相交的子集T1、T2、……、Tm,其中每一个子集本身又是一棵,并称为根的子的特点是:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点之外,每个子结点可以分为多个不相交的子。 下面是一个基本的的结构定义: ```c // 的结构体定义 typedef struct TreeNode { int data; // 数据域 struct TreeNode *firstChild;// 第一个子节点 struct TreeNode *nextSibling;// 兄弟节点 }TreeNode, *Tree; ``` 其中,data表示结点的数据域,firstChild指向该结点的第一个子节点,nextSibling指向该结点的下一个兄弟节点。 下面是一个创建的函数: ```c // 创建 Tree createTree(int data) { Tree root = (Tree)malloc(sizeof(TreeNode)); root->data = data; root->firstChild = NULL; root->nextSibling = NULL; return root; } ``` 下面是一个向中添加子节点的函数: ```c // 添加子节点 void addChild(Tree parent, int data) { Tree child = (Tree)malloc(sizeof(TreeNode)); child->data = data; child->firstChild = NULL; child->nextSibling = NULL; if (parent->firstChild == NULL) parent->firstChild = child; else { Tree p = parent->firstChild; while (p->nextSibling != NULL) p = p->nextSibling; p->nextSibling = child; } } ``` 下面是一个先序遍历的函数: ```c // 先序遍历 void preOrderTraversal(Tree root) { if (root != NULL) { printf("%d ", root->data); preOrderTraversal(root->firstChild); preOrderTraversal(root->nextSibling); } } ``` 下面是一个后序遍历的函数: ```c // 后序遍历 void postOrderTraversal(Tree root) { if (root != NULL) { postOrderTraversal(root->firstChild); postOrderTraversal(root->nextSibling); printf("%d ", root->data); } } ``` 下面是一个层次遍历的函数: ```c // 层次遍历 void levelOrderTraversal(Tree root) { Queue q; initQueue(&q); if (root != NULL) { enQueue(&q, root); while (!isQueueEmpty(&q)) { Tree p = deQueue(&q); printf("%d ", p->data); if (p->firstChild != NULL) enQueue(&q, p->firstChild); if (p->nextSibling != NULL) enQueue(&q, p->nextSibling); } } } ``` 其中,Queue是一个队列结构体。 以上就是一个简单的的实现。 ### 回答2: 是一种常见的数据结构,用于存储具有层次关系的数据。它由节点和边组成,通常包含一个根节点和若干子节点。 在C语言中,我们可以使用结构体来定义的节点。一个最基本的节点结构体可能包含两个成员变量:数据和指向子节点的指针。例如: ``` typedef struct TreeNode { int data; struct TreeNode* left; struct TreeNode* right; } TreeNode; ``` 上述代码定义了一个名为TreeNode的结构体,它包含一个整型数据成员和两个指向左子节点和右子节点的指针。 为了方便操作,我们可以定义一些基本的函数。其中,创建节点的函数可以使用动态内存分配来分配新节点的内存空间。例如: ``` TreeNode* createNode(int data) { TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode)); newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } ``` 我们还可以定义插入节点、删除节点、搜索节点等函数来操作的结构。 除此之外,还可以实现一些遍历的算法,如先序遍历、中序遍历和后序遍历。这些遍历方法可以递归地遍历的节点,并对节点进行指定的操作。 编写的操作函数时,需要考虑到不同的特点,例如二叉搜索要保持左子节点的值小于根节点,右子节点的值大于根节点。 总之,编写一个经典C语言数据结构-需要定义节点的结构体,实现节点的创建、插入、删除和搜索等操作函数,同时可以实现遍历的算法。 ### 回答3: 是一种经典的数据结构,C语言可以很方便地实现的相关操作。首先,我们可以定义一个的节点结构体: ```c typedef struct Node { int data; // 节点存储的数据 struct Node* left; // 左子指针 struct Node* right; // 右子指针 } Node; ``` 接下来,我们可以实现一些的基本操作,例如创建、插入节点、删除节点和遍历。下面是一个简单的示例: ```c // 创建一个节点 Node* createNode(int data) { Node* newNode = (Node*)malloc(sizeof(Node)); newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } // 在中插入一个节点 Node* insertNode(Node* root, int data) { if (root == NULL) { return createNode(data); } else if (data < root->data) { root->left = insertNode(root->left, data); } else if (data > root->data) { root->right = insertNode(root->right, data); } return root; } // 在中删除一个节点 Node* deleteNode(Node* root, int data) { if (root == NULL) { return root; } else if (data < root->data) { root->left = deleteNode(root->left, data); } else if (data > root->data) { root->right = deleteNode(root->right, data); } else { if (root->left == NULL) { Node* temp = root->right; free(root); return temp; } else if (root->right == NULL) { Node* temp = root->left; free(root); return temp; } Node* temp = findMinNode(root->right); root->data = temp->data; root->right = deleteNode(root->right, temp->data); } return root; } // 遍历:前序遍历(中--右) void preOrderTraversal(Node* root) { if (root != NULL) { printf("%d ", root->data); preOrderTraversal(root->left); preOrderTraversal(root->right); } } ``` 这只是的基本实现,还有很多其他操作,例如查找节点、判断是否为空、计算的高度等。可以根据具体需求进行扩展。通过以上实现,我们可以使用经典的C语言来构建和操作结构。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值