Skip to content

Enhancement of the knapsack algorithm #9266 #9682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
168 changes: 91 additions & 77 deletions data_structures/binary_tree/basic_binary_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,89 +12,103 @@ def __init__(self, data: int) -> None:
self.right: Node | None = None


def display(tree: Node | None) -> None: # In Order traversal of the tree
class BinaryTree:
"""
>>> root = Node(1)
>>> root.left = Node(0)
>>> root.right = Node(2)
>>> display(root)
0
1
2
>>> display(root.right)
2
A BinaryTree has a root node and methods for creating and manipulating binary trees.
"""
if tree:
display(tree.left)
print(tree.data)
display(tree.right)


def depth_of_tree(tree: Node | None) -> int:
"""
Recursive function that returns the depth of a binary tree.

>>> root = Node(0)
>>> depth_of_tree(root)
1
>>> root.left = Node(0)
>>> depth_of_tree(root)
2
>>> root.right = Node(0)
>>> depth_of_tree(root)
2
>>> root.left.right = Node(0)
>>> depth_of_tree(root)
3
>>> depth_of_tree(root.left)
2
"""
return 1 + max(depth_of_tree(tree.left), depth_of_tree(tree.right)) if tree else 0


def is_full_binary_tree(tree: Node) -> bool:
def __init__(self, root_data: int) -> None:
self.root = Node(root_data)

def display(self, tree: Node | None) -> None:
"""
In Order traversal of the tree
>>> tree = BinaryTree(1)
>>> tree.root.left = Node(0)
>>> tree.root.right = Node(2)
>>> tree.display(tree.root)
0
1
2
>>> tree.display(tree.root.right)
2
"""
if tree:
self.display(tree.left)
print(tree.data)
self.display(tree.right)

def depth_of_tree(self, tree: Node | None) -> int:
"""
Recursive function that returns the depth of a binary tree.
>>> tree = BinaryTree(0)
>>> tree.depth_of_tree(tree.root)
1
>>> tree.root.left = Node(0)
>>> tree.depth_of_tree(tree.root)
2
>>> tree.root.right = Node(0)
>>> tree.depth_of_tree(tree.root)
2
>>> tree.root.left.right = Node(0)
>>> tree.depth_of_tree(tree.root)
3
>>> tree.depth_of_tree(tree.root.left)
2
"""
return (
1 + max(self.depth_of_tree(tree.left), self.depth_of_tree(tree.right))
if tree
else 0
)

def is_full_binary_tree(self, tree: Node) -> bool:
"""
Returns True if this is a full binary tree
>>> tree = BinaryTree(0)
>>> tree.is_full_binary_tree(tree.root)
True
>>> tree.root.left = Node(0)
>>> tree.is_full_binary_tree(tree.root)
False
>>> tree.root.right = Node(0)
>>> tree.is_full_binary_tree(tree.root)
True
>>> tree.root.left.left = Node(0)
>>> tree.is_full_binary_tree(tree.root)
False
>>> tree.root.right.right = Node(0)
>>> tree.is_full_binary_tree(tree.root)
False
"""
if not tree:
return True
if tree.left and tree.right:
return self.is_full_binary_tree(tree.left) and self.is_full_binary_tree(
tree.right
)
else:
return not tree.left and not tree.right


def main() -> None:
"""
Returns True if this is a full binary tree

>>> root = Node(0)
>>> is_full_binary_tree(root)
True
>>> root.left = Node(0)
>>> is_full_binary_tree(root)
False
>>> root.right = Node(0)
>>> is_full_binary_tree(root)
True
>>> root.left.left = Node(0)
>>> is_full_binary_tree(root)
False
>>> root.right.right = Node(0)
>>> is_full_binary_tree(root)
False
Main function for testing.
"""
if not tree:
return True
if tree.left and tree.right:
return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right)
else:
return not tree.left and not tree.right


def main() -> None: # Main function for testing.
tree = Node(1)
tree.left = Node(2)
tree.right = Node(3)
tree.left.left = Node(4)
tree.left.right = Node(5)
tree.left.right.left = Node(6)
tree.right.left = Node(7)
tree.right.left.left = Node(8)
tree.right.left.left.right = Node(9)

print(is_full_binary_tree(tree))
print(depth_of_tree(tree))
tree = BinaryTree(1)
tree.root.left = Node(2)
tree.root.right = Node(3)
tree.root.left.left = Node(4)
tree.root.left.right = Node(5)
tree.root.left.right.left = Node(6)
tree.root.right.left = Node(7)
tree.root.right.left.left = Node(8)
tree.root.right.left.left.right = Node(9)

print(tree.is_full_binary_tree(tree.root))
print(tree.depth_of_tree(tree.root))
print("Tree is: ")
display(tree)
tree.display(tree.root)


if __name__ == "__main__":
Expand Down
23 changes: 15 additions & 8 deletions dynamic_programming/knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@
"""


def mf_knapsack(i, wt, val, j):
def mf_knapsack(i, wt, val, j, f):
"""
This code involves the concept of memory functions. Here we solve the subproblems
which are needed unlike the below example
F is a 2D array with -1s filled up
"""
global f # a global dp table for knapsack
if f[i][j] < 0:
if j < wt[i - 1]:
val = mf_knapsack(i - 1, wt, val, j)
val = mf_knapsack(i - 1, wt, val, j, f)
else:
val = max(
mf_knapsack(i - 1, wt, val, j),
mf_knapsack(i - 1, wt, val, j - wt[i - 1]) + val[i - 1],
mf_knapsack(i - 1, wt, val, j, f),
mf_knapsack(i - 1, wt, val, j - wt[i - 1], f) + val[i - 1],
)
f[i][j] = val
return f[i][j]
Expand All @@ -36,7 +35,7 @@ def knapsack(w, wt, val, n):
else:
dp[i][w_] = dp[i - 1][w_]

return dp[n][w_], dp
return dp[n][w], dp


def knapsack_with_example_solution(w: int, wt: list, val: list):
Expand Down Expand Up @@ -91,6 +90,7 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
)
raise TypeError(msg)

f = [[0] * (w + 1)] + [[0] + [-1] * (w + 1) for _ in range(num_items + 1)]
optimal_val, dp_table = knapsack(w, wt, val, num_items)
example_optional_set: set = set()
_construct_solution(dp_table, wt, num_items, w, example_optional_set)
Expand Down Expand Up @@ -137,10 +137,17 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
wt = [4, 3, 2, 3]
n = 4
w = 6
f = [[0] * (w + 1)] + [[0] + [-1] * (w + 1) for _ in range(n + 1)]
optimal_solution, _ = knapsack(w, wt, val, n)
print(optimal_solution)
print(mf_knapsack(n, wt, val, w)) # switched the n and w
print(
mf_knapsack(
n,
wt,
val,
w,
[[0] * (w + 1)] + [[0] + [-1] * (w + 1) for _ in range(n + 1)],
)
) # switched the n and w

# testing the dynamic programming problem with example
# the optimal subset for the above example are items 3 and 4
Expand Down