一、基础概念
1.定义
说人话就是:
长这样的图。
2、术语(说人话版本):
(先规定一般树都是上面小、下面大)
父(母)节点:上面那一个节点
子节点:自己下面那几个节点
节点的度:从自己开始,自己生出来的节点
叶节点或终端节点:没有子节点了的
子孙:子节点都是子孙
节点的祖先:从自己往上走,经过的都可以是祖先
层次:根节点算第一层,儿子算第二层、儿子的儿子算第三层…
兄弟节点:同一个父节点
堂兄弟:父节点在同一层
森林:多个树的集合
3、性质
- 每个节点都只有有限个子节点或无子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
- 树里面没有环路(cycle)
可以简单理解为:一旦分道扬镳,再也没有交集。
举几个反例就知道了:
这个有了交集(D和 E),就不能叫树。
二、表示法
1.双亲表示法
双亲表示法采用顺序表(也就是数组)存储普通树,其实现的核心思想是:顺序存储各个节点的同时,给各节点附加一个记录其父节点位置的变量。
简单地来说,就是运用类似于链表的方法,只不过是用的是数组的朴素方法,记录一下上一个(就是父节点)的数组下标。
#include<stdio.h>
#include<stdlib.h>
#define MAX_SIZE 20
typedef char ElemType;//宏定义树结构中数据类型
typedef struct Snode //结点结构
{
ElemType data;
int parent;
}PNode;
typedef struct //树结构
{
PNode tnode[MAX_SIZE];
int n; //结点个数
}PTree;
PTree InitPNode(PTree tree)
{
int i, j;
char ch;
printf("请输出节点个数:\n");
scanf("%d", &(tree.n));
printf("请输入结点的值其双亲位于数组中的位置下标:\n");
for (i = 0; i < tree.n; i++)
{
getchar();
scanf("%c %d", &ch, &j);
tree.tnode[i].data = ch;
tree.tnode[i].parent = j;
}
return tree;
}
void FindParent(PTree tree)
{
char a;
int isfind = 0;
printf("请输入要查询的结点值:\n");
getchar();
scanf("%c", &a);
for (int i = 0; i < tree.n; i++) {
if (tree.tnode[i].data == a) {
isfind = 1;
int ad = tree.tnode[i].parent;
printf("%c的父节点为 %c,存储位置下标为 %d", a, tree.tnode[ad].data, ad);
break;
}
}
if (isfind == 0) {
printf("树中无此节点");
}
}
int main()
{
PTree tree;
for (int i = 0; i < MAX_SIZE; i++) {
tree.tnode[i].data = " ";
tree.tnode[i].parent = 0;
}
tree = InitPNode(tree);
FindParent(tree);
return 0;
}
2.孩子表示法
这个就是先把所有节点先放入数组,顺序存储,但是每个后面又用链表衔接
各自的子节点。
每个节点的数据域依然是 数组下标
因为之前说了,所有节点都在数组里,所以有下标就有节点。
拿R根节点看,它后边跟着1、2、3,就是说,他的儿子们事在数组里面下标为1、2、3的A、B、C
#include<stdio.h>
#include<stdlib.h>
#define MAX_SIZE 20
#define TElemType char
//孩子表示法
typedef struct CTNode {
int child;//链表中每个结点存储的不是数据本身,而是数据在数组中存储的位置下标
struct CTNode * next;
}ChildPtr;
typedef struct {
TElemType data;//结点的数据类型
ChildPtr* firstchild;//孩子链表的头指针
}CTBox;
typedef struct {
CTBox nodes[MAX_SIZE];//存储结点的数组
int n, r;//结点数量和树根的位置
}CTree;
//孩子表示法存储普通树
CTree initTree(CTree tree) {
printf("输入节点数量:\n");
scanf("%d", &(tree.n));
for (int i = 0; i < tree.n; i++) {
printf("输入第 %d 个节点的值:\n", i + 1);
getchar();
scanf("%c", &(tree.nodes[i].data));
tree.nodes[i].firstchild = (ChildPtr*)malloc(sizeof(ChildPtr));
tree.nodes[i].firstchild->next = NULL;
printf("输入节点 %c 的孩子节点数量:\n", tree.nodes[i].data);
int Num;
scanf("%d", &Num);
if (Num != 0) {
ChildPtr * p = tree.nodes[i].firstchild;
for (int j = 0; j < Num; j++) {
ChildPtr * newEle = (ChildPtr*)malloc(sizeof(ChildPtr));
newEle->next = NULL;
printf("输入第 %d 个孩子节点在顺序表中的位置", j + 1);
scanf("%d", &(newEle->child));
p->next = newEle;
p = p->next;
}
}
}
return tree;
}
void findKids(CTree tree, char a) {
int hasKids = 0;
for (int i = 0; i < tree.n; i++) {
if (tree.nodes[i].data == a) {
ChildPtr * p = tree.nodes[i].firstchild->next;
while (p) {
hasKids = 1;
printf("%c ", tree.nodes[p->child].data);
p = p->next;
}
break;
}
}
if (hasKids == 0) {
printf("此节点为叶子节点");
}
}
int main()
{
CTree tree;
for (int i = 0; i < MAX_SIZE; i++) {
tree.nodes[i].firstchild = NULL;
}
tree = initTree(tree);
//默认数根节点位于数组notes[0]处
tree.r = 0;
printf("找出节点 F 的所有孩子节点:");
findKids(tree, 'F');
return 0;
}
3.孩子兄弟表示法
第三种是一种思路最特殊的—从兄弟的角度构建的
首先看引入语
这句话刚开始看了很久也没看懂。
应该理解为:规定:一个节点的首个儿子为唯一和其直接联系的,二儿子、三儿子都和大儿子联系;
同理,这个节点自己也可能是别人的大儿子,那就负责联系二弟、三弟(兄弟节点);可能是二儿子、三儿子,那就只能找大哥。
画图一目了然
总结
这里是树的基本概念,接下来将要引出的是二叉树,那才是重点