



# 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      UpperCAmelCase_						:

						'''simple docstring'''




						def __init__(					self   ,							_A   ,							_A	):







													'''simple docstring'''

													__SCREAMING_SNAKE_CASE								=      key
													__SCREAMING_SNAKE_CASE								=      freq






						def __str__(					self	):







													'''simple docstring'''

													return f"""Node(key={self.key}, freq={self.freq})"""







def 			__lowercase     (				a__							,     a__							,     a__							,     a__							,     a__							,     a__     )       ->				Dict:
							if i > j or i < 0 or j > len(a__     ) - 1:
														return

							__SCREAMING_SNAKE_CASE								=      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(a__							,     a__							,     a__							,     node - 1							,     key[node]							,     a__     )
							print_binary_search_tree(a__							,     a__							,     node + 1							,     a__							,     key[node]							,     a__     )






def 			__lowercase     (				a__     )       ->				Dict:
							nodes.sort(key=lambda a__     : node.key     )

							__SCREAMING_SNAKE_CASE								=      len(a__     )

							__SCREAMING_SNAKE_CASE								=      [nodes[i].key for i in range(a__     )]
							__SCREAMING_SNAKE_CASE								=      [nodes[i].freq for i in range(a__     )]

							# 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.
							__SCREAMING_SNAKE_CASE								=      [[freqs[i] if i == j else 0 for j in range(a__     )] for i in range(a__     )]
							# sum[i][j] stores the sum of key frequencies between i and j inclusive in nodes
							# array
							__SCREAMING_SNAKE_CASE								=      [[freqs[i] if i == j else 0 for j in range(a__     )] for i in range(a__     )]
							# stores tree roots that will be used later for constructing binary search tree
							__SCREAMING_SNAKE_CASE								=      [[i if i == j else 0 for j in range(a__     )] for i in range(a__     )]

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

																					__SCREAMING_SNAKE_CASE								=      sys.maxsize  # set the value to "infinity"
																					__SCREAMING_SNAKE_CASE								=      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
																												__SCREAMING_SNAKE_CASE								=      dp[i][r - 1] if r != i else 0  # optimal cost for left subtree
																												__SCREAMING_SNAKE_CASE								=      dp[r + 1][j] if r != j else 0  # optimal cost for right subtree
																												__SCREAMING_SNAKE_CASE								=      left + total[i][j] + right

																												if dp[i][j] > cost:
																																			__SCREAMING_SNAKE_CASE								=      cost
																																			__SCREAMING_SNAKE_CASE								=      r

							print('Binary search tree nodes:'     )
							for node in nodes:
														print(a__     )

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






def 			__lowercase     (				)       ->				Optional[int]:
							# A sample binary search tree
							__SCREAMING_SNAKE_CASE								=      [Node(a__							,     randint(1							,     50     )     ) for i in range(10							,     0							,     -1     )]
							find_optimal_binary_search_tree(a__     )


if __name__ == "__main__":
			main()











def 			__lowercase     (				a__     )       ->				int:
							return int(a__     ) if x - int(a__     ) <= 0 else int(a__     ) + 1


if __name__ == "__main__":
			import doctest

			doctest.testmod()






