动态查找—二叉排序树
2012-08-30 16:38
274 查看
动态查找的大多借助于树类型的结构,这里只介绍最简单的一种——二叉排序树。
二叉排序树是这样一种树:它的要么是空的;如果它的左子树不为空,那么左子树上所有节点的值均小于根节点的值;如果右子树不为空,那么右子树上的值均大于根节点上的值,并且它的左右子树还是二叉排序树。二叉排序树有一个重要的性质:当你中序遍历该树时,得到的遍历结果是有序的。
但这一切跟动态查找查找有什么关系呢?使用线性数据结构,也能较好地完成查找工作,比如之前提过的折半查找法。但是如果我想完成的是:如果找不到这个元素,就把它插入或者如果找到这个元素,就把它删除,那么就比较麻烦了。原因出在两个方面,如果使用类似于数组的结构,那么插入、删除都会导致大量元素的移动;如果使用类似于链表的结构,由于链表不支持随机访问特性,只能从头开始一个挨着一个查找,不能发挥折半查找的效率优势。此时,如果我们使用二叉排序树就能很好解决这两个问题:首先,它可以较方便的插入和删除元素,其次沿着跟向任意路径的遍历恰好就是折半查找!每次遇到根节点,如果待查找的值小于根节点的值,则访问根节点的左子树;否则访问根节点的右子树。
下面我们看看程序的实现:
数据结构和函数的声明如下:
函数的定义如下:函数的定义如下:
这些函数中,searchBST中的father函数看似多余,但是由于它的存在,在执行插入元素操作时,result能够获取被插入元素的“应该的”父节点。然后方便操作。删除一个节点就相对比较麻烦了,你得考虑3种情况:
1.被删除的节点是叶子节点,那么只需要把它删除并把它的父节点的lchild或者rchild设为NULL就行了。
2.如果被删除的节点有一个孩子节点,那么只需要把这个孩子节点接到被删除节点的父节点下面就行了。
3.如果被删除节点的左右孩子都存在,就比较麻烦了,因为你得保证删除之后重新拼起来的树还是二叉排序树。具体的说,你得在这个节点的左子树中找一个最大的,将它值放到这个节点,再把那个最大值的节点删除。具体的找法,如果左子树的右链为空,那么就把左子树这个节点的值放上去;否则沿着左子树的右链寻找到头。这个元素肯定是最大的。最后删除这个最大值。这时还分两种情况:这个最大值有左孩子与最大值没有左孩子。此时的处理对应于前两种情况中的一种。
在主函数中,需要通过类似于如下的代码建立一个树:
然后才能进行各种操作。
最后简单的提一下这种查找的效率:咋看之下,这种查找类似于折半查找,但实际上却依赖于二叉排序树的形状:极端情况下,假如所有的节点节点依次为前一个节点的左子树,那么这个二叉排序树退化为一个链表,那么它的查找效率就会大大降低。由此可见,我们希望数据在整个树上的分布是左右平衡的,这样查找效率才会更高。于是,人们提出了一种长相更加苛刻树,平衡二叉树,(AVL树)这种树的左子树与右子树高度的绝对值之差不超过1。如果使用这种树来做二叉排序树,那么可以提高二叉排序树的效率。
二叉排序树是这样一种树:它的要么是空的;如果它的左子树不为空,那么左子树上所有节点的值均小于根节点的值;如果右子树不为空,那么右子树上的值均大于根节点上的值,并且它的左右子树还是二叉排序树。二叉排序树有一个重要的性质:当你中序遍历该树时,得到的遍历结果是有序的。
但这一切跟动态查找查找有什么关系呢?使用线性数据结构,也能较好地完成查找工作,比如之前提过的折半查找法。但是如果我想完成的是:如果找不到这个元素,就把它插入或者如果找到这个元素,就把它删除,那么就比较麻烦了。原因出在两个方面,如果使用类似于数组的结构,那么插入、删除都会导致大量元素的移动;如果使用类似于链表的结构,由于链表不支持随机访问特性,只能从头开始一个挨着一个查找,不能发挥折半查找的效率优势。此时,如果我们使用二叉排序树就能很好解决这两个问题:首先,它可以较方便的插入和删除元素,其次沿着跟向任意路径的遍历恰好就是折半查找!每次遇到根节点,如果待查找的值小于根节点的值,则访问根节点的左子树;否则访问根节点的右子树。
下面我们看看程序的实现:
数据结构和函数的声明如下:
#include <stdio.h> #include <malloc.h> typedef struct BinarySortTree { int key; //父节点 BinarySortTree* parent; //左孩子 BinarySortTree* lchild; //右孩子 BinarySortTree* rchild; }BST,*pBST; //初始化指向一个节点的指针 BST* initNode(int k); //给parent节点增加一个名为child的孩子节点,lr=0为左孩子,否则为右孩子 bool addBranch(pBST parent,pBST child,int lr); //中序遍历树 void InOrderTraverse(pBST t); //查找key是否在t中,f为待查子树的父节点,找到的位置由p带出 bool searchBST(pBST t,int key,pBST f,pBST* p); //向t中插入key bool insertBST(BST* t,int key); //执行删除的实际函数 void Delete(pBST t); //从t中删除key bool deleteBST(pBST t,int key); //删除整个树 void destroyBST(pBST t);
函数的定义如下:函数的定义如下:
#include "BinarySortTree.h" pBST initNode(int k) { pBST p = (BST*)malloc(sizeof(BST)); p->key = k; p->lchild = p->parent = p->rchild = NULL; return p; } bool addBranch(pBST parent,pBST child,int lr) { //如果节点已经满了,则不能插入 if(parent->lchild != NULL &&parent->rchild != NULL) return false; if(0 == lr) parent->lchild = child; else parent->rchild = child; child->parent = parent; return true; } //中序遍历一棵树,遍历结果是有序的 void InOrderTraverse(pBST t) { if(NULL == t) return; else { InOrderTraverse(t->lchild); printf("%d ",t->key); InOrderTraverse(t->rchild); } } //二叉排序树的查找 //输入参数为待查找的树根、key,当前节点的父节点(第一次调用时为NULL),查找结果通过p返回出来 bool searchBST(pBST t,int key,pBST father,pBST* p) { //如果没有找到,p返回查找路径上指向的最后一个节点,返回false if(NULL == t) { *p = father; return false; } //若找到,则p返回指向该节点的指针,返回true if(t->key == key) { *p = t; return true; } if(t->key > key) return searchBST(t->lchild,key,t,p); else return searchBST(t->rchild,key,t,p); } //二叉排序树的插入,插入后依然是二叉排序树 //如果找到该元素,返回false,否则插入并返回true bool insertBST(pBST t,int key) { //查找结果的返回值从result带出去 pBST result; //如果找到了,返回假 if(searchBST(t,key,NULL,&result)) return false; else { //新分配一个节点 pBST pnew = initNode(key); //如果插入位置是头结点 if(NULL == result) pnew = t; //如果key小于节点的key,插到节点的左孩子 else if(result->key > key) addBranch(result,pnew,0); else //插到节点的右孩子 addBranch(result,pnew,1); return true; } } //从二叉排序树中删除元素,删除以后还是二叉排序树 bool deleteBST(pBST t,int key) { if(NULL == t) return false; //如果找到对应的节点,进行具体的删除操作 if(t->key == key) Delete(t); else if(t->key > key) return deleteBST(t->lchild,key); else return deleteBST(t->rchild,key); return true; } //具体删除操作的函数 void Delete(pBST p) { //如果被删除的是叶子节点 if(NULL == p->rchild && NULL == p->lchild) { if(p->key < p->parent->key) p->parent->lchild = NULL; else p->parent->rchild = NULL; free(p); p = NULL; return ; } //如果被删除节点的右子树为空,只需要重接左子树 if(NULL == p->rchild && NULL != p->lchild) { //先判断p是p->parent的左孩子还是右孩子 //如果是左孩子 if(p->parent->key >= p->key) p->parent->lchild = p->lchild; else p->parent->rchild = p->lchild; p->lchild->parent = p->parent; free(p); p = NULL; return; } //如果p的左子树为空,则只需要重接右子树 if(NULL == p->lchild && NULL != p->rchild) { p->rchild->parent = p->parent; //先判断p是p->parent的左孩子还是右孩子 //如果是左孩子 if(p->parent->lchild->key == p->key) p->parent->lchild = p->rchild; else p->parent->rchild = p->rchild; free(p); p = NULL; return ; } //如果左右子树都不为空: //寻找p的左子树的右链中的最大值 //如果左子树的右链为空,那么直接将左子树替换到最大值上 //如果不为空将这个值作为p的值 //将最大值删去,就是将最大值的左孩子与最大值的父亲相连 pBST q = p; pBST s = p->lchild; //找到p左子树的最大值 while(s->rchild) { q = s; s = s->rchild; } //循环完成以后 //如果左子树的右链为空,则不执行循环,q指向p,s为p的左孩子 //否则s记录的最大值的位置,q记录的是s的父节点 //左子树的右链为空 if(q == p ) { p->key = s->key; //等价于p->lchild = NULL; p->lchild = s->lchild; free(s); } //走到了右链的最后一个元素 else { p->key = s->key; //如果右链有左孩子 if(s->lchild != NULL) { //最大值的左孩子的父亲指向最大值的父亲 s->lchild->parent = q; //最大值的父节点的右孩子指向最大值的左孩子 q->rchild = s->lchild; } //如果右链没有左孩子 else q->rchild = NULL; free(s); } return ; } //删除整个树 //通过后序遍历完成 void destroyBST(pBST t) { if(NULL == t) return ; destroyBST(t->lchild); destroyBST(t->rchild); free(t); t = NULL; }
这些函数中,searchBST中的father函数看似多余,但是由于它的存在,在执行插入元素操作时,result能够获取被插入元素的“应该的”父节点。然后方便操作。删除一个节点就相对比较麻烦了,你得考虑3种情况:
1.被删除的节点是叶子节点,那么只需要把它删除并把它的父节点的lchild或者rchild设为NULL就行了。
2.如果被删除的节点有一个孩子节点,那么只需要把这个孩子节点接到被删除节点的父节点下面就行了。
3.如果被删除节点的左右孩子都存在,就比较麻烦了,因为你得保证删除之后重新拼起来的树还是二叉排序树。具体的说,你得在这个节点的左子树中找一个最大的,将它值放到这个节点,再把那个最大值的节点删除。具体的找法,如果左子树的右链为空,那么就把左子树这个节点的值放上去;否则沿着左子树的右链寻找到头。这个元素肯定是最大的。最后删除这个最大值。这时还分两种情况:这个最大值有左孩子与最大值没有左孩子。此时的处理对应于前两种情况中的一种。
在主函数中,需要通过类似于如下的代码建立一个树:
pBST root = initNode(45); pBST node1 = initNode(12); addBranch(root,node1,0); pBST node2 = initNode(53); addBranch(root,node2,1); pBST node3 = initNode(3); addBranch(node1,node3,0); pBST node4 = initNode(37); addBranch(node1,node4,1); pBST node5 = initNode(100); addBranch(node2,node5,1); pBST node6 = initNode(24); addBranch(node4,node6,0); pBST node7 = initNode(61); addBranch(node5,node7,0); pBST node8 = initNode(90); addBranch(node7,node8,1); pBST node9 = initNode(78); addBranch(node8,node9,0);
然后才能进行各种操作。
最后简单的提一下这种查找的效率:咋看之下,这种查找类似于折半查找,但实际上却依赖于二叉排序树的形状:极端情况下,假如所有的节点节点依次为前一个节点的左子树,那么这个二叉排序树退化为一个链表,那么它的查找效率就会大大降低。由此可见,我们希望数据在整个树上的分布是左右平衡的,这样查找效率才会更高。于是,人们提出了一种长相更加苛刻树,平衡二叉树,(AVL树)这种树的左子树与右子树高度的绝对值之差不超过1。如果使用这种树来做二叉排序树,那么可以提高二叉排序树的效率。
相关文章推荐
- 动态查找之-二叉排序树
- 动态查找——二叉排序树
- 动态查找表之二叉排序树
- 动态查找表之二叉排序树的查找、遍历、删除
- 动态查找之二叉排序树,C++代码实现
- 数据结构编程笔记二十五:第九章 查找 二叉排序树(动态查找表)查找算法的实现
- 动态查找之二叉排序树
- 数据结构(22)--动态查找之二叉排序树(二叉查找树)
- 动态查找之二叉排序树(BST)
- 查找(二)动态查找:二叉排序树的插入与输出操作
- 动态查找表之二叉排序树
- Java实现动态表查找--二叉排序树
- 12.动态查找.二叉排序树
- 初学数据结构---动态查找之二叉排序树
- 【查找--动态查找表】简单的二叉查找树(又称二叉排序树)
- 【数据结构与算法】动态查找--二叉排序树
- 数据结构_查找_动态查找表_二叉排序树
- 二叉排序树建立&查找
- 二叉排序树(创建,查找,删除)
- 二叉排序树_插入+删除+查找