Add ru version (#1865)

* Add Russian docs site baseline

* Add Russian localized codebase

* Polish Russian code wording

* Update ru code translation.

* Update code translation and chapter covers.

* Fix pythontutor extraction.

* Add README and landing page.

* placeholder of profiles

* Use figures of English version

* Remove chapter paperbook
This commit is contained in:
Yudong Jin
2026-03-28 04:24:07 +08:00
committed by GitHub
parent 2ca570cc33
commit 772183705e
1958 changed files with 108186 additions and 0 deletions
@@ -0,0 +1,101 @@
// File: array_binary_tree.go
// Created Time: 2023-07-24
// Author: Reanon (793584285@qq.com)
package chapter_tree
/* Класс двоичного дерева в массивном представлении */
type arrayBinaryTree struct {
tree []any
}
/* Конструктор */
func newArrayBinaryTree(arr []any) *arrayBinaryTree {
return &arrayBinaryTree{
tree: arr,
}
}
/* Вместимость списка */
func (abt *arrayBinaryTree) size() int {
return len(abt.tree)
}
/* Получить значение узла с индексом i */
func (abt *arrayBinaryTree) val(i int) any {
// Если индекс выходит за границы, вернуть null, обозначающий пустую позицию
if i < 0 || i >= abt.size() {
return nil
}
return abt.tree[i]
}
/* Получить индекс левого дочернего узла узла с индексом i */
func (abt *arrayBinaryTree) left(i int) int {
return 2*i + 1
}
/* Получить индекс правого дочернего узла узла с индексом i */
func (abt *arrayBinaryTree) right(i int) int {
return 2*i + 2
}
/* Получить индекс родительского узла узла с индексом i */
func (abt *arrayBinaryTree) parent(i int) int {
return (i - 1) / 2
}
/* Обход в ширину */
func (abt *arrayBinaryTree) levelOrder() []any {
var res []any
// Непосредственно обходить массив
for i := 0; i < abt.size(); i++ {
if abt.val(i) != nil {
res = append(res, abt.val(i))
}
}
return res
}
/* Обход в глубину */
func (abt *arrayBinaryTree) dfs(i int, order string, res *[]any) {
// Если это пустая позиция, вернуть
if abt.val(i) == nil {
return
}
// Предварительный обход
if order == "pre" {
*res = append(*res, abt.val(i))
}
abt.dfs(abt.left(i), order, res)
// Симметричный обход
if order == "in" {
*res = append(*res, abt.val(i))
}
abt.dfs(abt.right(i), order, res)
// Обратный обход
if order == "post" {
*res = append(*res, abt.val(i))
}
}
/* Предварительный обход */
func (abt *arrayBinaryTree) preOrder() []any {
var res []any
abt.dfs(0, "pre", &res)
return res
}
/* Симметричный обход */
func (abt *arrayBinaryTree) inOrder() []any {
var res []any
abt.dfs(0, "in", &res)
return res
}
/* Обратный обход */
func (abt *arrayBinaryTree) postOrder() []any {
var res []any
abt.dfs(0, "post", &res)
return res
}
@@ -0,0 +1,47 @@
// File: array_binary_tree_test.go
// Created Time: 2023-07-24
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
. "github.com/krahets/hello-algo/pkg"
)
func TestArrayBinaryTree(t *testing.T) {
// Инициализировать двоичное дерево
// Здесь используется функция, напрямую строящая двоичное дерево из массива
arr := []any{1, 2, 3, 4, nil, 6, 7, 8, 9, nil, nil, 12, nil, nil, 15}
root := SliceToTree(arr)
fmt.Println("\nИнициализация двоичного дерева")
fmt.Println("Массивное представление двоичного дерева:")
fmt.Println(arr)
fmt.Println("Связное представление двоичного дерева:")
PrintTree(root)
// Класс двоичного дерева в массивном представлении
abt := newArrayBinaryTree(arr)
// Доступ к узлу
i := 1
l := abt.left(i)
r := abt.right(i)
p := abt.parent(i)
fmt.Println("\nТекущий узел: индекс =", i, ", значение =", abt.val(i))
fmt.Println("Индекс левого дочернего узла =", l, ", значение =", abt.val(l))
fmt.Println("Индекс правого дочернего узла =", r, ", значение =", abt.val(r))
fmt.Println("Индекс родительского узла =", p, ", значение =", abt.val(p))
// Обходить дерево
res := abt.levelOrder()
fmt.Println("\nОбход в ширину =", res)
res = abt.preOrder()
fmt.Println("Предварительный обход =", res)
res = abt.inOrder()
fmt.Println("Симметричный обход =", res)
res = abt.postOrder()
fmt.Println("Обратный обход =", res)
}
+200
View File
@@ -0,0 +1,200 @@
// File: avl_tree.go
// Created Time: 2023-01-08
// Author: Reanon (793584285@qq.com)
package chapter_tree
import . "github.com/krahets/hello-algo/pkg"
/* AVL-дерево */
type aVLTree struct {
// Корневой узел
root *TreeNode
}
func newAVLTree() *aVLTree {
return &aVLTree{root: nil}
}
/* Получить высоту узла */
func (t *aVLTree) height(node *TreeNode) int {
// Высота пустого узла равна -1, высота листового узла равна 0
if node != nil {
return node.Height
}
return -1
}
/* Обновить высоту узла */
func (t *aVLTree) updateHeight(node *TreeNode) {
lh := t.height(node.Left)
rh := t.height(node.Right)
// Высота узла равна высоте более высокого поддерева + 1
if lh > rh {
node.Height = lh + 1
} else {
node.Height = rh + 1
}
}
/* Получить коэффициент баланса */
func (t *aVLTree) balanceFactor(node *TreeNode) int {
// Коэффициент баланса пустого узла равен 0
if node == nil {
return 0
}
// Коэффициент баланса узла = высота левого поддерева - высота правого поддерева
return t.height(node.Left) - t.height(node.Right)
}
/* Операция правого вращения */
func (t *aVLTree) rightRotate(node *TreeNode) *TreeNode {
child := node.Left
grandChild := child.Right
// Выполнить правое вращение узла node вокруг child
child.Right = node
node.Left = grandChild
// Обновить высоту узла
t.updateHeight(node)
t.updateHeight(child)
// Вернуть корневой узел поддерева после вращения
return child
}
/* Операция левого вращения */
func (t *aVLTree) leftRotate(node *TreeNode) *TreeNode {
child := node.Right
grandChild := child.Left
// Выполнить левое вращение узла node вокруг child
child.Left = node
node.Right = grandChild
// Обновить высоту узла
t.updateHeight(node)
t.updateHeight(child)
// Вернуть корневой узел поддерева после вращения
return child
}
/* Выполнить вращение, чтобы снова сбалансировать поддерево */
func (t *aVLTree) rotate(node *TreeNode) *TreeNode {
// Получить коэффициент баланса узла node
// В Go рекомендуется использовать короткие имена переменных, здесь bf обозначает t.balanceFactor
bf := t.balanceFactor(node)
// Левосторонне перекошенное дерево
if bf > 1 {
if t.balanceFactor(node.Left) >= 0 {
// Правое вращение
return t.rightRotate(node)
} else {
// Сначала левое вращение, затем правое
node.Left = t.leftRotate(node.Left)
return t.rightRotate(node)
}
}
// Правосторонне перекошенное дерево
if bf < -1 {
if t.balanceFactor(node.Right) <= 0 {
// Левое вращение
return t.leftRotate(node)
} else {
// Сначала правое вращение, затем левое
node.Right = t.rightRotate(node.Right)
return t.leftRotate(node)
}
}
// Дерево сбалансировано, вращение не требуется, вернуть сразу
return node
}
/* Вставка узла */
func (t *aVLTree) insert(val int) {
t.root = t.insertHelper(t.root, val)
}
/* Рекурсивная вставка узла (вспомогательная функция) */
func (t *aVLTree) insertHelper(node *TreeNode, val int) *TreeNode {
if node == nil {
return NewTreeNode(val)
}
/* 1. Найти позицию вставки и вставить узел */
if val < node.Val.(int) {
node.Left = t.insertHelper(node.Left, val)
} else if val > node.Val.(int) {
node.Right = t.insertHelper(node.Right, val)
} else {
// Повторяющийся узел не вставлять, сразу вернуть
return node
}
// Обновить высоту узла
t.updateHeight(node)
/* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */
node = t.rotate(node)
// Вернуть корневой узел поддерева
return node
}
/* Удаление узла */
func (t *aVLTree) remove(val int) {
t.root = t.removeHelper(t.root, val)
}
/* Рекурсивное удаление узла (вспомогательная функция) */
func (t *aVLTree) removeHelper(node *TreeNode, val int) *TreeNode {
if node == nil {
return nil
}
/* 1. Найти узел и удалить его */
if val < node.Val.(int) {
node.Left = t.removeHelper(node.Left, val)
} else if val > node.Val.(int) {
node.Right = t.removeHelper(node.Right, val)
} else {
if node.Left == nil || node.Right == nil {
child := node.Left
if node.Right != nil {
child = node.Right
}
if child == nil {
// Число дочерних узлов = 0, удалить node и сразу вернуть
return nil
} else {
// Число дочерних узлов = 1, удалить node напрямую
node = child
}
} else {
// Число дочерних узлов = 2, удалить следующий по симметричному обходу узел и заменить им текущий узел
temp := node.Right
for temp.Left != nil {
temp = temp.Left
}
node.Right = t.removeHelper(node.Right, temp.Val.(int))
node.Val = temp.Val
}
}
// Обновить высоту узла
t.updateHeight(node)
/* 2. Выполнить вращение, чтобы снова сбалансировать поддерево */
node = t.rotate(node)
// Вернуть корневой узел поддерева
return node
}
/* Поиск узла */
func (t *aVLTree) search(val int) *TreeNode {
cur := t.root
// Искать в цикле и выйти после прохода за листовой узел
for cur != nil {
if cur.Val.(int) < val {
// Целевой узел находится в правом поддереве cur
cur = cur.Right
} else if cur.Val.(int) > val {
// Целевой узел находится в левом поддереве cur
cur = cur.Left
} else {
// Найти целевой узел и выйти из цикла
break
}
}
// Вернуть целевой узел
return cur
}
+54
View File
@@ -0,0 +1,54 @@
// File: avl_tree_test.go
// Created Time: 2023-01-08
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
. "github.com/krahets/hello-algo/pkg"
)
func TestAVLTree(t *testing.T) {
/* Инициализация пустого AVL-дерева */
tree := newAVLTree()
/* Вставка узла */
// Обратите внимание, как AVL-дерево сохраняет баланс после вставки узла
testInsert(tree, 1)
testInsert(tree, 2)
testInsert(tree, 3)
testInsert(tree, 4)
testInsert(tree, 5)
testInsert(tree, 8)
testInsert(tree, 7)
testInsert(tree, 9)
testInsert(tree, 10)
testInsert(tree, 6)
/* Вставка повторяющегося узла */
testInsert(tree, 7)
/* Удаление узла */
// Обратите внимание, как AVL-дерево сохраняет баланс после удаления узла
testRemove(tree, 8) // Удаление узла степени 0
testRemove(tree, 5) // Удаление узла степени 1
testRemove(tree, 4) // Удаление узла степени 2
/* Поиск узла */
node := tree.search(7)
fmt.Printf("\nНайденный объект узла = %#v, значение узла = %d\n", node, node.Val)
}
func testInsert(tree *aVLTree, val int) {
tree.insert(val)
fmt.Printf("\nПосле вставки узла %d AVL-дерево имеет вид\n", val)
PrintTree(tree.root)
}
func testRemove(tree *aVLTree, val int) {
tree.remove(val)
fmt.Printf("\nПосле удаления узла %d AVL-дерево имеет вид\n", val)
PrintTree(tree.root)
}
@@ -0,0 +1,142 @@
// File: binary_search_tree.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
. "github.com/krahets/hello-algo/pkg"
)
type binarySearchTree struct {
root *TreeNode
}
func newBinarySearchTree() *binarySearchTree {
bst := &binarySearchTree{}
// Инициализировать пустое дерево
bst.root = nil
return bst
}
/* Получить корневой узел */
func (bst *binarySearchTree) getRoot() *TreeNode {
return bst.root
}
/* Поиск узла */
func (bst *binarySearchTree) search(num int) *TreeNode {
node := bst.root
// Искать в цикле и выйти после прохода за листовой узел
for node != nil {
if node.Val.(int) < num {
// Целевой узел находится в правом поддереве cur
node = node.Right
} else if node.Val.(int) > num {
// Целевой узел находится в левом поддереве cur
node = node.Left
} else {
// Найти целевой узел и выйти из цикла
break
}
}
// Вернуть целевой узел
return node
}
/* Вставка узла */
func (bst *binarySearchTree) insert(num int) {
cur := bst.root
// Если дерево пусто, инициализировать корневой узел
if cur == nil {
bst.root = NewTreeNode(num)
return
}
// Позиция узла, предшествующего вставляемому
var pre *TreeNode = nil
// Искать в цикле и выйти после прохода за листовой узел
for cur != nil {
if cur.Val == num {
return
}
pre = cur
if cur.Val.(int) < num {
cur = cur.Right
} else {
cur = cur.Left
}
}
// Вставка узла
node := NewTreeNode(num)
if pre.Val.(int) < num {
pre.Right = node
} else {
pre.Left = node
}
}
/* Удаление узла */
func (bst *binarySearchTree) remove(num int) {
cur := bst.root
// Если дерево пусто, сразу вернуть
if cur == nil {
return
}
// Позиция узла, предшествующего удаляемому
var pre *TreeNode = nil
// Искать в цикле и выйти после прохода за листовой узел
for cur != nil {
if cur.Val == num {
break
}
pre = cur
if cur.Val.(int) < num {
// Удаляемый узел находится в правом поддереве
cur = cur.Right
} else {
// Удаляемый узел находится в левом поддереве
cur = cur.Left
}
}
// Если узел для удаления отсутствует, сразу вернуть
if cur == nil {
return
}
// Число дочерних узлов равно 0 или 1
if cur.Left == nil || cur.Right == nil {
var child *TreeNode = nil
// Извлечь дочерний узел удаляемого узла
if cur.Left != nil {
child = cur.Left
} else {
child = cur.Right
}
// Удалить узел cur
if cur != bst.root {
if pre.Left == cur {
pre.Left = child
} else {
pre.Right = child
}
} else {
// Если удаляемый узел является корнем, заново назначить корневой узел
bst.root = child
}
// Число дочерних узлов равно 2
} else {
// Получить следующий после cur узел в симметричном обходе для удаляемого узла
tmp := cur.Right
for tmp.Left != nil {
tmp = tmp.Left
}
// Рекурсивно удалить узел tmp
bst.remove(tmp.Val.(int))
// Перезаписать cur значением tmp
cur.Val = tmp.Val
}
}
/* Вывести двоичное дерево поиска */
func (bst *binarySearchTree) print() {
PrintTree(bst.root)
}
@@ -0,0 +1,45 @@
// File: binary_search_tree_test.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
)
func TestBinarySearchTree(t *testing.T) {
bst := newBinarySearchTree()
nums := []int{8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15}
// Обратите внимание: разные порядки вставки порождают разные двоичные деревья; данная последовательность может построить совершенное двоичное дерево
for _, num := range nums {
bst.insert(num)
}
fmt.Println("\nИсходное двоичное дерево:")
bst.print()
// Получить корневой узел
node := bst.getRoot()
fmt.Println("\nКорневой узел двоичного дерева:", node.Val)
// Поиск узла
node = bst.search(7)
fmt.Println("Найденный объект узла =", node, ", значение узла =", node.Val)
// Вставка узла
bst.insert(16)
fmt.Println("\nПосле вставки узла 16 двоичное дерево имеет вид:")
bst.print()
// Удаление узла
bst.remove(1)
fmt.Println("\nПосле удаления узла 1 двоичное дерево имеет вид:")
bst.print()
bst.remove(2)
fmt.Println("\nПосле удаления узла 2 двоичное дерево имеет вид:")
bst.print()
bst.remove(4)
fmt.Println("\nПосле удаления узла 4 двоичное дерево имеет вид:")
bst.print()
}
@@ -0,0 +1,35 @@
// File: binary_tree_bfs.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"container/list"
. "github.com/krahets/hello-algo/pkg"
)
/* Обход в ширину */
func levelOrder(root *TreeNode) []any {
// Инициализировать очередь и добавить корневой узел
queue := list.New()
queue.PushBack(root)
// Инициализировать срез для хранения последовательности обхода
nums := make([]any, 0)
for queue.Len() > 0 {
// Извлечение из очереди
node := queue.Remove(queue.Front()).(*TreeNode)
// Сохранить значение узла
nums = append(nums, node.Val)
if node.Left != nil {
// Поместить левый дочерний узел в очередь
queue.PushBack(node.Left)
}
if node.Right != nil {
// Поместить правый дочерний узел в очередь
queue.PushBack(node.Right)
}
}
return nums
}
@@ -0,0 +1,24 @@
// File: binary_tree_bfs_test.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
. "github.com/krahets/hello-algo/pkg"
)
func TestLevelOrder(t *testing.T) {
/* Инициализация двоичного дерева */
// Здесь используется функция, напрямую строящая двоичное дерево из массива
root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7})
fmt.Println("\nИнициализация двоичного дерева:")
PrintTree(root)
// Обход в ширину
nums := levelOrder(root)
fmt.Println("\nПоследовательность печати узлов при обходе в ширину =", nums)
}
@@ -0,0 +1,44 @@
// File: binary_tree_dfs.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
. "github.com/krahets/hello-algo/pkg"
)
var nums []any
/* Предварительный обход */
func preOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: корень -> левое поддерево -> правое поддерево
nums = append(nums, node.Val)
preOrder(node.Left)
preOrder(node.Right)
}
/* Симметричный обход */
func inOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: левое поддерево -> корень -> правое поддерево
inOrder(node.Left)
nums = append(nums, node.Val)
inOrder(node.Right)
}
/* Обратный обход */
func postOrder(node *TreeNode) {
if node == nil {
return
}
// Порядок обхода: левое поддерево -> правое поддерево -> корень
postOrder(node.Left)
postOrder(node.Right)
nums = append(nums, node.Val)
}
@@ -0,0 +1,35 @@
// File: binary_tree_dfs_test.go
// Created Time: 2022-11-26
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
. "github.com/krahets/hello-algo/pkg"
)
func TestPreInPostOrderTraversal(t *testing.T) {
/* Инициализация двоичного дерева */
// Здесь используется функция, напрямую строящая двоичное дерево из массива
root := SliceToTree([]any{1, 2, 3, 4, 5, 6, 7})
fmt.Println("\nИнициализация двоичного дерева:")
PrintTree(root)
// Предварительный обход
nums = nil
preOrder(root)
fmt.Println("\nПоследовательность печати узлов при предварительном обходе =", nums)
// Симметричный обход
nums = nil
inOrder(root)
fmt.Println("\nПоследовательность печати узлов при симметричном обходе =", nums)
// Обратный обход
nums = nil
postOrder(root)
fmt.Println("\nПоследовательность печати узлов при обратном обходе =", nums)
}
@@ -0,0 +1,41 @@
// File: binary_tree_test.go
// Created Time: 2022-11-25
// Author: Reanon (793584285@qq.com)
package chapter_tree
import (
"fmt"
"testing"
. "github.com/krahets/hello-algo/pkg"
)
func TestBinaryTree(t *testing.T) {
/* Инициализация двоичного дерева */
// Инициализация узла
n1 := NewTreeNode(1)
n2 := NewTreeNode(2)
n3 := NewTreeNode(3)
n4 := NewTreeNode(4)
n5 := NewTreeNode(5)
// Построить связи между узлами (указатели)
n1.Left = n2
n1.Right = n3
n2.Left = n4
n2.Right = n5
fmt.Println("Инициализация двоичного дерева")
PrintTree(n1)
/* Вставка и удаление узлов */
// Вставка узла
p := NewTreeNode(0)
n1.Left = p
p.Left = n2
fmt.Println("После вставки узла P")
PrintTree(n1)
// Удаление узла
n1.Left = n2
fmt.Println("После удаления узла P")
PrintTree(n1)
}