树的存储结构
前面我们讲过二叉树的存储方式,包括顺序存储和链式存储,但如果用存储二叉树的方式来存储树可以吗,这显然是不行的,因为二叉树是规律的树,比如说我们可以利用顺序存储的方式来存储二叉树,但是树不同于二叉树是多种多样的,所以我们需要使用其他方法
1.双亲表示法
具体思想:
用数组顺序存储各个结点。每个结点中保存数据元素、指向双亲结点(父结点)的指针
非根结点的双亲指针 = 父结点在数组中的下标
根节点的双亲指针 = -1(根结点没有父结点)
代码实现:
#include<stdio.h>
#include<stdbool.h>
#define Max 100
//成员表示
typedef struct
{
int data;//数据域
int parent;//父结点在数组里的位置
}TreeMember;
//数组表示
typedef struct
{
TreeMember arr[Max];//双亲表示
int num;//结点总数
}TreeArray;
//声明根结点
void InitTree(TreeArray* tree)
{
tree->arr[0].data = 1;
tree->arr[0].parent = -1;//根结点无父结点
tree->num = 1;
return;
}
//插入树
bool EnTree(TreeArray* tree)
{
if (tree == NULL)//无根结点
return false;
if (tree->num >= Max)//数组无空间
return false;
//手动方式创建
tree->arr[1].data = 2;
tree->arr[1].parent = 0;
tree->num++;
tree->arr[2].data = 3;
tree->arr[2].parent = 0;
tree->num++;
tree->arr[3].data = 4;
tree->arr[3].parent = 0;
tree->num++;
tree->arr[4].data = 5;
tree->arr[4].parent = 1;
tree->num++;
tree->arr[5].data = 6;
tree->arr[5].parent = 1;
tree->num++;
tree->arr[6].data = 7;
tree->arr[6].parent = 2;
tree->num++;
tree->arr[7].data = 8;
tree->arr[7].parent = 3;
tree->num++;
tree->arr[8].data = 9;
tree->arr[8].parent = 3;
tree->num++;
tree->arr[9].data = 10;
tree->arr[9].parent = 3;
tree->num++;
tree->arr[10].data = 11;
tree->arr[10].parent = 4;
tree->num++;
return true;
}
//寻找父结点
bool FindParent(TreeArray* tree)
{
if (tree == NULL)
return false;//空树没有父结点
int n;
printf("输入孩子结点(9999退出)\n");
scanf("%d", &n);
while (n != 9999)//假设输入9999退出
{
if (n == 1)
return false;//根节点无父节点
if (n < 0 && n >= tree->num)
return false;//输入不合法
int parent = tree->arr[n - 1].parent;
printf("父节点为%d\n",tree->arr[parent].data);
printf("输入孩子结点(9999退出)\n");
scanf("%d", &n);
}
return true;
}
//寻找孩子结点
bool FindSon(TreeArray* tree)
{
if (tree == NULL)//表示空树
return false;
printf("请输入要寻找的孩子结点:\n");
int n;
scanf("%d", &n);
if (n < 0 && n > tree->num)
return false;//输入错误
int i = 0;
int flag = 0;
for (i = 0; i < tree->num; i++)
{
if (tree->arr[i].parent == n - 1)
{
printf("%d的孩子结点为%d\n", n, tree->arr[i].data);
flag++;
}
}
if (flag == 0)
printf("没找到\n");
return true;
}
int main()
{
TreeArray tree;//声明一棵树
InitTree(&tree);//初始化根结点
EnTree(&tree);//插入结点
FindParent(&tree);//假设寻找5的父结点
FindSon(&tree);
return 0;
}
//寻找父结点的时间复杂度为O(1)
//寻找孩子结点的时间复杂度为O(n)
优缺点:
优点:找双亲(父结点)很方便
缺点:找孩子不方便,只能从头到尾遍历真个数组
2.孩子表示法
具体思想:用数组顺序存储各个结点。每个结点中保存数据元素、孩子链表头指针
代码实现:
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#define Max 100
//定义链表(孩子结点)
typedef struct LinkNode
{
int place;//数据域,存在孩子在数组里的位置
struct LinkNode* next;//指针域
}LinkNode;
//定义成员
typedef struct TreeMember
{
int data;//数据域
struct LinkNode* FirstChild;//指针域(第一个孩子的地址)
}TreeMember;
//定义数组
typedef struct TreeArray
{
TreeMember arr[Max];
int root;//根结点的位置
int num;//总数
}TreeArray;
//初始化数组
void InitTree(TreeArray* Tree)
{
Tree->arr[0].data = 1;//初始化根节点为1
Tree->arr[0].FirstChild = NULL;//孩子结点暂时为空
Tree->root = 0;
Tree->num = 1;
}
//增加树结点
bool EnTree(TreeArray* Tree)
{
//插入树结点
if (Tree->num < Max)
{
Tree->arr[1].data = 2;
Tree->arr[1].FirstChild = NULL;
Tree->num++;
Tree->arr[0].FirstChild = (LinkNode*)malloc(sizeof(LinkNode));//创建根结点的孩子指针
if (Tree->arr[0].FirstChild == NULL)
return false;//分配失败
Tree->arr[0].FirstChild->place = 1;
Tree->arr[0].FirstChild->next = NULL;
LinkNode* q = Tree->arr[0].FirstChild;
int number = 3;
for (int i = 2; i <= 3; i++)
{
Tree->arr[i].data = number;
Tree->arr[i].FirstChild = NULL;//孩子结点指向空
Tree->num++;
LinkNode* p = (LinkNode*)malloc(sizeof(LinkNode));
if (p == NULL)
return false;//分配失败
q->next = p;
p->next = NULL;
p->place = i;
q = p;
number++;
}
Tree->arr[4].data = 5;
Tree->arr[4].FirstChild = NULL;
Tree->num++;
Tree->arr[1].FirstChild = (LinkNode*)malloc(sizeof(LinkNode));
if (Tree->arr[1].FirstChild == NULL)
return false;//分配失败
Tree->arr[1].FirstChild->place = 4;
Tree->arr[1].FirstChild->next = NULL;
Tree->arr[5].data = 6;
Tree->arr[5].FirstChild = NULL;
Tree->num++;
Tree->arr[1].FirstChild->next = (LinkNode*)malloc(sizeof(LinkNode));
if (Tree->arr[1].FirstChild->next == NULL)
return false;//分配失败
Tree->arr[1].FirstChild->next->place = 5;
Tree->arr[1].FirstChild->next->next = NULL;
Tree->arr[6].data = 7;
Tree->arr[6].FirstChild = NULL;
Tree->num++;
Tree->arr[2].FirstChild = (LinkNode*)malloc(sizeof(LinkNode));
if (Tree->arr[2].FirstChild == NULL)
return false;//分配失败
Tree->arr[2].FirstChild->place = 6;
Tree->arr[2].FirstChild->next = NULL;
Tree->arr[7].data = 8;
Tree->arr[7].FirstChild = NULL;
Tree->num++;
Tree->arr[3].FirstChild = (LinkNode*)malloc(sizeof(LinkNode));
if (Tree->arr[3].FirstChild == NULL)
return false;
Tree->arr[3].FirstChild->place = 7;
Tree->arr[3].FirstChild->next = NULL;
LinkNode* s = Tree->arr[3].FirstChild;
int j;
for (j = 8; j <= 9; j++)
{
Tree->arr[j].data = j + 1;
Tree->arr[j].FirstChild = NULL;
Tree->num++;
LinkNode* x = (LinkNode*)malloc(sizeof(LinkNode));
if (x == NULL)
return false;//分配失败
s->next = x;
x->next = NULL;
x->place = j;
s = x;
}
}
return true;
}
//寻找孩子结点
bool FindSon(TreeArray* Tree,int n)
{
if (Tree == NULL)
return false;
if (Tree->arr[n - 1].FirstChild == NULL)
{
printf("无孩子结点\n");
return false;
}
//n - 1是当前结点的位置
LinkNode* p = Tree->arr[n - 1].FirstChild;
while (p != NULL)
{
printf("%d的孩子结点为%d\n", n, Tree->arr[p->place].data);
p = p->next;
}
return true;
}
//寻找父亲结点
bool FindPar(TreeArray* Tree,int n)
{
if (Tree == NULL)
return false;//空树
if (n == Tree->root)
return false;//根结点无父结点
int i;
for (i = 0; i < Tree->num; i++)
{
LinkNode* p = Tree->arr[i].FirstChild;
int number = p->place;
while (p != NULL)
{
if (Tree->arr[number].data == Tree->arr[n - 1].data)
{
printf("%d的父结点为%d\n", n, Tree->arr[i].data);
return true;//找到了
}
else
{
p = p->next;
number = p->place;
}
}
}
return true;
}
int main()
{
TreeArray Tree;//定义一棵树
InitTree(&Tree);//初始化根结点
EnTree(&Tree);//新增树结点
FindSon(&Tree,4);//找孩子结点,假设寻找结点元素为4的孩子结点
FindPar(&Tree,4);//寻找父结点,假设寻找结点元素为4的父亲结点
return 0;
//找父结点的时间复杂度为O(n^2)
//找孩子结点的时间复杂度为O(1)
//空间复杂度O(n)
//用双亲方式存储还是用孩子方式存储各有利弊,要根据实际情况来使用更方便的一种方式
3.孩子兄弟表示法
具体思想:类似与二叉树的存储方式,构建两个指针,分别存储首个孩子指针和右兄弟指针
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
typedef struct TreeNode
{
int data;//数据域
struct TreeNode* firstchild, *rightbrother;//指针域(第一个孩子和右兄弟)
}TreeNode,*Tree;
//初始化
void InitTree(Tree* T)
{
(*T) = (TreeNode*)malloc(sizeof(TreeNode));//创建根结点
if ((*T) == NULL)
return;//分配失败
(*T)->firstchild = NULL;
(*T)->rightbrother = NULL;
return;
}
//插入树结点
bool EnTree(Tree* T)
{
if ((*T) == NULL)
return false;//没有根结点
TreeNode* p1 = (*T);
TreeNode*p2 = (TreeNode*)malloc(sizeof(TreeNode));
if (p2 == NULL)
return false;//创建失败
p2->data = 2;
p2->firstchild = NULL;
p2->rightbrother = NULL;
p1->firstchild = p2;
TreeNode* p3 = (TreeNode*)malloc(sizeof(TreeNode));
if (p3 == NULL)
return false;
p3->data = 3;
p3->firstchild = NULL;
p3->rightbrother = NULL;
p2->rightbrother = p3;
TreeNode* p4 = (TreeNode*)malloc(sizeof(TreeNode));
if (p4 == NULL)
return false;
p4->data = 4;
p4->firstchild = NULL;
p4->rightbrother = NULL;
p3->rightbrother = p4;
TreeNode* p5 = (TreeNode*)malloc(sizeof(TreeNode));
if (p5 == NULL)
return false;
p5->data = 5;
p5->firstchild = NULL;
p5->rightbrother = NULL;
p2->firstchild = p5;
TreeNode* p6 = (TreeNode*)malloc(sizeof(TreeNode));
if (p6 == NULL)
return false;
p6->data = 6;
p6->firstchild = NULL;
p6->rightbrother = NULL;
p5->rightbrother = p6;
TreeNode* p7 = (TreeNode*)malloc(sizeof(TreeNode));
if (p7 == NULL)
return false;
p7->data = 7;
p7->firstchild = NULL;
p7->rightbrother = NULL;
p3->firstchild = p7;
TreeNode* p8 = (TreeNode*)malloc(sizeof(TreeNode));
if (p8 == NULL)
return false;
p8->data = 8;
p8->firstchild = NULL;
p8->rightbrother = NULL;
p4->firstchild = p8;
TreeNode* p9 = (TreeNode*)malloc(sizeof(TreeNode));
if (p9 == NULL)
return false;
p9->data = 9;
p9->firstchild = NULL;
p9->rightbrother = NULL;
p8->rightbrother = p9;
TreeNode* p10 = (TreeNode*)malloc(sizeof(TreeNode));
if (p10 == NULL)
return false;
p10->data = 10;
p10->firstchild = NULL;
p10->rightbrother = NULL;
p9->rightbrother = p10;
return true;
}
//寻找孩子结点
//遍历树找到结点
TreeNode* PreTree(Tree T, int n)
{
if (T != NULL)
{
if (T->data == n)
return T;
//先找孩子结点
TreeNode* child = PreTree(T->firstchild,n);
if (child != NULL)
return child;
//再找兄弟结点
TreeNode*brother = PreTree(T->rightbrother, n);
if (brother != NULL)
return brother;
}
return NULL;
}
//寻找孩子结点
bool FindSon(Tree* T, int n)
{
if ((*T) == NULL)
return false;//空树
TreeNode * q = PreTree((*T), n);
if (q == NULL)
{
printf("不存在该结点\n");
return false;
}
if (q->firstchild == NULL)
{
printf("没有孩子结点\n");
return false;
}
TreeNode* s = q->firstchild;
while (s != NULL)
{
printf("%d的孩子结点为%d\n", n, s->data);
s = s->rightbrother;
}
return true;
}
int main()
{
Tree T;//构建树
InitTree(&T);//初始化树
EnTree(&T);//插入树结点
FindSon(&T,4);//假设寻找结点为4的孩子结点
return 0;
}
//寻找孩子结点的时间复杂度O(n)
//用该方法寻找父结点必须要加上一个格外的parent成员,否则无法寻找父结点及其困难
//空间复杂度为O(n)