diff --git a/lib/red_black_tree.ex b/lib/red_black_tree.ex index 50f1551..394273b 100644 --- a/lib/red_black_tree.ex +++ b/lib/red_black_tree.ex @@ -19,7 +19,7 @@ defmodule RedBlackTree do right: t() | nil } - # ОПЕРАЦИЯ ПОИСКА !!! + # LOOKUP OPERATION @spec get(t() | nil, any()) :: any() | nil def get(nil, _key), do: nil @@ -31,7 +31,7 @@ defmodule RedBlackTree do end end - # ОПЕРАЦИЯ ВСТАВКА !!! + # INSERT OPERATION @spec insert(t() | nil, any(), any()) :: t() def insert(tree, key, value) do tree @@ -52,7 +52,7 @@ defmodule RedBlackTree do key < k -> %RedBlackTree{node | left: do_insert(node.left, key, value)} - key > k -> + true -> %RedBlackTree{node | right: do_insert(node.right, key, value)} end @@ -65,27 +65,166 @@ defmodule RedBlackTree do defp ensure_black_root(tree), do: tree - defp rotate_left(%RedBlackTree{right: right} = h) do - h = %RedBlackTree{h | right: right.left} + # DELETE OPERATION + @spec delete(t() | nil, any()) :: t() | nil + def delete(tree, key) do + if tree == nil do + nil + else + tree = do_delete(tree, key) + + if tree != nil do + %RedBlackTree{tree | color: :black} + else + nil + end + end + end + + defp do_delete(nil, _key), do: nil + + defp do_delete(node, key) do + node = + if key < node.key do + node = + if node.left != nil do + node = + if not red?(node.left) and not red?(node.left.left) do + move_red_left(node) + else + node + end + + %RedBlackTree{node | left: do_delete(node.left, key)} + else + node + end + + node + else + node = + if red?(node.left) do + rotate_right(node) + else + node + end + + if key == node.key and node.right == nil do + # Node to delete found and has no right child + nil + else + node = + if node.right != nil do + node = + if not red?(node.right) and not red?(node.right.left) do + move_red_right(node) + else + node + end + + if key == node.key do + # Node to delete found + min_node = min(node.right) + + %RedBlackTree{ + node + | key: min_node.key, + value: min_node.value, + right: delete_min(node.right) + } + else + %RedBlackTree{node | right: do_delete(node.right, key)} + end + else + node + end + + node + end + end + + if node != nil do + balance(node) + else + nil + end + end + + # MOVE RED LEFT + defp move_red_left(node) do + node = flip_colors(node) + + node = + if node.right != nil and red?(node.right.left) do + node = %RedBlackTree{node | right: rotate_right(node.right)} + node = rotate_left(node) + flip_colors(node) + else + node + end + + node + end + + # MOVE RED RIGHT + defp move_red_right(node) do + node = flip_colors(node) + node = + if node.left != nil and red?(node.left.left) do + node = rotate_right(node) + flip_colors(node) + else + node + end + + node + end + + # FIND MINIMUM NODE + defp min(node) do + if node.left == nil do + node + else + min(node.left) + end + end + + # DELETE MINIMUM NODE + defp delete_min(node) do + if node.left == nil do + nil + else + node = + if not red?(node.left) and not red?(node.left.left) do + move_red_left(node) + else + node + end + + node = %RedBlackTree{node | left: delete_min(node.left)} + balance(node) + end + end + + # ROTATIONS AND BALANCING + defp rotate_left(%RedBlackTree{right: right} = h) do %RedBlackTree{ color: h.color, key: right.key, value: right.value, - left: %RedBlackTree{h | color: :red}, + left: %RedBlackTree{h | right: right.left, color: :red}, right: right.right } end defp rotate_right(%RedBlackTree{left: left} = h) do - h = %RedBlackTree{h | left: left.right} - %RedBlackTree{ color: h.color, key: left.key, value: left.value, left: left.left, - right: %RedBlackTree{h | color: :red} + right: %RedBlackTree{h | left: left.right, color: :red} } end @@ -99,13 +238,31 @@ defmodule RedBlackTree do end defp balance(node) do - node = if red?(node.right), do: rotate_left(node), else: node - node = if red?(node.left) and red?(node.left.left), do: rotate_right(node), else: node - node = if red?(node.left) and red?(node.right), do: flip_colors(node), else: node + node = + if red?(node.right) do + rotate_left(node) + else + node + end + + node = + if red?(node.left) and red?(node.left.left) do + rotate_right(node) + else + node + end + + node = + if red?(node.left) and red?(node.right) do + flip_colors(node) + else + node + end + node end - # Вспомогательные функции + # HELPER FUNCTIONS defp red?(%RedBlackTree{color: :red}), do: true defp red?(_), do: false @@ -118,7 +275,7 @@ defmodule RedBlackTree do defp toggle_color(:red), do: :black defp toggle_color(:black), do: :red - # ПРОВЕРЯЕМ ДЕРЕВО НА КОРРЕКТНОСТЬ + # VALIDATE RED-BLACK TREE PROPERTIES @spec valid_red_black_tree?(t()) :: boolean() def valid_red_black_tree?(tree) do case check_properties(tree) do @@ -130,11 +287,10 @@ defmodule RedBlackTree do defp check_properties(nil), do: {:ok, 1} defp check_properties(%RedBlackTree{color: color, left: left, right: right} = node) do - # Проверяем отсутствие последовательных красных узлов + # Check for consecutive red nodes if red?(node) and (red?(left) or red?(right)) do {:error, :red_red_violation} else - # Рекурсивно проверяем левое и правое поддеревья with {:ok, left_black_height} <- check_properties(left), {:ok, right_black_height} <- check_properties(right), true <- left_black_height == right_black_height do diff --git a/lib/tree_dict.ex b/lib/tree_dict.ex index aa02946..9f4f878 100644 --- a/lib/tree_dict.ex +++ b/lib/tree_dict.ex @@ -23,6 +23,12 @@ defmodule TreeDict do RedBlackTree.get(tree, key) end + @spec delete(t(), any()) :: t() + def delete(%TreeDict{tree: tree} = dict, key) do + new_tree = RedBlackTree.delete(tree, key) + %TreeDict{dict | tree: new_tree} + end + # def insert(%TreeDict{tree: tree} = dict, key, value) do # %TreeDict{tree: insert_into_tree(tree, key, value)} # end diff --git a/test/red_black_tree_unit_test.exs b/test/red_black_tree_unit_test.exs index 76156f8..f40b319 100644 --- a/test/red_black_tree_unit_test.exs +++ b/test/red_black_tree_unit_test.exs @@ -46,4 +46,87 @@ defmodule RedBlackTreeUnitTest do # Проверяем, что дерево является корректным красно-чёрным деревом assert RedBlackTree.valid_red_black_tree?(tree) end + + test "delete from empty tree returns nil" do + tree = nil + assert RedBlackTree.delete(tree, 10) == nil + end + + test "delete non-existing key does not change tree" do + tree = RedBlackTree.insert(nil, 10, "value") + tree_after_delete = RedBlackTree.delete(tree, 20) + assert tree == tree_after_delete + end + + test "delete leaf node" do + tree = nil + tree = RedBlackTree.insert(tree, 10, "value") + tree = RedBlackTree.insert(tree, 5, "value5") + tree = RedBlackTree.insert(tree, 15, "value15") + + tree = RedBlackTree.delete(tree, 5) + assert RedBlackTree.get(tree, 5) == nil + assert RedBlackTree.valid_red_black_tree?(tree) + end + + test "delete node with one child" do + tree = nil + tree = RedBlackTree.insert(tree, 10, "value") + tree = RedBlackTree.insert(tree, 5, "value5") + tree = RedBlackTree.insert(tree, 3, "value3") + + tree = RedBlackTree.delete(tree, 5) + assert RedBlackTree.get(tree, 5) == nil + assert RedBlackTree.get(tree, 3) == "value3" + assert RedBlackTree.valid_red_black_tree?(tree) + end + + test "delete node with two children" do + tree = nil + tree = RedBlackTree.insert(tree, 10, "value") + tree = RedBlackTree.insert(tree, 5, "value5") + tree = RedBlackTree.insert(tree, 15, "value15") + tree = RedBlackTree.insert(tree, 12, "value12") + tree = RedBlackTree.insert(tree, 18, "value18") + + tree = RedBlackTree.delete(tree, 15) + assert RedBlackTree.get(tree, 15) == nil + assert RedBlackTree.get(tree, 12) == "value12" + assert RedBlackTree.get(tree, 18) == "value18" + assert RedBlackTree.valid_red_black_tree?(tree) + end + + test "delete root node" do + tree = nil + tree = RedBlackTree.insert(tree, 10, "value") + tree = RedBlackTree.insert(tree, 5, "value5") + tree = RedBlackTree.insert(tree, 15, "value15") + + tree = RedBlackTree.delete(tree, 10) + assert RedBlackTree.get(tree, 10) == nil + assert RedBlackTree.get(tree, 5) == "value5" + assert RedBlackTree.get(tree, 15) == "value15" + assert RedBlackTree.valid_red_black_tree?(tree) + end + + test "delete multiple nodes" do + tree = nil + keys = Enum.shuffle(1..20) + tree = Enum.reduce(keys, tree, fn key, acc -> RedBlackTree.insert(acc, key, "value") end) + + keys_to_delete = Enum.shuffle(5..15) + tree = Enum.reduce(keys_to_delete, tree, fn key, acc -> RedBlackTree.delete(acc, key) end) + + Enum.each(keys_to_delete, fn key -> + assert RedBlackTree.get(tree, key) == nil + end) + + remaining_keys = Enum.filter(1..20, fn key -> not Enum.member?(keys_to_delete, key) end) + + Enum.each(remaining_keys, fn key -> + assert RedBlackTree.get(tree, key) == "value" + end) + + assert RedBlackTree.valid_red_black_tree?(tree) + end end diff --git a/test/tree_dict_test.exs b/test/tree_dict_test.exs index 2cda467..d8f96b5 100644 --- a/test/tree_dict_test.exs +++ b/test/tree_dict_test.exs @@ -87,4 +87,44 @@ defmodule TreeDictTest do assert TreeDict.get(dict, i) == "value #{i}" end) end + + test "delete from empty dictionary" do + dict = TreeDict.new() + dict = TreeDict.delete(dict, :key) + assert TreeDict.get(dict, :key) == nil + end + + test "insert and delete keys" do + dict = TreeDict.new() + dict = TreeDict.insert(dict, :a, 1) + dict = TreeDict.insert(dict, :b, 2) + dict = TreeDict.insert(dict, :c, 3) + + dict = TreeDict.delete(dict, :b) + assert TreeDict.get(dict, :b) == nil + assert TreeDict.get(dict, :a) == 1 + assert TreeDict.get(dict, :c) == 3 + end + + test "delete non-existing key" do + dict = TreeDict.new() + dict = TreeDict.insert(dict, :a, 1) + dict = TreeDict.delete(dict, :b) + assert TreeDict.get(dict, :a) == 1 + end + + test "delete all keys" do + dict = TreeDict.new() + dict = TreeDict.insert(dict, :a, 1) + dict = TreeDict.insert(dict, :b, 2) + dict = TreeDict.insert(dict, :c, 3) + + dict = TreeDict.delete(dict, :a) + dict = TreeDict.delete(dict, :b) + dict = TreeDict.delete(dict, :c) + + assert TreeDict.get(dict, :a) == nil + assert TreeDict.get(dict, :b) == nil + assert TreeDict.get(dict, :c) == nil + end end