前面几节的内容,链接如下:
单链表
双链表
栈
队列
查找排序算法
今天我们来看看数据结构中的另外一个重要内容:树
六、树
前面我们学习的都是线性结构,现在说的树属于一种非线性结构。树的模型,类似于一个家族的族谱,相信不用多说,大家就可以想象出他的样子。
1.树的定义
树是n(n>=0)个元素的有限集合。当n = 0时,称为空树。当n>0 时,则在这颗树中的结点有如下特征:
①有且仅有一个特定的称为根(root)结点,他只有直接后继,但是没有直接前驱;
②当n>1时,其余结点可分为m(m>0)个互不相交的有限集合,其中每个集合本身又是一棵树,并且称为根的子树。每颗子树的根结点有且只有一个直接前驱,即根结点root,但可以有0或多个直接后继。
如下图:
(a)(b)都好理解,看看(c),他有12个结点,其中A为根结点,其余的结点分为3个互不相交的子集,T1 = {B,E,F},T2 = {C,G,K,L},T3 = {D,H,I,J}。T1、T2和T3都是根A的子树,且本身也是一棵树。比如T1,其根为B,其余结点分为2个互不相交的子集,T11 = {E},T12 = {F}。T12和T11都是根B的子树,T11中E是根,T12中F是根。依次类推。
2.几个重要的术语
(以上面©为例讲解)
结点:树中每一个元素就是一个结点,如上面有12个结点;
结点的度:结点拥有的子树数目。如上面结点A的度为3;
树的度:树中所有结点的度的最大值,如上面树的度为3;
叶子结点:度为0的结点,简称叶结点、终端结点、外部结点,如上图中E/F/H/I/J/K/L都是叶结点;
分支结点:度大于0的节点,就是除了叶子结点外的其他结点都是分支结点;
树的高度:如上图©,他的高度为4;
孩子结点和双亲结点:
结点的层次:
兄弟结点:
堂兄弟结点:
祖先结点:
子孙结点:
上面几个没有解释的结点,应该都是很好理解的,按照字面上理解就好,他们并不重要,我们只需要知道有这些概念即可。若是有读者想知道,自己查阅即可。
路径:就是一个结点到另外一个结点的分支;
有序树:树中结点的各子树看成从左到右是有次序的,即子树之间存在着确定的次序关系,这样的树称为有序树;
无序树:根结点的各课子树之间存在不确定的次序关系,可以相互交换位置,称为无序树;
森林:m(m>=0)颗互不相交的树的集合构成森林。
3.二叉树
任何树和森林都可以转化为二叉树,比较适合计算机处理。二叉树是我们要学习掌握的重点。
二叉树的基本形态如下:
所谓的二叉树,只不过实在树的基础上做了更高的限制,规定,二叉树中各个结点的度最大为2.
这里也有两个个概念:
满二叉树:只含有度为0和2的结点,且度为0的结点只存在于最后一层的二叉树。
完全二叉树:对任意一颗满二叉树,从他的最后一层的最右边结点起,按照从上到下,从右到左的次序,去掉若干结点后,得到的二叉树
如下图:
完全二叉树的性质:
设完全二叉树结点数为n,将各结点从左到右依次标号,根节点为1,某结点为i。则:
当i>1时,父节点为结点i/2
当2i>n时,则结点i肯定没有左孩子,否则左孩子是2i
当2i+1>n,则结点i肯定没有右孩子,否则右孩子是结点2i+1
二叉树的存储:顺序存储与链式存储;
顺序存储:
用数组将二叉树中数据元素存储起来,此方法只适用于完全二叉树,如果想存储普通二叉树,需要将其转化为完全二叉树。
有n个结点的完全二叉树,用有n+1个元素的数组进行顺序存放,结点号和数组下标一一对应,下标为零的元素不用。
例:找到族谱中"li"的父亲是谁?如下图:
程序如下:
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char a[10][10]={"#","zhao","qian","sun","li","zhou","wu","zheng","wang","qwe"};
int i;
for(i=0;i<10;i++)
{
if (strcmp(a[i],"li")==0)
{
printf("%s\n",a[i/2]);
break;
}
}
return 0;
}
运行结果:
链式存储:
结点:
typedef struct Node
{
int data;
struct Node *lchild,rchild;
}node;
二叉树遍历:
沿某条搜索路径周游二叉树,对树的每个结点访问且仅访问一次。
二叉树三种遍历方式:使用递归的思想
先序遍历:先访问根节点,在遍历左右子树
中序遍历:遍历左子树,访问根节点然后遍历右子树
后序遍历:遍历完左右子树,在访问根节点
最后我们来看看例程:
编程练习:
定义结点;
建立树; 先序输入
三种遍历;
如下图:
#include <stdio.h>
#include <stdlib.h>
//1、定义树的结点
typedef struct Node
{
char data;
struct Node *lchild,*rchild;
}node;
//2、创建树
void create_tree(node **head)
{
char ch;
scanf("%c",&ch);
if (ch=='#')
{
*head=NULL;
}
else
{
*head=(node *)malloc(sizeof(node));
(*head)->data=ch;
create_tree(&((*head)->lchild)); //先存储左边的
create_tree(&((*head)->rchild));
}
}
//3、先序遍历
void pre_order(node *head)
{
if (head!=NULL)
{
printf("%c ",head->data);
pre_order(head->lchild);
pre_order(head->rchild);
}
}
//3、中序遍历
void mid_order(node *head)
{
if (head!=NULL)
{
mid_order(head->lchild);
printf("%c ",head->data);
mid_order(head->rchild);
}
}
//3、后序遍历
void lat_order(node *head)
{
if (head!=NULL)
{
lat_order(head->lchild);
lat_order(head->rchild);
printf("%c ",head->data);
}
}
int main(int argc, char const *argv[])
{
node *head;
create_tree(&head); //传址
pre_order(head);
printf("\n");
mid_order(head);
printf("\n");
lat_order(head);
printf("\n");
return 0;
}
运行结果: