数据结构——7.搜索树

一、复习

bst:搜索、插入、删除

二、二叉搜索树BST

特征:

1)每个元素有一个关键值,所有的关键值都唯一。

2)根节点左子树的关键值(如果有的话)小于根节点的关键值

3)根节点右子树的关键值(如果有的话)大于根节点的关键值

4)根节点的左右子树也都是二叉搜索树

一棵二叉树,如果其中序遍历的结果,得到的是关键值按升序排列的列表,则它是二叉搜索树

每个节点有一个索引值:左子树大小(结点个数)+1 (即节点在子树中从小到大的排名)

 1.二叉搜索树与搜索插入

template<class E, class K>
class BSTree : public BinaryTree<E> //继承二叉树
{
public:
    //将关键值为k的元素返回到e中;返回是否操作成功
    bool Search(const K& k, E& e) const;
    //将元素e插入到搜索树中
    BSTree<E, K>& Insert(const E& e);
    //删除关键值为k的元素并且将其返回到e中
    BSTree<E, K>& Delete(const K& k, E& e);
    //按照关键值的升序排列输出所有元素
    void Ascend() { InOutput(); }//升序输出:中序遍历
};

//搜索:从根节点开始,左小右大
template<class E, class K>
bool BSTree<E, K>::Search(const K& k, E& e) const
{
    BinaryTreeNode<E>* p = root;
    while (p) 
    {
        if (k > p->data)p = p->RightChild;
        else if (k < p->data)p = p->LeftChild;
        else 
        {
            e = p->data;
            return 1
        }
    }
    return 0;
}

//插入
template<class E, class K>
BSTree<E, K>& BSTree<E, K>::Insert(const E& e)
{// Insert e if not duplicate.
    BinaryTreeNode<E>* p = root,  // search pointer
        * pp = 0;    // parent of p
    // find place to insert
    while (p) {// examine p->data
        pp = p;
        // move p to a child
        if (e < p->data) p = p->LeftChild;
        else if (e > p->data) p = p->RightChild;
        else throw BadInput(); // duplicate
    }
    // get a node for e and attach to pp
    BinaryTreeNode<E>* r = new BinaryTreeNode<E>(e);
    if (root) {// tree not empty
        if (e < pp->data) pp->LeftChild = r;
        else pp->RightChild = r;
    }
    else // insertion into empty tree
        root = r;
    return *this;
}

2.bst删除

1.删除叶节点:  直接删除,让父节点指向它的指针变为0

2.p有且只有一个非空子树t,其根为q:    丢弃p,用q取代p的位置

3.p的两个子树都不为空:

        1)p与某个节点q(满足情况2或1)交换(交换后应保持二叉搜索树性质)

        2)删除q

#选择方法:左子树的最右节点或右子树的最左节点,排名恰与p相邻——之前或之后,即直接前驱或直接后继,一般用直接前驱(被删节点的左子树的最右节点)(最右就是一直选择向右走直到没有向右子节点)

//删除算法
template<class E, class K>
BSTree<E, K>& BSTree<E, K>::Delete(const K& k, E& e)
{
    BinaryTreeNode<E>* p = root, * pp = 0;
    //先搜索要删除的节点p
    while (p && p->data != k)
    {
        pp = p;//p的双亲结点
        if (k < p->data)
            p = p->LeftChild;
        else p = p->RightChild;
    }
    if (!p) throw BadInput(); // no element with key k
    e = p->data;
    //最坏情况,
    //两个子树均不空
    if (p->LeftChild && p->RightChild) 
    {
        // 搜索左子树最右(大)节点
        BinaryTreeNode<E>* s = p->LeftChild,
            * ps = p;  // 记录双亲结点,防止丢失
        while (s->RightChild) {
            ps = s;
            s = s->RightChild;
        }
        // 交换p和其左子树的最右节点
        p->data = s->data;
        //这里第一次略有疑惑
        p = s;
        pp = ps;
    }
    //此后2个子树的情况已经变为1/0个子树的情况
    
    //处理p只有1个孩子时,c指向孩子(p有0个孩子指向空指针)
    BinaryTreeNode<E>* c;
    if (p->LeftChild) c = p->LeftChild;
    else c = p->RightChild;

    // 删除p:即跨过p,让pp指向p的孩子
    //p为根,此时pp为空
    if (p == root) root = c;
    //c要么为空,要么是删除的下一个(1孩子),让父节点指向空或下一个
    else {// is p left or right child of pp?
        if (p == pp->LeftChild)
            pp->LeftChild = c;
        else pp->RightChild = c;
    }
    delete p;

    return *this;
}

3.复杂度分析

搜索、插入、删除的复杂性为O(h)

二叉搜索树的高度h最坏情况为n

若搜索、插入、删除是随机的,平均情况为O(logn)

升序输出为O(n)

三、AVL树

概念与特性

AVL——一种平衡树,提高二叉搜索树的最坏情况

如果T是一棵非空的二叉树,TLTR分别是其左子树和右子树,T是一棵AVL树,满足:

1)TLTRAVL

2)|hL-hR|≤1,hLhR分别是左子树和右子树的高度

特性:

n个元素(节点)的AVL树的高度是O(logn)

插入、删除、搜索都是O(logn)

AVL树的高度

Fh表示高度为h的节点数最少的AVL 

左、右子树也是AVL树,高度一个为h-1,另一个为h-1h-2

Fh节点数最少->子树中分别为Fh-1Fh-2

高度为h的AVL树的最少节点数:|F(h)|=|F(h-1)|+|F(h-2)|+1 菲波那契数列)

平衡因子域bf=左子树的高度-右子树的高度;

AVL搜索:和一般的二叉搜索树一致

AVL插入

思路:首先利用二叉搜索树的插入算法;可能出现不平衡的情况->调整结构

分析:

只有从根到新插入节点路径上的节点,其平衡因子才会在插入操作后发生改变。

A:假设A是离新插入节点最近的,平衡因子为-2或2的祖先节点,则在插入前,从A到新插入节点的路径上,所有节点的平衡因子都是0

X:在插入前bf(A)必然为-1/+1,x是满足条件的节点的最后一个

x不存在:bf全为0,插入不会导致不平衡

插入后X未变为A:插入后bf(X)=0;新元素插入原来较矮的子树

X变为A:新节点插入较高的子树

型号的判断只从被破坏节点开始判断两次

LL型:在被破坏节点的左边的左边插入而导致失衡;解决——以被破坏节点为基础进行右旋

RR型:以被破坏节点为基础进行左旋

LR型:左右型,在被破坏节点的左边的右边插入而导致失衡;

     解决:以被破坏节点的左节点为基础先进行一次左旋,再以被破坏节点为基础进行右旋

RL型:以被破坏节点的右节点为基础先进行一次右旋,再以被破坏节点为基础进行左旋

算法:

1.从根节点开始搜索,确定插入位置,同时寻找最后的平衡因子为-11的节点,记为A

2.若没有A:直接插入后平衡,从根节点遍历,修改平衡因子。

3.bf(A)=1且新节点插入A的右子树,或bf(A)=-1且新节点插入A的左子树:
->A 的新平衡因子是0,修改从A 到新节点路径中节点的平衡因子,然后终止

4.不平衡:执行旋转,修改平衡因子

typedef struct AVLNode
{
	struct AVLNode* left, * right;
	int val, height;
}*Node;

int getHeight(Node root)
{
	if (root == NULL)	return 0;
	else	return root->height;
}

Node rightrotate(Node root)//LL型旋转
{
	Node tmp = root->left;
	root->left = tmp->right;
	tmp->right = root;

	root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
	tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
	return tmp;
}
Node leftrotate(Node root)//RR型旋转
{
	Node tmp = root->right;
	root->right = tmp->left;
	tmp->left = root;

	root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
	tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
	return tmp;
}
Node rightleftrotate(Node root)//RL型旋转
{
	root->right = rightrotate(root->right);
	return leftrotate(root);
}
Node leftrightrotate(Node root)//LR型旋转
{
	root->left = leftrotate(root->left);
	return rightrotate(root);
}

AVL删除

1.执行bst删除,如不平衡则调整

删除的节点在右子树:

1.删除后被破坏节点的左节点的左边高度大于右边高度:相当于LL型,对“被破坏节点”进行右旋

2.删除后被破坏节点的左节点的左边高度小于右边高度:相当于LR型,先对“被破坏节点的左节点”左旋,再对“被破坏节点”右旋

3.删除后被破坏节点的左节点的左边高度等于右边高度:对“被破坏节点”进行右旋

删除的节点在左子树,对称处理

AVL非递归实现 

【C++ 学习 ㉒】- 超详解 AVL 树的插入、平衡调整以及删除(含源代码)-CSDN博客

AVL的实现(递归)

 平衡二叉树(AVL)的构造,插入与删除代码(利用递归)_wulizyzstc的博客-CSDN博客

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
//树结点
struct TreeNode
{
  int val;
  int height;
  TreeNode* left;
  TreeNode*right;
  TreeNode():val(0),left(nullptr),right(nullptr),height(1){}
  TreeNode(int x):val(x),left(nullptr),right(nullptr),height(1){}
  TreeNode(int x,TreeNode*left,TreeNode*right):val(x),height(1),left(left),right(right){}
};

//某树是不是完全二叉树
bool isbinarytree(TreeNode*root)
{
  queue<TreeNode*>que;
  que.push(root);
  TreeNode *cur;
  //遇到空结点
  while((cur=que.front())!=nullptr)
  {
    que.pop();
    que.push(cur->left);
    que.push(cur->right);
  }
  //验证que里面还有没有数
  while(!que.empty())
  {
    if (que.front()!=nullptr)
    {
      return false;
    }
    que.pop();
  }
  return true;
}

//层次遍历输出数组
vector<int> levelorder(TreeNode*root)
{
  vector<int>arr;
  if (root)
  {queue<TreeNode*>que;
  que.push(root);
  while (!que.empty())
  {
    auto x=que.front();
    arr.push_back(x->val);
    que.pop();
    if (x->left)que.push(x->left);
    if (x->right)que.push(x->right);
  }
  }
  return arr;
}

//getheight(递归)
int getheight(TreeNode* root)
{
  if (!root)return 0;
  return root->height;
} 

//LL右单旋
TreeNode* rightrotate(TreeNode* root)
{
  TreeNode* temp=root->left;
  root->left=temp->right;
  temp->right=root;
  root->height=max(getheight(root->left),getheight(root->right))+1;
  temp->height=max(getheight(temp->left),getheight(temp->right))+1;
  return temp;
}

//RR左单旋
TreeNode* leftrotate(TreeNode* root)
{
  TreeNode* temp=root->right;
  root->right=temp->left;
  temp->left=root;
  root->height=max(getheight(root->left),getheight(root->right))+1;
  temp->height=max(getheight(temp->left),getheight(temp->right))+1;
  return temp;

}

//LR 对root->left做左单旋,再对root做右单旋
TreeNode* lerigrotate(TreeNode* root)
{
  root->left=leftrotate(root->left);
  return rightrotate(root);
}

//RL 对root-right做右单旋,再对root做左单旋
TreeNode* riglerotate(TreeNode* root)
{
  root->right=rightrotate(root->right);
  return leftrotate(root);
}

//把数据插入到avl树上
TreeNode* insert(TreeNode * root,int key)
{
  if (!root)
  {
   root=new TreeNode(key);
   return root;
  }
  else
  {
    if (key<root->val)
    {
      root->left=insert(root->left,key);
      if (getheight(root->left)-getheight(root->right)==2)
      {
        if (key<root->left->val)root=rightrotate(root);
        else root=lerigrotate(root);
      } 
    }
    else if(key>root->val)
    {
      root->right=insert(root->right,key);
      if (getheight(root->right)-getheight(root->left)==2)
      {
        if (key>root->right->val)root=leftrotate(root);
        else root=riglerotate(root);
      }
    }
  }
  root->height=max(getheight(root->left),getheight(root->right))+1;
  return root;
}

bool isdelete=1;
//删除叶子结点的方法删除avl结点
TreeNode* deletekey(TreeNode* root,int key)
{
  if (!root) {isdelete=0;return root;}//没成功删除
  if (root->val==key)//删除叶子结点
  {
    if(!root->left&&!root->right){root=nullptr;return root;}
    if (getheight(root->left)>=getheight(root->right))//替换并且使得要删除的元素变成叶子结点
        {
          //对左孩子找到最里面的右孩子,改变key,迭代删掉它
          TreeNode* temp=root->left;
          while (temp->right)
          {
            temp=temp->right;
          }
          root->val=temp->val;
          key=temp->val;
          root->left=deletekey(root->left,key);
          if (getheight(root->right)-getheight(root->left)==2)root=leftrotate(root);

        }
    else
        {
          TreeNode* temp=root->right;
          while (temp->left)
          {
            temp=temp->left;
          }
          root->val=temp->val;
          key=temp->val;
          root->right=deletekey(root->right,key);
          if (getheight(root->left)-getheight(root->right)==2)root=rightrotate(root);

        }
  }
  else if(key<root->val)
  {
    root->left=deletekey(root->left,key);
    if (getheight(root->right)-getheight(root->left)==2)root=leftrotate(root);
  }
  else if(key>root->val)
  {
    root->right=deletekey(root->right,key);
    if (getheight(root->left)-getheight(root->right)==2)root=rightrotate(root);
  }
  root->height=max(getheight(root->left),getheight(root->right))+1;
  return root;
}





int main()
{
  int n;
  cin >> n;
	TreeNode* root = NULL;
	for(int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		root = insert(root, x);
	}
  
  vector<int>arr=levelorder(root);
  for (int i = 0; i < arr.size()-1; i++)cout<<arr[i]<<" ";
  cout<<arr[arr.size()-1]<<endl;
  
  if (isbinarytree(root))cout<<"Yes"<<endl;
  else cout<<"No"<<endl;
  
  int m;
  cin>>m;
  root=deletekey(root,m);
  
  if (isdelete)cout<<"删除成功"<<endl;
  else cout<<"删除失败"<<endl;
  

  arr=levelorder(root);
  for (int i = 0; i < arr.size()-1; i++)cout<<arr[i]<<" ";
  cout<<arr[arr.size()-1]<<endl;

  if (isbinarytree(root))cout<<"Yes"<<endl;
  else cout<<"No"<<endl;

  system ("pause");
  return 0;
}



 

oj——创建AVL树并判断是否为完全二叉树

#include <queue>
#include <vector>
#include <iostream>
using namespace std;

typedef struct AVLNode
{
	struct AVLNode* left, * right;
	int val, height;
}*Node;

int getHeight(Node root)
{
	if (root == NULL)	return 0;
	else	return root->height;
}

Node rightrotate(Node root)//LL型旋转
{
	Node tmp = root->left;
	root->left = tmp->right;
	tmp->right = root;

	root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
	tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
	return tmp;
}
Node leftrotate(Node root)//RR型旋转
{
	Node tmp = root->right;
	root->right = tmp->left;
	tmp->left = root;

	root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
	tmp->height = max(getHeight(tmp->left), getHeight(tmp->right)) + 1;
	return tmp;
}
Node rightleftrotate(Node root)//RL型旋转
{
	root->right = rightrotate(root->right);
	return leftrotate(root);
}
Node leftrightrotate(Node root)//LR型旋转
{
	root->left = leftrotate(root->left);
	return rightrotate(root);
}

Node insert(Node root, int key)
{
	if (root == NULL)
	{
		root = new AVLNode();
		root->val = key;
		root->left = root->right = NULL;
		root->height = 1;
		return root;
	}
	else
	{
		if (key < root->val)
		{
			//这一步递归体会一下,正常的bst插入
			root->left = insert(root->left, key);
			
			if (getHeight(root->left) - getHeight(root->right) == 2)
			{
				if (key < root->left->val)	root = rightrotate(root);
				else root = leftrightrotate(root);
			}
		}
		else if (key > root->val)
		{
			root->right = insert(root->right, key);
			if (getHeight(root->right) - getHeight(root->left) == 2)
			{
				if (key > root->right->val)	root = leftrotate(root);
				else root = rightleftrotate(root);
			}
		}
	}
	root->height = max(getHeight(root->left), getHeight(root->right)) + 1;
	return root;
}

//层次顺序遍历 和两个flag判断是否是完全二叉
void level_order_traverse(Node root)
{
	queue<Node>q;
	vector<int>ans;
	q.push(root);
	bool flag = false, flag2 = false;
	while (!q.empty())
	{
		Node p = q.front();
		q.pop();
		ans.push_back(p->val);
		if (p->left != NULL)
		{
			q.push(p->left);
			if (flag)	flag2 = true;
		}
		else	flag = true;

		if (p->right != NULL)
		{
			q.push(p->right);
			if (flag)	flag2 = true;
		}
		else flag = true;
	}
	for (int i = 0; i < ans.size(); i++)
	{
		if (i == ans.size() - 1)	cout << ans[i] << endl;
		else	cout << ans[i] << ' ';
	}
	if (!flag2)	cout << "Yes" << endl;
	else cout << "No" << endl;
}
int main()
{
	int n;
	cin >> n;
	Node root = NULL;
	for (int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		root = insert(root, x);
	}
	level_order_traverse(root);

	return 0;
}

四、B树B+树

m叉搜索树

可以为空,若非空:

1.在相应的扩充搜索树中(用外部节点替换零指针),每个内部节点最多可以m个子女包含1m-1个元素(外部节点不含元素和子女)

2.每个含p个元素的节点,有p+1个子女

3.和二叉搜索树类似,有大小关系

 高度为h,元素个数:h~m^h-1

n个元素,高度:n~logm(n+1)

mB-

是一棵m叉搜索树,若B-树非空,那么相应的扩充树满足下列特征:

1.根节点至少有2个孩子

2.除根节点外,所有内部节点至少有[m/2]个孩子(向上取整)

3.所有外部节点位于同一层上

B-树的高度

一棵高为hm阶B-树,d=ceil(m/2),n为元素个数

n:     2d^(h-1)-1 ~ m^h-1   (外部节点的数量比元素的个数多1)?

h:     logm(n+1)~logd((n+1)/2) +1

插入操作

1.插入节点元素数<m-1:直接插入

2.插入节点的元素数=m-1:该结点分成两半,将中间的关键字进行提升,加入到父亲结点中,不断向上回溯直到符合要求。

删除操作

1.删除叶节点

2.删除非叶节点:类似AVL,与叶节点交换

3.

元素数目>ceil(m/2)-1:直接删除

元素数目=ceil(m/2)-1:

        1)借用兄弟节点多余元素:(右)兄弟节点元素数>d-1,则将其最右(左)元素提升至父节点,父节点相应元素下降到删除节点     (6次磁盘操作)

        2)若兄弟节点无则合并:删除后本节点元素(d-2个)+兄弟结点元素(d-1个)+父节点中介于两者之间的元素(1个)合并,为2d-2(<=m-1 ?why)个元素的新结点   (5次磁盘操作),父节点元素数减少,可能<d-1,再继续重复上述两种方法。

B+

所有关键字都在叶节点中,按从小到大链接,上层非叶节点的关键字是子树中最大关键字的复写;

B+树插入

查找合适位置插入,若合法,结束;

非法:将该叶节点均匀分裂,更新父节点;递归检查是否合法直到结束。

B+树删除

首先在叶节点删除,满足最小元素要求,停止;

不满足:从兄弟结点借元素;借不了->合并兄弟节点;递归考察父节点

五、红黑树

参考:【精选】【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树_小七mod的博客-CSDN博客

特殊的二叉搜索树,对扩充(外部节点)的二叉搜索树满足:

1.根节点和所有外部节点的颜色是黑的

2.根至外部节点的路径上没有连续红节点

3.所有根至外部节点的路径具有相同数目的黑节点


另一种边的定义方式:

1.从内部节点指向外部节点的指针是黑的

2.根至外部节点的路径上没有连续红指针

3.所有根至外部节点的路径具有相同数目的黑指针

黑指针指向的孩子节点是黑的;红指针指向的孩子节点是红的

节点的阶:从该节点到其子树中任一外部节点的路径上的黑色指针数目

特性:

1.设从根到外部节点的路径的长度(length)是该路径中指针的数量,若PQ是红黑树中两条从根至外部节点的路径,那么length(P) 2length(Q)(根的阶为r,红指针数目最多为r,指针总数在r~2r之间)

2.设h是一棵红-黑树的高度(不包括外部节点),n是树中内部节点的数目,而r是根节点的阶,则有:h<=2r(由1.);n>=2^r-1(树高至少为r);h<=2log2(n+1) (结合前两个)

描述:

外部节点无需保存;每个节点保存颜色和两个指针的颜色

搜索:

与二叉搜索树相同,复杂度O(logn)

红黑树类似4B树:

ppt:

2节点(一个关键字,两个孩子)用二叉树结构表示;3节点、4节点进行转换:

红边表示的是2-3-4树的同节点关系;黑边表示父子关系

将所有的红色节点上移到和他们的父节点同一高度上,就会变为4阶B树的形态。可知:

  1. 黑色节点与它的红色子节点融合在一起,形成1个B树节点
  2. 红黑树的黑色节点个数 与 4阶B树的节点总个数相等
  3. 在所有的B树节点中,永远是黑色节点是父节点,红色节点是子节点。

红黑树的插入 :

1.二叉搜索树的插入

2.着色:新节点着红色(若插入黑色,该节点所在路径比其他路径多出一个黑色节点,调整较麻烦;着红色仅可能会出现两个连续的红色节点的情况,较好调整)

3.调整:

1)没有连续红边:无需处理,直接插入

2)LL插入:父节点为祖父节点的左节点,插入节点为父节点的左节点

        以祖父右旋;父亲变为黑色,祖父变为红色

3)RR插入:父节点为祖父节点的右节点,插入节点为父节点的右节点

        以祖父左旋;父亲变为黑色,祖父变为红色

4)RL插入:同AVL;父亲右旋,祖父左旋;插入节点变为黑色,父亲变为红色

5) LR插入:同AVL;父亲左旋,祖父右旋;插入节点变为黑色,父亲变为红色

6) 上溢情况:祖父的另一个孩子是红节点uncle(即转化为4阶B树形式后父节点有3个关键字)

parent、uncle染为黑色;grand染为红色,向上一层合并(若上溢继续递归,若上溢到根节点,将根节点染成黑色)

红黑树的删除 :

删除红色节点:直接删除

删除黑色节点:

1.拥有 2 个红色子节点的黑色节点:不可能被直接删除,因为会找它的子节点替代删除,因此不用考虑这种情况

2.拥有 1 个红色子节点的黑色节点:                                                                                          1)      1)用删除节点的唯一子节点对其进行替代;     

    2)将替代节点染成黑色

3.黑色叶子节点

删除节点为根节点:直接删除;

删除黑色叶子节点(删除节点的兄弟节点为黑色)

1)兄弟节点至少有一个红色子节点:(参考B删除)

删除黑色叶子节点->父亲下来->旋转(使符合左小右大规律)->旋转后,中心节点继承父节点颜色,左右节点为黑色

2)兄弟节点没有红色子节点:

父节点为红色:父节点向下与兄弟节点合并->将兄弟染成红色、父节点染成黑色;

父节点为黑色:处理后,将父节点当成删除节点,再处理

删除黑色叶子节点(删除节点的兄弟节点为红色)

转化为兄弟节点为黑色,将其染为黑色,父节点染为红色

根据兄弟节点的相对位置判断LL或RR,进行旋转之后继续用“兄弟节点为黑色”的办法

      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值