树和二叉树

树的相关知识

树型结构是一类重要的非线性数据结构。其中以树和二叉树最为常用,直观看来,树是以分支关系定义的层次结构。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构可用树来形象表示。

树的定义

树是n(n ≥ 0 )个结点的有限集。
若n = 0 ,称为空树;
若n>0,则它满足如下两个条件:
(1).有且仅有一个特定的称为根的结点;
(2)其余结点可分为m(m ≥ 0 )个互不相交的有限集T1,T2,T3,…,Tm,其中每一个集合本身又是一棵树,并称为根的子树。

显然,树的定义是一个递归的定义。

树一般表示为一个层次结构,如下图:

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) A-->B A-->C A-->D B-->E B-->F C-->G D-->H D-->I D-->J E-->K E-->L H-->M

树的相关术语

结点 :数据元素以及指向子树的分支。图中的A,B,C等都是结点。

根结点 :非空树中无前驱结点的结点。图中的A结点。

结点的度 :结点拥有的子树数。图中A结点有3个子树,其度为3。

树的度 :树内各结点的度的最大值。上图中树的度为3。

叶子结点(终端结点) :度为0的结点。图中的F,I,J等都是叶子结点。

分支结点(非终端结点) :度不为0的结点。A,B,C,D等都是分支结点。

内部结点(中间结点) :根结点以外的分支结点。B,C,D等都是内部结点。

孩子 结点的子树的根称为该结点的孩子,该结点称为孩子的双亲。图中,E是L的双亲,L是E的孩子。

兄弟节点 有一些结点,它们有共同的双亲,则称这些结点为兄弟结点。图中的H,I,J就是兄弟结点。

双亲在同一层上的结点称为 堂兄弟结点 。图中的G,H就是堂兄弟结点。

结点的祖先 :从根到该结点所经分支上的所有的结点。A,D,H结点都是结点M的祖先。

结点的子孙 :以某结点为根的子树中的任一结点。

树的深度 :树中结点的最大层次。图中的树的深度为4。

有序树 :树中结点的各子树从左至右右次序。

无序树 :树中结点的各子树无次序。

森林 :是m(m ≥ 0)棵互不相交的树的集合。把根结点删除树就变成了森林;一棵树可以看成是一个特殊的森林;给森林中的各子树加上一个双亲结点,森林就变成了树。

树一定是森林,而森林不一定是树。

树与线性结构的比较

线性结构 树型结构
第一个数据元素(无前驱) 根结点(无双亲,只有一个)
最后一个数据元素(无后继) 叶子结点(无孩子,可以有多个)
其他数据元素:一个前驱,一个后继 其它结点(中间结点):一个双亲,多个孩子
一对一 一对多

树的存储结构

双亲表示法

采用的一组连续的存储空间来存储每个节点。
根节点没有双亲,所以其在数组中存储的值为-1。
其余的节点,只需要存储其父节点对应的数组下标即可。
看图,一目了然。

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) A-->B A-->C A-->D B-->E B-->F C-->G D-->H D-->I D-->J E-->K E-->L H-->M
下标 0 1 2 3 4 5 6 7 8 9 10 11 12
元素 A B C D E F G H I J K L M
父节点 -1 0 0 0 1 1 2 3 3 3 4 4 7

代码

// 双亲表示法
#define MAX_SIZE 100
typedef int ElemType;

typedef struct PTNode{
    ElemType data;//结点元素
    int parent;//存储父节点下标
}PTNode;

typedef struct PTree
{
    PTNode nodes[MAX_SIZE];//创建树
    int n;
}PTree;

孩子表示法

将每个节点的孩子节点都用单链表连接起来形成一个线性结构,n个节点具有n个孩子链表。

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) A-->B A-->C A-->D B-->E B-->F C-->G D-->H D-->I D-->J E-->K E-->L H-->M
graph LR A--孩子1-->B B--孩子2-->C C--孩子3-->D
graph LR B--孩子1-->E E--孩子2-->F
graph LR C--孩子1-->G
graph LR D--孩子1-->H H--孩子2-->I I--孩子3-->J
graph LR E--孩子1-->K K--孩子2-->L
graph LR H--孩子1-->M

代码

// 孩子表示法
typedef int ElemType;
#define MAX_SIZE 100
typedef struct CNode
{
    int child;
    struct CNode *next;
}CNode;

typedef struct PNode
{
    ElemType data;
    struct CNode *child;
}PNode;

typedef struct CTree
{
    PNode nodes[MAX_SIZE];
    int n;
}CTree;

孩子兄弟表示法

以二叉链表作为树的存储结构,又称二叉树表示法。

指针域 数据域 指针域
FirstChild Data NextBrother
结点第一个孩子 结点值 结点的下一个兄弟
graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) A-->B A-->C A-->D B-->E B-->F C-->G D-->H D-->I D-->J E-->K E-->L H-->M

二叉树

二叉树的特点:

(1)每个结点最多有两棵子树,即二叉树不存在度大于2的结点。
(2)二叉树的子树有左右之分,其子树的次序不能颠倒。

二叉树的性质

二叉树具有以下几个性质:

1、二叉树中,第 i 层最多有 2^i-1 个结点。

2、如果二叉树的深度为 K,那么此二叉树最多有 2^K-1 个结点。

3、二叉树中,终端结点数(叶子结点数)为 n0,度为 2 的结点数为 n2,则 n0=n2+1 。

4、有n个结点的完全二叉树,对各节点从上到下、从左到右依次编号(1~n),则结点之间有如下关系。

若i为某结点a的编号。则a左孩子的编号为2i,右孩子编号为2i+1

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) A-->B A-->C B-->D B-->E C-->F C-->G D-->H D-->I E-->J E-->K F-->L G-->M

满二叉树

如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。

性质:

1、满二叉树中第 i 层的节点数为 2n-1 个。

2、深度为 k 的满二叉树必有 2^k-1 个节点 ,叶子数为 2^(k-1)。

3、满二叉树中不存在度为 1 的节点,每一个分支点中都两棵深度相同的子树,且叶子节点都在最底层。

4、具有 n 个节点的满二叉树的深度为 log2(n+1)。

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) L((L)) M((M)) N((N)) O((O)) A-->B A-->C B-->D B-->E C-->F C-->G D-->H D-->I E-->J E-->K F-->L F-->M G-->N G-->O

完全二叉树

如果二叉树中除去最后一层节点为满二叉树,且最后一层的结点依次从左到右分布,则此二叉树被称为完全二叉树

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) A-->B A-->C B-->D B-->E C-->F C-->G D-->H D-->I E-->J E-->K

二叉树的存储方式

孩子表示法

struct node{
    char value;//结点元素的值
    int left,right;//左孩子下标,右孩子下标
}data[101];
int root=0;//根结点下标
int cnt=0;//下标

二叉树的建立

//注:空结点用字符'.'填充
//方法1
int buildTree(int bt)//输入一个数,就采用先序的方式添加进二叉树中。
{
    char ch;
    cin>>ch;
    if(ch=='.'){
        bt=0;
        return bt;
    }
    else{

        bt=++cnt;
        data[bt].value=ch;
        data[bt].left=0;
        data[bt].right=0;
        data[bt].left=buildTree(bt);
        data[bt].right=buildTree(bt);
    }
    return bt;
}
//方法2
int buildTree2(int bt)
{
	char ch;
	cin>>ch;
	if(ch=='.')return 0;
	data[bt].value=ch;
	data[bt].left=bt*2;
	data[bt].right=bt*2+1;
	buildTree2(data[bt].left);
	buildTree2(data[bt].right);
	return 0;
}

二叉树的四种遍历方式

graph TB A((A)) B((B)) C((C)) D((D)) E((E)) F((F)) G((G)) H((H)) I((I)) J((J)) K((K)) A-->B A-->C B-->D B-->E C-->F C-->G D-->H D-->I E-->J E-->K

前序遍历(先序遍历):

先访问一棵树的根节点,再访问左子树,最后访问右子树。
遍历序列:ABDHIEJKCFG

void preorder(int bt)
{
    if(bt)
    {
        cout<<data[bt].value;//访问节点元素
        preorder(data[bt].left);//遍历左子树
        preorder(data[bt].right);//遍历右子树
    }
}

中序遍历:

先访问一棵树的左子树,再访问根节点,最后访问右子树。
遍历序列:HDIBJEKAFCG

void inorder(int bt)
{
    if(bt)
    {
        inorder(data[bt].left);//遍历左子树
        cout<<data[bt].value;//访问节点元素
        inorder(data[bt].right);//遍历右子树
    }
}

后序遍历:

先访问一棵树的左子树,再访问右子树,最后访问根节点。
遍历序列:HIDJKEBFGCA

void postorder(int bt)
{
    if(bt)
    { 
        postorder(data[bt].left);//遍历左子树
        postorder(data[bt].right);//遍历右子树
        cout<<data[bt].value;//访问节点元素
    }
}

层序遍历:

首先访问第一层的根结点,然后从左到右访问第2层上的节点,接着访问第三层的结点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
遍历序列:ABCDEFGHIGK

//层次遍历需要用到队列
void levelorder(int bt)
{
	queue<int>  q;
	q.push(bt);
	while(!q.empty())
	{
		int t=q.front();
		cout<<data[t].value;
		if(data[t].left!=0)	q.push(data[t].left);
		if(data[t].right!=0) q.push(data[t].right);
		q.pop();
	}
}
posted @ 2022-04-28 16:35  李墨韵  阅读(115)  评论(0编辑  收藏  举报