最近在学习数据结构基础,学习到二叉排序树的概念,文中讲到二叉排序数查找的时候,讲对集合{62,88,58,47,35,73,51,99,37,93}做查找,查找的前提是,将该集合用排序好的二叉树来存储,如下图文字描述:
根据自己学习的二叉树基础以及上图描述算法,写了下二叉排序树的构建,分别用迭代法和递归。
二叉树结构定义:
typedef struct treeNode{
int data;
struct treeNode *left, *right;
}BinTreeNode, *pBinTreeNode;
迭代法:
void BuildSortedBinTree(pBinTreeNode *tRoot, pBinTreeNode *node){
if (*tRoot == NULL){*tRoot = *node;}
pBinTreeNode temp = *tRoot;
while(temp){
if (temp == *node){return;}
if ((*node)->data < temp->data){
if (temp->left == NULL){
temp->left = *node;
}
else{
temp = temp->left;
}
}
if ((*node)->data > temp->data){
if (temp->right == NULL){
temp->right = *node;
}
else{
temp = temp->right;
}
}
}
}
递归:
void BuildSortedBinTree_2(pBinTreeNode *tRoot, pBinTreeNode *node){
if (*tRoot == NULL){*tRoot = *node;}
if ((*node)->data < (*tRoot)->data){
BuildSortedBinTree_2(&(*tRoot)->left, node);
}
else if((*node)->data > (*tRoot)->data){
BuildSortedBinTree_2(&(*tRoot)->right, node);
}
}
中序遍历打印验证正确性:
void PintTree(pBinTreeNode root){
if (root == NULL){return;}
PintTree(root->left);
printf("--data is %d\n", root->data);
PintTree(root->right);
}
主函数:
#include <stdio.h>
#include <stdlib.h>
int main(){
pBinTreeNode root = NULL;
int a[] = {62,88,58,47,35,73,51,99,37,93};
for (int i = 0; i < sizeof(a) / sizeof(a[0]); ++i){
pBinTreeNode node = (pBinTreeNode)malloc(sizeof(BinTreeNode));
if (node != NULL){
memset(node, 0, sizeof(BinTreeNode));
}
node->data = a[i];
BuildSortedBinTree(&root,&node);
}
PintTree(root);
}
验证结果:
OK,我这个人做事情比较着急,其实人家书本后面讲了如何构造一个二叉查找树(尴尬),然后阅读了书中的方法,遂明白作者先将查找的原因,构造二叉查找树,理解为给二叉树插入元素,即在二叉树中查找要插入的元素,如果已经存在,则不插入,不然则按照中序遍历为递增的方式插入,以下为书中代码。
二叉排序树查找:
bool SearchBST(pBinTreeNode T, pBinTreeNode f, int key, pBinTreeNode *node){
/*
pBinTreeNode T:二叉树根
pBinTreeNode f:用来记录二叉树节点的指针,如果元素存在,f该元素指针,并返回true,若不存在,f为最后一次比较的二叉树节点指针,返回false.
这样插入函数就知道把新的元素作为谁的孩子了。
int key:要查找的元素
pBinTreeNode *node:意义同f,只不过f是递归调用用到,node就只是作为出参。
*/
if (!T){
*node = f;
return false;
}
if (key < T->data){
return SearchBST(T->left, T, key, node);
}
else if (key > T->data){
return SearchBST(T->right, T, key, node);
}
else{
*node = f;
return true;
}
}
二叉排序树插入:
void InsertBST(pBinTreeNode *T, int key){
pBinTreeNode f = NULL;
pBinTreeNode parents = NULL;
if (!SearchBST(*T, f, key, &parents)){
pBinTreeNode newNode = (pBinTreeNode)malloc(sizeof(BinTreeNode));
if (newNode != NULL){
newNode->data = key;
newNode->left = newNode->right = NULL;
}
if (*T == NULL){
*T = newNode;
}
else if (key < parents->data){
parents->left = newNode;
}
else{
parents->right = newNode;
}
}
}
二叉排序树删除:
由于删除某个元素之后,希望还保持原始二叉排序树的排序顺序,因此删除的情况比较多,分为以下几种:
(1)要删除的节点是叶子节点,则不影响原始二叉排序树顺序,删除释放即可。
(2)要删除的节点只有左子树:
如下图58节点,只有左子树,要删除他,只需要让58的双亲62的左子树(本来是58)跳过58,指向58的左子树的根,即47即可,即让62->left = 47;free(58);(这里我用数字表示,只是为了通俗易懂,易于理解)。
(3)要删除的节点只有右子树:
如上图中的35,只有右子树,只需要让35的双亲47的左子树(本来是35),跳过35,指向35的右子树37即可。
(4)要删除的节点既有左子树,又有右子树:
书中如下描述,我自己感觉不好表达,直接贴图(我实在是太懒了)
意思就是:比如要删除47,我们找按照中序遍历的47的前驱节点(37),或者后驱节点(48),用前驱节点或者后驱节点来替换47节点的值,这样,我们中序遍历的顺序没有发生变化,删除了某个节点之后,不影响继续对该树进行有序查找或者插入等操作。
个人理解分两步骤:
(1)比如我们用前驱替换,找到47的前驱37,用37替换47的值,然后37需要删除,删除37之后37的前驱就找不到37这个右孩子了,因此还需要替换下37的前驱35的右孩子,替换成谁呢,这里就跟前面(2)(3)情况道理一样的了,37被删除了,那他只有左子树,就将37的前驱也就是37的双亲(35)的右孩子(本来是37),指向37的左孩子36了。
说了这么些,也就是帮助自己理解罢了,别的同学估计看着很古怪,希望万一有人翻到了,别误导大家。
二叉树删除操作代码如下:
void DeleteBST(pBinTreeNode *T, int key){
if(!T){
return;
}
if(key == (*T)->data){
DeletNode(T);
}
else if (key > (*T)->data){
DeleteBST(&(*T)->right, key);
}
else
{
DeleteBST(&(*T)->left, key);
}
}
void DeletNode(pBinTreeNode *p){
//删除二叉树的一个节点
pBinTreeNode q;
if ((*p)->left == NULL){
q = (*p);
(*p) = (*p)->right;
free(q);
}
else if ((*p)->right == NULL){
q = (*p);
(*p) = (*p)->left;
free(q);
}
else{
pBinTreeNode s = (*p)->left;//进去要删除的节点的左子树,寻找最右的节点即为要删除节点的前驱
q = (*p); //q记录s的双亲
while (s->right){
q = s; //q记录前驱s的双亲,初始化为(*p)(即当要删除的节点只有一个左孩子和一个右孩子(不存在左右子树)的时候,(*p)即为前驱s的双亲)
s = s->right;
}
(*p)->data = s->data;
if(q == (*p)){ //要删除的节点只有一个左孩子和一个右孩子(不存在左右子树),则p的前驱是s(p->left),q用来记录s的双亲,此时也就是p,即q == (*p)
q->left = s->left;
}
else{
q->right = s->left;
}
free(s);
}
}