内容简介
- 树
- 二叉树
- 完全二叉树
- 二叉树的性质
- 二叉树的抽象数据类型
- 二叉树的顺序存储结构
- 二叉树顺序存储结构基本操作
- 二叉树的链式存储结构
- 二叉树链式存储结构的基本操作
- 遍历二叉树
- 前序二叉树
- 中序二叉树
- 后序二叉树
- 层序二叉树
- DOS命令查看文件夹目录树
树
-
逻辑结构
-
树的定义:
树是n个结点的有限集
结点个数为零的树为空树
结点个数大于零的树为非空树-
非空树中:
有且仅有一个特定的结点称为根结点
非根的结点可分为互不相交的有限集,每个集合本身又是一棵树,这些树称为根的树
-
-
结点分类
根节点: 没有前驱结点只有后继结点
叶结点: 只有前驱结点没有后继结点
分支结点:结点的度不为零的结点
度: 结点下方挂接结点的个数
树的度:所有结点中最大的度
树的深度:树的最大层数 -
有序树
-
无序树
二叉树
-
二叉树的定义:
- 二叉树(Binary Tree)是n(n>=0)个节点所构成的集合,
- 它或为空树(n=0),
- 或为非空树(n >= 0),
对于非空树T:- 有且仅有一个称之为根的结点
- 除根以外的其余结点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树
二叉树的特点及树的异同
为何要重点研究二叉树?- 二叉树的结构最简单,规律性最强
- 可以证明,所有树都能转为唯一对应的二叉树,不失一般性
-
二叉树的各种形态
三节点二叉树的五种形态
完全二叉树
- 满二叉树和完全二叉树
将右边的完全二叉树移到左边满二叉树上,观察
示例:判断完全二叉树
- 完全二叉树的特点
二叉树的性质
-
性质1:在二叉树的第i层上至多有2(i-1)个节点
-
性质2:深度为k的二叉树至多有2(k-1)个结点
-
性质3:对于任何一棵二叉树,若2度的结点数有n2个,则叶子结点数n0必定为n2+1个,即n0=n2+1
证明:
-
性质4:具有n个结点的完全二叉树的深度必为[log2n]+1([ ]表示取整)
-
性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i的结点,其左支树编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为[i/2]。
-
性质小结:
性质1:在二叉树的第i层上至多有2i-1个结点
性质2:深度为k的二叉树至多有2k-1个结点
性质3:任何一颗二叉树,若2度的结点数有n2个,则椰子树n0必定为n2+1个(即n0=n2+1)
性质4:具有n个结点的完全二叉树的深度为[log2n]+1
性质5:对完全二叉树,若从上至下、从左至右编号,则编号为i的结点,其左孩子编号为2i,其右孩子编号为2i+1;其双亲的编号必为[i/2]
二叉树的抽象数据类型
-
抽象数据类型
ADT BinaryTree Data D是具有相同特性的数据元素的集合 Relative 若D = ⊙(空集),则R = ⊙; 若D ≠ ⊙,则R={H}; //存在二元关系: 1.root 唯一 //关于根的说明 2.Dj ∩ Dk = ⊙ //关于子树不相交的说明 3. ...... //关于数据元素的说明 4. ...... //关于左子树和右子树的说明 Operation createBiTree(&T,defination); //构建二叉树 preOrderTraverse(T); //先序遍历 inOrderTraverse(T); //中序遍历 postOrderTraverse(T); //后序遍历 levelOrderTraverse(T); //层序遍历 ...... //其他操作 endADT
二叉树的顺序存储结构
- 顺序存储结构示意图
特点:
1.结点间关系蕴含在其存储位置中
2.浪费空间,适于存满二叉树和完全二叉树- 最坏的情况,一个深度为k且只有k个结点的单支树(不存在度为2的结点)却需要2k-1长度的一维数组
- 最坏的情况,一个深度为k且只有k个结点的单支树(不存在度为2的结点)却需要2k-1长度的一维数组
- 创建顺序二叉树
二叉树顺序存储结构基本操作
/** 初始化空二叉树 */
void InitSeqTree(SeqTree tree)
{
//将字符数组中的每个元素都设置为空字符
for(int i = 0; i < MAX_SIZE; i++)
{
tree[i] = '\0';
}
}
/** 创建完全二叉树,i为数组中的下标 */
void CreateSeqTree(SeqTree tree, int i)
{
char ch;
ch = getchar();
fflush(stdin);
if(ch == '^') //输入^符号表示结束当前结点的输入
{
tree[i] = '\0';
return;
}
tree[i] = ch; //如果没有执行return
//某个结点输入完毕后,还需要让用户输入左支树结点和右支树结点
printf("左支树结点:");
CreateSeqTree(tree, 2 * i + 1); //递归调用
printf("右支树结点:");
CreateSeqTree(tree, 2 *(i + 1));
}
/** 获取输的根节点元素 */
char GetSeqTreeRoot(SeqTree tree)
{
return tree[0];
}
/** 获取树的结点总数 */
int GetSeqTreeLength(SeqTree tree)
{
/*
* 如果从头部开始遍历,中间会存在'\0'导致计算长度中断,算出的不是最终结果
* 正确的方式应该是:从尾部找非'\0'的叶结点,排除尾部所有的'\0',剩下的就是二叉树顺序存储的总结点数
*/
int len;
for(len = MAX_SIZE; len >= 1; len--)
{
if(tree[len-1] != '\0')
break;
}
return len;
}
/** 获取树的深度 */
int GetSeqTreeDepth(SeqTree tree)
{
//性质2:深度为k的二叉树最多有2^k - 1个结点
int depth = 0;
int len = GetSeqTreeLength(tree);
while((int)pow(2, depth) - 1 < len)
{
depth++;
}
return depth;
}
二叉树的链式存储结构
-
二叉链表的存储结构示意图:
注意:在含有n个结点的二叉链表中有n+1个空链域,利用这些空链域可以存储其他有用信息,从而得到另一种链式存储结构——线索链表 -
三叉链表的存储结构示意图:
-
创建链式二叉树
二叉树链式存储结构的基本操作
#define NAME_SIZE 255
/** 数据元素 */
typedef struct
{
int id;
char name[NAME_SIZE];
}ElementType;
/** 树结点 */
typedef struct TreeNode
{
ElementType data; //树结点的数据域
struct TreeNode * left; //左子树
struct TreeNode * right; //右子树
}TreeNode;
/** 二叉链表 */
typedef struct
{
TreeNode * root; //二叉链的根节点
int length; //二叉链表结点的总数
int depth; //二叉链表的深度
int diameter; //直径 - 从叶结点到叶结点的最长路径(某些考试会涉及)
}BinaryTree;
/** 初始化空二叉树 */
void InitBinaryTree(BinaryTree * tree)
{
tree->root = NULL;
tree->depth = 0;
tree->diameter = 0;
tree->length = 0;
}
/** 构造二叉树 - (外部需要事先对结点分配内存); 创建成功返回0,创建失败返回1 */
static int id = 0; //用来实现结点id的自增长
int CreateBinaryTree(TreeNode * root)
{
if(!root) return 0; //根结点如果为空,就退出创建过程
char inputName[NAME_SIZE]; //用户输入的结点名
gets(inputName); //用户输入回车表示结束当前子树的创建
if(strcmp(inputName, "\0") == 0) return 0;
//创建当前结点
root->data.id = ++id;
strcpy(root->data.name, inputName);
//为输入左右结点做准备 - 为左右结点指针分配内存
root->left = (TreeNode *)malloc(sizeof(TreeNode));
root->right = (TreeNode *)malloc(sizeof(TreeNode));
//分别递归创建左子树和右子树
printf("左结点:");
if(CreateBinaryTree(root->left) == 0)
{
//不再创建这个结点则销毁刚分配的结点内存
free(root->left);
root->left = NULL;
}
printf("右结点:");
if(CreateBinaryTree(root->right) == 0)
{
free(root->right);
root->right = NULL;
}
return 1;
}
遍历二叉树
- 遍历:按某条搜索路线遍访每个结点且不重复(又称周游)
- 作用:
是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心 - 遍历二叉树
从根节点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点均被访问一次且仅被访问一次
前序遍历
特点:第一位一定是根节点
/** 前序遍历 -- 可以记作:根-左-右 */
void PreOrderTraverse(TreeNode * node)
{
//先访问根结点,然后遍历左子树,最后遍历右子树
if(node)
{
printf("[%d, %s]-", node->data.id, node->data.name);
PreOrderTraverse(node->left);
PreOrderTraverse(node->right);
}
}
中序遍历
特点:根节点一定在中间
/** 中序遍历:左-根-右 */
void InOrderTraverse(TreeNode * node)
{
if(node)
{
InOrderTraverse(node->left);
printf("[%d, %s]-", node->data.id, node->data.name);
InOrderTraverse(node->right);
}
}
后序遍历
特点:最后一位一定是根节点
/** 后序遍历 */
void PostOrderTraverse(TreeNode * node)
{
if(node)
{
PostOrderTraverse(node->left);
PostOrderTraverse(node->right);
printf("[%d, %s]-", node->data.id, node->data.name);
}
}
层序遍历
特点:自上而下,从左往右
层序遍历的特点决定了它需要队列技术实现
文件夹目录树–DOS命令查看
文件资源管理器中输入cmd,然后回车
输入tree /f 回车(注意:只输入tree,只显示文件夹名字,不显示具体文件名)
输入 D:/info.txt 会在D盘下生成该文件目录树的 .txt文件