您的位置:首页 > 其它

二叉查找树(BST)

2017-01-15 12:43 274 查看
二叉查找树应该是最最基本的树,其基本原理较简单:

1、每一个节点最多有两个子节点

2、它的左子节点肯定比它小

3、它的右子节点肯定比他大

可以了。

下面直接上代码:

1、首先是头文件b.h:

//1.1、结构体nd是节点的数据结构,包括值(data)、左右子节点
template<class T> struct nd {
T data;
struct nd *lchild;
struct nd *rchild;
nd (T _data, struct nd *_lchild, struct nd *_rchild): data(_data), lchild(_lchild), rchild(_rchild) {}
};
//1.2、btree是二叉查找树的数据结构,包括插入、删除、查找三方法,以及前中后遍历方法
template <class T> class btree {
nd<T> *root;
nd<T> *getNearest(nd<T> *nodae);
void show(T data);
void free(nd<T> *node);
void swap(nd<T> *node1, nd<T> *node2);
void pre(nd<T> *root);
void mid(nd<T> *root);
void post(nd<T> *root);
public:
btree(){}
btree(T *data, int size);
~btree();
int find(T data);
void insert(T data);
void del(T data);
void preshow();
void midshow();
void postshow();
};


2、然后是二叉查找树的功能实现b_func.h:

2.1、首先是二叉查找树的创建和销毁

#include "b.h"
#include <iostream>
//构造二叉查找树,就是一个不断插入的过程,insert方法后面介绍
template<class T> btree<T>::btree (T *data, int size) {
root = new nd<T>(data[0], 0, 0);
for (int i = 1; i < size; i++) {
T curdata = data[i];
insert(curdata);
}
}
//销毁二叉查找树,按后序遍历删除,所有二叉树的销毁必然都是这样的顺序
template<class T> void btree<T>::free (nd<T> *node) {
if (!node->lchild && !node->rchild) {
delete node;
node = 0;
return;
}

if (node->lchild) {
free(node->lchild);
}
if (node->rchild) {
free(node->rchild);
}
}

template<class T> btree<T>::~btree () {
free(root);
}


2.2、二叉查找树的插入新节点的方法:
//按新节点的大小找到合适的坑
template<class T> void btree<T>::insert (T data) {
nd<T> *node = root;
while (node) {
if (node->data == data) {
break;
} else if (node->data < data) {
if (node->rchild) {
node = node->rchild;
} else {
node->rchild = new nd<T>(data, 0, 0);
break;
}
} else if (node->data > data) {
if (node->lchild) {
node = node->lchild;
} else {
node->lchild = new nd<T>(data, 0, 0);
break;
}
}
}
}


插入的平均时间复杂度在O(logN),但如果不是很平衡,比如这样一个二叉查找树,那就是O(N)了:

1-2-3-4-5-6-7-8-9,这是插入10,很明显10的插入过程,需要从1走到9

即平均时间复杂度O(logN),最坏情况为O(N)
二叉查找树完全没有考虑如何尽可能平衡的问题,所以可以讲除教学以外,毫无实际用途。

2.3、二叉查找树的查找方法:

template<class T> int btree<T>::find (T data) {
nd<T> *node = root;
if (root) {
while (node) {
if (node->data == data) {
return 1;
} else if (node->data > data) {
if (node->lchild) {
node = node->lchild;
} else {
return 0;
}
} else if (node->data < data) {
if (node->rchild) {
node = node->rchild;
} else {
return 0;
}
}
}
}

return 0;
}
二叉查找树的查找,和插入完全没有实质区别,时间复杂度完全一模一样。

2.4、二叉查找树的节点删除方法:

//swap方法,交换两个节点的值
template<class T> void btree<T>::swap (nd<T> *node1, nd<T> *node2) {
T tmp = node1->data;
node1->data = node2->data;
node2->data = tmp;
}
//寻找和节点node的值最接近的。什么是最接近的,如果节点node有右子节点,那么就优先选右子树中,最小的节点;
//如果node没有右子节点了,但是有左子节点,那么找到左子树中,最大的节点。这就是离它最近的节点。
//如果node既没有左子节点又没有右子节点,那么是不会调用这个方法的
template<class T> nd<T> *btree<T>::getNearest (nd<T> *node) {
if (node->rchild) {
nd<T> *right = node->rchild, *lchild = right->lchild;
while (right && lchild) {
if (!lchild->lchild) {
break;
}
right = lchild;
lchild = lchild->lchild;
}
return right;
} else {
nd<T> *left = node->lchild, *rchild = left->rchild;
while (left && rchild) {
if (!rchild->rchild) {
break;
}
left = rchild;
rchild = rchild->rchild;
}
return left;
}
}
//不仅是二叉查找树,所有的树种删除节点的方式,都
//是首先寻找被删除节点的最优替代者节点,然后把替代者节点和要删的节点交换值,然后实际删除替代者节点,而不是实际删除要删的节点,这是一个重要技巧。
//1、首先找到要删除的节点是哪个,如果都没有找到那么可以直接返回了
//2、如果要删除的节点,既没有左子节点也没有右子节点,那么这肯定是个叶子节点,直接删除即可
//3、如果要删除的节点,有右子节点,那么从右子树中找到最小的节点,这个就是它的替代者节点(右子树中比要删除节点大的节点中最小的节点),交换值后实际删除替代者节点
//   注意这里的判断逻辑比较细一点,要判断替代者节点是否就是要删除节点的右子节点,包括下面的判断左子树也有这个逻辑
//4、如果要删除的节点,没有右子节点但是有左子节点,那么从左子树中找到最大的节点,这个就是它的替代者节点(右子树中比要删除节点小的节点中最大的节点),后面过程同第三步
template<class T> void btree<T>::del (T data) {
nd<T> *node = root, *parent = root;
if (!node) {
return;
}
while (node) {
if (node->data == data) {
break;
} else if (node->data > data) {
if (node->lchild) {
parent = node;
node = node->lchild;
} else {
return;
}
} else if (node->data < data) {
if (node->rchild) {
parent = node;
node = node->rchild;
} else {
return;
}
}
}

if (!node->lchild && !node->rchild) {
if (parent->lchild == node) {
parent->lchild = 0;
} else {
parent->rchild = 0;
}
delete node;
} else if (node->rchild) {
nd<T> *nearest = getNearest(node), *delnode = 0;
if (nearest == node->rchild) {
delnode = nearest;
node->rchild = delnode->rchild;
node->data = delnode->data;
delete delnode;
} else {
delnode = nearest->lchild;
node->data = delnode->data;
nearest->lchild = 0;
delete delnode;
}
} else if (node->lchild) {
nd<T> *nearest = getNearest(node), *delnode = 0;
if (nearest == node->lchild) {
delnode = nearest;
node->lchild = delnode->lchild;
node->data = delnode->data;
delete delnode;
} else {
delnode = nearest->rchild;
node->data = delnode->data;
nearest->rchild = 0;
delete delnode;
}
}
}


删除操作确实是比插入/查找多了找替代者节点的过程,但就总体而言时间复杂度实质上和查找/插入也差不多,平均在O(logN)最坏在O(N)

2.5、前中后序遍历

template<class T> void btree<T>::show (T data) {
std::cout << data << std::endl;
}

template<class T> void btree<T>::pre (nd<T> *root) {
if (root) {
show(root->data);
pre(root->lchild);
pre(root->rchild);
}
}

template<class T> void btree<T>::preshow () {
std::cout << "preshow" << std::endl;
pre(root);
}

template<class T> void btree<T>::mid (nd<T> *root) {
if (root) {
mid(root->lchild);
show(root->data);
mid(root->rchild);
}
}

template<class T> void btree<T>::midshow () {
std::cout << "midshow" << std::endl;
mid(root);
}

template<class T> void btree<T>::post (nd<T> *root) {
if (root) {
post(root->lchild);
post(root->rchild);
show(root->data);
}
}

template<class T> void btree<T>::postshow () {
std::cout << "postshow" << std::endl;
post(root);
}
二叉查找树及其所有衍生品的中序遍历都是有序的,这个可以作为判断程序对错的依据。

3、测试程序test_b.cpp:

g++ -g test_b.cpp -o test_b

#include "b_func.h"
#include <stdlib.h>

using namespace std;

void initdata (int *testdata, int size) {
srand((int)time(0));
for (int i = 0; i < size; i++) {
testdata[i] = rand() % 100;
std::cout << testdata[i];
if (i != size - 1) {
std::cout << ",";
}
}
std::cout << std::endl << "inited" << std::endl;
}

int main () {
int testdata[100] = {0};
initdata(testdata, sizeof(testdata)/sizeof(testdata[0]));
btree<int> bt(testdata, sizeof(testdata)/sizeof(testdata[0]));
bt.preshow();
bt.midshow();
bt.postshow();
bt.del(testdata[5]);
std::cout << "del: " << testdata[5] << std::endl;
bt.preshow();
bt.midshow();
bt.postshow();
std::cout << bt.find(-1) << std::endl;
return 0;
}


对于二叉查找树及其所有衍生品,越平衡,增删查改的时间复杂度越往O(logN)的方向去发展,反之则往O(N)的方向去发展,所以其所有衍生品都是致力于如何在代价不太高的情况下做到尽可能平衡。比如比较极端的avl数和比较合理的红黑树。

有了二叉查找树的概念,接下来介绍的首先是二叉堆,这个可以解决不少日常实际问题的东西。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: