数据结构-复习(二):树~二叉搜索树

本文深入介绍了二叉树的概念、性质及遍历方法,并详细讲解了二叉搜索树的查找、插入与删除操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

1 树的基本概念

1.1 定义和基本术语:

1.2 树的基本性质:

1.3 树的逻辑表示方式:

2 二叉树

2.1 二叉树的定义和相关概念:

2.2 几种特殊的二叉树:

2.3 二叉树的性质:

2.4 二叉树的存储结构:

2.5 二叉树的抽象数据结构

2.6 树的遍历

2.7 线索二叉树

2.8 二叉搜索树

 


1 树的基本概念

1.1 定义和基本术语:

  • 树的定义:树是由n个结点组成的有限集合。其中:

                  1)当n=0时,它是一个空结构,或者空树。

                  2)当n>0时,所有结点中存在且仅存在一个根结点。其余结点可以分为m个互不相交的子集合。每个子集合都是一颗满足定义的树,并成为根的子树。

结点:树形结构中的每个元素称为一个节点,结点包含该元素的值和该元素的逻辑关系。

边:用<m,n>表示从结点m指向结点n的连线,代表结点m与结点n之间存在某种联系。

结点的度:该结点所拥有的孩子节点的数量就是该节点的度。

树的度:该树形结构中的所有结点的度的最大值。

结点的层数:根结点的层数为0,其它结点的层数等于其父节点的层数+1.

树的深度:树中所有结点的层数的最大值。

树的高度:树的深度+1;

1.2 树的基本性质:

  • 性质1:树的结点数等于其所有结点的度数加1;
  • 性质2:度为m的树,其第i层上至多有 m^i 个结点。
  • 性质3:高度为h度为m的树至多有 (m^h - 1)/(m-1)个结点。
  • 性质4:具有n个节点的度为m的树,其最小高度为 \left \lceil log_m(n(m-1)+1) \right \rceil

1.3 树的逻辑表示方式:

  • 树形表示法:将关系集合抽象成一颗倒置的树。
  • 文氏图表示法:
  • 凹入表表示法:类似于书的章节目录。
  • 嵌套括号表示法:

2 二叉树

2.1 二叉树的定义和相关概念:

定义:二叉树是树形结构中的一种特殊形式。其定义是:所有结点的度小于等于2的树为二叉树。

二叉树有5个基本的形态:空二叉树,只有根结点的二叉树,右子树为空的,左子树为空的,左右子树都非空的。

二叉树的子树有左右之分,次序不可颠倒,因此二叉树是有序树。

2.2 几种特殊的二叉树:

  • 完全二叉树:一颗高度为h的二叉树,除最后一层以外的其他所有层上的结点数都达到最大值,而最后一层上的所有结点分布在该层最左边的连续位置上。
  • 满二叉树:每一层的结点数都达到最大值。所有分支结点的度都为2,叶子结点都出现在最后一层上。高度为h的满二叉树有2^h-1个结点。
  • 扩充二叉树:把二叉树所有结点上出现空子树的位置上增加一个特殊的结点-空树叶。

2.3 二叉树的性质:

  • 性质1:任何一个二叉树,度数为0的结点比度数为2的结点多一个。(由n=x0+x1+x2;  n=0*x0+1*x1+2*x2+1;可得x0 = x2+1;)
  • 性质2:二叉树的第i(i>=0)层上至多有2^i个节点。
  • 性质3:高度为h的二叉树至多有2^h-1个结点。
  • 性质4:非空满二叉树的叶子节点的数量等于其分支结点的数量加1。
  • 性质5:有n个结点的完全二叉树的高度为\left \lceil log_2(n+1) \right \rceil .
  • 性质6: 如果对一棵有n个结点的完全二叉树的结点按层编号(从跟往下,从左往右),如果从0开始标号,那么i号结点的编号为(i-1)/2,左孩子的编号为2i+1,右孩子的编号为2i+2.

2.4 二叉树的存储结构:

  • 顺序存储结构:一般用来存储完全二叉树。
  • 链式存储结构:二叉链表(data,leftChild,rightChild);三叉链表(data,parent,leftChild,rightChild).

2.5 二叉树的抽象数据结构

template<class T>
class BinaryTreeNode{
    friend class BinaryTreeNode<T>;
private:
    T element;
    BinaryTreeNode<T> *leftChild;
    BinaryTreeNode<T> *rightChild;
public:
    //member function:constructor/get/set/IsLeaf
};
template<class T>
class BinaryTree
{
private:
    BinaryTree<T> * root;
public:
    //member function:constructor/get/set/IsEmpty
    
    void breadthFirstOrder(BinaryTreeNode<T>* root); //广度优先遍历

    void preOrder(BinaryTreeNode<T>* root);//先序遍历
    void inOrder(BinaryTreeNode<T>* root);//中序遍历
    void postOrder(BinaryTreeNode<T>* root);//后序遍历

};

2.6 树的遍历

  • 广度优先遍历(层次遍历):从第0层开始向下逐层访问结点,每一层从左向右逐个访问结点。

可以用队列来实现。对于非空树,先将根结点加入队列;每次访问队列中的头结点,并把它的左右孩子(如果有的话)加入队列,弹出已访问头结点,直到队列为空为止。

template<class T>
void BinaryTree<T>::breadthFirstOrder(BinaryTreeNode<T>* root)
{
    using std::queue;
    queue<BinaryTreeNode<T>*> NodeQueue;
    BinaryTreeNode<T>* pointer = root;
    if(pointer)
        NodeQueue.push(pointer);
    while(! NodeQueue.empty() )
    {
        pointer = NodeQueue.front();
        visit(pointer);
        if(pointer->leftChild)
            NodeQueue.push(pointer->leftChild);
        if(pointer->rightChild)
            NodeQueue.push(pointer->rightChild);
        NodeQueue.pop();
    }

};
  • 深度优先遍历:
  • 前序:访问根结点,先序遍历左子树,先序遍历右子树。     

主要思想:每遇到一个节点,先访问该节点,并把该节点的非空右子树的根结点压入的栈中,然后遍历其左子树,直到左子树为空,然后从栈顶弹出待访问的结点,继续遍历,直至栈为空为止。

  • 中序:中序遍历左子树,访问根结点,中序遍历右子树。     

主要思想:从根结点开始向左搜索,每遇到一个节点,就将其压入栈中,然后去遍历其左子树。遍历完左子树后,弹出栈顶结点并访问它,然后遍历其右子树。

  • 后序:后序遍历左子树,后序遍历右子树,访问根结点。     

主要思想:从根结点开始向左搜索,每遇到一个节点,就将其压入栈中,直到栈中的结点不在有左子树为止。读取栈顶的结点,如果该节点有右子树且未被访问,则访问其右子树,否则访问该节点并从栈中移除。

//前序遍历的非递归实现
template<class T>
void BinaryTree<T>::preOrder(BinaryTreeNode<T> *root)
{
    using std::stack;
    stack<BinaryTreeNode<T> * > nodeStack;
    BinaryTreeNode<T> * pointer =root;

    while(!nodeStack.empty() || pointer)
    {
        if(pointer)
        {
            visit(pointer);
            if(pointer->rightChild != NULL)
                nodeStack.push(pointer->rightChild);
            pointer = pointer->leftChild;
        }
        else
        {
            pointer = nodeStack.top();
            nodeStack.pop();
        }
    }

}


//中序遍历的非递归实现
template<class T>
void BinaryTree<T>::inOrder(BinaryTreeNode<T> *root)
{
    using std::stack;
    stack<BinaryTreeNode<T> *> nodeStack;
    BinaryTreeNode<T> * pointer = root;

    while(!nodeStack.empty() || pointer)
    {
        if(pointer)
        {              
                nodeStack.push(pointer);
                pointer = pointer->leftChild;           
        }
        else
        {
            pointer = nodeStack.top();

            visit(pointer);
            pointer = pointer->rightChild;
            nodeStack.pop();
        }
    }
}

//后序遍历的非递归实现
template<class T>
void BinaryTree<T>::postOrder(BinaryTreeNode<T> *root)
{
    using std::stack;
    stack<BinaryTreeNode<T> *> nodeStack;
    BinaryTreeNode<T> * pointer = root;
    BinaryTreeNode<T> * preNode= NULL;

    while(pointer || !nodeStack.empty() )
    {
        if(pointer)
        {
            nodeStack.push(pointer);
            pointer = pointer->leftChild;
        }
        else
        {
            pointer = nodeStack.top();
            if(pointer->rightChild != preNode && pointer->rightChild !=nullptr)
            {
                pointer = pointer->rightChild;
            }
            else
            {
                visit(pointer);
                preNode = pointer;
                nodeStack.pop();                
                pointer = NULL;
            }
        }
    }
}

2.7 线索二叉树

n个结点的二叉链表中含有n+1个空指针域,如果利用二叉链表中的空指针域来存放指向结点在某种遍历次序下的前驱和后继结点的指针,则又可以节省空间开销,又能提高时间效率。

为了区分指针所指向的是孩子结点还是前驱、后继结点,对每个结点增加两个标志域。

二叉链表结点的数据结构:(leftChild,leftTag,data,rightTag,rightChild)

leftTag=0,leftChild指向该节点的左孩子。leftTag=1,leftChild指向该节点的前驱结点。

rightTag=0,rightChild指向该节点的右孩子。rightTag=1,rightChild指向该节点的后继结点。

……(To be continued)

2.8 二叉搜索树

二叉搜索树又称为二叉排序树:该树的每个结点都有一个作为搜索依据的关键码,对于任意结点而言,其左子树(如果存在)上的所有结点的关键码均小于该结点的关键码,其右子树(如果存在)的所有结点的关键码均大于该结点的关键码。

  • 二叉搜索树的查找:

由于二叉搜索树的特殊定义,通常用分割式查找法:

1)若根结点的关键码的值等于待查找的键值,则查找成功。

2)否则,若键值小于根结点的关键码的值,则在其左子树中查找;若键值大于根结点的关键码的值,则在其右子树中查找。

//还是利用的前面二叉树的结构,记得在BinaryTree中添加查找函数的声明

//二叉搜索树的查找
template<class T>
BinaryTreeNode<T> * BinaryTree<T>::search(BinaryTreeNode<T> * root,const T & value)
{
	BinaryTreeNode<T> * current = root;
	while(current)
	{
		if(current->element == value)
			return current;
		current = value < current->element ? current->leftChild:current->rightChild;
	}
	return nullptr;
}
  • 二叉搜索树的插入:

插入关键是要找到合适的插入位置。所有,进行插入操作时,首先进行位置的查找。

方法是从根结点开始,将要插入的结点的关键码与根结点比较,如果小于根结点,则和左子树继续比较,如果大于根结点,则和右子树继续比较,直到到达叶子结点。比较要插入结点的关键码和该叶子结点的关键码,如果插入的关键码比较小,则把它置为该叶子结点的左孩子,否则置为右孩子;

//二叉搜索树的插入
template<class T>
void BinaryTree<T>::insert(const T & value)
{
	BinaryTreeNode<T> * current = root,* parent=nullptr;
	while(current !=NULL)
	{
		parent = current;
		if(value<current->element)
			current = current->leftChild;
		else 
			current = current->rightChild;
	}
	if(parent ==NULL)
		root = new BinaryTreeNode<T>(value);
	else if(value < parent->element)
		parent->leftChild = new BinaryTreeNode<T>(value);
	else
		parent->rightChild = new BinaryTreeNode<T>(value);

}
  • 二叉搜索树的删除:

在删除结点操作中,最重要的是如何在删除结点之后,仍然保持二叉搜索树的特征。结点的删除包括以下三种情况:

  1. 如果被删除的结点p没有子树,直接删除该结点;
  2. 如果被删除的结点p只有一颗子树,那么就用唯一子树的根结点代替要删除的那个结点p。
  3. 如果被删除的结点p有两颗子树,那么可以采取两种方法:

          1)合并删除:查找到被删除结点p的左子树按中序遍历的最后一个结点r,将结点r的右指针赋值为指向结点p的右子树的根,然后用结点p的左子树的根代替被删除的结点p,最后删除结点p。

          2)复制删除:选取一个合适的结点r,并将该节点的关键码复制给被删除结点p,然后将r结点删除。选取结点r的两种方法:结点p的左子树中关键码最大的结点或者结点p的右子树中关键码最小的结点。

//二叉搜索树的删除
template<class T>
void BinaryTree<T>::remove(const T &value)
{
	BinaryTreeNode<T> * current = root,*parent=NULL;
	while(current)
	{
		if(current->element == value)
		{
			if(parent==NULL)
				deleteByCopying(root);//deleteByMerging
			else if(parent->leftChild = current)
				deleteByCopying(parent->leftChild);//deleteByMerging
			else
				deleteByCopying(parent->rightChild);//deleteByMerging
			break;
		}
		parent = current;
		current = value < current->element ? current->leftChild:current->rightChild;
	}
}

template<class T>
void BinaryTree<T>::deleteByMerging(BinaryTreeNode<T> * &node)
{	
	if(node != NULL)
	{
		BinaryTreeNode<T> * tmp = node;
		if(node->rightChild == NULL)
			node=node->leftChild;
		else if(node->leftChild==NULL)
			node=node->rightChild;
		else
		{
			tmp = node->leftChild;
			while(tmp->rightChild != NULL)
				tmp = tmp->rightChild;
			tmp->rightChild = node->rightChild;
			tmp = node;
			node = node->leftChild;
		}
		delete tmp;		
	}
}

template<class T>
void BinaryTree<T>::deleteByCopying(BinaryTreeNode<T> * &node)
{	
	if(node!=NULL)
	{
		BinaryTreeNode<T> * tmp = node;
		if(node->rightChild==NULL)
			node = node->leftChild;
		else if(node->leftChild==NULL)
			node = node->rightChild;
		else
		{			 
			tmp = node->leftChild;
			BinaryTreeNode<T> * preTmp = tmp;
			while(tmp->rightChild)
			{	
				preTmp = tmp;
				tmp = tmp->rightChild;
			}
			node->element = tmp->element;
			if(preTmp != tmp)
				preTmp->rightChild = tmp->leftChild;
		}
		delete tmp;
	}
}

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值