C++ 数据结构算法 学习笔记(20) - 二叉树
原理精讲
我们以前介绍的线性表一样,一个没有限制的线性表应用范围可能有限,但是我们对线性表进行一些限制就可 以衍生出非常有用的数据结构如栈、队列、优先队列等。
树也是一样,一个没有限制的树由于太灵活,控制起来比较复杂。如果对普通的树加上一些人为的限制,比如 节点只允许有两个子节点,这就是我们接下来要介绍的二叉树。
二叉树是一个每个结点最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树。
二叉树分类
-
完全二叉树 - 若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶 子节点,并且叶子结点都是从左到右依次排布,这就是完全二叉树(堆就是完全二叉树)。
-
满二叉树 - 除了叶结点外每一个结点都有左右子节点且叶子结点都处在最底层的二叉树
-
平衡二叉树 - 又被称为 AVL 树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都 是一棵平衡二叉树
-
二叉搜索树 - 又称二叉查找树、二叉排序树(Binary Sort Tree)。它是一颗空树或是满足下列性质的二叉树:
-
若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;
-
若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值
-
左、右子树也分别为二叉排序树。
-
红黑树 - 是每个节点都带有颜色属性(颜色为红色或黑色)的自平8衡9二7叉94查3找8树40,1满1足1下1列性质:
-
节点是红色或黑色
-
根节点是黑色
-
所有叶子节点都是黑色
-
每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
-
从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点
-
-
-
-
二叉搜索树的算法实现
-
当我们要在一组数中要找到你女朋友(或未来女朋友)的年龄,比如 26?你该怎么找
答:从左至右 或 从右至左遍历一次,找到这个数字
-
当我们把数据进行排序(按照从小到大的顺序排列)后,再查找相应的这条记录?还是用上面的方法吗?
答:最快的方式,是采用折半法(俗称二分查找)
思考点:
当我们有新的数据加进来,或者删除其中的一条记录,为了保障查找的效率,我们仍然要保障数组有序,但是,会 碰到我们讲顺序表时的问题,涉及到大量数据的移动!在插入和删除操作上,就需要耗费大量的时间(需进行元素 的移位),能否有一种既可以使得插入和删除效率不错,又可高效查找的数据结构和算法呢?
联想:
首先解决一个问题,插入时不移动元素,我们可以想到链表,但是要保证其有序的话,首先得遍历链表寻找合适的 位置,那么又如何高效的查找合适的位置呢,能否可以像二分一样,通过一次比较排除一部分元素?
解决办法:
那么我们可以用二叉树的形式,以数据集第一个元素为根节点,之后将比根节点小的元素放在左子树中,将比根节 点大的元素放在右子树中,在左右子树中同样采取此规则。那么在查找 x 时,若 x 比根节点小可以排除右子树所有元素, 去左子树中查找(类似二分查找),这样查找的效率非常好,而且插入的时间复杂度为 O(h),h 为树的高度,较 O(n) 来说效率提高不少。故二叉搜索树用作一些查找和插入使用频率比较高的场景。
二叉树储存方式
二叉树一般采用链式存储方式:每个结点包含两个指针域,指向两个孩子结点,还包含一个数据域,存储结点信息
二叉树的节点结构体定义
代码实现:
#define MAX_NODE 1024
#define isLess(a, b) (a<b)
#define isEqual(a, b) (a==b)
typedef int ElemType;
typedef struct _Bnode {
ElemType data;
struct _Bnode* lchild, * rchild;
}Bnode, Btree;
二叉树搜索树插入节点
将要插入的结点 e,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上 操作直到找到一个空位置用于放置该新节点。
代码实现:
bool InsertBtree(Btree** root, Bnode* node) {
Bnode* tmp = NULL;
Bnode* parent = NULL;
if (!node) {
return false;
}
else {
node->lchild = NULL;
node->rchild = NULL;
}
if (*root) {
tmp = *root;
}
else {
*root = node;
return true;
}
while (tmp != NULL) {
parent = tmp;
if (isLess(node->data, tmp->data)) {
tmp = tmp->lchild;
}
else {
tmp = tmp->rchild;
}
}
if (isLess(node->data, parent->data)) {
parent->lchild = node;
}
else {
parent->rchild = node;
}
return true;
}
二叉树搜索树删除节点
将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以 上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:
-
删除节点不存在左右子节点,即为叶子节点,直接删除
-
删除节点存在左子节点,不存在右子节点,直接把左子节点替代删除节点
-
删除节点存在右子节点,不存在左子节点,直接把右子节点替代删除节点
-
删除节点存在左右子节点,则取左子树上的最大节点或右子树上的最小节点替换删除节点。
代码实现:
###Btree* DeleteNode(Btree* root, int key, Bnode*& deletedNode) {
if (root == NULL)return NULL;
if (root->data > key)
{
root->lchild = DeleteNode(root->lchild, key, deletedNode);
return root;
}
if (root->data < key)
{
root->rchild = DeleteNode(root->rchild, key, deletedNode);
return root;
}
deletedNode = root;
if (root->lchild == NULL && root->rchild == NULL)return NULL;
if (root->lchild == NULL && root->rchild != NULL)return root->rchild;
if (root->lchild != NULL && root->rchild == NULL)return root->lchild;
int val = findMax(root->lchild);
root->data = val;
root->lchild = DeleteNode(root->lchild, val, deletedNode);
return root;
}
二叉搜索树搜索
代码实现:
Bnode* QueryByLoop(Bnode* root, int e) {
while (NULL != root && !isEqual(root->data, e)) {
if (isLess(e, root->data)) {
root = root->lchild;
}
else {
root = root->rchild;
}
}
return root;
}
二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问所有结点,使得每个结点被当且访问一次。共分为四种方式
方式一:前序遍历 - 先访问根节点,然后前序遍历左子树,再前序遍历右子树
代码实现(递归方式):
void PreOrderRec(Btree* root)
{
if (root == NULL)
{
return;
}
printf("- %d -", root->data);
PreOrderRec(root->lchild);
PreOrderRec(root->rchild);
}
代码实现(栈方式):
具体过程: 首先申请一个新的栈,记为 stack; 将头结点 head 压入 stack 中; 每次从 stack 中弹出栈顶节点,记为 cur,然后打印 cur 值,如果 cur 右孩子不为空,则将右孩子压入栈中;如果 cur 的左 孩子不为空,将其压入 stack 中; 重复步骤 3,直到 stack 为空.
void PreOrder(Btree* root)
{
Bnode cur;
if (root == NULL)
{
return;
}
SqStack stack;
InitStack(stack);
PushStack(stack, *root);
while (!(IsEmpty(stack)))
{
PopStack(stack, cur);
printf("- %d -", cur.data);
if (cur.rchild != NULL)
{
PushStack(stack, *(cur.rchild));
}
if (cur.lchild != NULL)
{
PushStack(stack, *(cur.lchild));
}
}
DestroyStack(stack);
}
方式二:中序遍历
先访问根节点的左子树,然后访问根节点,最后遍历右子树
方式三:后序遍历
从左到右,先叶子后节点的方式遍历访问左右子树,最后访问根节点
方式四: 底层遍历
从根节点从上往下逐层遍历,在同一层,按从左到右的顺序对节点逐个访问
完整源码
完整源码实现:
stack.h
#pragma once
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include "tree.h"
#define MaxSize 128
typedef struct _SqStack {
Bnode* base;
Bnode* top;
}SqStack;
bool InitStack(SqStack& S)
{
S.base = new Bnode[MaxSize];
if (!S.base) return false;
S.top = S.base;
return true;
}
bool PushStack(SqStack& S, Bnode e)
{
if (S.top - S.base == MaxSize) return false;
*(S.top++) = e;
S.top++;
return true;
}
bool PopStack(SqStack& S, Bnode& e)
{
if (S.base == S.top) {
return false;
}
e = *(--S.top);
return true;
}
Bnode* GetTop(SqStack& S)
{
if (S.top != S.base) {
return S.top - 1;
}
else {
return NULL;
}
}
int GetSize(SqStack& S) {
return (S.top - S.base);
}
bool IsEmpty(SqStack& S) {
if (S.top == S.base) {
return true;
}
else {
return false;
}
}
void DestroyStack(SqStack& S) {
if (S.base) {
free(S.base);
S.base = NULL;
S.top = NULL;
}
}
tree.h
#ifndef __TREE_H__
#define __TREE_H__
#define MAX_NODE 1024
#define isLess(a, b) (a<b)
#define isEqual(a, b) (a==b)
typedef int ElemType;
typedef struct _Bnode {
ElemType data;
struct _Bnode* lchild, * rchild;
}Bnode, Btree;
#endif
main.cpp
#include <stdio.h>
#include <stdlib.h>
#include "51-tree.h"
#include "51-Stack.h"
bool InsertBtree(Btree** root, Bnode* node) {
Bnode* tmp = NULL;
Bnode* parent = NULL;
if (!node) {
return false;
}
else {
node->lchild = NULL;
node->rchild = NULL;
}
if (*root) {
tmp = *root;
}
else {
*root = node;
return true;
}
while (tmp != NULL) {
parent = tmp;
if (isLess(node->data, tmp->data)) {
tmp = tmp->lchild;
}
else {
tmp = tmp->rchild;
}
}
if (isLess(node->data, parent->data)) {
parent->lchild = node;
}
else {
parent->rchild = node;
}
return true;
}
int findMax(Btree* root)
{
if (root->rchild == NULL) {
return root->data;
}
return findMax(root->rchild);
}
Btree* DeleteNode(Btree* root, int key, Bnode*& deletedNode) {
if (root == NULL)return NULL;
if (root->data > key)
{
root->lchild = DeleteNode(root->lchild, key, deletedNode);
return root;
}
if (root->data < key)
{
root->rchild = DeleteNode(root->rchild, key, deletedNode);
return root;
}
deletedNode = root;
if (root->lchild == NULL && root->rchild == NULL)return NULL;
if (root->lchild == NULL && root->rchild != NULL)return root->rchild;
if (root->lchild != NULL && root->rchild == NULL)return root->lchild;
int val = findMax(root->lchild);
root->data = val;
root->lchild = DeleteNode(root->lchild, val, deletedNode);
return root;
}
Bnode* QueryByRec(Btree* root, ElemType e) {
if (isEqual(root->data, e) || NULL == root) {
return root;
}
else if (isLess(e, root->data)) {
return QueryByRec(root->lchild, e);
}
else {
return QueryByRec(root->rchild, e);
}
}
Bnode* QueryByLoop(Bnode* root, int e) {
while (NULL != root && !isEqual(root->data, e)) {
if (isLess(e, root->data)) {
root = root->lchild;
}
else {
root = root->rchild;
}
}
return root;
}
void PreOrderRec(Btree* root)
{
if (root == NULL)
{
return;
}
printf("- %d -", root->data);
PreOrderRec(root->lchild);
PreOrderRec(root->rchild);
}
//This is using stack to do the PreOrder printing function
void PreOrder(Btree* root)
{
Bnode cur;
if (root == NULL)
{
return;
}
SqStack stack;
InitStack(stack);
PushStack(stack, *root);
while (!(IsEmpty(stack)))
{
PopStack(stack, cur);
printf("- %d -", cur.data);
if (cur.rchild != NULL)
{
PushStack(stack, *(cur.rchild));
}
if (cur.lchild != NULL)
{
PushStack(stack, *(cur.lchild));
}
}
DestroyStack(stack);
}
int main(void) {
int test[] = { 19, 7, 25, 5, 11, 15, 21, 61 };
Bnode* root = NULL, * node = NULL;
node = new Bnode;
node->data = test[0];
InsertBtree(&root, node);
for (int i = 1; i < sizeof(test) / sizeof(test[0]); i++) {
node = new Bnode;
node->data = test[i];
if (InsertBtree(&root, node)) {
printf("Node %d Insert Successfully\n", node->data);
}
else {
}
}
printf("The Printout result is: \n");
PreOrderRec(root);
printf("\n");
system("pause");
printf("Delete the node 15\n");
Bnode* deletedNode = NULL;
root = DeleteNode(root, 15, deletedNode);
printf("Bit tree delete the node 15, %s\n", deletedNode ? "Successfully delete" : "Failed to delete, the node is not exist");
if (deletedNode) delete deletedNode;
printf("After delete, the result is \n");
PreOrderRec(root);
printf("\n");
Bnode* node1 = QueryByLoop(root, 20);
printf("Search element from the bit tree 20 %s\n", node1 ? "Exist" : "Not Exist");
Bnode* node2 = QueryByLoop(root, 21);
printf("Seach element from the node 21 %s\n", node2 ? "Exist" : "Not exist");
system("pause");
return 0;
}