7.树和二叉树
1.树的基本概念
▷ 树的定义:
由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称作这个根的子树 。
▷树的结构特点
■非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)
■树的定义具有递归性,树中还有树。
■树可以为空,即节点个数为0。
▷ 若干术语
■根:即根结点(没有前驱)
■叶子:即终端结点(没有后继)
■森林:指m棵不相交的树的集合(例如删除A后的子树个数)
■有序树:结点各子树从左至右有序,不能互换(左为第一)
■无序树:结点各子树可互换位置。
■双亲:即上层的那个结点(直接前驱) parent
■孩子:即下层结点的子树 (直接后继) child
■兄弟:同一双亲下的同层结点(孩子之间互称兄弟)sibling
■堂兄弟:即双亲位于同一层的结点(但并非同一双亲)cousin
■祖先:即从根到该结点所经分支的所有结点
■子孙:即该结点下层子树中的任一结点
■结点:即树的数据元素
■结点的度:结点挂接的子树数(有几个直接后继就是几度)
■结点的层次:从根到该结点的层数(根结点算第一层)
■终端结点:即度为0的结点,即叶子
■分支结点:除树根以外的结点(也称为内部结点)
■树的度:所有结点度中的最大值(Max{各结点的度})
■树的深度(或高度): 指所有结点中最大的层数(Max{各结点的层次})
上图中的结点数= 13,树的度= 3,树的深度= 4
2.树的表示法
2.1图形表示法
事物之间的逻辑关系可以通过数的形式很直观的表示出来,如下图:
2.2广义表表示法
用广义表表示法表示上图:
中国(河北(保定,石家庄),广东(广州,东莞),山东(青岛,济南))
根作为由子树森林组成的表的名字写在表的左边。
2.3左孩子右兄弟表示法
左孩子右兄弟表示法可以将一颗多叉树转化为一颗二叉树:
节点的结构:
节点有两个指针域,其中一个指针指向子节点,另一个指针指向其兄弟节点。
3.二叉树概念
3.1二叉树基本概念
▷ 定义:
■n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成 。
▷ 逻辑结构:
一对二(1:2)
▷基本特征:
■每个结点最多只有两棵子树(不存在度大于2的结点);
■左子树和右子树次序不能颠倒(有序树)。
▷基本形态:
●二叉树性质
■性质1: 在二叉树的第i层上至多有2i-1个结点(i>0)
■性质2: 深度为k的二叉树至多有2k-1个结点(k>0)
■性质3: 对于任何一棵二叉树,若度为2的结点数有n2个,则叶子数(n0)必定为n2+1 (即n0=n2+1)
概念解释:
♢满二叉树
一棵深度为k 且有2k -1个结点的二叉树。
特点:每层都“充满”了结点
♢完全二叉树
除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点。
理解:k-1层与满二叉树完全相同,第k层结点尽力靠左
■性质4: 具有n个结点的完全二叉树的深度必为[log2n]+1
(如 log215 在计算器上点击 15 log / 2 log =)
■性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)
使用此性质可以使用完全二叉树实现树的顺序存储。
如果不是完全二叉树咋整???
------ 将其转换成完全二叉树即可
-------------------------------缺点:①浪费空间;②插入、删除不便 ------------------------------------------------
3.2二叉树的表示
▷二叉链表示法
一般从根结点开始存储。相应地,访问树中结点时也只能从根开始。
■存储结构
■结点数据类型定义:
typedef struct BiTNode
{
int data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
▷三叉链表表示法
■存储结构
每个节点有三个指针域,其中两个分别指向子节点(左孩子,右孩子),还有一个指针指向该节点的父节点。
■节点数据类型定义
//三叉链表
typedef struct TriTNode
{
int data;
//左右孩子指针
struct TriTNode *lchild, *rchild;
struct TriTNode *parent;
}TriTNode, *TriTree;
3.3二叉树的遍历
▷遍历定义
指按某条搜索路线遍访每个结点且不重复(又称周游)。
▷遍历用途
它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
▷遍历方法
牢记一种约定,对每个结点的查看都是“先左后右” 。
限定先左后右,树的遍历有三种实现方案:
DLR | LDR | LRD |
---|---|---|
先 (根)序遍历 | 中 (根)序遍历 | 后(根)序遍历 |
■DLR — 先序遍历,即先根再左再右
■LDR — 中序遍历,即先左再根再右
■LRD — 后序遍历,即先左再右再根
注:“先、中、后”的意思是指访问的结点D是先于子树出现还是后于子树出现。
从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问结点的时机不同。
从虚线的出发点到终点的路径上,每个结点经过3次。
■第1次经过时访问=先序遍历
■第2次经过时访问=中序遍历
■第3次经过时访问=后序遍历
//03 二叉树的递归遍历
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct BinaryNode
{
//数据域
char ch;
//指针域
struct BinaryNode * lChild;
struct BinaryNode * rChild;
};
void recursion(struct BinaryNode * root)
{
if (root == NULL)
{
return;
}
//先序遍历
printf("%c ", root->ch);
recursion(root->lChild);
recursion(root->rChild);
}
void test01()
{
struct BinaryNode nodeA = { 'A', NULL, NULL };
struct BinaryNode nodeB = { 'B', NULL, NULL };
struct BinaryNode nodeC = { 'C', NULL, NULL };
struct BinaryNode nodeD = { 'D', NULL, NULL };
struct BinaryNode nodeE = { 'E', NULL, NULL };
struct BinaryNode nodeF = { 'F', NULL, NULL };
struct BinaryNode nodeG = { 'G', NULL, NULL };
struct BinaryNode nodeH = { 'H', NULL, NULL };
//建立关系
nodeA.lChild = &nodeB;
nodeA.rChild = &nodeF;
nodeB.rChild = &nodeC;
nodeC.lChild = &nodeD;
nodeC.rChild = &nodeE;
nodeF.rChild = &nodeG;
nodeG.lChild = &nodeH;
//递归遍历
recursion(&nodeA);
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
3.4二叉树编程实践
3.4.1 计算二叉树叶子节点数目
3.4.2 计算二叉树高度(深度)
思想:
●求根结点左子树高度,根结点右子树高度,比较的子树最大高度,再+1。
●若左子树还是树,重复步骤1;若右子树还是树,重复步骤1。
3.4.3 拷贝二叉树
思想:
●malloc新结点
●拷贝左子树,拷贝右子树,让新结点连接左子树,右子树。
若左子树还是树,重复步骤1、2;若右子树还是树,重复步骤1、2。
3.5 二叉树的非递归遍历
利用栈容器可以实现二叉树的非递归遍历
首先将每个节点都设置一个标志,默认标志为假,根据节点的的状态进行如下流程。
执行上述流程,可以得到先序遍历的结果,如果想得到其他二叉树遍历结果,修改2.4步骤即可。
05 二叉树非递归遍历.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "seqStack.h"
struct BinaryNode
{
//数据域
char ch;
//指针域
struct BinaryNode * lChild;
struct BinaryNode * rChild;
//标志
int flag;
};
/*
1、将根节点 入栈
2、只要栈中元素个数大于 0 执行循环
获取栈顶元素
出栈
如果标志位真 直接输出 并且执行下一次循环
如果为假 将标志改为真
将右子树 左子树 根 入栈
执行下一次循环
*/
void nonRecursion(struct BinaryNode * root)
{
//初始化栈
SeqStack myStack = init_SeqStack();
push_SeqStack(myStack, root);
while (size_SeqStack(myStack) > 0)
{
//获取栈顶元素
struct BinaryNode * pTop = top_SeqStack(myStack);
//出栈
pop_SeqStack(myStack);
//如果标志位真 直接输出 并且执行下一次循环
if (pTop->flag == 1)
{
printf("%c ", pTop->ch);
continue;
}
//如果为假 将标志改为真
pTop->flag = 1;
//将右子树 左子树 根 入栈
if (pTop->rChild != NULL)
{
push_SeqStack(myStack, pTop->rChild);
}
if (pTop->lChild != NULL)
{
push_SeqStack(myStack, pTop->lChild);
}
push_SeqStack(myStack, pTop);
}
//销毁栈
destroy_SeqStack(myStack);
}
void test01()
{
struct BinaryNode nodeA = { 'A', NULL, NULL,0 };
struct BinaryNode nodeB = { 'B', NULL, NULL,0 };
struct BinaryNode nodeC = { 'C', NULL, NULL,0 };
struct BinaryNode nodeD = { 'D', NULL, NULL,0 };
struct BinaryNode nodeE = { 'E', NULL, NULL,0 };
struct BinaryNode nodeF = { 'F', NULL, NULL,0 };
struct BinaryNode nodeG = { 'G', NULL, NULL,0 };
struct BinaryNode nodeH = { 'H', NULL, NULL,0 };
//建立关系
nodeA.lChild = &nodeB;
nodeA.rChild = &nodeF;
nodeB.rChild = &nodeC;
nodeC.lChild = &nodeD;
nodeC.rChild = &nodeE;
nodeF.rChild = &nodeG;
nodeG.lChild = &nodeH;
//非递归遍历
nonRecursion(&nodeA);
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
seqStack.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAX 1024
//struct SStack
//{
// void * data[MAX]; //栈的数组
//
// int m_Size; //栈大小
//};
typedef void * SeqStack;
//初始化栈
SeqStack init_SeqStack();
//入栈
void push_SeqStack(SeqStack stack, void * data);
//出栈
void pop_SeqStack(SeqStack stack);
//返回栈顶
void * top_SeqStack(SeqStack stack);
//返回栈大小
int size_SeqStack(SeqStack stack);
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack);
//销毁栈
void destroy_SeqStack(SeqStack stack);
seqStack.c
#include "seqStack.h"
struct SStack
{
void * data[MAX]; //栈的数组
int m_Size; //栈大小
};
//初始化栈
SeqStack init_SeqStack()
{
struct SStack * myStack = malloc(sizeof(struct SStack));
if (myStack == NULL)
{
return NULL;
}
//初始化数组
memset(myStack->data, 0, sizeof(void *)* MAX);
//初始化栈大小
myStack->m_Size = 0;
return myStack;
}
//入栈
void push_SeqStack(SeqStack stack, void * data)
{
//入栈本质 --- 数组尾插
if (stack == NULL)
{
return;
}
if (data == NULL)
{
return;
}
struct SStack * mystack = stack;
if (mystack->m_Size == MAX)
{
return;
}
mystack->data[mystack->m_Size] = data;
mystack->m_Size++;
}
//出栈
void pop_SeqStack(SeqStack stack)
{
//出栈本质 --- 数组尾删
if (stack == NULL)
{
return;
}
struct SStack * mystack = stack;
if (mystack->m_Size == 0)
{
return;
}
mystack->data[mystack->m_Size - 1] = NULL;
mystack->m_Size--;
}
//返回栈顶
void * top_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return NULL;
}
struct SStack * mystack = stack;
if (mystack->m_Size == 0)
{
return NULL;
}
return mystack->data[mystack->m_Size - 1];
}
//返回栈大小
int size_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1;
}
struct SStack * mystack = stack;
return mystack->m_Size;
}
//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return -1;//返回-1代表真 空栈
}
struct SStack * mystack = stack;
if (mystack->m_Size == 0)
{
return 1;
}
return 0; //返回0 代表 不是空栈
}
//销毁栈
void destroy_SeqStack(SeqStack stack)
{
if (stack == NULL)
{
return;
}
free(stack);
stack = NULL;
}
参考资料来源:
黑马程序员