
# This Python program implements an optimal binary search tree (abbreviated BST)
# building dynamic programming algorithm that delivers O(n^2) performance.
#
# The goal of the optimal BST problem is to build a low-cost BST for a
# given set of nodes, each with its own key and frequency. The frequency
# of the node is defined as how many time the node is being searched.
# The search cost of binary search tree is given by this formula:
#
# cost(1, n) = sum{i = 1 to n}((depth(node_i) + 1) * node_i_freq)
#
# where n is number of nodes in the BST. The characteristic of low-cost
# BSTs is having a faster overall search time than other implementations.
# The reason for their fast search time is that the nodes with high
# frequencies will be placed near the root of the tree while the nodes
# with low frequencies will be placed near the leaves of the tree thus
# reducing search time in the most frequent instances.
import sys
from random import randint





class 		__lowerCamelCase     :



							def __init__(     self	,				__snake_case	,				__snake_case							)						->     int:






								"""simple docstring"""



								UpperCAmelCase:					List[str] 						=     key
								UpperCAmelCase:					Optional[Any] 						=     freq




							def __str__(     self							)						->     Any:






								"""simple docstring"""



								return F'Node(key={self.key}, freq={self.freq})'



def      __UpperCAmelCase    (  snake_case_							:		Tuple				,					snake_case_							:		int				,					snake_case_							:		str				,					snake_case_							:		Tuple				,					snake_case_							:		List[Any]				,					snake_case_							:		Tuple				):



	'''simple docstring'''



	if i > j or i < 0 or j > len(snake_case_				) - 1:
		return

	UpperCAmelCase:					str 						=     root[i][j]
	if parent == -1:  # root does not have a parent
		print(F'{key[node]} is the root of the binary search tree.'				)
	elif is_left:
		print(F'{key[node]} is the left child of key {parent}.'				)
	else:
		print(F'{key[node]} is the right child of key {parent}.'				)

	print_binary_search_tree(snake_case_				,					snake_case_				,					snake_case_				,					node - 1				,					key[node]				,					snake_case_				)
	print_binary_search_tree(snake_case_				,					snake_case_				,					node + 1				,					snake_case_				,					key[node]				,					snake_case_				)



def      __UpperCAmelCase    (  snake_case_							:		List[str]				):



	'''simple docstring'''



	nodes.sort(key=lambda snake_case_				: node.key				)

	UpperCAmelCase:					Optional[Any] 						=     len(snake_case_				)

	UpperCAmelCase:					Optional[int] 						=     [nodes[i].key for i in range(snake_case_				)]
	UpperCAmelCase:					Tuple 						=     [nodes[i].freq for i in range(snake_case_				)]

	# This 2D array stores the overall tree cost (which's as minimized as possible);
	# for a single key, cost is equal to frequency of the key.
	UpperCAmelCase:					Union[str, Any] 						=     [[freqs[i] if i == j else 0 for j in range(snake_case_				)] for i in range(snake_case_				)]
	# sum[i][j] stores the sum of key frequencies between i and j inclusive in nodes
	# array
	UpperCAmelCase:					Union[str, Any] 						=     [[freqs[i] if i == j else 0 for j in range(snake_case_				)] for i in range(snake_case_				)]
	# stores tree roots that will be used later for constructing binary search tree
	UpperCAmelCase:					int 						=     [[i if i == j else 0 for j in range(snake_case_				)] for i in range(snake_case_				)]

	for interval_length in range(2				,					n + 1				):
		for i in range(n - interval_length + 1				):
			UpperCAmelCase:					Tuple 						=     i + interval_length - 1

			UpperCAmelCase:					Any 						=     sys.maxsize  # set the value to "infinity"
			UpperCAmelCase:					int 						=     total[i][j - 1] + freqs[j]

			# Apply Knuth's optimization
			# Loop without optimization: for r in range(i, j + 1):
			for r in range(root[i][j - 1]				,					root[i + 1][j] + 1				):  # r is a temporal root
				UpperCAmelCase:					Union[str, Any] 						=     dp[i][r - 1] if r != i else 0  # optimal cost for left subtree
				UpperCAmelCase:					Tuple 						=     dp[r + 1][j] if r != j else 0  # optimal cost for right subtree
				UpperCAmelCase:					Optional[int] 						=     left + total[i][j] + right

				if dp[i][j] > cost:
					UpperCAmelCase:					Optional[int] 						=     cost
					UpperCAmelCase:					Dict 						=     r

	print("Binary search tree nodes:"				)
	for node in nodes:
		print(snake_case_				)

	print(F'\nThe cost of optimal BST for given tree nodes is {dp[0][n - 1]}.'				)
	print_binary_search_tree(snake_case_				,					snake_case_				,					0				,					n - 1				,					-1				,					snake_case_				)



def      __UpperCAmelCase    (  ):



	'''simple docstring'''



	UpperCAmelCase:					List[str] 						=     [Node(snake_case_				,					randint(1				,					5_0				)				) for i in range(1_0				,					0				,					-1				)]
	find_optimal_binary_search_tree(snake_case_				)


if __name__ == "__main__":
	main()


def      __UpperCAmelCase    (  snake_case_							:		str				):



	'''simple docstring'''



	UpperCAmelCase:					Tuple 						=     str(snake_case_				).strip()
	if not bin_string:
		raise ValueError("Empty string was passed to the function"				)
	UpperCAmelCase:					List[Any] 						=     bin_string[0] == "-"
	if is_negative:
		UpperCAmelCase:					Optional[int] 						=     bin_string[1:]
	if not all(char in "01" for char in bin_string				):
		raise ValueError("Non-binary value was passed to the function"				)
	UpperCAmelCase:					List[str] 						=     0
	for char in bin_string:
		UpperCAmelCase:					Union[str, Any] 						=     2 * decimal_number + int(snake_case_				)
	return -decimal_number if is_negative else decimal_number


if __name__ == "__main__":
	from doctest import testmod

	testmod()
