Learning notes of data structure, algorithm and application - C + + language description - competition tree

Posted by excence on Sat, 25 Sep 2021 12:59:48 +0200

1, Winner tree

Suppose n players take part in a tennis match. The rule of the game is "sudden death": as long as a player loses a game, he will be eliminated. A pair of players play one-on-one, and finally only one player remains unbeaten. We use binary tree to describe the game process. Each external node represents a player, each internal node represents a game, and the children of the internal node represent the players of the game. The internal nodes on the same layer represent a round of competition, which can be carried out at the same time:

As shown in the figure, in the first round, the winners of b and c are b, and the winners of d and e are d; In the second round, the winner of a and b is b, and the winner of d and f is d;
In the last round, the winner of b and d is b.

In fact, if we use the complete binary tree, we can minimize the number of games( ⌈ log ⁡ 2 n ⌉ \lceil \log_2n \rceil ⌈log2​n⌉):

The competition tree listed above is called the winner tree because each internal node records the winners of the competition. In addition, there is a loser tree. Each internal node records the losers of the competition. The competition tree is also called the selection tree.

The definition of winner tree is as follows (to facilitate implementation, we limit the winner tree to a complete binary tree):
A winner tree with n players is a complete binary tree. It has n external nodes and n-1 internal nodes. Each internal node records the winners of the competition at that node.

In order to determine the winner tree of a game, we assume that each player has a score, and there is a rule to compare the scores of two players to determine the winner. In the smallest winner tree, the player with the largest score wins. When the scores are equal, that is, when there is a draw, the player represented by the left child wins.

One advantage of the winner tree is that when a player's score changes, it is easier to modify the competition tree. For example, when the score of the elected hand d is changed from 9 to 1, only the matches marked by the nodes on the path from d to the root may need to be replayed, while the results of other competitions will not be affected. Sometimes, even some competitions on this path do not need to be replayed. When the score is changed When the result does not affect the competition result of an internal node, all ancestor nodes of the node are not affected.

In a winner tree of n players, when a player's score changes, the number of matches to be modified is between 1~ ⌈ log ⁡ 2 n ⌉ \lceil \log_2n \rceil ⌈ log2 ⌉ n ⌉, therefore, the reconstruction of the winner tree takes time ⌈ log ⁡ 2 n ⌉ \lceil \log_2n \rceil ⌈ log2 ⌉ n ⌉ in addition, the winner tree of N players can be O ( n ) O(n) Initialization in O(n) time is performed at the internal node along the direction from leaf to root n − 1 n-1 n − 1 game.

2, Array description of binary tree (supplementary)

In order to implement the winner tree, it is obvious that the chained binary tree we implemented earlier cannot meet the requirements. Because for the winner tree, the data types of internal nodes and external nodes are not the same. Therefore, we choose to use array to implement the winner binary tree. For this purpose, we first need to use the array description to implement the binary tree.

1. Statement

#pragma once
#include "binaryTree.h"
#include <deque>
#include <algorithm>

template<typename T>
class ArrayBinaryTree : public binaryTree<T>
{
public:
	ArrayBinaryTree();
	virtual ~ArrayBinaryTree();
	ArrayBinaryTree(const ArrayBinaryTree& other);
	ArrayBinaryTree(ArrayBinaryTree&& other);
	ArrayBinaryTree& operator=(const ArrayBinaryTree& other);
	ArrayBinaryTree& operator=(ArrayBinaryTree&& other);

	virtual bool empty() const override;
	virtual int size() const override;
	virtual void preOrder(std::function<void(T)> visitFunc) override;
	virtual void inOrder(std::function<void(T)> visitFunc) override;
	virtual void postOrder(std::function<void(T)> visitFunc) override;
	virtual void levelOrder(std::function<void(T)> visitFunc) override;

	int height();
	void makeTree(T* elements, int treeSize);

protected:
	T* elements = nullptr;
	int treeSize = 0;
	std::function<void(T)> visitFunc;

	void makeCopyAndSwap(const ArrayBinaryTree& other);
	ArrayBinaryTree makeCopy(const ArrayBinaryTree& other);
	void swap(ArrayBinaryTree& other);
	void clear();

	void preOrder(int index);
	void inOrder(int index);
	void postOrder(int index);
	void levelOrder(int index);

	int height(int index);
};

2. Realize

Most logic is similar to chained binary trees. Take middle order traversal as an example (note that the array elements corresponding to the data in the tree start from 1):

template<typename T>
inline void ArrayBinaryTree<T>::inOrder(std::function<void(T)> visitFunc)
{
	this->visitFunc.swap(visitFunc);
	inOrder(1);
}

template<typename T>
inline void ArrayBinaryTree<T>::inOrder(int index)
{
	if (index >= treeSize + 1)
	{
		return;
	}

	inOrder(index * 2);
	visitFunc(elements[index]);
	inOrder(index * 2 + 1);
}

3, Winner tree

1. Abstract data type

#pragma once
#include <memory>

template<typename T>
class AbstractWinnerTree
{
public:
	virtual ~AbstractWinnerTree() {}

	virtual void initialize(T* player, int playerNum) = 0;

	virtual int winner() const = 0;

	virtual void replay(int playerIndex, T newValue) = 0;
};

2. Representation of winner tree

Suppose that the winner tree is represented by an array representation of a complete binary tree. A winner tree has n players (player[1:n]) and requires n-1 internal nodes (tree[1:n-1]).

The key point here is to find the mapping relationship between the external node and the associated internal node. First, we can divide the external node into two parts, the lower external node (i.e. 1-6) and the upper external node (i.e. 7).

For the bottom external node, it is not difficult to see that its number is linear with the parent node number, and the offset depends on the start number of the lowest internal node (i.e. 4). So how to calculate the start number? According to the number of internal nodes n-1, we can get the height of the tree h = ⌊ l o g 2 n − 1 ⌋ + 1 h=\lfloor log_2{n-1} \rfloor +1 h = ⌊ log2 − n − 1 ⌋ + 1. Then the starting number should be s = 2 h − 1 s = 2^{h-1} s=2h − 1, the parent node number corresponding to the bottom external node is: i − 1 2 + s \frac{i-1}{2}+s 2i − 1 + s. in addition, we can get that the total number of external nodes in the lower layer is ( n − 1 − s + 1 ) ∗ 2 = ( n − s ) ∗ 2 (n-1-s+1)*2=(n-s)*2 (n−1−s+1)∗2=(n−s)∗2

The number of upper nodes depends on the number of nodes in the tree. Specifically, the upper node is equivalent to supplementing the binary tree into a full binary tree. Then we can calculate the corresponding relationship between the number of each upper internal node and the number it should have when it is converted into an internal node. That is, if we convert an external node with number 7 into an internal node, its number is 7 (the same is a coincidence). There is actually an offset between the two. Take the nth supplementary internal node as an example, and its external node number is ( n − s ) ∗ 2 + 1 (n-s)*2+1 (n − s) * 2 + 1. Therefore, the offset between them is n − 2 ∗ s + 1 n-2*s+1 n − 2 * s+1. Therefore, the external node corresponding to the upper node is p − n + 2 ∗ s − 1 p-n+2*s-1 p − n+2 * s − 1. We can then get the parent node corresponding to the upper node.

To sum up, we conclude that the transformation relationship between internal nodes and external nodes is as follows:
p l a y e r = { i − 1 2 + s i ≤ ( n − s ) ∗ 2 p − n + 2 ∗ s − 1 2 i > ( n − s ) ∗ 2 player=\left\{ \begin{array}{rcl} \frac{i-1}{2}+s& {i \leq (n-s)*2}\\ \frac{p-n+2*s-1}{2}& {i > (n-s)*2}\\ \end{array} \right. player={2i−1​+s2p−n+2∗s−1​​i≤(n−s)∗2i>(n−s)∗2​

3. Statement

For simplicity, we do not implement copy assignment and other functions here.

#pragma once
#include "AbstractWinnerTree.h"
#include "../binaryTree/ArrayBinaryTree.h"
#include <cmath>

template<typename T>
class WinnerTree : public AbstractWinnerTree<T>, public ArrayBinaryTree<int>
{
public:
	WinnerTree();
	WinnerTree(const WinnerTree& other) = delete;
	WinnerTree& operator=(const WinnerTree& other) = delete;
	~WinnerTree();

	void initialize(T* player, int playerNum) override;
	int winner() const override;
	void replay(int playerIndex, T newValue) override;

private:
	T* player = nullptr;
	int playerNum = 0;
	int startIndexOfBottomInternalNodes = 0;
	int bottomExternalNodesNum = 0;

	void initExternalNodes(T* player, int playerNum);

	void initInternalNodes();	
	void initInternalNodesWithExternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum);
	int initBottomInternalNodes(std::unique_ptr<int[]>& internalNodes);
	int initBorderInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum, int currentExternalIndex);
	void initUpperInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum, int currentExternalIndex);
	void initPureInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum);
	
	int replayBottomInternalNode(int playerIndex);
	int replayUpperInternalNode(int playerIndex, int parentNode);
	int replayBorderInternalNode(int playerIndex, int parentNode);
	void replayPureInternalNode(int parentNode);

	void comparePlayersAndSetInternal(int leftPlayer, int rightPlayer, int* internalNodes, int internalNodeIndex);
};

4. Initialization

The construction process is complicated. First, we need to make a copy of the external nodes (initExternalNodes) because we support modifying the external node values to re compete. Then we can initialize the internal nodes. The initialization of the internal nodes is also divided into two parts: initializing the internal nodes with external nodes as leaves (initInternalNodesWithExternalNodes) and pure internal nodes (initPureInternalNodes). External nodes need to be initialized according to different internal nodes. First, the underlying external nodes (initBottomInternalNodes) and then the boundary external nodes (initBottomInternalNodes) Note that the boundary external node refers to the external node whose brother node is the internal node, and finally the upper external nodes (initupperinternal nodes). The calculation of the number mapping relationship of the three types is different. The way to initialize the pure internal node is similar to the way to initialize the heap.

template<typename T>
inline void WinnerTree<T>::initialize(T* player, int playerNum)
{
	initExternalNodes(player, playerNum);
	initInternalNodes();
}

template<typename T>
inline void WinnerTree<T>::initExternalNodes(T* player, int playerNum)
{
	this->playerNum = playerNum;
	this->player = new T[playerNum + 1];
	std::copy(player, player + playerNum + 1, this->player);
}

template<typename T>
inline void WinnerTree<T>::initInternalNodes()
{
	int internalNodesNum = playerNum - 1;
	std::unique_ptr<int[]> internalNodes(new int[internalNodesNum + 1]);

	startIndexOfBottomInternalNodes = std::pow(2, std::floor(std::log2(internalNodesNum)));
	bottomExternalNodesNum = (playerNum - startIndexOfBottomInternalNodes) * 2;

	initInternalNodesWithExternalNodes(internalNodes, internalNodesNum);
	initPureInternalNodes(internalNodes, internalNodesNum);

	this->makeTree(internalNodes.get(), internalNodesNum);
}

template<typename T>
inline void WinnerTree<T>::initInternalNodesWithExternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum)
{
	int currentExternalIndex = initBottomInternalNodes(internalNodes);
	currentExternalIndex = initBorderInternalNodes(internalNodes, internalNodesNum, currentExternalIndex);
	initUpperInternalNodes(internalNodes, internalNodesNum, currentExternalIndex);
}

template<typename T>
inline int WinnerTree<T>::initBottomInternalNodes(std::unique_ptr<int[]>& internalNodes)
{
	int currentExternalIndex = 1;
	for (; currentExternalIndex < bottomExternalNodesNum + 1; currentExternalIndex += 2)
	{
		// In actual implementation, since rounding in c + + is rounded to 0 by default, we can consider putting the s value on the numerator
		int internalIndex = (currentExternalIndex + 2 * startIndexOfBottomInternalNodes - 1) / 2;
		comparePlayersAndSetInternal(currentExternalIndex, currentExternalIndex + 1, internalNodes.get(), internalIndex);
	}

	return currentExternalIndex;
}

template<typename T>
inline int WinnerTree<T>::initBorderInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum, int currentExternalIndex)
{
	if ((playerNum - currentExternalIndex) % 2 == 0)
	{
		int index = internalNodes[internalNodesNum];
		comparePlayersAndSetInternal(index, currentExternalIndex, internalNodes.get(), internalNodesNum / 2);
		currentExternalIndex++;
	}

	return currentExternalIndex;
}

template<typename T>
inline void WinnerTree<T>::initUpperInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum, int currentExternalIndex)
{
	for (; currentExternalIndex < playerNum + 1; currentExternalIndex += 2)
	{
		int internalIndex = (currentExternalIndex - playerNum - 1 + 2 * startIndexOfBottomInternalNodes) / 2;
		comparePlayersAndSetInternal(currentExternalIndex, currentExternalIndex + 1, internalNodes.get(), internalIndex);
	}
}

template<typename T>
inline void WinnerTree<T>::initPureInternalNodes(std::unique_ptr<int[]>& internalNodes, int internalNodesNum)
{
	for (int index = (internalNodesNum - 1) / 2; index > 0; --index)
	{
		auto leftIndex = internalNodes[index * 2];
		auto rightIndex = internalNodes[index * 2 + 1];
		this->player[leftIndex] > this->player[rightIndex] ? internalNodes[index] = leftIndex : internalNodes[index] = rightIndex;
	}
}

template<typename T>
inline void WinnerTree<T>::comparePlayersAndSetInternal(int leftPlayer, int rightPlayer, int* internalNodes, int internalNodeIndex)
{
	if (this->player[leftPlayer] > this->player[rightPlayer])
	{
		internalNodes[internalNodeIndex] = leftPlayer;
	}
	else
	{
		internalNodes[internalNodeIndex] = rightPlayer;
	}
}

5. Reorganize the game

We have studied the calculation method of reorganizing the game before. It should be noted that reorganizing the game also needs to be analyzed in three cases, similar to the above.

template<typename T>
inline void WinnerTree<T>::replay(int playerIndex, T newValue)
{
	this->player[playerIndex] = newValue;
	
	int parentNode = replayBottomInternalNode(playerIndex);
	parentNode = replayUpperInternalNode(playerIndex, parentNode);
	parentNode = replayBorderInternalNode(playerIndex, parentNode);
	replayPureInternalNode(parentNode);
}

template<typename T>
inline int WinnerTree<T>::replayBottomInternalNode(int playerIndex)
{
	if (playerIndex < bottomExternalNodesNum + 1)
	{
		int parentNode = (playerIndex + 2 * startIndexOfBottomInternalNodes - 1) / 2;
		int leftPlayer = (parentNode - startIndexOfBottomInternalNodes + 1) * 2 - 1;
		int rightPlayer = leftPlayer + 1;
		comparePlayersAndSetInternal(leftPlayer, rightPlayer, this->elements, parentNode);
		
		parentNode /= 2;
		return parentNode;
	}
	
	return 0;
}

template<typename T>
inline int WinnerTree<T>::replayUpperInternalNode(int playerIndex, int parentNode)
{
	if (playerIndex > bottomExternalNodesNum)
	{
		parentNode = (playerIndex - playerNum - 1 + 2 * startIndexOfBottomInternalNodes) / 2;
		if (parentNode * 2 > this->treeSize)
		{
			int leftPlayer = parentNode * 2 - this->treeSize + bottomExternalNodesNum;
			int rightPlayer = leftPlayer + 1;
			comparePlayersAndSetInternal(leftPlayer, rightPlayer, this->elements, parentNode);

			parentNode /= 2;
		}
	}

	return parentNode;
}

template<typename T>
inline int WinnerTree<T>::replayBorderInternalNode(int playerIndex, int parentNode)
{
	if (parentNode * 2 == this->treeSize)
	{
		int leftPlayer = this->elements[parentNode * 2];
		int rightPlayer = bottomExternalNodesNum + 1;
		comparePlayersAndSetInternal(leftPlayer, rightPlayer, this->elements, parentNode);

		parentNode /= 2;
	}

	return parentNode;
}

template<typename T>
inline void WinnerTree<T>::replayPureInternalNode(int parentNode)
{
	while (parentNode >= 1)
	{
		int leftPlayer = this->elements[parentNode * 2];
		int rightPlayer = this->elements[parentNode * 2 + 1];
		comparePlayersAndSetInternal(leftPlayer, rightPlayer, this->elements, parentNode);

		parentNode /= 2;
	}
}

6. Get the winner

template<typename T>
inline int WinnerTree<T>::winner() const
{
	if (this->treeSize == 0)
	{
		throw std::overflow_error("no element in the tree");
	}

	return this->elements[1];
}

Topics: C++ Algorithm data structure Binary tree