8-1 二叉搜索树BST的元素删除、AVL的插入算法以及Set的底层实现 (《数据结构》第九章 查找 课后作业)

一、BST的元素删除

//注意传引用,才能作用到树本身
void delete(node* &root, int x){
	if(!root){                 //空树,则没找到
		cout << "Not Found\n";
		return; 
	}
	
    //当前结点值大于x时,根据BST的性质,则去左子树上继续找
	if(root->data > x) delete(root->left, x);
	else if(root->data < x) delete(root->right, x);        //小于x时,去右子树上找
	else{                       //相等了,那就删这个结点
		if(!root->left && !root->right) root = NULL;      //没孩子(叶子结点),直接设为NULL即可
		else if(root->left){         //有左子树,那就找前驱(左子树的最右孩子,不一定是直接的)
			node*temp = root->left;
			while(temp->right) temp = temp->right;        //找到最右子
			root->data = temp->data;                  //用前驱的值覆盖与删除结点的值
			delete(root->left, temp->data);            //递归调用delete函数,在左子树中删除掉最右子(关键步骤)
		}
		else {                //有右子树,那就找后继(原理同上)
			node*temp = root->right;
			while(temp->left) temp = temp->left;
			root->data = temp->data;
			delete(root->right, temp->data);
		}	
	}
} 

 

二、AVL的插入

//结点对应的结构体
struct node{  
	int data, h;    //data是内容域,h是高度,
	node*lc, *rc;     //lc和rc分别对应着左右孩子 
};


//得到结点的高度
int geth(node*root){
	return root ? root->h : 0;
}


//更新结点的高度
void update(node*root){
	root->h = max(geth(root->lc), geth(root->rc)) + 1;
}


//得到结点的平衡因子
int getfac(node*root){
	return geth(root->lc) - geth(root->rc);
}


//左旋
void L(node* &root){
	node*temp = root->rc;
	root->rc = temp->lc;
	temp->lc = root;
	update(root);
	update(temp);
	root = temp;
}


//右旋
void R(node* &root){
	node*temp = root->lc;
	root->lc = temp->rc;
	temp->rc = root;
	update(root);
	update(temp);
	root = temp;
}


//插入(关键部分)
void insert(node* &root, int x){
	if(!root){          //空树,直接插
		node*temp = new node;
		temp->data = x, temp->lc = temp->rc = NULL, temp->h = 1;  //把高度设为1
		root = temp;
	}
	
	if(root->data == x) return;     //已存在,不插了
	if(root->data < x) insert(root->rc, x);    //当前结点值小于x,则在右子树插
	else insert(root->lc, x);            //否则,在左子树插

	update(root);                 //插完之后,得更新高度
	

    //然后判断是哪种类型
	if(getfac(root) == 2)                              
		if(getfac(root->lc) == 1) 
            R(root);                               //LL型,直接右旋即可		
		else{                                     //LR型,先左旋左孩子得到LL型,然后右旋
			L(root->lc);
			R(root); 
		}
	else if(getfac(root) == -2)
		if(getfac(root->rc) == -1) L(root);       //RR型,直接左旋即可
		else{                                     //RL型,先右旋右孩子得到RR型,然后左旋
			R(root->rc);
			L(root);
		} 
}

 

总结分析:

  1. 使用递归,树这一节很多代码都可以用递归实现,递归的边界一般都是到达空树停止(需要重点掌握)

  2. 注意传引用,因为只有传引用才能作用到树本身(注意点)

  3. 熟练写出BST的删除函数(其他的比较简单)

  4. 熟练写出AVL的插入函数(其实附带的有好几个函数!!!)

 

三、set的底层实现

 

set

set是一种关联式容器,其特性如下:

  • set以RBTree作为底层容器
  • 所得元素的只有key没有value,value就是key
  • 不允许出现键值重复
  • 所有的元素都会被自动排序
  • 不能通过迭代器来改变set的值,因为set的值就是键

针对这五点来说,前四点都不用再多作说明,第五点需要做一下说明。如果set中允许修改键值的话,那么首先需要删除该键,然后调节平衡,在插入修改后的键值,再调节平衡,如此一来,严重破坏了set的结构,导致iterator失效,不知道应该指向之前的位置,还是指向改变后的位置。所以STL中将set的迭代器设置成const,不允许修改迭代器的值。

set的数据结构

// 比较器默认采用less,内部按照升序排列,配置器默认采用alloc
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set
{
public:
  //  在set中key就是value, value同时也是key
  typedef Key key_type;
  typedef Key value_type;

  // 用于比较的函数
  typedef Compare key_compare;
  typedef Compare value_compare;

private:
  // 内部采用RBTree作为底层容器
  typedef rb_tree<key_type, value_type,
                  identity<value_type>, key_compare, Alloc> rep_type;
  rep_type t;   // t为内部RBTree容器

public:
  // 用于提供iterator_traits<I>支持
  typedef typename rep_type::const_pointer pointer;            
  typedef typename rep_type::const_pointer const_pointer;
  typedef typename rep_type::const_reference reference;        
  typedef typename rep_type::const_reference const_reference;
  typedef typename rep_type::difference_type difference_type; 
  // 设置成const迭代器,set的键值不允许修改
  typedef typename rep_type::const_iterator iterator;          
  typedef typename rep_type::const_iterator const_iterator;
  // 反向迭代器
  typedef typename rep_type::const_reverse_iterator reverse_iterator;
  typedef typename rep_type::const_reverse_iterator const_reverse_iterator;
  typedef typename rep_type::size_type size_type;

  iterator begin() const { return t.begin(); }
  iterator end() const { return t.end(); }
  reverse_iterator rbegin() const { return t.rbegin(); }
  reverse_iterator rend() const { return t.rend(); }
  bool empty() const { return t.empty(); }
  size_type size() const { return t.size(); }
  size_type max_size() const { return t.max_size(); }

  // 返回用于key比较的函数
  key_compare key_comp() const { return t.key_comp(); }
  // 由于set的性质, value比较和key使用同一个比较函数
  value_compare value_comp() const { return t.key_comp(); }

  // 声明了两个友元函数,重载了==和<操作符
  friend bool operator== __STL_NULL_TMPL_ARGS (const set&, const set&);
  friend bool operator< __STL_NULL_TMPL_ARGS (const set&, const set&);
  // ...
}

set的构造函数

set提供了如下几个构造函数用于初始化一个set

// 注:下面相关函数都在set类中定义,为了介绍方便才抽出来单独讲解
// 空构造函数,初始化一个空的set
set() : t(Compare()) {}
// 支持自定义比较器,如set<int,greater<int> > myset的初始化
explicit set(const Compare& comp) : t(comp) {}
// 实现诸如set<int> myset(anotherset.begin(),anotherset.end())这样的初始化
template <class InputIterator>
set(InputIterator first, InputIterator last)
: t(Compare()) { t.insert_unique(first, last); }
// 支持自定义比较器的初始化操作
template <class InputIterator>
set(InputIterator first, InputIterator last, const Compare& comp)
: t(comp) { t.insert_unique(first, last); }
// 以另一个set来初始化
set(const set<Key, Compare, Alloc>& x) : t(x.t) {}
// 赋值运算符函数
set<Key, Compare, Alloc>& operator=(const set<Key, Compare, Alloc>& x)
{
    t = x.t;
    return *this;
}

set的操作函数

insert

插入函数,调用RBTree的插入函数即可

typedef  pair<iterator, bool> pair_iterator_bool;
// 由于set不允许键值重复,所以必须调用RBTree的insert_unique函数
// second表示插入操作是否成功
pair<iterator,bool> insert(const value_type& x)
{
  pair<typename rep_type::iterator, bool> p = t.insert_unique(x);
  return pair<iterator, bool>(p.first, p.second);
}

// 在position处插入元素, 但是position仅仅是个提示, 如果给出的位置不能进行插入,
// STL会进行查找, 这会导致很差的效率
iterator insert(iterator position, const value_type& x)
{
  typedef typename rep_type::iterator rep_iterator;
  return t.insert_unique((rep_iterator&)position, x);
}
// 将[first,last)区间内的元素插入到set中
template <class InputIterator>
void insert(InputIterator first, InputIterator last)
{
  t.insert_unique(first, last);
}

erase

擦除函数,用于擦除单个元素或者区间内的元素,直接调用RBTree的函数即可

// 擦除指定位置的元素, 会导致内部的红黑树重新排列
void erase(iterator position)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)position);
}

// 会返回擦除元素的个数, 其实就是标识set内原来是否有指定的元素
size_type erase(const key_type& x)
{
  return t.erase(x);
}

// 擦除指定区间的元素, 会导致红黑树有较大变化
void erase(iterator first, iterator last)
{
  typedef typename rep_type::iterator rep_iterator;
  t.erase((rep_iterator&)first, (rep_iterator&)last);
}

clean

清除整个set容器,直接调用RBTree的clean函数即可

  void clear() { t.clear(); }

find

查找函数,RBTree也提供了,直接调用即可

  // 查找指定的元素
  iterator find(const key_type& x) const { return t.find(x); }

count

查找制定元素的个数

  // 返回指定元素的个数, set不允许键值重复,其实就是测试元素是否在set中
  size_type count(const key_type& x) const { return t.count(x); }

重载操作符

set重载了==和<操作符,基本上都是调用RBTree的接口函数即可,如下所示:

template <class Key, class Compare, class Alloc>
inline bool operator==(const set<Key, Compare, Alloc>& x,
                       const set<Key, Compare, Alloc>& y) {
  return x.t == y.t;
}

template <class Key, class Compare, class Alloc>
inline bool operator<(const set<Key, Compare, Alloc>& x,
                      const set<Key, Compare, Alloc>& y) {
  return x.t < y.t;
}

其他操作函数

// 返回小于当前元素的第一个可插入的位置
iterator lower_bound(const key_type& x) const
{
  return t.lower_bound(x);
}

// 返回大于当前元素的第一个可插入的位置
iterator upper_bound(const key_type& x) const
{
  return t.upper_bound(x);
}
// 返回与指定键值相等的元素区间
pair<iterator,iterator> equal_range(const key_type& x) const
{
  return t.equal_range(x);
}

 

参考文献:带你深入理解STL之Set和Map

1.对于二叉排序树,下面的说法( )是正确的。 A.二叉排序树是动态树表,查找不成功时插入新结点时,会引起树的重新分裂和组合 B.对二叉排序树进行层序遍历可得到有序序列 C.用逐点插入法构造二叉排序树时,若先后插入的关键字有序,二叉排序树的深度最大 D.在二叉排序树中进行查找,关键字的比较次数不超过结点数的1/2 2.在有n个结点且为完全二叉树的二叉排序树中查找一个键值,其平均比较次数的数量级为( )。 A.O(n) B.O(log2n) C.O(n*log2n) D.O(n2) 3.静态查找与动态查找的根本区别在于( )。 A. 它们的逻辑结构不一样 B. 施加在其上的操作不同 C. 所包含的数据元素类型不一样 D. 存储实现不一样 4.已知一个有序表为{12,18,24,35,47,50,62,83,90,115,134},当折半查找值为90的元素时,经过( )次比较后查找成功。 A.2 B.3 C.4 D.5 5.已知数据序列为(34,76,45,18,26,54,92,65),按照依次插入结点的方法生成一棵二叉排序树,则该树的深度为( )。 A. 4 B. 5 C. 6 D. 7 6.设散列表表长m=14,散列函数H(k)=k mod 11 。表中已有15,38,61,84四个元素,如果用线性探测法处理冲突,则元素49的存储地址是( )。 A. 8 B. 3 C. 5 D. 9 7. 平衡二叉树查找效率呈( )数量级。 A. 常数阶 B. 线性阶 C. 对数阶 D. 平方阶 8. 设输入序列为{20,11,12,…},构造一棵平衡二叉树,当插入值为12的结点时发生了不平衡,则应该进行的平衡旋转是( )。 A. LL B. LR C. RL D. RR 二、填空题(每空3分,共24分)。 1.在有序表A[1..18]中,采用二分查找算法查找元素值等于A[7]的元素,所比较过的元素的下标依次为 。 2.利用逐点插入法建立序列(61,75,44,99,77,30,36,45)对应的二叉排序树以后,查找元素36要进行 次元素间的比较,查找序列为 。 3. 用顺序查找法在长度为n的线性表中进行查找,在等概率情况下,查找成功的平均比较次数是 。 4. 二分查找算法描述如下: intSearch_Bin(SST ST, KT key) { low=1 ; high=ST. length; while(low<=high) { mid=(low+high)/2; if(key==ST.elem[mid].key) return mid; else if(key<ST.elem[mid].key) ; else ; } return 0; } 5.链式二叉树的定义如下: typedef struct Btn{ TElemType data; ; }BTN ,*BT; 6.在有n个叶子结点的哈夫曼树中,总结点数是 。 三、综合题(共52分)。 1. (共12分)假定关键字输入序列为19,21,47,32,8,23,41,45,40,画出建立二叉平衡树的过程。 2. (共15分)有关键字{13,28,31,15,49,36,22,50,35,18,48,20},Hash 函数为H=key mod 13,冲突解决策略为链地址法,请构造Hash表(12分),并计算平均查找长度(3分)。 ASL= 3. (共10分)设关键字码序列{20,35,40,15,30,25},给出平衡二叉树的构造过程。 4. (共15分)设哈希表长为m=13,散列函数为H(k)=k mod 11,关键字序列为5,7,16,12,11,21,31,51,17,81;试求:散列后的表中关键字分布(假定解决冲突的方法为线性探测再散列法);求平均查找长度ASL;计算该表的装填因子。 (1)按要求求哈希表(10分): 0 1 2 3 4 5 6 7 8 9 10 11 12 (2)计算ASL(3分): ASL= (3)计算装填因子(2分):装填因子=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值