mirror of
https://github.com/krahets/hello-algo.git
synced 2026-04-15 10:50:23 +08:00
First version.
This commit is contained in:
503
ru/docs/chapter_backtracking/backtracking_algorithm.md
Normal file
503
ru/docs/chapter_backtracking/backtracking_algorithm.md
Normal file
@@ -0,0 +1,503 @@
|
||||
# Поиск с возвратом
|
||||
|
||||
<u>Алгоритм поиска с возвратом (backtracking algorithm)</u> -- это метод решения задач путем перебора. Его основная идея заключается в том, чтобы, начиная с начального состояния, осуществлять грубый поиск всех возможных решений, фиксируя правильное найденное решение. Процесс поиска продолжается до тех пор, пока не будет найдено решение или не будут исчерпаны все возможные варианты.
|
||||
|
||||
Алгоритмы поиска с возвратом обычно используют поиск в глубину для обхода пространства решений. В разделе «Двоичные деревья» упоминалось, что прямой, симметричный и обратный обходы относятся к поиску в глубину. Далее, используя прямой обход, мы реализуем задачу поиска с возвратом, чтобы постепенно понять принцип работы этого алгоритма.
|
||||
|
||||
!!! question "Пример 1"
|
||||
|
||||
Дано двоичное дерево, найти и записать все узлы со значением $7$, вернуть список узлов.
|
||||
|
||||
Для решения этой задачи мы выполняем предварительный обход дерева и проверяем, равно ли значение текущего узла $7$. Если равно, то добавляем значение этого узла в список результатов `res`. Процесс представлен на следующем рисунке и в коде ниже:
|
||||
|
||||
```src
|
||||
[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order}
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Попытка и возврат
|
||||
|
||||
**Алгоритм называется поиском с возвратом, потому что при поиске в пространстве решений он использует стратегию попытки и возврата**. Когда алгоритм сталкивается с состоянием, в котором невозможно продолжать или невозможно получить удовлетворительное решение, он отменяет предыдущий выбор, возвращается к предыдущему состоянию и пробует другие возможные варианты.
|
||||
|
||||
В примере 1 посещение каждого узла представляет собой попытку, а переход через листовой узел или возврат к родительскому узлу через `return` означает возврат.
|
||||
|
||||
Стоит отметить, что **откат включает не только возврат функции**. Чтобы объяснить это, мы немного расширим пример 1.
|
||||
|
||||
!!! question "Пример 2"
|
||||
|
||||
В двоичном дереве найти все узлы со значением $7$, **вернуть пути от корневого узла до этих узлов**.
|
||||
|
||||
Возьмем за основу код для примера 1. Нам потребуется добавить список `path` для записи пути посещенных узлов. Когда будет найден узел со значением $7$, скопируем `path` и добавим его в список результатов `res`. После завершения обхода `res` будет содержать все решения. Код реализации представлен ниже:
|
||||
|
||||
```src
|
||||
[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order}
|
||||
```
|
||||
|
||||
В каждой попытке мы добавляем текущий узел в `path` для записи пути. Перед возвратом необходимо удалить этот узел из `path`, чтобы **восстановить состояние до этой попытки**.
|
||||
|
||||
Изучив процесс выполнения алгоритма на следующем рисунке, **можно представить попытку и возврат как движение вперед и отмену**, как два противоположных действия.
|
||||
|
||||
=== "<1>"
|
||||

|
||||
|
||||
=== "<2>"
|
||||

|
||||
|
||||
=== "<3>"
|
||||

|
||||
|
||||
=== "<4>"
|
||||

|
||||
|
||||
=== "<5>"
|
||||

|
||||
|
||||
=== "<6>"
|
||||

|
||||
|
||||
=== "<7>"
|
||||

|
||||
|
||||
=== "<8>"
|
||||

|
||||
|
||||
=== "<9>"
|
||||

|
||||
|
||||
=== "<10>"
|
||||

|
||||
|
||||
=== "<11>"
|
||||

|
||||
|
||||
## Обрезка
|
||||
|
||||
Сложные задачи поиска с возвратом обычно содержат одно или несколько ограничений, **которые можно использовать для обрезки**.
|
||||
|
||||
!!! question "Пример 3"
|
||||
|
||||
В двоичном дереве найти все узлы со значением $7$, вернуть пути от корневого узла до этих узлов, **при этом путь не должен содержать узлы со значением $3$**.
|
||||
|
||||
Для выполнения данного условия **требуется добавить операцию обрезки**: в процессе поиска, если встречается узел со значением $3$, следует немедленно вернуться, не продолжая поиск. Код реализации представлен ниже:
|
||||
|
||||
```src
|
||||
[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order}
|
||||
```
|
||||
|
||||
Обрезка является очень наглядным термином. В процессе поиска, как показано на следующем рисунке, **мы обрезаем ветви поиска, не удовлетворяющие заданным условиям**, и избегаем множества бессмысленных попыток, тем самым повышая эффективность поиска.
|
||||
|
||||

|
||||
|
||||
## Каркас кода
|
||||
|
||||
Далее мы попытаемся сформировать основной каркас операций «попытка, возврат, обрезка» для повышения универсальности кода.
|
||||
|
||||
В следующем каркасе кода `state` обозначает текущее состояние задачи, а `choices` -- возможные выборы в текущем состоянии:
|
||||
|
||||
=== "Python"
|
||||
|
||||
```python title=""
|
||||
def backtrack(state: State, choices: list[choice], res: list[state]):
|
||||
"""Каркас алгоритма поиска с возвратом"""
|
||||
# Проверка, является ли состояние решением
|
||||
if is_solution(state):
|
||||
# Запись решения
|
||||
record_solution(state, res)
|
||||
# Не продолжать поиск
|
||||
return
|
||||
# Перебор всех вариантов
|
||||
for choice in choices:
|
||||
# Обрезка: проверка легитимности выбора
|
||||
if is_valid(state, choice):
|
||||
# Попытка: сделать выбор, обновить состояние
|
||||
make_choice(state, choice)
|
||||
backtrack(state, choices, res)
|
||||
# Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undo_choice(state, choice)
|
||||
```
|
||||
|
||||
=== "C++"
|
||||
|
||||
```cpp title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
void backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (Choice choice : choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Java"
|
||||
|
||||
```java title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
void backtrack(State state, List<Choice> choices, List<State> res) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (Choice choice : choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "C#"
|
||||
|
||||
```csharp title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
void Backtrack(State state, List<Choice> choices, List<State> res) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (IsSolution(state)) {
|
||||
// Запись решения
|
||||
RecordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
foreach (Choice choice in choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (IsValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
MakeChoice(state, choice);
|
||||
Backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
UndoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Go"
|
||||
|
||||
```go title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
func backtrack(state *State, choices []Choice, res *[]State) {
|
||||
// Проверка, является ли состояние решением
|
||||
if isSolution(state) {
|
||||
// Запись решения
|
||||
recordSolution(state, res)
|
||||
// Не продолжать поиск
|
||||
return
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for _, choice := range choices {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if isValid(state, choice) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice)
|
||||
backtrack(state, choices, res)
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Swift"
|
||||
|
||||
```swift title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
func backtrack(state: inout State, choices: [Choice], res: inout [State]) {
|
||||
// Проверка, является ли состояние решением
|
||||
if isSolution(state: state) {
|
||||
// Запись решения
|
||||
recordSolution(state: state, res: &res)
|
||||
// Не продолжать поиск
|
||||
return
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for choice in choices {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if isValid(state: state, choice: choice) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state: &state, choice: choice)
|
||||
backtrack(state: &state, choices: choices, res: &res)
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state: &state, choice: choice)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "JS"
|
||||
|
||||
```javascript title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
function backtrack(state, choices, res) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (let choice of choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "TS"
|
||||
|
||||
```typescript title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
function backtrack(state: State, choices: Choice[], res: State[]): void {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (let choice of choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Dart"
|
||||
|
||||
```dart title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
void backtrack(State state, List<Choice>, List<State> res) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (Choice choice in choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
fn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<State>) {
|
||||
// Проверка, является ли состояние решением
|
||||
if is_solution(state) {
|
||||
// Запись решения
|
||||
record_solution(state, res);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for choice in choices {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if is_valid(state, choice) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
make_choice(state, choice);
|
||||
backtrack(state, choices, res);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undo_choice(state, choice);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "C"
|
||||
|
||||
```c title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res, numRes);
|
||||
// Не продолжать поиск
|
||||
return;
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (int i = 0; i < numChoices; i++) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, &choices[i])) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, &choices[i]);
|
||||
backtrack(state, choices, numChoices, res, numRes);
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, &choices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Kotlin"
|
||||
|
||||
```kotlin title=""
|
||||
/* Каркас алгоритма поиска с возвратом */
|
||||
fun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {
|
||||
// Проверка, является ли состояние решением
|
||||
if (isSolution(state)) {
|
||||
// Запись решения
|
||||
recordSolution(state, res)
|
||||
// Не продолжать поиск
|
||||
return
|
||||
}
|
||||
// Перебор всех вариантов
|
||||
for (choice in choices) {
|
||||
// Обрезка: проверка легитимности выбора
|
||||
if (isValid(state, choice)) {
|
||||
// Попытка: сделать выбор, обновить состояние
|
||||
makeChoice(state, choice)
|
||||
backtrack(state, choices, res)
|
||||
// Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undoChoice(state, choice)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== "Ruby"
|
||||
|
||||
```ruby title=""
|
||||
### Каркас алгоритма поиска с возвратом ###
|
||||
def backtrack(state, choices, res)
|
||||
# Проверка, является ли состояние решением
|
||||
if is_solution?(state)
|
||||
# Запись решения
|
||||
record_solution(state, res)
|
||||
return
|
||||
end
|
||||
|
||||
# Перебор всех вариантов
|
||||
for choice in choices
|
||||
# Обрезка: проверка легитимности выбора
|
||||
if is_valid?(state, choice)
|
||||
# Попытка: сделать выбор, обновить состояние
|
||||
make_choice(state, choice)
|
||||
backtrack(state, choices, res)
|
||||
# Возврат: отмена выбора, возврат к предыдущему состоянию
|
||||
undo_choice(state, choice)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Теперь на основе каркаса кода решим пример 3. Состояние `state` -- это путь обхода узлов, выбор `choices` -- это левый и правый дочерние узлы текущего узла, результат `res` -- список путей:
|
||||
|
||||
```src
|
||||
[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack}
|
||||
```
|
||||
|
||||
Согласно условию задачи после нахождения узла со значением $7$ необходимо продолжать поиск, **поэтому следует удалить оператор `return` после записи решения**. На следующем рисунке сравнивается процесс поиска с сохранением и удалением оператора `return`.
|
||||
|
||||

|
||||
|
||||
По сравнению с реализацией на основе предварительного обхода, реализация на основе каркаса поиска с возвратом выглядит более громоздкой, но обладает большей универсальностью. На самом деле **многие задачи поиска с возвратом можно решить в рамках этого каркаса**. Необходимо лишь определить `state` и `choices` в соответствии с конкретной задачей и реализовать методы каркаса.
|
||||
|
||||
## Основные термины
|
||||
|
||||
Для более четкого понимания алгоритмических задач мы систематизируем значения часто используемых терминов обратного поиска и приведем соответствующие примеры для задачи 3, как показано в следующей таблице.
|
||||
|
||||
<p align="center"> Таблица <id> Основные термины обратного поиска </p>
|
||||
|
||||
| Термин | Определение | Пример 3 |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Решение (solution) | Ответ, удовлетворяющий определенным условиям задачи, может быть одно или несколько решений | Все пути от корневого узла до узла $7$, удовлетворяющие условиям |
|
||||
| Ограничение (constraint) | Ограничение на допустимость решения, обычно используется для обрезки | Путь не содержит узлов со значением $3$ |
|
||||
| Состояние (state) | Ситуация задачи в определенный момент, включая сделанные выборы | Текущий посещенный путь узлов, т. е. список узлов `path` |
|
||||
| Попытка (attempt) | Процесс исследования пространства решений на основе доступных выборов, включая выбор, обновление состояния, проверку на решение | Рекурсивный доступ к левому (правому) дочернему узлу, добавление узла в `path`, проверка значения узла на равенство $7$ |
|
||||
| Возврат (backtracking) | Отмена предыдущих выборов и возврат к предыдущему состоянию при встрече состояния, не удовлетворяющего ограничению | При переходе через листовой узел, завершении посещения узла, встрече узла со значением $3$ поиск прекращается, происходит выход из функции |
|
||||
| Обрезка (pruning) | Метод избежания бессмысленных путей поиска на основе особенностей задачи и ограничений, может повысить эффективность поиска | При встрече узла со значением $3$ не продолжать поиск |
|
||||
|
||||
!!! tip
|
||||
|
||||
Понятия задачи, решения, состояния и т. д. являются универсальными и встречаются в алгоритмах «разделяй и властвуй», поиска с возвратом, динамического программирования, жадных алгоритмах и других.
|
||||
|
||||
## Преимущества и ограничения
|
||||
|
||||
Алгоритм поиска с возвратом по своей сути является алгоритмом поиска в глубину, который пытается найти все возможные решения до тех пор, пока не будет найдено удовлетворяющее условиям решение. Преимущество этого метода заключается в том, что он может найти все возможные решения, и при разумной обрезке обладает высокой эффективностью.
|
||||
|
||||
Однако при обработке крупномасштабных или сложных задач **эффективность работы алгоритма поиска с возвратом может быть неприемлемой**.
|
||||
|
||||
- **Время**: Алгоритм поиска с возвратом обычно требует обхода всех возможных состояний пространства состояний, временная сложность может достигать экспоненциального или факториального порядка.
|
||||
- **Пространство**: При рекурсивных вызовах необходимо сохранять текущее состояние (например, путь, вспомогательные переменные для обрезки и т. д.), когда глубина очень велика, требования к пространству могут стать очень большими.
|
||||
|
||||
Несмотря на это, **алгоритм поиска с возвратом остается лучшим решением для некоторых задач поиска и задач удовлетворения ограничений**. Для этих задач, поскольку невозможно предсказать, какие выборы приведут к правильному решению, необходимо перебрать все возможные варианты. В этом случае **ключевым является оптимизация эффективности**, обычно используются два метода оптимизации эффективности.
|
||||
|
||||
- **Обрезка**: Избежание поиска путей, которые определенно не приведут к решению, тем самым экономя время и пространство.
|
||||
- **Эвристический поиск**: Введение некоторых стратегий или оценочных значений в процессе поиска для приоритетного поиска путей, которые с наибольшей вероятностью приведут к правильному решению.
|
||||
|
||||
## Типичные примеры задач поиска с возвратом
|
||||
|
||||
Алгоритм поиска с возвратом может использоваться для решения многих задач поиска, задач удовлетворения ограничений и задач комбинаторной оптимизации.
|
||||
|
||||
**Задачи поиска**: Цель этих задач -- найти решение, удовлетворяющее определенным условиям.
|
||||
|
||||
- Задача о полной перестановке: Дано множество, найти все возможные перестановки и комбинации.
|
||||
- Задача о сумме подмножества: Дано множество и целевая сумма, найти все подмножества множества, сумма которых равна целевой сумме.
|
||||
- Задача о Ханойской башне: Даны три стержня и серия дисков разного размера, требуется переместить все диски с одного стержня на другой, перемещая за раз только один диск, и нельзя класть большой диск на маленький.
|
||||
|
||||
**Задачи удовлетворения ограничений**: Цель этих задач -- найти решение, удовлетворяющее всем ограничениям.
|
||||
|
||||
- $n$ ферзей: На шахматной доске $n \times n$ разместить $n$ ферзей так, чтобы они не атаковали друг друга.
|
||||
- Судоку: В сетке $9 \times 9$ заполнить числа от $1$ до $9$ так, чтобы в каждой строке, каждом столбце и каждой подсетке $3 \times 3$ числа не повторялись.
|
||||
- Задача о раскраске графа: Дан неориентированный граф, раскрасить каждую вершину графа минимальным количеством цветов так, чтобы смежные вершины имели разные цвета.
|
||||
|
||||
**Задачи комбинаторной оптимизации**: Цель этих задач -- найти оптимальное решение в комбинаторном пространстве, удовлетворяющее определенным условиям.
|
||||
|
||||
- Задача о рюкзаке 0-1: Дан набор предметов и рюкзак, каждый предмет имеет определенную ценность и вес, требуется выбрать предметы в пределах ограничения вместимости рюкзака так, чтобы общая ценность была максимальной.
|
||||
- Задача коммивояжера: В графе, начиная с одной точки, посетить все остальные точки ровно один раз и вернуться в начальную точку, найти кратчайший путь.
|
||||
- Задача о максимальной клике: Дан неориентированный граф, найти максимальный полный подграф, т. е. подграф, в котором между любыми двумя вершинами есть ребро.
|
||||
|
||||
Обратите внимание, что для многих задач комбинаторной оптимизации поиск с возвратом не является оптимальным решением.
|
||||
|
||||
- Задача о рюкзаке 0-1 обычно решается с помощью динамического программирования для достижения более высокой временной эффективности.
|
||||
- Задача коммивояжера -- это известная NP-трудная задача, обычно используются генетические алгоритмы, алгоритмы муравьиной колонии и другие методы решения.
|
||||
- Задача о максимальной клике -- это классическая задача теории графов, может быть решена с помощью жадных алгоритмов и других эвристических методов.
|
||||
Reference in New Issue
Block a user