数据结构之树(一)

文章详细介绍了树的基本概念,包括节点的度、高度、深度等,并重点讲解了二叉树的特性,如二叉树的层序遍历、求高度的方法。此外,还讨论了如何判断完全二叉树,以及二叉树的反转。最后,文章提到了二叉搜索树的复杂度分析和平衡二叉搜索树,如AVL树和红黑树。
摘要由CSDN通过智能技术生成

1.树的概念

1.1基本概念

节点的度:子树的个数

树的度:所有节点度中的最大值

叶子节点:度为0的节点

非叶子节点:度不为0的节点

层数:根节点在第一层

节点的深度:从根节点到当前节点的唯一路径上的节点总数

节点的高度:从当前节点到最远叶子节点的路径上的节点总数

有序树:树中任意节点的子节点之间有顺序关系

1.2 二叉树的特点

二叉树的特点:

每个节点的度最大为2
左子树和右子树是有顺序的
二叉树是有序树还是无序树?有序树

非空二叉树的性质:
​​​​​​二叉排序树的性质
满二叉树完全二叉树
完全二叉树的性质
完全二叉树的性质
n0=floor((n+1)/2);
在这里插入图片描述
在这里插入图片描述
二叉搜索树

2.树的遍历

2.1层次遍历

层序遍历
1.将根节点入队
2.循环执行以下操作,直到队列为空
a.将队头节点A出队,访问
b.将A的左子节点入队
c.将A的右子节点入队

 	/*
层序遍历
1.将根节点入队
2.循环执行以下操作,直到队列为空
	a.将队头节点A出队,访问
	b.将A的左子节点入队
	c.将A的右子节点入队
*/
void levelOrderTravelsal(Node<T>* node)
{
	//先将头节点入队
	std::queue<T> queue;
	queue.push(node);
	//循环操作,直到队列为空
	while (!queue.empty()) {
		Node<T>* head = queue.front();
		std::cout << head->element;
		queue.pop();
		if (head->m_left != nullptr)
			queue.push(head->m_left);
		if (head->m_right != nullptr)
			queue.push(head->m_right);
	}
}

2.2求二叉树的高度

2.2.1利用递归

int heightByRecursion(Node<T>* node)
	{
		if (node == nullptr) return 0;
		return 1 + std::max(height(node->m_left), height(node->m_right));
	}

2.2.2迭代版本 每一层访问完后,下一层的长度就是队列的长度

int heightByIterator(Node<T>* node)
	{
		if (node == nullptr) return 0;
		int iHeight = 0;
		int iLaySize = 1;// 每一层的元素数量  初始化第一层的长度为1

		//先将头节点入队
		std::queue<T> q;
		q.push(node);
		//循环操作,直到队列为空
		while (!q.empty()) {
			Node<T>* head = q.front();
			std::cout << head->element;
			q.pop();
			iLaySize--;

			if (head->m_left != nullptr)
				q.push(head->m_left);
			if (head->m_right != nullptr)
				q.push(head->m_right);
			if (0 == iLaySize)//意味着即将要访问下一层
			{
				iLaySize = q.size();
				iHeight++;
			}
		}
		return iHeight;
	}

2.3如何判断一棵树是否为完全二叉树

在这里插入图片描述

bool isCompleteBT()
	{
		std::queue<Node<T>*> q;
		q.push(m_root);
		bool bLeaf = false;
		while (!q.empty()) {
			Node<T>* head = q.front();
			q.pop();
			if (bLeaf && head->isLeaf())
			{
				return false;
			}
			/*************************/
			if (head->m_left != nullptr && head->m_right != nullptr)
			{
				q.push(head->m_left);
				q.push(head->m_right);
			}
			else if (head->m_left == nullptr && head->m_right != nullptr)
			{
				return false;
			}
			else if 
				(
					(head->m_left != nullptr && head->m_right == nullptr) ||
					(head->m_left == nullptr && head->m_right == nullptr)
				)//后面遍历的节点必须是叶子节点
			{
				if(head->m_left != nullptr) q.push(head->m_left);
				bLeaf = true;
			}
			/*************************/
		}
		return true;
	}

2.4反转二叉树(所有左右子节点交换)

	//利用前序遍历访问每一个节点  递归方式 中序方式有问题
	Node<T>* inverTree(Node<T>* root)
	{
		if (root == nullptr) return root;

		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;

		inverTree(root->m_left);
		inverTree(root->m_right);
	}

	//利用中序遍历访问每一个节点  递归方式 
	Node<T>* inverTreePreOrder(Node<T>* root)
	{
		if (root == nullptr) return root;
		inverTree(root->m_left);

		Node<T>* node = root->m_left;
		root->m_left = root->m_right;
		root->m_right = node;

		inverTree(root->m_left);//这里仍然要传左子节点,因为左右已经交换
	}

	//利用迭代方式 层序遍历 反转二叉树
	bool inverTreeLayOrder(Node<T>* root)
	{
		Node<T>* head = root;
		std::queue<T> q;
		q.emplace(head);
		while (!q.empty())
		{
			head = q.front();
			q.pop();
			Node<T>* tempNode = head->m_left;
			head->m_left = head->m_right;
			head->m_right = tempNode;
			if (head->m_left != nullptr)
				q.emplace(head->m_left);
			if (head->m_right != nullptr)
				q.emplace(head->m_right);

		}
	}

2.4根据遍历结果服用唯一的一棵二叉树

1.前序遍历+中序遍历

2.后序遍历+中序遍历

前序遍历+后序遍历,如果它是一棵真二叉树,结果是唯一的。不然,结果不唯一。

原因:如果左右子树有一个为空,通过前序遍历和后序遍历不能确定是左子树为空还是右子树为空。如果是一棵真二叉树,则左右子树要么都存在,要么为空。

2.5前驱节点

前驱节点指 中序遍历时的前一个结点
中序遍历路径
前驱节点

	/*查找前驱结点*/
	Node<T>* preDecessor(Node<T>* node)
	{
		if (node == nullptr) return nullptr;
		Node<T>* tempNode = nullptr;
		
		if (node->m_left != nullptr)
		{
			Node<T>* p = node->m_left;
			while (p != nullptr)
			{
				p = p->m_right;
			}
			return p;
		}
		//从父节点 祖父结点中寻找前驱结点
		while(node->m_parent != nullptr && node == node->m_parent->m_left)
			//父节点不为空并且该节点是父节点的左子树
		{
			node = node->m_parent;
		}
		//
		return node->m_parent;
	}

对于二叉排序树,前驱结点是该结点的左子树的最大结点。

2.6后驱节点

后继结点:中序遍历时的后一个结点,如果是二叉搜索树,后继结点就是后一个比它大的结点

2.7删除节点

  • a.如果是叶子结点,直接删除

  • b.度为1的结点,用子结点替代原结点的位置

     			   1.如果node是左子结点
     			               child.parent = node.parent;  
     			               node->parent->left = child;
     			   2.如果node是右子节点
     			               child.parent = node.parent;
     			               node->parent->right = child;
     			  3.如果node是root
     			               root = child;child.parent=nullptr;
    
  • c.度为2的结点
    找前驱或者后继结点覆盖该节点,删除前驱或者后继结点。
    注意:如果一个结点的度为2,那么它的前驱,后继节点的度只能是1和0
    (代码有问题,需要改进)

    	void remove(Node<T>* node)
{
	if (node == nullptr) return;
	--m_size;
	//度为2的情况
	if (node->hasTwoChilden())
	{
		//找到后驱节点
		Node<T>* successorNode = successorNode(node);
		//用后继节点的值覆盖度为2的节点的值
		node->element = successorNode->element;
		//删除后继节点
		node = successorNode;
	}
	//删除node节点  (代码走到这里度必然为1或者为0)
	Node<T>* replacement = node->m_left ? node->m_left : node->m_right;
	
	if (replacement != nullptr)//node的度为1
	{
		//更改parent
		replacement->m_parent = node->m_parent;
		if (node->m_parent == nullptr)//node是度为1的节点并且是根节点
		{
			node = replacement;
		}
		else if (node == node->m_parent->m_left)//要删除的节点是父节点的左节点
		{
			node->m_parent->m_left = replacement;
		}
		else if (node == node->m_parent->m_right)//要删除的节点是父节点的右节点
		{
			node->m_parent->m_right = replacement;
		}
		delete node; node = nullptr;
	}
	else if (replacement == nullptr)//node是叶子节点
	{
		if (node->m_parent == nullptr)//node是根节点
		{
			delete node;
			node = nullptr;
		}
		else {//node是叶子节点但不是根节点
			if (node == node->m_parent->m_right)//是父节点的右节点
			{
				node->m_parent->m_right = nullptr;
				delete node; node = nullptr;
			}else//node是父节点的左节点
			{
				node->m_parent->m_left = nullptr;
				delete node; node = nullptr;
			}
		}
	}
}
/*根据一个元素查找对应的节点*/
Node<T>* node(T element)
{
	Node<T>* node = m_root;
	while (node != nullptr)
	{
		int cmp = compare(element, node->element);
		if (cmp == 0)//相等
		{
			return node;
		}
		else if (cmp > 0)//element比node->element大,去右边找
		{
			node = node->m_right;
		}
		else if (cmp < 0)//element比node->element小,去左边找
		{
			node = node->m_left;
		}
	}
	//while循环退出,未找到节点,返回空
	return nullptr;
}

3.二叉搜索树的复杂度分析

添加查找删除的复杂度 O(h) = O(logn)
如果元素从下到大添加元素,二叉树就是一个链表,时间复杂度为O(n)

最好情况:O(logn)

当二叉搜索树完全平衡时,最好情况下每次均能把当前搜索范围减半,所以时间复杂度为O(logn)。

最坏情况:O(n)

当二叉搜索树极度不平衡时,最坏情况下退化成链表,每次搜索都要遍历所有节点,时间复杂度为O(n)。

平均情况:O(logn)

在添加、删除节点时,为了保持二叉搜索树的平衡性,通常需要进行旋转等操作,而这些操作可能会导致树的平衡变得不那么完美,但经验结果表明,这种情况下的平均复杂度依然为O(logn)。

4.平衡二叉搜索树BBST Balanced Binary Search Tree

常用的平衡二叉树
AVL树
AVL树的特点:

  • 平衡因子:某节点的左右子树的高度差
  • 每个节点的平衡因子只可能是1 0 -1,超过称之为失衡
  • 每个节点的左右子树高度差不超过1
  • 搜索,添加,删除的时间复杂度O(log(n))

红黑树(C++中STL的map,set)
红黑树必须满足的性质

  1. 红色节点的parent都是黑色
  2. 节点必须是红色或者黑色
  3. 根节点必须是黑色
  4. 叶子节点都是黑色
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值