目录
1.树的基础知识概述
树状图是一种数据结构,它是由 n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树;
专业术语 | 中 文 | 描 述 |
Root | 根节点 | 一棵树的顶点 |
Child | 孩子节点 | 一个结点含有的子树的根结点称为该结点的子结点 |
Leaf | 叶子节点 | 没有孩子的节点(度为0) |
Degree | 度 | 一个节点包含的子树的数量 |
Edge | 边 | 一个节点与另外一个节点的连接 |
Depth | 深度 | 根节点到这个节点经过的边的数量 |
Height | 节点高度 | 从当前节点到叶子节点形成路径中边的数量 |
Level | 层级 | 节点到根节点最长路径的边的总和 |
Path | 路径 | 一个节点和另一个节点之间经过的边和 Node 的序列 |
如图所示:
2.二叉树 原理精讲
2.1二叉树
二叉树是一个每个结点最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树~
(1)在风控二叉树,第i-1层的结点总数不超过,i>=1;(2)深度为h-1的二叉树最多有-1个结点(h>=1),最少有h个结点
(3)对于任意一棵二叉树,如果叶节点为N0,而度数为2 的结点总数为N2,则N0=N2+1;
每个度为1的结点有1条边,度为2的结点有两条边。所以总边数为1*n1+2*n2,总边数等于结点数减1,即 N-1 = 1*n1+2*n2。其中总结点数又等于度为1、度为2、度为0的结点数和,即 N = n1 + n2 + n0;联立可得N0=N2+1。
2.2常见二叉树 分类
1)完全二叉树 ——若设二叉树的高度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层有叶子节点,并且叶子结点都是从左到右依次排布,这就是完全二叉树(堆就是完全二叉树)。
2)满二叉树 ——除了叶结点外每一个结点都有左右子节点且叶子结点都处在最底层的二叉树。
3)平衡二叉树 ——又被称为 AVL 树,它是一颗空树或左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。
4)二叉搜索树 ——又称二叉查找树、二叉排序树(Binary Sort Tree)。它是一颗空树或是满足下列性质的二叉树:(右边>=根>=左边)
1)若左子树不空,则左子树上所有节点的值均小于或等于它的根节点的值;
2)若右子树不空,则右子树上所有节点的值均大于或等于它的根节点的值;
3)左、右子树也分别为二叉排序树。
5)红黑树 ——是每个节点都带有颜色属性(颜色为红色或黑色)的自平衡二叉查找树,满足下列性质:
1)节点是红色或黑色;
2)根节点是黑色;
3)所有叶子节点都是黑色;
4)每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续为红色的结点);
5)从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。 (没有度为1的结点)。
以上规则可以保证左右子树结点数差距不超过两倍~
3.二叉搜索树的算法实现
比如有以下数据:
当我们想保证查找效率时,可以用顺序表存储,当我们想保证插入和删除效率时,我们可以用链式表存储,有没有一种存储方法可以同时兼顾顺序表和链式表的优点?
使用二叉树,便可兼顾查找效率和插入删除效率~
二叉树一般采用链式存储方式:每个结点包含两个指针域,指向两个孩子结点,还包含一个数据域,存储结点信息。
结点结构体定义:
typedef struct _BNode {
int data;
struct _BNode *lchild, *rchild;
}Bnode,*Btree;
3.1二叉搜索树插入结点
//二叉树插入结点
/*将插入结点e,与结点root结点进行比较,若小于则去到左子树,否则
去右子树进行比较,重复以上操作直到找到一个空位置放置该结点
*/
bool InsertBtree(Btree* root, Bnode* node) {
Bnode* temp = NULL;
Bnode* parent = NULL;
if (!node) { //如果插入结点为空,返回false
return false;
}else { //清空插入结点的左右子树
node->lchild = NULL;
node->rchild = NULL;
}
if (!(*root)) { //如果根节点不存在,将插入结点作为根节点
*root = node;
return true;
}else {
temp = *root;
}
while (temp != NULL) {
parent = temp;
if (temp->data>node->data) { //小于,继续向左
temp = temp->lchild;
}
else { //大于,继续向右(不可以有相同的值)
temp=temp->rchild;
}
//while循环结束,parent所处位置即为要插入结点的父结点
}
if (node->data < parent->data) {
parent->lchild = node;
}
else {
parent->rchild = node;
}
return true;
}
3.2二叉书搜索树删除结点
将要删除的节点的值,与节点 root 节点进行比较,若小于则去到左子树进行比较,若大于则去到右子树进行比较,重复以上操作直到找到一个节点的值等于删除的值,则将此节点删除。删除时有 4 中情况须分别处理:
1.删除节点不存在左右子节点,即为叶子节点,直接删除
2.删除节点存在左子节点,不存在右子节点,直接把左子节点替代删除节点
3.删除节点存在右子节点,不存在左子节点,直接把右子节点替代删除节点
4.删除节点存在左右子节点,则取左子树上的最大节点或右子树上的最小节点替换删除节点。
代码实现:
//查找左子树最大值
int findLeftMax(Btree* root) {
/*采用递归方式查找
* if (root->rchild == NULL)
return root->data;
return findMax(root->rchild);
*/
//采用循环查找
Btree indexNode = *root;
while (indexNode->rchild)
indexNode = indexNode->rchild;
return indexNode->data;
}
//采用递归的方式删除结点
/*
这种递归方式,是将要修改的结点的一层一层的返回
*/
Btree deleteNode(Btree* root, int value) {
Btree compareNode = *root;
//节点为空(递归找不到value/根节点为空),直接返回
if (!compareNode)return compareNode;
//大于
if (compareNode->data > value) {
//左子树重新被赋值
compareNode->lchild = deleteNode(&(compareNode->lchild), value);
return compareNode;
}
//小于
else if (compareNode->data < value) {
//右子树重新被赋值
compareNode->rchild = deleteNode(&(compareNode->rchild), value);
return compareNode;
}
else {//等于
Btree temp = NULL;
//无左右子节点,直接返回NULL
if (compareNode->lchild == NULL && compareNode->rchild == NULL) {
delete compareNode;
}
//有左子树,返回左子树
else if (compareNode->lchild && compareNode->rchild == NULL) {
temp = compareNode->lchild;
delete compareNode;
}
//有右子树,返回右子树
else if (compareNode->rchild && compareNode->lchild == NULL) {
temp = compareNode->rchild;
delete compareNode;
}
else {
//这里采用左子树最大值替换
int leftMax = findLeftMax(&(compareNode->lchild));
//最大值替换删除结点的值
compareNode->data = leftMax;
//将最大值从树中删除
compareNode->lchild = deleteNode(&(compareNode->lchild), leftMax);
temp= compareNode;
}
return temp;
}
}
3.3二叉搜索树查找
// 采用递归方式查找结点
/*
Bnode* queryByRec(Btree root, int value) {
if (root == NULL || root->data==value ){
return root;
}
else if (root->data < value) {
return queryByRec(root->lchild, value);
}
else {
return queryByRec(root->rchild, value);
}
}*/
// 使用非递归方式查找结点
Bnode* queryByLoop(Btree root, int value) {
while (root != NULL && root->data!=value) {
if (root->data>value) {
root = root->lchild;
}
else {
root = root->rchild;
}
}
return root;
}
3.4二叉搜索树遍历结点
3.4.1前序遍历
前序遍历 - 先访问根节点,然后前序遍历左子树,再前序遍历右子树
上图前序遍历结果: 19 7 5 11 15 25 21 61
void PreOrderRec(Btree* root) {
Btree indexNode = *root;
if (indexNode == NULL)return;
printf("—%d", indexNode->data);
PreOrderRec(&indexNode->lchild);
PreOrderRec(&indexNode->rchild);
}
或者使用栈
具体过程:
1.首先申请一个新的栈,记为 stack;
2.将头结点 head 压入 stack 中;
3.每次从 stack 中弹出栈顶节点,记为 cur,然后打印 cur 值,如果 cur 右孩子不为空,则将 右孩子压入栈中;如果 cur 的左孩子不为空,将其压入 stack 中;
重复步骤 3,直到 stack 为空。
关于栈的代码将会放在文章末尾~
3.4.2中序遍历
中序遍历 - 先访问根节点的左子树,然后访问根节点,最后遍历右子树
中序遍历结果: 5 7 11 15 19 21 25 61
3.4.3后序遍历
后序遍历 - 从左到右,先叶子后节点的方式遍历访问左右子树,最后访问根节点
后序遍历结果: 5 15 11 7 21 61 25 19
4.二叉搜索树完整代码
#include<iostream>
#include <stdlib.h>
using namespace std;
typedef struct _BNode {
int data;
struct _BNode *lchild, *rchild;
}Bnode,* Btree;
//二叉树插入结点
/*将插入结点e,与结点root结点进行比较,若小于则去到左子树,否则
去右子树进行比较,重复以上操作直到找到一个空位置放置该结点
*/
bool InsertBtree(Btree* root, Bnode* node) {
Bnode* temp = NULL;
Bnode* parent = NULL;
if (!node) { //如果插入结点为空,返回false
return false;
}else { //清空插入结点的左右子树
node->lchild = NULL;
node->rchild = NULL;
}
if (!(*root)) { //如果根节点不存在,将插入结点作为根节点
*root = node;
return true;
}else {
temp = *root;
}
while (temp != NULL) {
parent = temp;
if (temp->data>node->data) { //小于,继续向左
temp = temp->lchild;
}
else { //大于,继续向右(不可以有相同的值)
temp=temp->rchild;
}
//while循环结束,parent所处位置即为要插入结点的父结点
}
if (node->data < parent->data) {
parent->lchild = node;
}
else {
parent->rchild = node;
}
return true;
}
//二叉搜索树删除结点
//1.删除叶节点,直接删除
//2.删除结点存在左子树,不存在右子树,直接把左子节点替代删除结点
//3.删除结点存在有子节点,不存在左子节点,直接把右子节点代替删除结点
//4.删除结点存在左右子节点,则取左子树最大结点或右子树最小系欸DNA替换删除
//结点
int findLeftMax(Btree* root) {
/*采用递归方式查找
* if (root->rchild == NULL)
return root->data;
return findMax(root->rchild);
*/
//采用循环查找
Btree indexNode = *root;
while (indexNode->rchild)
indexNode = indexNode->rchild;
return indexNode->data;
}
//采用递归的方式删除结点
/*
这种递归方式,是将要修改的结点的一层一层的返回
*/
Btree deleteNode(Btree* root, int value) {
Btree compareNode = *root;
//节点为空(递归找不到value/根节点为空),直接返回
if (!compareNode)return compareNode;
//大于
if (compareNode->data > value) {
//左子树重新被赋值
compareNode->lchild = deleteNode(&(compareNode->lchild), value);
return compareNode;
}
//小于
else if (compareNode->data < value) {
//右子树重新被赋值
compareNode->rchild = deleteNode(&(compareNode->rchild), value);
return compareNode;
}
else {//等于
Btree temp = NULL;
//无左右子节点,直接返回NULL
if (compareNode->lchild == NULL && compareNode->rchild == NULL) {
delete compareNode;
}
//有左子树,返回左子树
else if (compareNode->lchild && compareNode->rchild == NULL) {
temp = compareNode->lchild;
delete compareNode;
}
//有右子树,返回右子树
else if (compareNode->rchild && compareNode->lchild == NULL) {
temp = compareNode->rchild;
delete compareNode;
}
else {
//这里采用左子树最大值替换
int leftMax = findLeftMax(&(compareNode->lchild));
//最大值替换删除结点的值
compareNode->data = leftMax;
//将最大值从树中删除
compareNode->lchild = deleteNode(&(compareNode->lchild), leftMax);
temp= compareNode;
}
return temp;
}
}
// 采用递归方式查找结点
/*
Bnode* queryByRec(Btree root, int value) {
if (root == NULL || root->data==value ){
return root;
}
else if (root->data < value) {
return queryByRec(root->lchild, value);
}
else {
return queryByRec(root->rchild, value);
}
}*/
// 使用非递归方式查找结点
Bnode* queryByLoop(Btree root, int value) {
while (root != NULL && root->data!=value) {
if (root->data>value) {
root = root->lchild;
}
else {
root = root->rchild;
}
}
return root;
}
void PreOrderRec(Btree* root) {
Btree indexNode = *root;
if (indexNode == NULL)return;
printf("—%d", indexNode->data);
PreOrderRec(&indexNode->lchild);
PreOrderRec(&indexNode->rchild);
}
int main() {
//_CrtSetBreakAlloc(86);
int test[] = { 19,7,25,5,11,15,21,61 ,4};
Btree root = NULL;
Btree node = NULL;
node = new Bnode;
node->data = test[0];
InsertBtree(&root, node);
for (int i = 1; i < sizeof(test) / sizeof(int); i++) {
node = new Bnode;
node->data = test[i];
InsertBtree(&root, node);
}
printf("前序遍历\n");
PreOrderRec(&root);
root= deleteNode(&root, 19);
printf("\n");
printf("删除19后的前序遍历\n");
PreOrderRec(&root);
printf("\n");
Btree temp;
printf("查找数据为61\n");
temp = queryByLoop(root, 61);
cout << temp->data << endl;
system("pause");
return 0;
}
运行结果:
stack代码实现:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#define MaxSize 128 //预先分配空间,这个数值根据实际需要预估确定
typedef struct _SqStack {
Bnode* base; //栈底指针
Bnode* top; //栈顶指针
}SqStack;
bool InitStack(SqStack& S) //构造一个空栈 S
{
S.base = new Bnode[MaxSize];//为顺序栈分配一个最大容量为 Maxsize 的空间
if (!S.base) //空间分配失败
return false;
S.top = S.base; //top 初始为 base,空栈
return true;
}
bool PushStack(SqStack& S, Bnode e) // 插入元素 e 为新的栈顶元素
{
if (S.top - S.base == MaxSize) //栈满
return false;
*(S.top++) = e; //元素 e 压入栈顶,然后栈顶指针加 1,等价于*S.top=e;
S.top++;
return true;
}
bool PopStack(SqStack& S, Bnode& e) //删除 S 的栈顶元素,暂存在变量 e中
{
if (S.base == S.top) { //栈空
return false;
}
e = *(--S.top); //栈顶指针减 1,将栈顶元素赋给 e
return true;
}
Bnode* GetTop(SqStack& S) //返回 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;
}
}