您的位置:首页 > 理论基础 > 数据结构算法

数据结构之二叉搜索树

2016-02-29 19:36 387 查看
一颗二叉搜索树是以一颗二叉树组织的,这样一颗可以使用一个链表数据结构来表示,其中每个节点都是一个对象,除了key之外,每个节点还包含属性left、right和p,它们分别指向节点的左孩子、右孩子和双亲。如果某个孩子结点和父节点不存在,则相应属性的值为NULL,根节点是树中唯一一个父指针为NULL的节点。

6

/ \

5 7

/ \ \

2 5 8

二叉搜索树。对于任何节点x,其左子树中的关键字最大不超过x.key,其有子树中的关键字最小不低于x.key。大部分的搜索树操作的最坏运行时间与树的高度成正比。

二叉搜索树中的关键字总是以满足二叉搜索树性质的方式来存储:

设x二叉搜索树中的一个节点。如果y是x左子树中的一个节点,那么y.key <= x.key。如果y是x右子树中的一个节点,那么y.key >= x.key 。

接下来我们实现二叉搜索树的创建、销毁(递归、非递归)、遍历(先序、中序、后序)、得到最大最小值、插入和删除。

首先我们来看二叉搜索树的对于接口的声明和节点类型的定义:

binaryserach_tree.h:

#ifndef _SERACH_TREE_
#define _SERACH_TREE_

#include "tools.h"
#define N (10)
typedef struct Tree_node{
int                      data;    //数据区域
struct Tree_node *left_child ;    //左孩子
struct Tree_node *right_child;    //右孩子
struct Tree_node           *p;    //双亲
}Tree_node;

typedef Tree_node *Bin_tree;    //二叉搜索树节点指针

//二叉搜索树的接口

//二叉搜索树的创建和销毁
Bin_tree creat_tree(int a[],int n);    //创建二叉搜索树
void destroy_tree(Bin_tree *root);    //二叉搜索树的销毁
void destroy_tree_nr(Bin_tree *root);    //二叉搜索树的非递归销毁

//二叉搜索树的遍历
void pre_order_print(Bin_tree root);    //先序遍历
void mid_order_print(Bin_tree root);    //中序遍历
void last_order_print(Bin_tree root);    //后序遍历
void pre_order_print_nr(Bin_tree root);    //先序遍历(非递归)
void mid_order_print_nr(Bin_tree root);    //中序遍历(非递归)
void last_order_print_nr(Bin_tree root);    //后序遍历(非递归)
void level_order_print(Bin_tree root);    //层序遍历

//二叉搜索树的查找
Tree_node * Tree_serach(Bin_tree root,int k);    //递归
Tree_node * Tree_serach_nr(Bin_tree root,int k);   //非递归

//得到最大关键字元素和得到最小关键字元素
Tree_node *get_tree_minnum(Bin_tree root);
Tree_node *get_tree_maxnum(Bin_tree root);

//返回节点的后继(右孩子)
Tree_node *tree_successor(Bin_tree root);

//插入和删除
void tree_insert(Bin_tree root,Tree_node *value);
Bin_tree tree_delete(Bin_tree root,Tree_node *value);

#endif


本次的节点类型的声明和以往的声明有一点不同,就是在Tree_node的结构体中加入了双亲节点,使得我们的后序操作变得方便。

(a)二叉搜索树的创建:首先根结点单独创建,对于剩余节点遍历数组剩下的元素,创建节点,寻找节点的双亲,其创建的代码如下:

Bin_tree creat_tree(int a[],int n)    //创建二叉搜索树
{
Tree_node *root = NULL;
Tree_node *p_node = NULL;
Tree_node *c = NULL;
Tree_node *pa = NULL;
int i = 0;
if(a == NULL || n < 0){
return NULL;
}
root = (Tree_node *)Malloc(sizeof(Tree_node));
root ->data = a[0];
root->left_child = NULL;
root ->right_child = NULL;
root ->p = NULL;
for(i = 1; i < n; ++i){
p_node = (Tree_node *)Malloc(sizeof(Tree_node));
p_node ->data = a[i];
p_node ->left_child = NULL;
p_node ->right_child = NULL;
c = root;
while(c){
pa = c;
if(c ->data > p_node ->data){
c = c ->left_child;
}else{
c = c ->right_child;
}
}
if(pa ->data > p_node ->data){
pa ->left_child = p_node;
}else{
pa ->right_child = p_node;
}
p_node ->p = pa;
}
return root;
}


其中p_node用于创建新的节点,pa 和 c联动进行寻找p_node插入的位置,最终pa为p_node的双亲,然后比较pa数据域的值和p_node数据域的值,并根据大小将p_node插入到pa的左子女或者右子女。

看完创建过程,接下来我们来看二叉搜索树的销毁。

(b)二叉搜索树的销毁:本文章给大家介绍两种方式:递归销毁和非递归销毁。递归销毁的方式比较简单,非递归的销毁方式需要借助于队列,队列的实现在之前的二叉树已经详细的说明,今天则不重点说明,下面我们具体来看实现:

static void destroy(Bin_tree root)
{
if(root){
destroy(root ->left_child);
destroy(root ->right_child);
free(root);
}
}
void destroy_tree(Bin_tree *root)    //二叉搜索树的销毁
{
if(root == NULL || *root == NULL){
return;
}
destroy(*root);
*root = NULL;
}
void destroy_tree_nr(Bin_tree *root)    //二叉搜索树的非递归销毁
{
Queue *queue = NULL;
Tree_node *node = NULL;
if(root == NULL || *root == NULL){
return ;
}
queue = init_queue();
in(queue,*root);
*root = NULL;
while(!is_queue_empty(queue)){
get_queue_front(queue,(void **)&node);
out(queue);

if(node ->left_child){
in(queue,node ->left_child);
}
if(node ->right_child){
in(queue,node->right_child);
}
free(node);
}
destory_queue(&queue);
}
创建和销毁都和大家介绍完了,我们通过查看内存是否泄露来判断程序的对错:

对于非递归销毁的检测:



对于非递归的检测:



(c)二叉搜索树的遍历:对于遍历比较特殊的是,对于二叉搜索树的中序遍历即可得到升序序列,二叉搜索树的遍历和二叉树遍历方式一样,分为:先序、中序、后序、层序。下面我们来看具体的实现(包括递归、非递归):

//二叉搜索树的遍历
void pre_order_print(Bin_tree root)    //先序遍历
{
if(root){
printf("%5d",root ->data);
pre_order_print(root ->left_child);
pre_order_print(root ->right_child);
}
}
void mid_order_print(Bin_tree root)    //中序遍历
{
if(root){
mid_order_print(root ->left_child);
printf("%5d",root ->data);
mid_order_print(root ->right_child);
}

}
void last_order_print(Bin_tree root)    //后序遍历
{
if(root){
last_order_print(root ->left_child);
last_order_print(root ->right_child);
printf("%5d",root ->data);
}

}
void level_order_print(Bin_tree root)    //层序遍历
{
Queue *queue = NULL;
Tree_node *node = NULL;
if(root == NULL){
return ;
}
queue = init_queue();
in(queue,root);
while(!is_queue_empty(queue)){
get_queue_front(queue,(void **)&node);
out(queue);
printf("%5d",node ->data);
if(node ->left_child){
in(queue,node ->left_child);
}
if(node ->right_child){
in(queue,node ->right_child);
}
}
destory_queue(&queue);
}
void pre_order_print_nr(Bin_tree root)    //先序遍历非递归
{
Stack *stack = NULL;
Tree_node *node = NULL;
if(root == NULL){
return;
}
stack = init_stack();
push(stack,root);
while(!is_stack_empty(stack)){
get_top(stack,(void**)&node);
pop(stack);
printf("%5d",node ->data);
if(node ->right_child){
push(stack,node ->right_child);
}
if(node ->left_child){
push(stack,node ->left_child);
}
}
destroy_stack(&stack);
}
void mid_order_print_nr(Bin_tree root)    //中序遍历非递归
{
Stack * stack = NULL;
Tree_node *node = NULL;
if(root == NULL){
return;
}
stack = init_stack();
node = root;
while(!is_stack_empty(stack) || node != NULL){
while(node != NULL){
push(stack,node);
node  = node ->left_child;
}
get_top(stack,(void **)&node);
printf("%5d",node ->data);
pop(stack);
node = node ->right_child;
}
destroy_stack(&stack);
}
void last_order_print_nr(Bin_tree root)    //后序遍历非递归
{
Stack *stack = NULL;
Tree_node *node = NULL;
Tree_node *prev = NULL;
if(root == NULL){
return ;
}
stack = init_stack();
node = root;
push(stack,node);
while(!is_stack_empty(stack)){
get_top(stack,(void **)&node);
if((node ->left_child == NULL && node ->right_child == NULL)
|| (prev != NULL && (node ->left_child == prev
||node ->right_child == prev))){
printf("%5d",node ->data);
pop(stack);
prev = node;
}else{
if(node ->right_child){
push(stack,node ->right_child);
}
if(node ->left_child){
push(stack,node ->left_child);
}
}
}
destroy_stack(&stack);
}


对于遍历的检测:



(d)二叉搜索树的查找:对于二叉搜索树的查找可以使用两种方式:递归和非递归,对于二叉搜索树的查找的时间复杂度为o(h),h为树的高度,下面是查找的代码:

//二叉搜索树的查找
Tree_node *Tree_serach(Bin_tree root,int k)    //递归
{
if(root == NULL || root ->data == k){
return root;
}
if(k < root ->data){
return Tree_serach(root ->left_child,k);
}else{
return Tree_serach(root ->right_child,k);
}
}
Tree_node * Tree_serach_nr(Bin_tree root,int k)  //非递归
{
Tree_node *node = NULL;
node = root;
while(node != NULL && k!= node ->data){
if(k < root ->data){
node = node ->left_child;
}else{
node = node ->right_child;
}
}
return node;
}
(d)得到最大和最小的值:由于二叉搜索树的性质,我们可以得出,二叉搜索树的最大值位于右子树的最右边的最后一个叶子,最小值位于左子树的最左边的最后一个叶子,其代码如下:

//得到最大关键字元素和得到最小关键字元素
Tree_node *get_tree_minnum(Bin_tree root)
{
Tree_node *node = NULL;
node = root;
while(node && node ->left_child){
node = node ->left_child;
}
return node;
}
Tree_node *get_tree_maxnum(Bin_tree root)
{
Tree_node *node = NULL;
node = root;
while(node && node ->right_child){
node = node ->right_child;
}
return node;
}
(e)得到前驱、后继节点:给定一颗二叉搜索树中的一个节点,有时候需要按中序遍历的次序查找它的后继,如果所有的关键字互不相同,则一个节点x的后继是大于x.key的最小关键字的节点,其代码的实现如下:

//返回节点的后继、前驱
Tree_node *tree_successor(Bin_tree r)
{
//               15
//             /   \
//            6     18
//           / \    / \
//          3   7  17  20
//         / \   \
//        2   4   13
//                /
//                9
//   给定一颗二叉树中的节点,有时则需要按中序遍历的次序查找它的后继
//   如果所有的关键字都不相同,则一个节点x的后继是大于x.key的最小关键字
//   的节点
//   因此可分为两种情况:
//   第一种:节点x 的右子树非空,那么x的后继恰是x右子树的最左节点
//   可以通过get_tree_minnum(x.right)的调用找到 例如 关键字为15的节点
//   的后继是关键字为17的节点
//   第二种:如果节点x的右子树为空并有一个后继y,那么y是x的最底层祖先
//   例如:关键字为13的节点的后继是关键字为15的节点
Tree_node *parent = NULL;
Tree_node *node = NULL;
node = r;
if(r == NULL){
return NULL;
}
if(r ->right_child){
return get_tree_minnum(r ->right_child);
}
parent = node ->p;
while(parent && node == parent ->right_child){
node = parent;
parent = parent ->p;
}
return parent;
}


(f)二叉搜索树的插入:二叉树的插入操作的代码如下:node和parent用于确定新节点插入的位置,parent为新节点的双亲节点,最后再将新节点挂在parent的左子女上或右子女上。

void tree_insert(Bin_tree root,Tree_node *value)
{
Tree_node *node = NULL;
Tree_node *parent = NULL;
if(value == NULL){
return;
}
node = root;
while(node){
parent = node;
if(node ->data < value ->data){
node = node ->right_child;
}else {
node = node ->left_child;
}
}
value ->p = parent;
if(parent == NULL){
root = value;
}else{
if(value ->data > parent ->data){
parent ->right_child = value;
}else{
parent->left_child = value;
}
}
}
(g) 二叉搜索树删除:

从一颗二叉搜索树T中删除一个节点z的整个策略可以分为三种基本情况(如下所述),但是只有一种情况比较棘手。

1>如果z没有孩子节点,那么只是简单的删除,并修改它的父节点,用NULL作为孩子来替换z

2>如果在z只有一个孩子,那么将这个孩子提升到树中的z的位置,并修改z的父节点,用z的孩子来替换z;

3>如果z有两个孩子,那么找z的后继y(一定在z的右子树中),并让y占据树中z的位置。z的原来的右子树部分成为y的新的右子树,并且z的左子树成为y的新的左子树。这种情况稍显麻烦,因为还与y是否为z的右子树相关。

从一颗二叉搜索树T中删除一个节点z,这个过程取指向T和z的指针作为输入参数,考虑如图所示的4种情况,它与前面概括出的三种情况有所不同。

a>

Z

/ \ R

NULL R -------> / \

/ \

(a)

如图(a)所示 ,z没有左孩子,那么用其右孩子来替换z,这个右孩子可以是NULL,也可以不是。当右孩子为NULL时,此时这种情况归为z没有孩子节点的情形,当右孩子非NULL时,此时这种情况就是z仅有一个孩子节点的情况,该孩子是右孩子;

b>

Z

/ \ ------> L

L NULL / \

/ \

(b)

如图(b)所示,如果z仅有一个孩子为左孩子,那么用其左孩子来替换z;否则z既有一个左孩子又有一个右孩子。我们要查找z的后继y,这个后继位于z的右子树并且没有左孩子,则需要将y移出原来的位置进行拼接,并替换树中的z。

c>

Z

/ \

L Y -------> Y

/ \ / \

NULL X L X

/ \ / \ / \

如图(c)所示,用y替换z,并仅留下y的右孩子。

d>

Z

/ \ Z Y Y

L R -------> / / \ ------> / \

/ \ / \ L NULL R L R

Y / \ / \ / \ / \

/ \ X X

NULL X / \ / \

/ \

其代码实现如下:

static Bin_tree tree_transplant(Bin_tree root,Bin_tree u,Bin_tree v)
{
if(u ->p == NULL){
root = v;
}else if(u == u ->p ->left_child){
u ->p ->left_child = v;
}else{
u ->p ->right_child = v;
}
if(v != NULL){
v ->p = u ->p;
}
return root;
}
Bin_tree tree_delete(Bin_tree root,Tree_node *value)
{
Tree_node *p_node = NULL;
if(root == NULL || value == NULL){
return;
}
if(value ->left_child == NULL){
root = tree_transplant(root,value,value ->right_child);
}else if(value ->right_child == NULL){
root = tree_transplant(root,value,value ->left_child);
}else {
p_node = get_tree_minnum(value ->right_child);
if(p_node ->p != value){
root = tree_transplant(root,p_node,p_node ->right_child);
p_node ->right_child = value ->right_child;
p_node ->right_child ->p = p_node;
}
root = tree_transplant(root,value,p_node);
p_node ->left_child = value ->left_child;
p_node ->left_child ->p = p_node;
}
return root;
}
接下来我们看测试程序的书写:

#include <stdio.h>
#include <stdlib.h>
#include "binaryserach_tree.h"

int main(int argc,char **argv)
{
int a
= {15,6,18,3,7,17,20,2,4,13};
Bin_tree root = NULL;
Tree_node *p_node = NULL;
Tree_node *node = NULL;
Tree_node *node1 = NULL;
root = creat_tree(a,N);
#if 0
printf("level_order:\n");
level_order_print(root);
printf("\n");

printf("pre_order:\n");
pre_order_print(root);
printf("\n");

printf("mid_order:\n");
mid_order_print(root);
printf("\n");

printf("last_order:\n");
last_order_print(root);
printf("\n");

printf("pre_order_nr:\n");
pre_order_print_nr(root);
printf("\n");

printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

printf("last_order_nr:\n");
last_order_print_nr(root);
printf("\n");
#endif
#if 1
p_node = Tree_serach(root,6);
if(p_node){
printf("%d is found !\n",p_node ->data);
}else{
printf(" is not found !\n");
}

p_node = Tree_serach(root,18);
if(p_node){
printf("%d is found !\n",p_node ->data);
}else{
printf(" is not found !\n");
}

p_node = Tree_serach_nr(root,9);
if(p_node){
printf("%d is found !\n",p_node ->data);
}else{
printf(" is not found !\n");
}
#endif
#if 0
p_node = get_tree_minnum(root);
if(p_node){
printf("minnum :%d\n",p_node ->data);
}

p_node = get_tree_maxnum(root);
if(p_node){
printf("maxnum :%d\n",p_node ->data);
}

p_node = tree_successor(root);
if(p_node){
printf("root:%d\n",p_node ->data);

}else{
printf("root:not found!\n");
}

p_node = tree_successor(root ->right_child ->right_child);
if(p_node){
printf("root ->right_child ->right_child:%d\n",p_node ->data);
}else{
printf("root ->right_child ->right_child:not found!\n");
}

p_node = tree_successor(root ->left_child ->left_child);
if(p_node){
printf("root ->left_child ->left_child:%d\n",p_node ->data);
}else{
printf("root ->left_child->left_child:not found !\n");
}

p_node = tree_successor(root ->right_child ->left_child);
if(p_node){
printf("root ->right_child ->left_child:%d\n",p_node ->data);
}else{
printf("root ->right_child->left_child:not found !\n");
}

p_node = tree_successor(root ->left_child ->left_child ->right_child);
if(p_node){
printf("root ->left_child ->left_child->right_child:%d\n",p_node ->data);
}else{
printf("root ->left_child ->left_child->right_child:not found !\n");
}

//二叉搜索树的插入:
node = (Tree_node *)Malloc(sizeof(Tree_node));
node ->data = 10;
node ->left_child = NULL;
node ->right_child = NULL;
node ->p = NULL;
tree_insert(root,node);

printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

node1 = (Tree_node *)Malloc(sizeof(Tree_node));
node1 ->data = 19;
node1 ->left_child = NULL;
node1 ->right_child = NULL;
node1 ->p = NULL;
tree_insert(root,node1);

printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

p_node = root ->right_child;
root = tree_delete(root,p_node);
printf("delete root ->right_child:%d\n",p_node ->data);
printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

root = tree_delete(root,node1);
printf("delete node1:%d\n",node1 ->data);
printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

root = tree_delete(root,node);
printf("delete node:%d\n",node->data);
printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");

p_node = root;
root = tree_delete(root,p_node);
printf("delete root:%d\n",p_node->data);
printf("mid_order_nr:\n");
mid_order_print_nr(root);
printf("\n");
#endif
//destroy_tree(&root);
destroy_tree_nr(&root);
return 0;
}
我们先来看其测试结果:



为了方便大家理解程序,我跟大家大致画出最初创建出的二叉树:

15

/ \

6 18

/ \ / \

3 7 17 20

/ \ \

2 4 13

把节点10和19插入到二叉搜索树中后:

15

/ \

6 18

/ \ / \

3 7 17 20

/ \ \ \

2 4 13 19

/
10

至此二叉搜索树的实现已完成,此后会和大家一起讨论红黑树、B树、AVL树等等,敬请期待!!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: