双亲表示法、孩子表示法、孩子兄弟表示法(二叉树表示法),森林和二叉树的转换

一、双亲表示法

实现:定义数组结构存放树的结点,每个结点含两个域:
数据域:存放结点本身数据信息。
双亲域:指示本结点的双亲结点在数组中的位置。

结点结构表示为:

dataparent

data是数据域,parent是指针域
例如:
在这里插入图片描述
可表示为:
在这里插入图片描述
r=0,n=10.(根结点位置和结点个数)

这样的存储结构,根据结点parent指针很容易找到它的双亲结点,所用时间复杂度为O(1),直到parent为-1时,找到了树的根结点,但是我们要知道结点的孩子是什么,对不起,请遍历整个结构才行。

所以双亲表示法特点是:找双亲容易,找孩子难。

结点结构:

typedef struct PTNode{
	int data;
	int parent;//双亲位置域
}PTNode;

树结构定义:

#define MAX_TREE_SIZE 100
typedef struct{
	PTNode nodes[MAX_TREE_SIZE];
	int r,n;//根结点的位置和结点个数
}PTree;

我们可以把结点扩展来解决找孩子结点的问题,但存储结构的设计是一个非常灵活的过程。一个存储结构设计的是否合理,取决于基于该存储结构的运算是否适合、是否方便,时间复杂度好不好等等。

二、孩子表示法(孩子链表)

具体办法是:把每个结点的孩子结点排列起来,看成是一个线性表,以单链表作存储结构,则n个结点有n个孩子链表(叶子结点的孩子链表为空),然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中,如图所示
在这里插入图片描述

为此,设计两种结点结构,一个是孩子链表的孩子结点:

childnext

child是数据域,用来存储某个结点在表头数组中的下标,next为指针域,存储指向某结点的下一个孩子结点的指针。

另一个是表头数组的表头结点,也就是双亲结点:

datafirst child

其中,data是数据域,firstchild是头指针域,存储该结点的孩子链表的头指针。

孩子表示法的结构定义代码:

孩子结点结构


typedef struct CTNode{//孩子结点
	int child;
	struct CTNode *next;	
}*ChildPtr;

表头结构(双亲结构)

typedef struct		//表头结构
{		
	int data;
	ChildPtr firstchild;
}CTBox;//孩子链表

树结构:

#define MAX_TREE_SIZE 100
typedef syruct		//树结构
{
	CTBox nodes[MAX_TREE_SIZE];//结点数组
	int n,r;//结点数和根结点的位置
}CTree;

特点:找孩子易,找双亲难

这样的结构对于我们要查找某个结点的某个孩子,或者找某个结点的兄弟,只需要查找这个结点的孩子单链表即可。对于遍历整棵树也是很方便的,对头结点的数组循环即可。


但是,要找双亲结点却很麻烦,我们可以把双亲表示法和孩子表示法综合一下,称为双亲孩子表示法,算是孩子表示法的改进。

我们在表头结构中新增加一个成员即可,存储双亲下标。为了多存储双亲,操作方便,我们牺牲了空间。
在这里插入图片描述

三、孩子兄弟表示法(二叉树表示法、二叉链表表示法)

任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们用二叉链表作树的存储结构,链表中每个结点的两个指针域分别指向第一个孩子结点此结点的下一个兄弟结点
,根结点没有兄弟,置为空。
结点结构如表:


画的有些丑,将就着看哈~(最右边是nextsibling,写错了)

//树的孩子兄弟表示法结构定义
typedef struct CSNode 
{
	int data;
	struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;//结点类型和指向结点类型的指针,指针表示孩子兄弟表示的二叉链表结构

举例:
在这里插入图片描述

这种表示法,给查找某个结点的某个孩子带来方便,当然想找到结点的双亲,这个表示法也是有缺陷的,所以真的有必要的话,完全可以再增加一个parent指针域来解决快速查找双亲的问题,大家试试吧

此表示法的最大好处就是把一棵复杂度的树变成了一颗二叉树。


四、森林(树)和二叉树的转换

1)将树转化为二叉树进行处理,利用二叉树的算法来实现对树的操作
2)由于树和二叉树都可以用二叉链表作存储结构,则以二叉链表作媒介可以导出树与二叉树之间的一个对应关系。

  1. 将森林转换成二叉树
    1)加线:在兄弟之间加一连线
    2)抹线:对每个结点,除了其左孩子外,去除其与其余孩子之间的关系
    3)旋转:以树的根结点为轴心,将整数顺时针转45度。
    记忆口诀:兄弟相连留长子
    过程如下:加线->抹线->旋转
    在这里插入图片描述

  2. 将二叉树转换为森林
    步骤:
    1)加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的右孩子… …沿着分支找到所有的右孩子,都与p的双亲用线连起来
    2)抹线:抹掉原二叉树中双亲与右孩子之间的连线
    3)调整:将结点按层次排列,形成树结构

记忆口诀:左孩右右连双线,去掉原来右孩线
过程如下:加线->抹线->调整(旋转)
在这里插入图片描述


内容参考:
《数据结构》严蔚敏

  • 133
    点赞
  • 578
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
以下是使用双亲-孩子链表表示法实现树的各算法,和树与二叉树转换算法的代码,包括主函数测试: ```c #include <stdio.h> #include <stdlib.h> #define MAX_TREE_SIZE 100 // 树的最大结点数 #define MAX_QUEUE_SIZE 100 // 队列的最大容量 // 双亲-孩子链表结点结构体 typedef struct CTNode { int child; // 孩子结点的索引 struct CTNode *next; // 指向下一个孩子结点的指针 } CTNode, *ChildPtr; // 双亲-孩子链表头结点结构体 typedef struct { int data; // 结点数据 int parent; // 双亲结点的索引 ChildPtr firstChild; // 指向第一个孩子结点的指针 } CTBox; // 双亲-孩子链表树结构体 typedef struct { CTBox nodes[MAX_TREE_SIZE]; // 结点数组 int r, n; // 根结点的索引和结点数 } CTree; // 初始化双亲-孩子链表树 void InitTree(CTree *T) { int i; for (i = 0; i < MAX_TREE_SIZE; i++) { T->nodes[i].parent = -1; T->nodes[i].firstChild = NULL; } T->r = -1; T->n = 0; } // 向双亲-孩子链表树中插入结点 void InsertNode(CTree *T, int i, int j) { ChildPtr p = (ChildPtr) malloc(sizeof(CTNode)); p->child = j; p->next = T->nodes[i].firstChild; T->nodes[i].firstChild = p; T->nodes[j].parent = i; T->n++; } // 遍历双亲-孩子链表树 void TraverseTree(CTree *T) { int i, j; ChildPtr p; printf("Tree:\n"); for (i = 0; i < T->n; i++) { printf("%d: %d -> ", i, T->nodes[i].data); p = T->nodes[i].firstChild; while (p != NULL) { j = p->child; printf("%d -> ", T->nodes[j].data); p = p->next; } printf("NULL\n"); } } // 双亲-孩子链表树转换二叉树 void CTreeToBiTree(CTree *T, int i, int *j, BiTree *p) { ChildPtr q; int k; q = T->nodes[i].firstChild; if (q == NULL) { *p = NULL; } else { CTreeToBiTree(T, q->child, &k, p); *j = k; while (q->next != NULL) { q = q->next; CTreeToBiTree(T, q->child, &k, &((*p)->rchild)); (*p)->rchild = (k == -1) ? NULL : (BiTree) k; } } } // 二叉树转换为双亲-孩子链表树 void BiTreeToCTree(BiTree T, int i, int *j, CTree *p) { if (T == NULL) { *j = -1; } else { p->nodes[i].data = T->data; p->nodes[i].parent = -1; (*j)++; BiTreeToCTree(T->lchild, *j, j, p); if (*j != -1) { InsertNode(p, i, *j); } BiTreeToCTree(T->rchild, *j, j, p); } } // 层序遍历二叉树 void LevelOrderTraverse(BiTree T) { BiTree Q[MAX_QUEUE_SIZE]; int front = 0, rear = 0; if (T != NULL) { Q[++rear] = T; } while (front != rear) { BiTree p = Q[++front]; printf("%d ", p->data); if (p->lchild != NULL) { Q[++rear] = p->lchild; } if (p->rchild != NULL) { Q[++rear] = p->rchild; } } printf("\n"); } int main() { CTree T; BiTree B; int j = -1; InitTree(&T); T.r = 0; T.n = 10; T.nodes[0].data = 0; T.nodes[1].data = 1; T.nodes[2].data = 2; T.nodes[3].data = 3; T.nodes[4].data = 4; T.nodes[5].data = 5; T.nodes[6].data = 6; T.nodes[7].data = 7; T.nodes[8].data = 8; T.nodes[9].data = 9; InsertNode(&T, 0, 1); InsertNode(&T, 0, 2); InsertNode(&T, 1, 3); InsertNode(&T, 1, 4); InsertNode(&T, 2, 5); InsertNode(&T, 3, 6); InsertNode(&T, 3, 7); InsertNode(&T, 5, 8); InsertNode(&T, 5, 9); TraverseTree(&T); CTreeToBiTree(&T, 0, &j, &B); printf("Binary Tree:\n"); LevelOrderTraverse(B); BiTreeToCTree(B, 0, &j, &T); TraverseTree(&T); return 0; } ``` 在该代码中,我们首先定义了双亲-孩子链表结点结构体 CTNode 和双亲-孩子链表头结点结构体 CTBox,然后定义了双亲-孩子链表树结构体 CTree。在 InitTree 函数中,我们将树中所有结点的双亲结点索引设置为 -1,孩子结点指针设置为 NULL,根结点的索引设置为 -1,结点数设置为 0。在 InsertNode 函数中,我们向树中插入一个孩子结点,并建立孩子结点与双亲结点之间的关系。在 TraverseTree 函数中,我们遍历整个树,输出每个结点的数据以及它的孩子结点。在 CTreeToBiTree 函数中,我们将双亲-孩子链表树转换二叉树,首先遍历树的第一个孩子结点,得到它的索引 k,然后遍历它的兄弟结点,为每一个兄弟结点创建一个右孩子,并递归地将它的子树转换二叉树。在 BiTreeToCTree 函数中,我们将二叉树转换为双亲-孩子链表树,首先为每个结点创建一个对应的双亲-孩子链表结点,并递归地将左子树和右子树插入到它们的双亲结点中。在 LevelOrderTraverse 函数中,我们使用队列实现层序遍历二叉树。最后,在主函数中,我们创建了一个双亲-孩子链表树,并向其中插入一些结点,然后遍历该树,将它转换二叉树,输出二叉树,并将二叉树转换为双亲-孩子链表树,输出双亲-孩子链表树。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值