二叉树的傻瓜教程,简单实现
二叉树是数据结构初学者所要了解的重要概念,如果能用代码实现它的简单功能,能帮助我们更好的理解它的结构和用途。
在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。
一棵深度为k,且有2^k-1个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且或者最后一层是满的,或者是在右边缺少连续若干结点,则此二叉树为完全二叉树。具有n个结点的完全二叉树的深度为floor(log2n)+1。深度为k的完全二叉树,至少有2k-1个叶子结点,至多有2k-1个结点。
如下图:
以上来自————二叉树百度百科
了解单向链表结构的同学会知道链表的C语言实现需要用到结构体的知识,在单向链表中,结构体包含一个存放数据的域和指向下一个节点的地址的指针域。
而二叉树的节点包含数据域之余,还包含指向左指数的指针域——left,和指向右子树的指针域——right。就如上图F点的左子树是C,右子树是E,这时问题来了,如何用C语言表示如上图指向关系呢?(这也是许多数据结构需要理解的重要之处)。
重点来了!!!
我们先定义如下结构体:
typedef 相当于重命名,以后声明一个如下的结构体变量p就不需要struct node p;
了,直接Node p;
就行。
typedef struct node
{
char data;
struct node * left , *right;
}Node;
假设我们声明了三个如上结构的变量:Node F,C,E;
然后执行如下操作:
//涉及结构体和指针的简单知识,需要提前理解。
F.left = F.right = NULL;
F.data = 'F';
C.left = C.right = NULL;
C.data = 'C';
E.left = E.right = NULL;
E.data = 'E';
F.left = &C;
F.right = &E;
经过上述的代码运行后,F,C,E节点就向上图所表示的连接起来了!
当然以上的操作是为了便于理解而实现的。而且问题十分的明显,仅仅三个节点的关系实现就要如此复杂,是不可能真正在实际应用的。我们自然而然的想到要实现简单,需要将各种操作函数化,比如涉及插入的函数,涉及遍历的函数,涉及开辟空间的函数(比如我有一百个节点,不可能一一声明)等等…
关于插入函数,我们需要注意下,一个节点只能插入左右子树,而且插入的位置也需要分摊下来,如果所有新节点全部往右边或左边插入,就成线性表了。如下图:
所以我们规定一个插入的规则,比如一个数如果比当前节点大就放到节点的右边,如果比它小或者等于它就放在左边,如下图:这样树会显得平衡匀称点。这样的插入方式其实是实现二叉搜索树的插入方式。当然这里仅仅提一下,笔者后续可能会更新相关的文章,大家也可自行在网上搜索学习。
插入理解了,遍历的操作也需要明白。任何二叉树的遍历都是从根节点开始的(上图 3所在的节点)
遍历包含先序,中序,后序,层序遍历。
前三种遍历是用递归实现的,最后原代码会实现,层序遍历需要用到队列的知识,限于篇幅不会提及。
先序遍历顺序如下:3 1 2 4 (先中间,再左 再右)
中序遍历:1 2 3 4(先左 再中间 再右)
后序遍历:2 1 4 3(先左,再右,最后中间)
所谓先,中,后指的就是父节点的遍历顺序。
遍历方法是简单的递归实现,比较容易理解和记忆。
最重要的实现插入的操作。
我们先来看函数的参数声明。这里我定义了一个指向Node结构体的二级指针,和需要插入的数据data。为什么要定义二级指针呢,总的来说为了方便吧
举个例子:
我有一个Node类型的变量 n
如果我要在某个函数中改变n的值,我需要向该函数传入n的地址,也就是 &n
同理我有一个指针 p 我需要在函数中改变p的内容,我就得在函数中传入指针的地址,也就是 &p
假设一个Node类型指针的定义 是 Node * t;
所以二级指针的定义则为 Node ** p;
把t的地址赋给p的语句是p=&t;
需要通过p访问t的内容的语句是*p;
void insert(Node** t, int data)
void insert(Node** t, int data)
{
if (*t == NULL) //如果当前节点为空,则为其开辟空间,并赋值给它
{
(*t) = (Node*)malloc(sizeof(Node));
(*t)->data = data;
(*t)->left = (*t)->right = NULL; //不忘初始化指针指向为空。
}
else if (data >= (*t)->data) //比根节点(父节点)大则插入右边
{
insert(&((*t)->right), data);
}
else insert(&((*t)->left), data);//同理
}
接下来就是完整代码的提供了。
#include<stdio.h>
#include<malloc.h>
/**/
typedef struct node
{
int data;
struct node* left, * right;
}Node;
void insert(Node** t, int data)
{
if (*t == NULL)
{
(*t) = (Node*)malloc(sizeof(Node));
(*t)->data = data;
(*t)->left = (*t)->right = NULL;
}
else if (data >= (*t)->data)
{
insert(&((*t)->right), data);
}
else insert(&((*t)->left), data);
}
/**前序遍历 根左右**/
void PreOrderTravel(Node* T)
{
if (T == NULL )
return;
printf("%d ", T->data);
PreOrderTravel(T->left);
PreOrderTravel(T->right);
}
/**中序遍历 左根右**/
void InOrderTravel(Node* T)
{
if (T == NULL)
return;
InOrderTravel(T->left);
printf("%d ", T->data);
InOrderTravel(T->right);
}
/**后序遍历 左右根**/
void TailOrderTravel(Node* T)
{
if (T == NULL)
return;
TailOrderTravel(T->left);
TailOrderTravel(T->right);
printf("%d ", T->data);
}
int main()
{
Node* t = NULL;
int i,a[4] = { 3,1,2,4 };
for(i = 0; i < 4; i++)
insert(&t, a[i]);
PreOrderTravel(t); printf("\n");
InOrderTravel(t); printf("\n");
TailOrderTravel(t); printf("\n");
}
测试一下:
感谢观看。
希望点个赞~~~~~