基于树的数组 Array Based on Tree

        基于树的数组(Array Based on Tree,简称ABT),是一种基本数据结构,它是基于平衡二叉树的数组,并根据其左右子树的节点数来维持平衡。不同于二叉搜索树(Binary Search Tree),ABT维持的是元素的插入顺序而非节点的键值顺序。ABT不仅具备随机访问能力,而且具备动态编辑能力,其随机访问、插入和删除操作的时间复杂度均为O(log n) 。

适用场景

        数组(Array)和链表(List)是两种常见的数据结构。数组的随机访问时间复杂度为O(1),而链表的插入和删除时间复杂度为O(1)。然而,有时我们需要一种数据结构,能够结合数组和链表的优点。因此,基于树的数组(Array Based on Tree)的概念应运而生。ABT很好地平衡了随机访问、插入和删除操作的性能,适用于同时需要随机访问与动态编辑的应用场景。

特性

        ABT是一种基于非排序二叉树的数据结构,具有以下特性:

        (1) 左子树节点数不小于其两个侄子所在子树的节点数。

        (2) 右子树节点数不小于其两个侄子所在子树的节点数。

        (3) 左右子树均为ABT。

        考虑以下示例,其中T是ABT的节点,L和R是其子节点,A、B、C和D是满足上述ABT特性的子树。

         根据ABT的特性,节点T必须满足:

        (1) size(L) >= max(size(C), size(D))

        (2) size(R) >= max(size(A), size(B))

节点

        ABT的节点包括一个父节点、两个子节点以及该节点所在子树中的节点数量。

         节点定义的C++代码如下:

// Class template abt_node
template <class T>
struct abt_node
{
	using node_type            = abt_node<T>;
	using node_pointer         = node_type*;
	using const_node_pointer   = const node_type*;
	using node_reference       = node_type&;
	using const_node_reference = const node_type&;

	node_pointer               parent;
	node_pointer               left;
	node_pointer               right;
	size_t                     size;
	T                          data;
};

旋转操作

        如同其他自平衡二叉树,当插入或删除节点导致大小平衡树失去平衡时,需要通过旋转操作来恢复平衡。

        常见的旋转操作包括左旋转和右旋转,这可以通过交换节点和子树的位置来实现。下面介绍左右旋转的操作过程。

左旋转

        左旋转用于使节点T的右子节点R成为其父节点,并使其右子节点R的左子节点A成为其右子节点。此操作使原节点T成为其右子节点R的左子节点,从而维持二叉树的平衡。

         左旋转的C++代码如下:

node_pointer rotate_left(node_pointer t)
{
	node_pointer r = t->right;
	t->right = r->left;
	if (r->left)
		r->left->parent = t;
	r->parent = t->parent;
	if (t == _head->parent)
		_head->parent = r;
	else if (t == t->parent->left)
		t->parent->left = r;
	else
		t->parent->right = r;
	r->left = t;
	r->size = t->size;
	t->parent = r;
	t->size = (t->left ? t->left->size : 0) + (t->right ? t->right->size : 0) + 1;
	return r;
}

右旋转

        右旋转与左旋转类似,但方向相反。右旋转用于使节点T的左子节点L成为其父节点,并使其左子节点L的右子节点B成为其左子节点。此操作使原节点T成为其左子节点L的右子节点,从而维持二叉树的平衡。

        右旋转的C++代码如下:

node_pointer rotate_right(node_pointer t)
{
	node_pointer l = t->left;
	t->left = l->right;
	if (l->right)
		l->right->parent = t;
	l->parent = t->parent;
	if (t == _head->parent)
		_head->parent = l;
	else if (t == t->parent->right)
		t->parent->right = l;
	else
		t->parent->left = l;
	l->right = t;
	l->size = t->size;
	t->parent = l;
	t->size = (t->left ? t->left->size : 0) + (t->right ? t->right->size : 0) + 1;
	return l;
}

重新平衡

        当对ABT进行插入或者删除操作后,可能违反ABT的特性,需要对以T为根的ABT进行重新平衡。再平衡的前提是T的子树均满足ABT的特性。重新平衡需要考虑以下4种情况:

        (1) size(T.left)<size(T.right.left)

        可能发生在节点T的右子树插入节点或者左子树删除节点后。先对T的右子节点R进行右旋转,再对T进行左旋转。此时,子树A、B、D、E、F 和 L 仍然满足 ABT的特性;而左子树T和右子树R可能违反ABT的特性。对于左子树T,由于其右子树的节点减少,可能出现size(T.right)<size(T.left.child),需要重新平衡。对于右子树R,由于其左子树的节点减少,可能出现size(R.left)<size(R.right.child),需要重新平衡。最后,对节点C及其祖先节点逐一进行重新平衡,直到根节点为止。

        (2) size(T.left) < size(T.right.right)

        可能发生在节点T的右子树插入节点或者左子树删除节点后。对节点T进行左旋转后,子树A、B、C、D、E、F、L仍然满足ABT的特性;而左子树T的右子树的节点减少,可能出现size(T.right)<size(T.left.child),需要重新平衡。最后,对节点R及其祖先节点逐一进行重新平衡,直到根节点为止。

        (3) size(T.right) < size(T.left.right)

        可能发生在节点T的左子树插入节点或者右子树删除节点后。先对T的左子节点进行左旋转,再对T进行右旋转。此时,子树A、C、D、E、F、R仍然满足ABT的性质;而左子树L和右子树T可能违反ABT的特性。对于左子树L,由于其右子树的节点减少,可能出现size(L.right)<size(L.left.child),需要重新平衡。对于右子树T,由于其左子树的节点减少,可能出现size(T.left)<size(T.right.child),需要重新平衡。最后,对节点B及其祖先节点逐一进行重新平衡,直到根节点为止。

        (4) size(T.right)<size(T.left.left)

        可能发生在节点T的左子树插入节点或者右子树删除节点后。对节点T进行右旋转后,子树A、B、C、D、E、F、R仍然满足SBT的性质;而右子树T的左子树的节点减少,可能出现size(T.left)<size(T.right.child),需要重新平衡。最后,对节点L及其祖先节点逐一进行重新平衡,直到根节点为止。

        向节点T的子节点插入节点后进行重新平衡的C++代码如下:

node_pointer insert_rebalance(node_pointer t, bool flag)
{
	if (flag)
	{
		if (t->right)
		{
			size_type left_size = t->left ? t->left->size : 0;
			// case 1: size(T.left) < size(T.right.left)
			if (t->right->left && left_size < t->right->left->size)
			{
				t->right = rotate_right(t->right);
				t = rotate_left(t);
				t->left = insert_rebalance(t->left, false);
				t->right = insert_rebalance(t->right, true);
				t = insert_rebalance(t, true);
			}
			// case 2. size(T.left) < size(T.right.right)
			else if (t->right->right && left_size < t->right->right->size)
			{
				t = rotate_left(t);
				t->left = insert_rebalance(t->left, false);
				t = insert_rebalance(t, true);
			}
		}
	}
	else
	{
		if (t->left)
		{
			size_type right_size = t->right ? t->right->size : 0;
			// case 3. size(T.right) < size(T.left.right)
			if (t->left->right && right_size < t->left->right->size)
			{
				t->left = rotate_left(t->left);
				t = rotate_right(t);
				t->left = insert_rebalance(t->left, false);
				t->right = insert_rebalance(t->right, true);
				t = insert_rebalance(t, false);
			}
			// case 4. size(T.right) < size(T.left.left)
			else if (t->left->left && right_size < t->left->left->size)
			{
				t = rotate_right(t);
				t->right = insert_rebalance(t->right, true);
				t = insert_rebalance(t, false);
			}
		}
	}
	return t;
}

        从节点T的子节点删除节点后进行重新平衡的C++代码如下:

node_pointer erase_rebalance(node_pointer t, bool flag)
{
	if (!flag)
	{
		if (t->right)
		{
			size_type left_size = t->left ? t->left->size : 0;
			// case 1: size(T.left) < size(T.right.left)
			if (t->right->left && left_size < t->right->left->size)
			{
				t->right = rotate_right(t->right);
				t = rotate_left(t);
				t->left = erase_rebalance(t->left, true);
				t->right = erase_rebalance(t->right, false);
				t = erase_rebalance(t, false);
			}
			// case 2. size(T.left) < size(T.right.right)
			else if (t->right->right && left_size < t->right->right->size)
			{
				t = rotate_left(t);
				t->left = erase_rebalance(t->left, true);
				t = erase_rebalance(t, false);
			}
		}
	}
	else
	{
		if (t->left)
		{
			size_type right_size = t->right ? t->right->size : 0;
			// case 3. size(T.right) < size(T.left.right)
			if (t->left->right && right_size < t->left->right->size)
			{
				t->left = rotate_left(t->left);
				t = rotate_right(t);
				t->left = erase_rebalance(t->left, true);
				t->right = erase_rebalance(t->right, false);
				t = erase_rebalance(t, true);
			}
			// case 4. size(T.right) < size(T.left.left)
			else if (t->left->left && right_size < t->left->left->size)
			{
				t = rotate_right(t);
				t->right = erase_rebalance(t->right, false);
				t = erase_rebalance(t, true);
			}
		}
	}
	return t;
}

插入操作

        如果ABT为空,则直接添加该节点作为根节点。否则,根据插入节点T的子节点的情况,可以分为以下两种情况:

        (1) 节点T没有左子节点

        在这种情况下直接插入到节点T的左子树中。其祖先节点的节点数应全部加1。可能出现上述第(3)种情况或者第(4)种情况,因此,节点T需要重新平衡。

        (2) 节点T已有左子节点

       在这种情况下选择其左子树中的最后一个节点作为实际插入节点X。插入到节点X的右子树中,其祖先节点的节点数应全部加1。可能会出现上述第(1)种情况或者第(2)种情况,因此,节点X需要重新平衡。

        插入操作的C++代码如下:

template <class ...Args>
node_pointer insert_node(node_pointer t, Args&&... args)
{
	// creates a new node
	node_pointer n = this->create_node(std::forward<Args>(args)...);
	n->left = nullptr;
	n->right = nullptr;
	n->size = 1;
	if (t == _head)
	{
		// if the tree is not empty
		if (_head->parent)
		{
			t = _head->right;
			// inserts the node
			n->parent = t;
			t->right = n;
			_head->right = n;
			// increases the size of nodes
			for (node_pointer p = t; p != _head; p = p->parent)
				++p->size;
			do
			{
				// rebalance after insertion
				t = insert_rebalance(t->parent, t == t->parent->right);
			} while (t->parent != _head);
		}
		else
		{
			// inserts the node
			n->parent = t;
			_head->parent = n;
			_head->left = n;
			_head->right = n;
		}
	}
	else if (t->left)
	{
		t = t->left;
		while (t->right)
			t = t->right;
		// inserts the node
		n->parent = t;
		t->right = n;
		// increases the size of nodes
		for (node_pointer p = t; p != _head; p = p->parent)
			++p->size;
		do
		{
			// rebalance after insertion
			t = insert_rebalance(t->parent, t == t->parent->right);
		} while (t->parent != _head);
	}
	else
	{
		// inserts the node
		n->parent = t;
		t->left = n;
		if (t == _head->left)
			_head->left = n;
		// increases the size of nodes
		for (node_pointer p = t; p != _head; p = p->parent)
			++p->size;
		do
		{
			// rebalance after insertion
			t = insert_rebalance(t->parent, t == t->parent->right);
		} while (t->parent != _head);
	}
	return n;
}

删除操作

        假设要删除的节点是T,根据节点T的子节点数量,可以分为以下两种情况:

        (1) 节点T最多有一个子节点

        在这种情况下,可以直接删除节点T。其祖先节点的节点数减少1。如果节点T具有子节点L或R,则用其子节点替换节点T。最后,重新平衡节点T的父节点。

        (2) 节点T有两个子节点

        在这种情况下,不能直接删除节点T,否则整个树将被破坏。当节点T的左子树L的节点数小于右子树R的节点数时,选择其右子树R中值最小的节点作为实际删除节点X;否则,选择其左子树L中值最大的节点作为实际删除节点X。然后交换节点T和节点X的位置,此时,与第(1)种情况完全相同。

         删除操作的C++代码如下:

void erase_node(node_pointer t)
{
	bool flag;
	node_pointer x;
	node_pointer parent;
	// if node t has two child nodes
	if (t->left && t->right)
	{
		if (t->left->size < t->right->size)
		{
			x = leftmost(t->right);
			// the rebalance flag
			flag = (x == x->parent->right);
			// reduces the size of nodes
			for (node_pointer p = x->parent; p != _head; p = p->parent)
				--p->size;
			// replaces node t with node x and removes node t
			t->left->parent = x;
			x->left = t->left;
			if (x != t->right)
			{
				x->parent->left = x->right;
				if (x->right)
					x->right->parent = x->parent;
				t->right->parent = x;
				x->right = t->right;
				parent = x->parent;
			}
			else
				parent = x;
			if (t == _head->parent)
				_head->parent = x;
			else if (t == t->parent->left)
				t->parent->left = x;
			else
				t->parent->right = x;
			x->parent = t->parent;
			x->size = t->size;
		}
		else
		{
			x = rightmost(t->left);
			// the rebalance flag
			flag = (x == x->parent->right);
			// reduces the size of nodes
			for (node_pointer p = x->parent; p != _head; p = p->parent)
				--p->size;
			// replaces node t with node x and removes node t
			t->right->parent = x;
			x->right = t->right;
			if (x != t->left)
			{
				x->parent->right = x->left;
				if (x->left)
					x->left->parent = x->parent;
				t->left->parent = x;
				x->left = t->left;
				parent = x->parent;
			}
			else
				parent = x;
			if (t == _head->parent)
				_head->parent = x;
			else if (t == t->parent->left)
				t->parent->left = x;
			else
				t->parent->right = x;
			x->parent = t->parent;
			x->size = t->size;
		}
		// rebalance after deletion
		node_pointer p = erase_rebalance(parent, flag);
		while (p != _head)
			p = erase_rebalance(p->parent, p == p->parent->right);
	}
	// if node t has one child node at most
	else
	{
		x = t->left ? t->left : t->right;
		// the rebalance flag
		flag = (t == t->parent->right);
		// removes node t
		if (x)
			x->parent = t->parent;
		if (t == _head->parent)
			_head->parent = x;
		else if (t == t->parent->left)
			t->parent->left = x;
		else
			t->parent->right = x;
		if (t == _head->left)
			_head->left = x ? leftmost(x) : t->parent;
		if (t == _head->right)
			_head->right = x ? rightmost(x) : t->parent;
		// reduces the size of nodes
		for (node_pointer p = t->parent; p != _head; p = p->parent)
			--p->size;
		if (t != _head)
		{
			// rebalance after deletion
			node_pointer p = erase_rebalance(t->parent, flag);
			while (p != _head)
				p = erase_rebalance(p->parent, p == p->parent->right);
		}
	}
	// destroy node
	this->destroy_node(t);
}

选择操作

        选择操作为ABT提供随机访问功能。

        选择操作的C++代码如下:

node_pointer select_node(size_type k)
{
	node_pointer t = header->parent;
	while (t)
	{
		size_type left_size = t->left ? t->left->size : 0;
		if (left_size < k)
		{
			t = t->right;
			k -= (left_size + 1);
		}
		else if (k < left_size)
			t = t->left;
		else
			return t;
	}
	return header;
}

代码下载

ab_tree 源代码-优快云博客icon-default.png?t=O83Ahttps://blog.youkuaiyun.com/CQRuler/article/details/144518592

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值