Форум программистов, компьютерный форум, киберфорум
alhaos
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  

[golang] Двоичная куча, min-heap

Запись от alhaos размещена 20.05.2026 в 18:57
Показов 1699 Комментарии 0
Метки datastructure, golang

Двоичная куча



Двоичная куча — структура данных, которая всегда держит самый важный элемент наготове.

Представьте очередь к хилеру в игре, и очередь из игроков в приоритете те у кого меньше всего HP.

Как она устроена внутри

Куча хранится в обычном массиве, но если смотреть на неё как на дерево — каждый узел имеет двух детей. Отсюда название: двоичная (два ребёнка) куча.

Главное правило — каждый родитель меньше своих детей (в min-heap). Это значит, что самый маленький элемент всегда сидит в корне — на нулевом индексе массива. Не где-то в середине, не в конце — всегда в начале.

Реализация на го:



Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package binaryHeap
 
import "errors"
 
var ErrEmptyHeap = errors.New("heap is empty")
 
// Интерфейс двоичной кучи
type BinaryHeap interface {
    Push(n int)
    Pop() (int, error)
    Peek() (int, error)
    Len() int
    IsEmpty() bool
}
 
// binaryHeap структура двоичной кучи
type binaryHeap struct {
    data []int
}
 
// NewBinaryHeap возвращает новый экземпляр структуры BinaryHeap
func NewBinaryHeap() BinaryHeap {
    return &binaryHeap{
        data: make([]int, 0, 16),
    }
}
 
// Push добавляет элемент в двоичную кучу
func (h *binaryHeap) Push(n int) {
 
    // Добавляем в конец массива элемент
    h.data = append(h.data, n)
 
    // Фиксируем индекс элемента для просеивания вверх
    currentIndex := len(h.data) - 1
 
    // Цикл к корню кучи
    for currentIndex > 0 {
 
        // Находим индекс родительского элемента
        parentIndex := (currentIndex - 1) / 2
 
        // Если родительский элемент больше
        if h.data[parentIndex] > h.data[currentIndex] {
 
            // Меняем местами с текущим
            h.data[parentIndex], h.data[currentIndex] = h.data[currentIndex], h.data[parentIndex]
 
            // Изменяем индекс текущего элемента на индекс родительского элемента
            currentIndex = parentIndex
        } else { // Если родительский элемент не больше
            break // прерываем цикл
        }
    }
}
 
// Pop извлекает корневой элемент из бинарной кучи
func (h *binaryHeap) Pop() (int, error) {
 
    // Если куча пуста возвращаем 0 и ошибку
    if len(h.data) == 0 {
        return 0, ErrEmptyHeap
    }
 
    // Забираем элемент для возврата
    returnValue := h.data[0]
 
    // На его место вставляем крайний элемент
    h.data[0] = h.data[len(h.data)-1]
 
    // Обрезаем слайс
    h.data = h.data[:len(h.data)-1]
 
    // Устанавливаем 0 как индекс просматриваемого элемента
    currentIndex := 0
 
    // Просеиваем вниз
    for {
        leftChildIndex := 2*currentIndex + 1
        if leftChildIndex >= len(h.data) {
            break
        }
        rightChildIndex := 2*currentIndex + 2
 
        // выбираем наименьшего ребёнка
        smallestIndex := leftChildIndex
        if rightChildIndex < len(h.data) && h.data[rightChildIndex] < h.data[leftChildIndex] {
            smallestIndex = rightChildIndex
        }
 
        // если текущий элемент уже меньше — стоп
        if h.data[currentIndex] <= h.data[smallestIndex] {
            break
        }
 
        h.data[currentIndex], h.data[smallestIndex] = h.data[smallestIndex], h.data[currentIndex]
        currentIndex = smallestIndex
    }
 
    return returnValue, nil
}
 
// Peek Возвращает элемент в корне кучи
func (h *binaryHeap) Peek() (int, error) {
    if len(h.data) == 0 {
        return 0, ErrEmptyHeap
    }
    return h.data[0], nil
}
 
// Len возвращает длину двоичной кучи
func (h *binaryHeap) Len() int {
    return len(h.data)
}
 
// IsEmpty возвращает true если куча пуста, иначе false
func (h *binaryHeap) IsEmpty() bool {
    return len(h.data) == 0
}

Тесты:



Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package binaryHeap
 
import (
    "errors"
    "testing"
)
 
func TestPeekOnEmptyHeap(t *testing.T) {
    h := NewBinaryHeap()
    _, err := h.Peek()
    if err == nil {
        t.Error("expected error on empty heap")
    }
}
 
func TestPopOnEmptyHeap(t *testing.T) {
    h := NewBinaryHeap()
    _, err := h.Pop()
    if err == nil {
        t.Error("expected error on empty heap")
    }
}
 
func TestIsEmptyOnNewHeap(t *testing.T) {
    h := NewBinaryHeap()
    if !h.IsEmpty() {
        t.Error("expected new heap to be empty")
    }
}
 
func TestLenOnNewHeap(t *testing.T) {
    h := NewBinaryHeap()
    if h.Len() != 0 {
        t.Errorf("expected len 0, got %d", h.Len())
    }
}
 
func TestPushSingleElement(t *testing.T) {
    h := NewBinaryHeap()
    h.Push(42)
 
    if h.IsEmpty() {
        t.Error("expected heap to be non-empty after Push")
    }
    if h.Len() != 1 {
        t.Errorf("expected len 1, got %d", h.Len())
    }
 
    val, err := h.Peek()
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if val != 42 {
        t.Errorf("expected 42, got %d", val)
    }
}
 
func TestPushMultipleElementsPeekReturnsMin(t *testing.T) {
    h := NewBinaryHeap()
    h.Push(30)
    h.Push(10)
    h.Push(20)
 
    val, err := h.Peek()
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if val != 10 {
        t.Errorf("expected min 10, got %d", val)
    }
}
 
func TestPopReturnsSortedOrder(t *testing.T) {
    h := NewBinaryHeap()
    input := []int{50, 10, 40, 20, 30}
    for _, v := range input {
        h.Push(v)
    }
 
    expected := []int{10, 20, 30, 40, 50}
    for _, want := range expected {
        got, err := h.Pop()
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
        if got != want {
            t.Errorf("expected %d, got %d", want, got)
        }
    }
}
 
func TestPopDecreasesLen(t *testing.T) {
    h := NewBinaryHeap()
    h.Push(1)
    h.Push(2)
    h.Push(3)
 
    h.Pop()
    if h.Len() != 2 {
        t.Errorf("expected len 2, got %d", h.Len())
    }
}
 
func TestIsEmptyAfterPopAll(t *testing.T) {
    h := NewBinaryHeap()
    h.Push(1)
    h.Pop()
 
    if !h.IsEmpty() {
        t.Error("expected heap to be empty after popping all elements")
    }
}
 
func TestPeekDoesNotRemoveElement(t *testing.T) {
    h := NewBinaryHeap()
    h.Push(5)
 
    h.Peek()
    if h.Len() != 1 {
        t.Errorf("expected len 1 after Peek, got %d", h.Len())
    }
}
 
func TestErrorType(t *testing.T) {
    h := NewBinaryHeap()
    _, err := h.Pop()
    if !errors.Is(err, ErrEmptyHeap) {
        t.Errorf("expected ErrEmptyHeap, got %v", err)
    }
}
Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
go test -v
=== RUN   TestPeekOnEmptyHeap
--- PASS: TestPeekOnEmptyHeap (0.00s)
=== RUN   TestPopOnEmptyHeap
--- PASS: TestPopOnEmptyHeap (0.00s)
=== RUN   TestIsEmptyOnNewHeap
--- PASS: TestIsEmptyOnNewHeap (0.00s)
=== RUN   TestLenOnNewHeap
--- PASS: TestLenOnNewHeap (0.00s)
=== RUN   TestPushSingleElement
--- PASS: TestPushSingleElement (0.00s)
=== RUN   TestPushMultipleElementsPeekReturnsMin
--- PASS: TestPushMultipleElementsPeekReturnsMin (0.00s)
=== RUN   TestPopReturnsSortedOrder
--- PASS: TestPopReturnsSortedOrder (0.00s)
=== RUN   TestPopDecreasesLen
--- PASS: TestPopDecreasesLen (0.00s)
=== RUN   TestIsEmptyAfterPopAll
--- PASS: TestIsEmptyAfterPopAll (0.00s)
=== RUN   TestPeekDoesNotRemoveElement
--- PASS: TestPeekDoesNotRemoveElement (0.00s)
=== RUN   TestErrorType
--- PASS: TestErrorType (0.00s)
PASS
ok      github.com/alhaos/problems/dataStructures/binaryHeap    0.338s
Метки datastructure, golang
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Был там один разговор по поводу свободы в материальном мире.
kumehtar 19.08.2026
Суть: рассматривается живое существо, оказавшееся внутри довольно странной системы (этого мира) и пытающееся обустроить в ней свой кусок пространства. Жизнь действительно предъявляет каждому. . .
Когда логика программы не спасает от человеческих ошибок
Maks 18.08.2026
В последнее время всё чаще и чаще сталкиваюсь с таким явлением, как абсолютная невнимательность (или глупость) пользователей. Проявляется это чаще всего на работе в коллективе. Допустим, человек с. . .
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru