对于树的基本认识,我们很容易通过我们平常所见到的树来理解:一棵树,有一个根,根往上又会分叉出大树枝,大树枝又会分叉出小树枝,以此往复,直到最后是叶子。而作为数据结构的树也是类似的,只不过我们通常将它倒着画。树的应用也相当广泛,例如在文件系统,数据库索引中的应用等。本文会对树的基本概念做介绍,但重点介绍二叉查找树。
软件是通过数据和算法实现对现实世界的抽象,具有层次关系的数据在现实世界中能找到很多实例,比如:
- 公司组织架构:董事长-CXO-总监-经理-主管-员工;
- 中国行政区域划分:中国-省-市(县)-街道(小区)-门牌号;
二叉查找树,也称作二叉搜索树,有序二叉树,排序二叉树,而当一棵空树或者具有下列性质的二叉树,就可以被定义为二叉查找树:
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
二叉查找树相比于其他数据结构的优势在查找、插入的时间复杂度较低,为O(log n)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、multiset、关联数组等。对于大量的输入数据,链表的线性访问时间太慢,不宜使用。
查找
步骤:
- 若根结点的关键字值等于查找的关键字,成功;
- 若小于根结点的关键字值,递归查左子树;
- 若大于根结点的关键字值,递归查右子树;
- 若子树为空,查找不成功。
插入
首先执行查找算法,找出被插结点的父亲结点。 判断被插结点是其父亲结点的左、右儿子。将被插结点作为叶子结点插入。
若二叉树为空。则首先单独生成根结点。
注意:新插入的结点总是叶子结点。
删除
在二叉排序树删去一个结点,情况相对比较复杂,分三种情况讨论:
- 若*p结点为叶子结点,即PL(左子树)和PR(右子树)均为空树。由于删去叶子结点不破坏整棵树的结构,则可以直接删除此子结点。
- 若p结点只有左子树PL或右子树PR,此时只要令PL或PR直接成为其双亲结点f的左子树(当p是左子树)或右子树(当p是右子树)即可,作此修改也不破坏二叉排序树的特性。
- 若p结点的左子树和右子树均不空。在删去p之后,为保持其它元素之间的相对位置不变,可按中序遍历保持有序进行调整,可以有两种做法: 其一是令p的左子树为f的左/右(依p是f的左子树还是右子树而定)子树,s为p左子树的最右下的结点,而p的右子树为s的右子树;
其二是令p的直接前驱(或直接后继)替代p,然后再从二叉排序树中删去它的直接前驱(或直接后继)-即让f的左子树(如果有的话)成为p左子树的最左下结点(如果有的话),再让f成为p的左右结点的父结点。
遍历
常见的遍历主要分以下三种:
- 前序遍历,先检查节点值(根节点),然后递归遍历左子树和右子树;
- 中序遍历,先遍历左子树,然后检查当前节点值,最后遍历右子树;
- 后序遍历,先递归遍历左右子树,然后检查当前节点值
/**********2020.8.27*******/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
struct TreeNode;
typedef struct TreeNode *Position;
typedef struct TreeNode *SearchTree;
typedef int ElementType;
SearchTree MakeEmpty( SearchTree T );
Position Find( ElementType X, SearchTree T );
Position FindMin( SearchTree T );
Position FindMax( SearchTree T );
SearchTree Insert( ElementType X, SearchTree T );
SearchTree Delete( ElementType X, SearchTree T );
ElementType Retrieve( Position P );
typedef int Status;
struct TreeNode
{
ElementType Element;//节点值
SearchTree Left; //左节点
SearchTree Right; //右节点
};
SearchTree MakeEmpty(SearchTree T)
{
if (T != NULL)
{
MakeEmpty(T->Left);
MakeEmpty(T->Right);
free(T);
}
return NULL;
}
/*查找值为X的节点*/
Position Find(ElementType X, SearchTree T)
{
if( T == NULL ) /*最后一层还没有找到*/
return NULL;
if (X < T->Element ) /*从左子树查找*/
return Find(X, T->Left);
else
if (X > T->Element) /*从右边子树查找*/
return Find(X, T->Right);
else
return T; /*找到*/
}
/*找到一棵树中最小的节点*/
Position FindMin(SearchTree T)
{
if ( T == NULL )
return NULL;
else
if ( T-> Left == NULL )
return T;
else
return FindMin( T->Left );
}
/*找到一棵树中最大的节点*/
Position FindMax(SearchTree T)
{
if ( T != NULL )
while(T->Right != NULL)
T = T->Right;
return T;
}
/*将X插入到树中*/
SearchTree Insert(ElementType X, SearchTree T)
{
if (T == NULL)
{
/* Create and return a one-node tree */
T =(struct TreeNode*) malloc(sizeof( struct TreeNode ));
if ( T == NULL )
printf("Out of space!!!\n");
else
{
T->Element = X; /*将节点信息存储在子节点中*/
T->Left = T->Right = NULL;
}
}
else if (X < T->Element) /*比当前节点小,则插入到左子树*/
T->Left = Insert(X, T->Left);
else if (X > T->Element) /*比当前节点大,则插入到右子树*/
T->Right = Insert(X, T->Right);
/* Else X is in the tree already; we'll do nothing */
return T;
}
/*删除值为X的节点*/
SearchTree Delete(ElementType X, SearchTree T)
{
Position TmpCell;
if (T == NULL)
printf("Element not found\n");
/*比当前节点值小,从左子树查找并删除*/
else if (X < T->Element) /* Go left */
T->Right = Delete(X, T->Left);
/*比当前节点值大,从右子树查找并删除*/
else if (X > T->Element) /* Go Right */
T->Right = Delete(X, T->Left);
/*等于当前节点值,并且当前节点有左右子树*/
else if (T->Left && T->Right) /* Two Children */
{
/*用右子树的最小值代替该节点,并且递归删除该最小值节点*/
/* Replace with smallest in right subtree */
TmpCell = FindMin(T->Right);
T->Element = TmpCell->Element;
T->Right = Delete(T->Element, T->Right);
}
/*要删除的节点只有一个子节点或没有子节点*/
else /* One or zero children */
{
TmpCell = T;
/*要删除节点有右孩子*/
if (T->Left == NULL) /* Also handles 0 children */
T = T->Right;
/*要删除节点有左孩子*/
else if (T->Right == NULL)
T = T->Left;
free( TmpCell );
}
return T;
}
ElementType Retrieve(Position P)
{
return P->Element;
}
/**
* 前序遍历"二叉树"
* @param T Tree
*/
void PreorderTravel(SearchTree T)
{
if (T != NULL)
{
printf("%d\n", T->Element);
PreorderTravel(T->Left);
PreorderTravel(T->Right);
}
}
/**
* 中序遍历"二叉树"
* @param T Tree
*/
void InorderTravel(SearchTree T)
{
if (T != NULL)
{
InorderTravel(T->Left);
printf("%d\n", T->Element);
InorderTravel(T->Right);
}
}
/**
* 后序遍历二叉树
* @param T Tree
*/
void PostorderTravel(SearchTree T)
{
if (T != NULL)
{
PostorderTravel(T->Left);
PostorderTravel(T->Right);
printf("%d\n", T->Element);
}
}
/*打印树*/
void PrintTree(SearchTree T, ElementType Element, int direction)
{
if (T != NULL)
{
if (direction == 0)
printf("%2d is root\n", T->Element);
else
printf("%2d is %2d's %6s child\n", T->Element, Element, direction == 1 ? "right" : "left");
PrintTree(T->Left, T->Element, -1);
PrintTree(T->Right, T->Element, 1);
}
}
int main(int argc, char const *argv[])
{
printf("Hello Leon\n");
SearchTree T;
MakeEmpty(T);
T = Insert(21, T);
T = Insert(2150, T);
T = Insert(127, T);
T = Insert(121, T);
printf("树的详细信息: \n");
PrintTree(T, T->Element, 0);
printf("前序遍历二叉树: \n");
PreorderTravel(T);
printf("中序遍历二叉树: \n");
InorderTravel(T);
printf("后序遍历二叉树: \n");
PostorderTravel(T);
printf("最大值: %d\n", FindMax(T)->Element);
printf("最小值: %d\n", FindMin(T)->Element);
return 0;
}
参考:
https://www.jianshu.com/p/8cb54efab7da
https://www.jianshu.com/p/802ac3148540
https://www.yanbinghu.com/2019/04/07/55964.html