二叉树是数据结构中的基础,也是基础中最重要的一部分知识点,以前学过二叉树、但是对于二叉树的知识从来没有完整的整理过,秋招就要开始,将二叉树的所有知识进行整理,准备面试。
一、树基础概念以及定义
树是一种数据结构,它是由n(n>1)个有限节点组成的一个具有层次关系的集合。
树的基本概念:
1、双亲:若有一个结点有子树,那么该结点就称为子树根的“双亲”。
2、孩子结点:子树的根称为改子树双亲结点的“孩子结点”。
3、兄弟结点:有相同的双亲的结点称为“兄弟结点”。
4、结点的度:结点拥有的子树的数目。
5、叶子结点:度为0的结点。
6、分支结点:度不为0的结点
6、树的度:数中结点的最大的度
7、层次:根节点层次为1,其余节点的层次等于该节点的双亲节点的层次+1.
8、树的高度:数中结点的最大层次
9:森林:0个或多个不想交的树的组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
二、二叉树的基本概念
定义:二叉树是每个结点最多有两个子树的树的结构。通常子树被称作“左子树”和“右子树”。
二叉树的五种状态:
1、空二叉树:
2、只有一个根的二叉树
3、只有左子树的二叉树
4、只有右子树的二叉树
5、完全二叉树
二叉树的三种重要类型
1、完全二叉树:若二叉树的高度为h,除h层外,其他各层(1~h-1)的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右一次排布,这就是完全二叉树。
2、满二叉树:除了叶子结点外每一个结点都有左右子树且叶子结点都在最底层的二叉树。
3、平衡二叉树:平衡二叉树又被称为AVL树(区别于AVL算法),它是一颗二叉排序树,且具有以下性质:他是一颗空树或它的左右子树的高度差的绝对值不超过1,并且左右两个子树都是一颗平衡二叉树。
三、二叉树的基本性质
性质1:在二叉树的第i层最多拥有个节点。
性质2:深度为k的二叉树最多拥有个结点。
性质3:包含n个结点的二叉树的高度至少是
性质4:在任意一个二叉树,如终端节点的个数为n0,度为2的结点个数为n2,则有n0 = n2+1成立。
四、二叉树的遍历
二叉树的结点构成:
typedef struct node
{
int data;
struct node *left;
struct node *right;
}BTNode;
对于二叉树的遍历我们基本都在使用三种方式:
1、先序遍历:
对于每一个结点都有:
(1)先访问根节点。
(2)再访问左子树。
(3)后访问右子树。
2、中序遍历
对于每一个结点都有:
(1)先访问左子树。
(2)再访问根节点。
(3)后访问右子树。
3、后序遍历
对于每一个结点都有
(1)先访问左子树。
(2)再访问右子树。
(3)后访问根结点。
(一)递归实现二叉树遍历
前序遍历
void Forder(BTNode *root)
{
if(root!=NULL)
{
cout<<root->data<<' ';
Forder(root->left);
Forder(root->right);
}
}
中序遍历
void Middle(BTNode *tree)
{
if(tree!=NULL);
{
Middle(tree->left);
cout<<tree->data<<" ";
Middle(tree->right);
}
}
后序遍历
void After(BTNode * tree)
{
if(tree!=NULL)
{
After(tree->left);
After(tree->right);
cout<<tree->data<<" ";
}
}
(二)非递归实现二叉树遍历
对于前序,中序遍历和后序遍历的思路,都是使用栈。
(1)非递归实现前序遍历
根据前序遍历的访问顺序,优先访问根节点,然后再分别访问左子树和右子树。即对于任一结点,其可看做是根节点,因此可以直接访问(出栈操作),因此对于任一结点:
(1)出栈P;
(2)判断结点P的右孩子是否为空,若为空,则不处理,如不为空,将右子树的根节点入栈;
(3)判断P结点的左子树是否为空,如果存在,则进行左子树入栈。
void NonRecursiveForwardOutTreee(BTNode *tree)
{
int top = -1;
BTNode **Stack = (BTNode **)malloc(sizeof(BTNode*) * 100);
//创建栈空间,栈中存放的是每个结点的指针即BTNode * 类型的数据
BTNode *P = tree;
Stack[++top] = tree;
while (top != -1)
{
//输出根节点的数据,即根节点先出栈
P = Stack[top--];
cout << P->data << " ";
if (P->right)//如果存在右子树,再将右子树的根节点存入栈
{
Stack[++top] = P->right;
}
if (P->left) //再将左子树入栈 #因为栈的先入后出模型,所以同一节点的左子树总是要先比右子树输出的早
{
Stack[++top] = P->left;
}
}
}
(2)非递归实现中序遍历
对于任意一点:
(1)P左子树如果存在,则入栈,直到P的左子树为空。
(2)只要P的右子树也不存在,则该节点一定是,叶子结点,那么P出栈,直到,P不是叶子结点。
(3)P不是叶子结点,那么,P一定存在为入栈的右子树,右子树入栈。
void NonRecursiveMiddleOutTree(BTNode *tree)
{
BTNode **Stack = (BTNode**)malloc(sizeof(BTNode*) * 100);
//构造栈空间
BTNode *P = tree;
int top = -1; //栈顶指针
Stack[++top] = tree;
while (top !=-1) //栈不空
{
//因为中序遍历最先输出的一定是左侧最小的左子树,
//并且从该左子树开始输出,然后该左子树的根节点,然后是左子树
//所以是所有的左子树全部入栈后开始输出。
while (P->left)
{
//该节点的左子树,不空就入栈
P = P->left;
Stack[++top] = P;
}
while (P->right == NULL && top!=-1)
{
//找到了最小的左子树了,叶子结点
P = Stack[top--];
cout << P->data << " ";
}
//当出栈到某个节点的右子树存在是停止出栈,右子树入栈,
if (P->right)
{
P = P->right;
Stack[++top] = P;
}
}
free(Stack);
}
(3)非递归实现后序遍历
保证根节点的左子树和右子树都已经被访问过了,因此对于任意结点P,先将其入栈,如果P不存在在左子树或右子树,可直接出栈P;或者P存在左子树或右子树,但是其左子树和右子树都已经被出栈,则同样可一访问该结点。如果不是上面两种情况,则将P的左右子树依次入栈。这样就保证每次访问栈顶元素的时候,左子树和右子树的根节点都可以在前面被访问到。
(4)按层输出二叉树:
二叉树按层输出一定使用队列;
(1)、根节点入队
(2)、输出跟节点
(3)、左子节点非空 入队;
(4)、右子节点非空 入队;
(5)、队列非空 出队;
void DuilieCeng(BTNode* root,int size)
{
BTNode **S =(BTNode**)malloc(sizeof(BTNode*) * size);//分配队内存;;
BTNode *P;
int qian = -1, hou = -1;
S[((++hou)% size)] = root;
while (hou!=qian)
{
P = S[((++qian)% size)]; //出队;
cout << " " << P->data << " ";
if (P->left)
{
S[((++hou) % size)] = P->left; //左侧入队;
}
if (P->right)
{
S[((++hou) % size)] = P->right; //右侧入队;
}
}
}
(四)、二叉树的创建
上面是二叉树的三种遍历方式,我们可以使用这三种方式读取一个二叉树中的数据,我们也可使用这三种遍历方式去创建一颗二叉树。下面是三种创建方式:
(1)先序遍历
BTNode * ForwardCreateTree()
{
char data;
BTNode *node = NULL;
cin >> data;
if (data == '#')
node = NULL;
else
{
node = (BTNode*)malloc((sizeof(BTNode)));
if(node)
{
node->data = data - '0';
node->left = ForwardCreateTree();
node->right = ForwardCreateTree();
}
}
return node;
}
测试结果:
4 5 6 # # 7 # # 1 2 # # 3 # #
前序输出二叉树:
4 5 6 7 1 2 3
请按任意键继续. . .
(2)中序遍历
BTNode * MiddleCreateTree()
{
char data;
BTNode *node = NULL;
cin >> data;
if (data == '#')
node = NULL;
else
{
node = (BTNode *)malloc(sizeof(BTNode));
node->left = MiddleCreateTree();
node->data = data - '0';
node->right = MiddleCreateTree();
}
return node;
}
测试代码:中序遍历创建二叉树时,虽然输入的顺序与前序相同,但是创建的方式不一样。
4 5 6 # # 7 # # 1 2 # # 3 # #
中序输出二叉树:
6 5 7 4 2 1 3
前序输出二叉树:
4 5 6 7 1 2 3
请按任意键继续. . .
(3)后序遍历
BTNode * AfterCreateTree()
{
char data;
BTNode *node = NULL;
cin >> data;
if (data == '#')
node = NULL;
else
{
node = (BTNode *)malloc(sizeof(BTNode));
node->left = AfterCreateTree();
node->right = AfterCreateTree();
node->data = data - '0';
}
return node;
}
测试代码:后序遍历创建二叉树时,虽然输入的顺序与前序相同,但是创建的方式不一样
4 5 6 # # 7 # # 1 2 # # 3 # #
后序遍历输出二叉树
6 7 5 2 3 1 4
中序输出二叉树:
6 5 7 4 2 1 3
前序输出二叉树:
4 5 6 7 1 2 3
请按任意键继续. . .