一、二叉搜索树的查找和插入:
#pragma once
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<malloc.h>
typedef int DataType;
typedef struct BSTreeNode
{
struct BSTreeNode* _pLeft;
struct BSTreeNode* _pRight;
DataType _data;
}BSTreeNode;
void InitBST(BSTreeNode** pRoot)
{
*pRoot = NULL;
}
BSTreeNode* BuyBSTreeNode(DataType data)
{
BSTreeNode* pNewNode = (BSTreeNode*)malloc(sizeof(BSTreeNode));
if (NULL == pNewNode)
{
assert(0);
return NULL;
}
pNewNode->_data = data;
pNewNode->_pLeft = NULL;
pNewNode->_pRight = NULL;
return pNewNode;
}
void InsertBSTree(BSTreeNode** pRoot,DataType data)
{
BSTreeNode* pCur=NULL;
BSTreeNode* pParent = NULL;
assert(pRoot);
if (NULL == *pRoot)
{
*pRoot = BuyBSTreeNode(data);
return;
}
//树不为空
//查找待插入节点的位置(从根的根的位置往下找
pCur = *pRoot;
while (pCur){
if (data < pCur->_data)
{
//先将双亲节点保存下来
pParent = pCur;
pCur = pCur->_pLeft;
}
else if (data>pCur->_data)
{
//先将双亲节点保存下来,否则不知道插入节点时将要插入到哪里
pParent = pCur;
pCur = pCur->_pRight;
}
else//数据存在则不需要发送
return;
}
//插入新节点
//新节点所在的位置
pCur = BuyBSTreeNode(data);
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
}
int FindBSTree(BSTreeNode* pRoot, DataType data)
{
BSTreeNode* pCur = pRoot;
while (pCur){
if (data == pCur->_data)
return 1;//要查找的节点在树里面
else if (data < pCur->_data)
pCur = pCur->_pLeft;
else
pCur = pCur->_pRight;
}
return 0;
}
void PreOrder(BSTreeNode* pRoot)
{
if (pRoot){
PreOrder(pRoot->_pLeft);
printf("%d ", pRoot->_data);
PreOrder(pRoot->_pRight);
}
}
void DestroyBSTree(BSTreeNode** pRoot)
{
assert(pRoot);
if (*pRoot){
DestroyBSTree(&(*pRoot)->_pLeft);
DestroyBSTree(&(*pRoot)->_pRight);
free(*pRoot);
*pRoot = NULL;
}
}
void TestBSTree()
{
int a[] = { 1, 5, 3, 71, 8, 9, 42, 36 };
BSTreeNode* pRoot;
int i = 0;
InitBST(&pRoot);
for (; i < sizeof(a) / sizeof(a[0]);++i)
{
InsertBSTree(&pRoot, a[i]);
}
printf("中序遍历:");
PreOrder(pRoot);
printf("\n");
DestroyBSTree(&pRoot);
}
删除时:1、找值为x的节点
2、删除:
(1)没有左右孩子(叶子节点):可以直接删除,删除后将叶子节点的上一级节点指向空(否则会成为野指针)。
(2)只有左孩子:
a、待删除节点不是根节点:带删除节点是双亲的左孩子:删除左子树的左孩子(该左孩子没有右孩子):让双亲的左孩子指向要删除的左孩子
b、待删除节点不是根节点:待删除节点是双亲的右孩子:删除右子树的左孩子:让双亲的右指向待删除元素的左
c、待删除节点是根节点:直接删除:根节点只有左子树,没有右子树:不改变根的指向
(3)只有右孩子:
(4)左右孩子均存在:在右子树中找最小的节点,交给待删除节点,在右子树中删除替代节点
void DeleteBSTree(BSTreeNode** pRoot,DataType data)
{
BSTreeNode* pCur = NULL;
BSTreeNode* pPrent = NULL;
assert(pRoot);//指向头节点的地址,一定不会为空
//找待删除节点的位置
pCur = *pRoot;//从根的位置开始找
while (pCur)
{
//查看要删除的节点是不是要删除的数据
if (data == pCur->_data)
break;//找到了则退出
else if (data < pCur->_data)
{
pPrent = pCur;//把双亲保存出来
pCur = pCur->_pLeft;
}
else
{
pPrent = pCur;
pCur = pCur->_pRight;
}
}
//待删除元素不在树中
if (NULL==pCur)
{
return;
}
//找到该节点
if (NULL==pCur->_pLeft)
//左孩子为空,只有右子树,要么是叶子节点,要么是右子树
{
//待删除节点是根节点
if (pCur == *pRoot)
*pRoot = pCur->_pRight;
else
{
//待删除节点是其双亲的左孩子
if (pCur == pPrent->_pLeft)
pPrent->_pLeft = pCur->_pRight;
else
pPrent->_pRight = pCur->_pRight;
}
}
else if (NULL == pCur->_pRight)//只有左子树
{
//待删除节点为右子树
if (pRoot == pCur)
//直接删除pRoot
{
*pRoot = pCur->_pLeft;
}
else
{
//待删除节点是其双亲的左孩子
if (pCur == pPrent->_pLeft)
pPrent->_pLeft = pCur->_pLeft;
else
pPrent->_pRight = pCur->_pLeft;
}
}
else
{
//待删除节点左右孩子都存在
//在其右子树中找最小(最左边)的节点或者
//在右子树中找最大(最右侧)的节点
BSTreeNode* pDel = pCur->_pRight;//在右子树中查找
pParent=pCur;//不加这句话,会导致pParent不进行更新
while (pDel->_pLeft){//左子树存在
pPrent = pDel;
pDel = pDel->_pLeft;
}
//找到了最左边的节点
pCur->_data = pDel->_data;
//删除pDel
if (pDel == pPrent->_pLeft)
pPrent->_pLeft = pDel->_pRight;
else
pPrent->_pRight = pDel->_pRight;
pCur=pDel;
}
free(pCur);
}
二、递归实现二叉树的增加,查找,删除
int FindBSTree(BSTreeNode* pRoot,DataType data)
{
if (NULL == pRoot)
return;
if (data == pRoot->_data)
return 1;
else if (data < pRoot->_data)
return FindBSTree(pRoot->_pLeft, data);
else
return FindBSTree(pRoot->_pRight, data);
}
void InserBSTree(BSTreeNode** pRoot,DataType data)
{
if (NULL == *pRoot)
*pRoot = BuyBSTreeNode(data);
else{
if (data < (*pRoot)->_data)//在左子树里
return InserBSTree(&(*pRoot)->_pLeft, data);
else if (data>(*pRoot)->_data)//在右子树里
return InserBSTree(&(*pRoot)->_pRight, data);
else//相等,元素已经存在
return 0;
}
}
void DeleteBSTree(BSTreeNode** pRoot, DataType data)
{
if (NULL == *pRoot)
return;
if (data < (*pRoot)->_data)
return DeleteBSTree(&(*pRoot)->_pLeft, data);
else if (data>(*pRoot)->_data)
return DeleteBSTree(&(*pRoot)->_pRight, data);
else
{
BSTreeNode* pDel = *pRoot;
if (NULL == pDel->_pLeft)
{
*pRoot = pDel->_pRight;
free(pDel);
}
else if (NULL == pDel->_pRight)
{
*pRoot = pDel->_pLeft;
free(pDel);
}
else{//左右子树都存在,到右子树里面找节点
BSTreeNode* pDel = (*pRoot)->_pRight;
while (pDel->_pLeft)
pDel = pDel->_pLeft;
//替代节点
(*pRoot)->_data = pDel->_data;
return DeleteBSTree(&(*pRoot)->_pRight, pDel->_data);
}
}
}
二叉树的插入先找位置(若树不为空,则从根开始插入,二叉搜索树中插入的每一个值都是唯一的。比根节点小则朝左边插入,比根节点大则向右侧插入,在这个过程中需要把双亲节点保存起来,pCur走到空的位置,位置找到了保存在双亲的指针域中),再插入。
二叉树的删除:也分为两步:(1)找节点(先和根相比,如果和根相等则找到了;如果不相等,比根小则朝左边找,若是比根大则朝右边找,只要节点找到了就可有将节点删除(2)删除:分为四种情况:a、叶子节点;b、只有右孩子;c、只有左孩子;
d、左右孩子都存在【叶子节点可以和只有左孩子或者只有右孩子合并起来】
三、二叉搜索树的应用
1、判断一个单词是否拼写正确
(1)方式一:把单词和集合里面的单词一个个进行对比,如果单词和集合里面的某一个单词相等,则单词的拼写没有问题;如果不相等则出错了:时间复杂度为:O(n),操作也不方便
(2)方式二:利用二叉搜索树的方式实现:单词比根小则在左子树的位置,单词比根大则在右子树的位置。比到单词找到为止,找到叶子节点的位置都没有找到,则单词拼写错误,没有找到
typedef char DataType;//char*用于记录字符串
2、模拟实现一个简单的字典
存放单词及单词对应的中文含义(一一对应的关系),把具有一一对应关系的称之为键值对key和value具有一一对应的关系。一般来说key是唯一的是否唯一取决于具体的场景。有些场景key是可以重复的,比如一个单词对应多个意思或者容器类(multimap中键值对是可以重复的)
struct pair
{
char *word;//单词//key
char *Chinese;//单词对应的中文含义//value
//比较时只需要比单词,这个字符串的大小
};
3、log文件中有许多异常重复的IP地址,请统计每个异常IP出现了多少次
将IP地址作为键值对key,将每个单词出现的次数作为value,用这个键值对建立起二叉索搜树,
也可以不存放字符串而是把字符串转换成整型的数据(一个库函数)
atoi:把数字类型的字符串转换成整型的数据
二叉搜索树里面查找某一个元素有序(接近有序)时,二叉索搜树退化为一个线性的结构,的时间复杂度时O(n)
四、解决二叉索搜树单支的情况(不平衡的情况):平衡树
平衡树:左子树和右子树的高度之差的绝对值不能超过1(个节点下面只有一个节点时),空树也是平衡树。
AVL树的左右子树都是AVL树,左右子树高度之差,叫做平衡因子,平衡因子不超过1,则是平衡树。
平衡树也是一棵二叉搜索树。