您的位置:首页 > 其它

二叉搜索树-数组的实现方式

2010-11-28 17:55 183 查看
树是一种相对比较复杂的数据结构,它要么为空,要么具有一个值并具有零个或多个孩子,每个孩子本身就是树,这是一个递归的定义,二叉树是树的一种特殊形式,也是数据结构中最常用到的形式,二叉树又可以根据左右孩子节点与跟节点的大小关系分为很多种,这里以二叉搜索树为例学习,二叉搜索树有一个额外的特点:每一个节点的值比它的左子树的所有节点的值都要大,但比它的右子树的所有节点的值都要小,这个定义排除了树中存在值相同的节点的可能性。二叉搜索树是一种关键值快速查找数据的优秀工具。

和分析堆栈、队列一样我们首先还是分析二叉搜索树提供的接口,首先就是数据的插入InsertBST()、数据的删除DeleteBST()。既然它是一个查找数据的优秀工具那么必不可少的就是查找接口了SerchBST(),和堆栈和队列不同,二叉树并没有限制只能访问某一个值,因此二叉树具有一个基本操作遍历。除此之外,那就是二叉搜索树的建立与销毁。

接口如下:

/*二叉搜索树模块的接口定义*//***此处定义数的值的类型为整型 **在实际使用中可以根据自己的需要进行修改 */#define  TREE_TYPE   int   #define  TRUE         1#define  FALSE        0 /***Insert()向树添加一个新值,参数是需要被添加的值**它必须原先并不存在于树中,**因为二叉搜索树不存在相同的值函数返回真或假 */int  insertBST( TREE_TYPE  value ); /***find()查找一个特定的值,这个值作为第一个参数传递给函数**函数返回一个指向这个值的指针,若未找到函数返回空 */TREE_TYPE  *findBST( TREE_TYPE value )/***pre_order_traverse**执行属的前序遍历,它的参数是一个回调函数指针,**它所指向的函数将在树中处理每一个节点被调用、**节点的值作为参数传递给这个函数。 */ void pre_order_traverse( void (*callback)(TREE_TYPE value));


建立一个二叉树实际上就是一个不断插入新值的过程,因为这个新值必须插入到一个合适的位置才能继续保持二叉搜索树的特性。插入的基本思路如下:

如果树为空:

把新值作为跟节点插入

否则:

如果新值小于当前节点的值:

把新值插入到当前节点的左子树

否则:

把新值插入当前节点的右子树

这个算法是按照搜索二叉树的定义来完成的,可以看书这个算法也是一个递归,并且是一个尾部递归,因此可以很容易使用迭代来代替递归提高程序的效率。

从二叉搜索树删除一个值是比较困难的,从一颗数的中部删除一个节点将导致它的的子树和属的其余部分断开,我们必须重新链接他们,并且面临三种处理情况:删除没有孩子的节点;删除有一个孩子的节点;删除有两个孩子的节点。第一种情况比较简单,直接删除该节点就可以了不会导致子树断开,因为这个节点已经没有子树了,删除有一个孩子的节点时把这个节点的双亲节点和它的孩子连接起来就可以了,最后一种情况是比较困难的,因为一个节点有两个孩子,它的双亲不能链接到它的两个孩子,解决这个问题的一个策略是删除它的左子树最大的那个值,并用这个值代替原先被删除的那个节点的值。

由于二叉搜索树的有序性,所以在树中查找一个也定的值是比较容易的,同意是一个递归的算法:

如果树为空:

这个值不存在于树中

否则:

如果这个值和根节点的值相等:

成功找到这个值

否则:

如果这个值小于根几点的值:

查找左子树

否则:

查找右子树

这也是一个尾部递归可以使用迭代代替提高算法的效率。

遍历树的节点有几种不同的次序,最常用的是前序、中序、后序和层次遍历。所有类型的遍历都是从树的跟节点或你希望开始遍历的子树的根节点开始。

前序遍历:简单点记就是“根左右”,先查看根节点然后遍历左子树,遍历左子树也是“根左右”的方式,当做子树遍历完成以后开始遍历右子树也是以“根左右”的方式,也就是说遍历也是一个递归的方式。

中序遍历:可记作“左根右”;后序遍历可以记作“左右根”。遍历的方式可以参考前序遍历的方式。

层次遍历:层次遍历先从跟节点开始,同一层从左向右开始遍历。一种比较普遍的做法是使用队列来实现层次遍历,当访问某给节点时首先将这个节点的左右孩子进入队列,然后依次从队列中出列遍历。

接下来就是实现二叉搜索树的各种是接口了,还是堆栈和队列一样,使用静态数组、动态数组、链式结构来分别实现。

静态数组的实现比较简单,某个节点的左右孩子很容易计算处理,可以使用数组的下标来代替指针,因此不需要额外的空间来存储指向左右节点的指针,计算公式如下:

节点N的双亲节点为: N/2 (注意这里没有问题,因为整数的除法会截去小数部分)

节点N的左孩子节点为: 2*N

节点N的右孩子节点为: 2*N+1

注意这个规则是建立在根节点是数组下标为1的元素,但是实际上数组都是以下标0开始的,如果不想忽略数组的第0个空间,可以将公式稍微修改一下:

节点N的双亲节点为: (N+1)/2 -1

节点N的左孩子节点为: 2*N + 1

节点N的右孩子节点为: 2*N + 2

由于数组元素的个数是预先确定的,不管二叉树有没有只有这个空间这个空间已经分配,因此必须使用一个特殊的值用于标识这个值是否被使用,这里我们使用0值作为此节点未被使用的标识。

具体的实现方式如下:

/*使用静态数组实现的二叉搜索树*/#include  <stdio.h>#include  "tree.h"/*定义用于存储二叉树的数组最大长度*/#define   TREE_SIZE    100 /*定义一个特殊值用于标识节点未使用*/#define  BLANK   0 static  TREE_TYPE  tree[TREE_SIZE];/***计算一个节点的左孩子的下标*/static  int  left_child( int current ){return  current*2 + 1;}/***计算一个节点右孩子的下标 */ static  int  right_child( int current ){return  current*2 + 2;}/***插入新值 */int  insert( TREE_TYPE  value ){int current;/*确保值为非 0 ,因为 0值用于标识一个未使用的节点*/if( value == BLANK ){printf( "插入的值不合法!/n" );return FALSE; }/*从根节点开始比较,使用迭代来代替递归*/current = 0;while( tree[current] != BLANK ){if( value < tree[current] ){current = left_child( current );}else{if( value == tree[current] ){printf( "需要插入的值已存在!/n" );return FALSE;}else{current = right_child( current );}}}/*需要插入的值是否超过了数组的范围*/if( current >= TREE_SIZE ){printf( "插入的值已超出数组的范围!/n" );return FALSE;} tree[current] = value;return TRUE;}/***查找节点 */TREE_TYPE  *find( TREE_TYPE  value ){int current;/***首先还是判断需要查找的值是不是特殊的用于标识未使用的节点*/if( value != BLANK ){current = 0;while( (current < TREE_SIZE) && (tree[current] != value) ){if( value < tree[current] ){current = left_child( current );}else{current = right_child( current );}}if( current < TREE_SIZE ){return tree+current;}}return  NULL; } /***二叉树的遍历,这里将节点值打印出来反映遍历的结果*/static void  print( TREE_TYPE value ){printf( "%d/n", value );} /***do_pre_order_traverse**执行一层前序遍历即根左右,利用递归实现*/static void do_pre_order_traverse( int current, void (*print)( TREE_TYPE value ) ){if( (current < TREE_SIZE) && (tree[current] != BLANK) ){print( tree[current] );do_pre_order_traverse( left_child( current), print );do_pre_order_traverse( right_child( current ), print);}}/***二叉树的遍历 */ void pre_order_traverse( void ){do_pre_order_traverse( 0, print );}/***二叉树节点的删除**需分三种情况讨论 */int delet( TREE_TYPE value ){TREE_TYPE  *del;del = find( value );if( del != NULL ){/*若无孩子节点*/if( ((left_child(del-tree) > TREE_SIZE) || (tree[left_child(del-tree)] == BLANK))&& ( (right_child(del-tree) > TREE_SIZE) ||(tree[right_child(del-tree)] == BLANK)) ){tree[del-tree] = BLANK;}/*若有两个孩子节点*/else{if( (left_child(del-tree) < TREE_SIZE) && (tree[left_child(del-tree)] != BLANK) && (right_child(del-tree) < TREE_SIZE) && (tree[right_child(del-tree)] != BLANK) ){ 			/* 		**寻找该节点左孩子中值最大的节点用于替换需要删除的节点 		**这里也可以使用右孩子最小的节点替换需要删除的节点  		**在这里寻找左孩子最大的节点实际上就是左孩子中最右边的叶子节点  		*/ TREE_TYPE  *right_max; right_max = del-tree; while( (right_max < TREE_SIZE)&&(tree[(int)right_max] != BLANK) ) { 			 	right_max = right_child( right_max );  			 }  /*因为判断是否是最右端的节点的缘故,因此最大值应该是其父节点*/ right_max = (TREE_TYPE *)(((int)right_max - 2)/2);  /*使用左孩子的最大节点替换需要删除的节点*/ tree[del-tree] = tree[(int)right_max]; tree[(int)right_max] = BLANK;  			}/*若只有一个孩子节点*/else{if( (right_child( del-tree ) < TREE_SIZE) && (tree[right_child(del-tree)] != BLANK) ){tree[del-tree] = tree[right_child(del-tree)];tree[right_child(del-tree)] = BLANK;}else{tree[del-tree] = tree[left_child(del-tree)];tree[left_child(del-tree)] = BLANK;}} 	 }return TRUE;}printf( "您需要删除的节点未找到!/n" );return  FALSE;}


动态数组的实现只是多了一个创建数组的过程,其他实现方式和静态数组的实现完全一样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: