[数据结构]二叉搜索树概念及基本操作
2014-05-20 01:21
387 查看
二叉搜索树是一种动态数据结构,其最重要的特点是其保持有序性,这样可以保证在进行搜索时比其他的结构快很多。一棵二叉搜索树要保证如下的属性和特点:
满足二叉树定义
若其左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若其右子树不空,则右子树上所有节点的值均大于它的根节点的值;
其左右子树各自均满足二叉搜索树定义
BST(尤其是平衡时)在插入和删除时非常快。其相关代码代码简单高效。
BST是构成一些更加复杂的数据结构的基础(如set容器)。但是当插入序列是已排好的数据时,其形成的BST的形状会退化成链表类型结构,此时在这个树中的搜索和插入都会变的低效率。基于此的改善包括Self-Balancing Binary Search Tree.
比较典型的包括AVL树和RBT树(红黑树)。
数据结构采用二叉结构,包含Datatype类型(此处定义为)以及左孩子和右孩子指针。
次二分查找。以下提
供递归和非递归方法。
case1:对应无孩子节点,则直接删除该节点,并将其双亲节点的孩子指针置空。
case2:只有一个孩子节点,将双亲节点孩子指向此节点的孩子,删除该节点。
case3:有两个孩子节点,此时情况略微复杂一些。即我们需要做的不是删除直接对应的节点,而是要用其直接前驱 或者直接后继节点代替此节点,并删除那个相应的前驱或者后继。而且此时的那个前驱或者后继对应的就是上面的case1或者case2情况之一。
道理也好理解,因为受制于搜索二叉树特有的中序遍历有序结构,对于第三种情况,选用左子树中最大的节点(直接前驱)或者右子树中最小的节点(直接后继)本质上是没有区别的。实际编写代码时,开始采用非递归方法,但是有一个很大的问题是要知道被删除节点的双亲节点,因为在释放被删除节点后,务必还有将其双亲节点的孩子指针置空。处理case3时,同样也要得到直接前驱(或后继)的那个节点的双亲。如此一来,代码量很大,情况分析也很复杂。基于此,还是选择了《数据结构》书中的递归方法,代码简介一目了然。考虑到二叉搜索树的删除复杂度属于O(n)级别,所以递归的代价不会非常大。
以上的代码包含了对于二叉搜索树的插入,搜索和删除节点操作。
还有一个简洁的代码涉及判定给定二叉树是否是二叉搜索树。由二叉搜索树定义可知,仅仅判定每个节点是否介于左右两个孩子节点的值之间是不够的,因为在左子树中不能保证左子树的根节点是最大值。所以这个情况是我们知道每次判定节点的值区间时,所涉及的比较应该是两个门限而不是仅仅对应的两个孩子节点。
在一般情况下,考虑随机数列,那么二叉搜索树应该是接近平衡,此时的搜索比较次数即从根节点到每个叶子的路径(深度),其复杂度(包括删除,搜索和插入)均为 O(logn),但是对于动态插入有序数列,那么此时作为最坏情况,BST接近一维结构,这时前面所述的复杂度均为O(n).
可见,降低复杂度的形象的办法是使得BST尽量分布均匀。而这就引出了自平衡二叉排序树,通过如左旋,右旋等操作动态控制生成的二叉树的分布尽量均匀,从而保证复杂度始终在O(logn).
满足二叉树定义
若其左子树不空,则左子树上所有节点的值均小于它的根节点的值;
若其右子树不空,则右子树上所有节点的值均大于它的根节点的值;
其左右子树各自均满足二叉搜索树定义
BST(尤其是平衡时)在插入和删除时非常快。其相关代码代码简单高效。
BST是构成一些更加复杂的数据结构的基础(如set容器)。但是当插入序列是已排好的数据时,其形成的BST的形状会退化成链表类型结构,此时在这个树中的搜索和插入都会变的低效率。基于此的改善包括Self-Balancing Binary Search Tree.
比较典型的包括AVL树和RBT树(红黑树)。
数据结构采用二叉结构,包含Datatype类型(此处定义为)以及左孩子和右孩子指针。
typedef int Datatype; typedef struct NODE{ Datatype data; NODE *left, *right; }Node; Node *creatNode(const Datatype &key){ Node *p = new Node; p->data = key; p->left = NULL; p->right = NULL; return p; }
插入
BST的插入很简单,从根节点开始进行一次二分查找。以下提
供递归和非递归方法。
Node *insert(Node *&p, Datatype &key){ //递归调用时要控制返回,此返回新插入的节点 if (!p){ p = creatNode(key); return p; } if (key == p->data) return p;//包含此树则不建立新节点 else if (key < p->data) p->left = insert(p->left, key); else p->right = insert(p->right, key); return p; } void insert_2(Node *&p, Datatype &key){ //非递归调用,重点在while部分的控制 if (!p) { p = creatNode(key); //p是引用,这样在空树的时候可以建树 return; } Node *temp = p; //p是引用,循环时会被破坏。所以用另一个变量替代 while (temp){ if (key == temp->data) break;//已包含此节点,无作为。 if (key < temp->data){ if (temp->left) temp = temp->left; else { temp->left = creatNode(key); break; } } else{ if (temp->right) temp = temp->right; else { temp->right = creatNode(key); break; } } } }
搜索
搜索代码与插入代码基本一致Node *search(Node *&p, const Datatype &key){ //递归搜索 空指针则失败。 if (!p) return p; if (p->data == key) return p; if (key < p->data) return search(p->left, key); else return search(p->right, key); } Node *search_2(Node *p, const Datatype &key){ //非递归搜索 返回找到的指针,空指针则失败 if (!p) return p; while (p){ if (key == p->data) return p; if (key < p->data) p = p->left; else p = p->right; } return p; }
删除节点
BST的删除分三种情况,即按照要删除节点无孩子,只有一个孩子,或者有两个孩子分三类。case1:对应无孩子节点,则直接删除该节点,并将其双亲节点的孩子指针置空。
case2:只有一个孩子节点,将双亲节点孩子指向此节点的孩子,删除该节点。
case3:有两个孩子节点,此时情况略微复杂一些。即我们需要做的不是删除直接对应的节点,而是要用其直接前驱 或者直接后继节点代替此节点,并删除那个相应的前驱或者后继。而且此时的那个前驱或者后继对应的就是上面的case1或者case2情况之一。
道理也好理解,因为受制于搜索二叉树特有的中序遍历有序结构,对于第三种情况,选用左子树中最大的节点(直接前驱)或者右子树中最小的节点(直接后继)本质上是没有区别的。实际编写代码时,开始采用非递归方法,但是有一个很大的问题是要知道被删除节点的双亲节点,因为在释放被删除节点后,务必还有将其双亲节点的孩子指针置空。处理case3时,同样也要得到直接前驱(或后继)的那个节点的双亲。如此一来,代码量很大,情况分析也很复杂。基于此,还是选择了《数据结构》书中的递归方法,代码简介一目了然。考虑到二叉搜索树的删除复杂度属于O(n)级别,所以递归的代价不会非常大。
bool delNode(Node *&target){ //子函数,用来删除节点 Node *temp; if (!target->right){ temp = target; target = target->left; delete temp; } else if (!target->left){ temp = target; target = target->right; delete temp; } else{ //以上两个判断合起来对应case2 //这个else对应处理case3 //case1 实际上被内化在以上两个判断中了 Node *temp2; temp = target; temp2 = target->left; while (temp2->right){ temp = temp2; temp2 = temp2->right; } target->data = temp2->data; if (temp == target) temp->left = temp2->left; else temp->right = temp2->left; delete temp2; } return true; } bool del_BST(Node *&p, const Datatype &key){ //删除函数,用来找到被删除元素并进行删除操作。 if (!p) return false; if (p->data == key) return delNode(p); else if (key < p->data) return del_BST(p->left,key); else return del_BST(p->right, key); }
以上的代码包含了对于二叉搜索树的插入,搜索和删除节点操作。
还有一个简洁的代码涉及判定给定二叉树是否是二叉搜索树。由二叉搜索树定义可知,仅仅判定每个节点是否介于左右两个孩子节点的值之间是不够的,因为在左子树中不能保证左子树的根节点是最大值。所以这个情况是我们知道每次判定节点的值区间时,所涉及的比较应该是两个门限而不是仅仅对应的两个孩子节点。
bool isBST(Node *p, Datatype min, Datatype max){ //min 表示应该大于的值,max表示应该小于的值 if (!p) return true; if (p->data > max || p->data < min) return false; else return(isBST(p->left, min, p->data) && isBST(p->right, p->data, max)); }
总结
至此,简单的二叉搜索树的相关操作均给出了代码实现。最后对于动态数据结构,给出其一些操作的复杂度。在一般情况下,考虑随机数列,那么二叉搜索树应该是接近平衡,此时的搜索比较次数即从根节点到每个叶子的路径(深度),其复杂度(包括删除,搜索和插入)均为 O(logn),但是对于动态插入有序数列,那么此时作为最坏情况,BST接近一维结构,这时前面所述的复杂度均为O(n).
可见,降低复杂度的形象的办法是使得BST尽量分布均匀。而这就引出了自平衡二叉排序树,通过如左旋,右旋等操作动态控制生成的二叉树的分布尽量均匀,从而保证复杂度始终在O(logn).
相关文章推荐
- 数据结构:二叉搜索树(BST)的基本操作
- Java数据结构与算法之数据结构-逻辑结构-线性结构(9)------Java线性结构概念及其基本操作
- 数据结构:二叉搜索树(BST)的基本操作
- 二叉搜索树的基本操作(查找、插入、删除)【数据结构】
- 数据结构 — 堆基本概念以及基本操作
- 数据结构――堆的基本概念及其操作
- 数据结构回顾和总结(二叉搜索树(BST)的基本操作)
- 【数据结构】二叉搜索树的插入,删除,查找等基本操作的实现
- [数据结构]堆的基本概念及其操作
- [数据结构][C语言]图的基本介绍和操作实现之基本概念
- 【数据结构】二叉搜索树的基本操作(第二遍)
- 数据结构:二叉搜索树(BST)全部基本操作
- 基本数据结构——堆(Heap)的基本概念及其操作
- 数据结构-堆基本概念以及操作实现
- CVS和SVN的中的基本概念和操作
- 数据结构 链表的基本操作
- C#网络编程(基本概念和操作) - Part.1
- C#网络编程(基本概念和操作) - Part.1
- 二叉搜索树基本操作
- 线性表的概念、结构与基本操作