Приёмная комиссия 2024

Что такое рекурсия в программировании и как не ошибиться

Что такое рекурсия в программировании и как не ошибиться
Содержание

Рекурсия — это мощный инструмент в программировании, который позволяет функции вызывать саму себя для решения задач, которые можно разбить на аналогичные по структуре подзадачи. Этот метод широко используется в таких языках программирования, как Python, JavaScript, Java, C++ и C#, чтобы эффективно решать сложные проблемы, например, обходить деревья и графы. Несмотря на свою полезность, рекурсия может привести к ошибкам, таким как бесконечные циклы и переполнение стека. Что такое рекурсия, как использовать её в разных языках программирования и как избежать типичных ошибок — расскажем в статье.

Подберите программу обучения и начните учиться бесплатно

Оставьте заявку и мы откроем бесплатный доступ к вводной части обучения

Что такое рекурсия

В программировании рекурсия — это метод, при котором функция вызывает саму себя непосредственно или косвенно (через другую функцию), чтобы решить задачу. Она активно применяется в разных языках программирования для обработки задач, которые можно разделить на подзадачи, аналогичные первоначальной.

Принципы рекурсии:

  1. Базовый случай (base case): Условие, при котором рекурсивные вызовы прекращаются.
  2. Рекурсивный случай (recursive case): Часть функции, где задача разбивается на более простую подзадачу, и функция вызывает себя для её решения.

Примеры рекурсии в разных языках программирования:

Python

def factorial (n):
if n == 0:
return 1
else:
return n * factorial (n-1)
print (factorial (5)) # Вывод: 120

JavaScript

function factorial (n) {
if (n === 0) {
return 1;
} else {
return n * factorial (n — 1);
}
}

console.log (factorial (5)); // Вывод: 120

C++

#include <iostream>
using namespace std;

int factorial (int n) {
if (n == 0) {
return 1;
} else {
return n * factorial (n — 1);
}
}

int main () {
cout << factorial (5) << endl; // Вывод: 120
return 0;
}


Зачем она нужна

Рекурсия — это мощный инструмент, который, при правильном применении, может значительно упростить решение сложных задач. Она позволяет эффективно работать с рекурсивными структурами данных и использовать алгоритмы «разделяй и властвуй».

Рекурсия играет важную роль в программировании по нескольким причинам:

1. Решение задач, которые естественно рекурсивны:

Некоторые задачи легче решать с помощью рекурсии. Например:

  • Факториал числа: Легко определить как n≠nx (n−1)!.
  • Числа Фибоначчи: Легко определить как F (n)=F (n−1)+F (n−2).

2. Упрощение сложных задач:

Рекурсия позволяет разбивать сложные задачи на более простые и понятные подзадачи, которые легче анализировать и решать. Это делает алгоритм более простым и лёгким для понимания.

  • Обход деревьев: Рекурсивные функции облегчают обход бинарных деревьев (например, обход в глубину или ширину).
  • Обход графов: Поиск в глубину (DFS) реализуется просто с помощью рекурсии.

3. Алгоритмы «разделяй и властвуй»:

Рекурсия часто применяется в алгоритмах, которые делят сложную задачу на более простые задачи того же типа.

  • Сортировка слиянием (Merge Sort): Разделяет массив на две части, сортирует их и объединяет.
  • Быстрая сортировка (Quick Sort): Разделяет массив на части, меньшие и большие по отношению к опорному элементу, и сортирует их.

4. Работа с рекурсивными структурами данных:

Некоторые структуры данных, такие как деревья и связанные списки, что делает их обработку с помощью рекурсии естественной и эффективной.

  • Связанные списки: Легко обходятся и изменяются рекурсивно.
  • Деревья и деревоподобные структуры: Например, вычисление высоты дерева или проверка его сбалансированности.

5. Элегантность и читаемость кода:

Рекурсивные решения могут быть более компактными и интуитивно понятными, чем их итеративные аналоги.

  • Чтение и изменение вложенных структур данных: Например, вложенные списки или объекты.

Чем отличается от цикла

Критерий

Циклы

Рекурсия

Принцип работы

Повторение блока кода, пока выполняется условие

Функция вызывает сама себя до достижения базового случая

Использование памяти

Постоянное, выделяется один блок памяти

Каждый рекурсивный вызов создаёт новый фрейм в стеке вызовов, что увеличивает использование памяти.

Производительность

Более эффективен по времени и памяти

Менее эффективен из-за накладных расходов на каждый вызов функции

Простота понимания

Легче понять и использовать для простых задач

Может быть сложнее для понимания, особенно при неправильном определении базового случая

Применимость

Подходит для итеративных задач и простых повторяющихся процессов

Подходит для задач, которые имеют рекурсивную природу, таких как обход деревьев и графов

Преимущества

Читаемость кода

Высокая читаемость для простых задач

Высокая читаемость для задач, решаемых естественным образом с помощью рекурсии

Эффективность памяти

Экономное использование памяти

Могут привести к переполнению стека вызовов при глубокой рекурсии

Простота отладки

Легче отлаживать итеративный код

Могут быть сложнее для отладки из-за множества вызовов функции

Недостатки

Ограничения использования

Могут быть неудобны или неэффективны для задач, требующих сложных рекурсивных решений

Риск переполнения стека при глубокой рекурсии

Производительность

Хорошая производительность

Могут быть менее эффективны по времени выполнения

Сложность реализации

Простая реализация для большинства задач

Может быть сложнее реализовать правильно, требует внимательного определения базового и рекурсивного случаев

Как прервать рекурсию

В языках программирования рекурсия прерывается за счёт правильного определения базового случая, когда рекурсивные вызовы перестают выполняться. В некоторых случаях также можно использовать дополнительные условия или возвращать специальные значения, чтобы досрочно завершить выполнение функции или изменить её поведение. Давайте рассмотрим примеры для разных языков программирования:

Python

В Python рекурсию прерывают с помощью условия, которое проверяется перед выполнением рекурсивного вызова.

def factorial (n):
if n == 0: # Базовый случай
return 1
else:
return n * factorial (n — 1)
print (factorial (5)) # Вывод: 120

JavaScript

В JavaScript также используют условие, которое проверяется перед выполнением рекурсивного вызова.

function factorial (n) {
if (n === 0) { // Базовый случай
return 1;
} else {
return n * factorial (n — 1);
}
}

console.log (factorial (5)); // Вывод: 120

C++

В C++ рекурсию прерывают с помощью условия, которое проверяется перед выполнением рекурсивного вызова.

#include <iostream>

using namespace std;

int factorial (int n) {
if (n == 0) { // Базовый случай
return 1;
} else {
return n * factorial (n — 1);
}
}

int main () {
cout << factorial (5) << endl; // Вывод: 120
return 0;
}

Как работать с рекурсией

Работа с рекурсией в разных языках программирования основана на общих принципах, но каждый язык имеет свои особенности. В этой главе мы рассмотрим, как пошагово написать рекурсивную функцию на примере вычисления факториала числа.

Общие шаги:

Определите базовый случай (условие завершения): Это условие, при котором рекурсивные вызовы функции прекращаются.

Определите рекурсивный случай: Это часть функции, в которой происходит рекурсивный вызов с уменьшенной задачей.

Убедитесь в корректности условий: проверьте, чтобы базовый случай достигался и рекурсивные вызовы двигались в сторону этого случая.

Тестируйте функцию: убедитесь, что функция работает правильно для различных входных значений, включая крайние случаи.

Примеры на разных языках программирования:

Python

  1. Определение базового и рекурсивного случаев:
  • Базовый случай: n == 0, возвращает 1.
  • Рекурсивный случай: n * factorial (n — 1).

def factorial (n):
if n == 0: # Базовый случай
return 1
else:
return n * factorial (n — 1) # Рекурсивный случай

# Тестирование
print (factorial (5)) # Вывод: 120
print (factorial (0)) # Вывод: 1

JavaScript

  1. Определение базового и рекурсивного случаев:
  • Базовый случай: n === 0, возвращает 1.
  • Рекурсивный случай: n * factorial (n — 1).

function factorial (n) {
if (n === 0) { // Базовый случай
return 1;
} else {
return n * factorial (n — 1); // Рекурсивный случай
}
}

// Тестирование
console.log (factorial (5)); // Вывод: 120
console.log (factorial (0)); // Вывод: 1

C++

  1. Определение базового и рекурсивного случаев:
  • Базовый случай: n == 0, возвращает 1.
  • Рекурсивный случай: n * factorial (n — 1).

#include <iostream>
using namespace std;

int factorial (int n) {
if (n == 0) { // Базовый случай
return 1;
} else {
return n * factorial (n — 1); // Рекурсивный случай
}
}

int main () {
// Тестирование
cout << factorial (5) << endl; // Вывод: 120
cout << factorial (0) << endl; // Вывод: 1
return 0;
}

Пошаговое руководство для работы с рекурсией:

  1. Определите, подходит ли задача для рекурсивного решения.
  2. Выберите базовый случай:
    • Убедитесь, что базовый случай корректно определяет условие для завершения рекурсии и легко разрешим.
    • Пример: для факториала n == 0 возвращает 1.
  3. Определите рекурсивный случай:
    • Убедитесь, что каждый рекурсивный вызов уменьшает размер задачи и приближает её к базовому случаю.
    • Пример: для факториала n * factorial (n — 1).
  4. Напишите функцию, которая включает правильно определённые базовый и рекурсивный случаи.
  5. Проверьте, что функция достигает базового случая и возвращает правильные значения для различных входных данных.
  6. Оптимизируйте, если необходимо:
    • Для глубокой рекурсии подумайте об использовании хвостовой рекурсии или итеративного решения.


Примеры использования в проектах

Примечание: примеры на языке Python

Рекурсия — это мощный инструмент, который находит применение во многих областях программирования. Её способность разбивать задачи на более мелкие подзадачи делает её незаменимой. Для большей наглядности рассмотрим несколько примеров использования рекурсии в реальных проектах.

  1. Обход и обработка деревьев и графов

    Обход бинарного дерева

    Рекурсия часто используется для обхода структур данных, таких как бинарные деревья (например, бинарные деревья поиска или бинарные деревья разбора).

    class Node:
    def __init__(self, value):
    self. value = value
    self. left = None
    self. right = None

    def inorder_traversal (root):
    if root:
    inorder_traversal (root.left)
    print (root.value)
    inorder_traversal (root.right)

    # Пример использования
    root = Node (10)
    root.left = Node (5)
    root.right = Node (15)
    inorder_traversal (root) # Вывод: 5 10 15

  2. Алгоритмы поиска и сортировки

    Быстрая сортировка (Quick Sort)

    Быстрая сортировка — это популярный рекурсивный алгоритм сортировки, основанный на принципе «разделяй и властвуй».

    def quick_sort (arr):
    if len (arr) <= 1:
    return arr
    pivot = arr[len (arr) // 2]
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort (left) + middle + quick_sort (right)

    # Пример использования
    arr = [3, 6, 8, 10, 1, 2, 1]
    print (quick_sort (arr)) # Вывод: [1, 1, 2, 3, 6, 8, 10]

  3. Решение задач оптимизации

    Задача о рюкзаке (Knapsack Problem)

    Рекурсия часто применяется для решения задач оптимизации, например, задачи о рюкзаке, с использованием динамического программирования, для улучшения производительности.

    def knapsack (w, wt, val, n):
    if n == 0 or w == 0:
    return 0
    if wt[n-1] > w:
    return knapsack (w, wt, val, n-1)
    else:
    return max (
    val[n-1] + knapsack (w-wt[n-1], wt, val, n-1),
    knapsack (w, wt, val, n-1)
    )

    # Пример использования
    val = [60, 100, 120]
    wt = [10, 20, 30]
    w = 50
    n = len (val)
    print (knapsack (w, wt, val, n)) # Вывод: 220

  4. Решение головоломок и игр

    Ханойские башни

    Классическая головоломка, которая часто решается с помощью рекурсии.

    def hanoi (n, source, target, auxiliary):
    if n == 1:
    print (f"Переместить диск 1 с { source} на { target}")
    return
    hanoi (n-1, source, auxiliary, target)
    print (f"Переместить диск { n} с { source} на { target}")
    hanoi (n-1, auxiliary, target, source)

    # Пример использования
    hanoi (3, 'A', 'C', 'B')


    Рекурсия — это полезный инструмент при решении задач, которые можно разделить на подзадачи того же типа. Она позволяет упростить код и сделать его более понятным, особенно если задача естественным образом допускает рекурсивное разбиение.

Бонус!

Чтобы понять, насколько полезна рекурсия, рассмотрим пример пяти компаний, которые применяют её в своих проектах.

  1. Google
    • Поисковые алгоритмы: Алгоритмы обхода и индексации веб-страниц (например, Googlebot) часто используют рекурсивные методы для обхода графов ссылок.
    • Обработка естественного языка (NLP): Рекурсивные нейронные сети (RNN) и другие рекурсивные структуры используются для синтаксического анализа и обработки текстов.
  2. IBM
    • Искусственный интеллект и Watson: Рекурсивные нейронные сети и другие рекурсивные алгоритмы для обработки естественного языка, анализа данных и машинного обучения.
  3. Amazon
    • Рекомендательные системы: Использование рекурсивных моделей для анализа поведения пользователей и рекомендаций продуктов.
    • Облачные сервисы (AWS): Рекурсивные подходы для обработки данных и управления инфраструктурой.
  4. Netflix
    • Рекомендательные алгоритмы:Рекурсивные алгоритмы используются для анализа предпочтений пользователей и персонализации контента.
    • Потоковая передача данных: Управление сетью и распределение контента с использованием рекурсивных методов.
  5. Microsoft
    • Поисковая система Bing:Рекурсивные методы для обхода и индексации веб-страниц.
    • Системы безопасности: Обнаружение вредоносных программ и анализ данных с использованием рекурсивных алгоритмов.

Типичные ошибки и как их исправить

Работа с рекурсией может привести к различным ошибкам, особенно если базовые и рекурсивные случаи не определены или обработаны правильно.

Python

Ошибка: Отсутствие базового случая

Если не определить базовый случай в рекурсивной функции, рекурсивные вызовы будут продолжаться бесконечно, что приведет к переполнению стека и вызову ошибки RecursionError: maximum recursion depth exceeded.

def factorial (n):
return n * factorial (n — 1) # Базовый случай отсутствует

print (factorial (5)) # Приведет к переполнению стека

Исправление:

def factorial (n):
if n == 0: # Базовый случай
return 1
else:
return n * factorial (n — 1)

print (factorial (5)) # Вывод: 120

Ошибка: Неправильное определение базового случая

Если базовый случай определен неверно, рекурсивная функция может возвращать неправильные результаты или привести к неожиданным ошибкам, таким как бесконечная рекурсия.

def factorial (n):
if n == 1: # Неправильный базовый случай
return 1
else:
return n * factorial (n — 1)

print (factorial (0)) # Приведет к ошибке

Исправление:

def factorial (n):
if n == 0: # Правильный базовый случай
return 1
else:
return n * factorial (n — 1)

print (factorial (0)) # Вывод: 1

JavaScript

Ошибка: Отсутствие условия выхода при обработке массива

Если не предусмотреть условие выхода, рекурсия будет продолжаться бесконечно, что приведет к переполнению стека и вызову ошибки RangeError: Maximum call stack size exceeded.

function sumArray (arr, index) {
return arr[index] + sumArray (arr, index + 1); // Условие выхода отсутствует
}

let arr = [1, 2, 3, 4, 5];
console.log (sumArray (arr, 0)); // Приведет к переполнению стека

Исправление:

function sumArray (arr, index) {
if (index >= arr. length) { // Условие выхода
return 0;
} else {
return arr[index] + sumArray (arr, index + 1);
}
}

let arr = [1, 2, 3, 4, 5];
console.log (sumArray (arr, 0)); // Вывод: 15

C++

Ошибка 4: Изменение параметра вместо передачи нового значения

Если изменять параметр напрямую, это может привести к неправильному поведению рекурсивной функции и потенциальным ошибкам времени выполнения.

#include <iostream>
using namespace std;

void printDescending (int n) {
cout << n << ««;
n--; // Неправильное изменение параметра
if (n > 0) {
printDescending (n);
}
}

int main () {
printDescending (5); // Вывод: 5 (некорректный вывод)
return 0;
}

Исправление:

#include <iostream>
using namespace std;

void printDescending (int n) {
cout << n << ««;
if (n > 1) { // Условие выхода
printDescending (n — 1);
}
}

int main () {
printDescending (5); // Вывод: 5 4 3 2 1
return 0;
}

Ошибка: Неинициализированное значение в базовом случае

Если не инициализировать значение в базовом случае, это может привести к непредсказуемому поведению.

#include <iostream>
using namespace std;

int fibonacci (int n) {
if (n <= 2) {
return; // Нет возвращаемого значения
} else {
return fibonacci (n — 1) + fibonacci (n — 2);
}
}

int main () {
cout << fibonacci (5) << endl; // Приведет к неопределенному поведению
return 0;
}

Исправление:

#include <iostream>
using namespace std;

int fibonacci (int n) {
if (n <= 2) {
return 1; // Правильное возвращаемое значение
} else {
return fibonacci (n — 1) + fibonacci (n — 2);
}
}

int main () {
cout << fibonacci (5) << endl; // Вывод: 5
return 0;
}


Главное, что нужно знать

  • Рекурсия — это метод программирования, при котором функция вызывает саму себя для решения подзадач. Этот подход используется, когда задача может быть разбита на более простые и аналогичные подзадачи.
  • Каждый рекурсивный алгоритм должен иметь базовый случай — условие завершения, при котором рекурсивные вызовы прекращаются. Без этого условия функция будет вызывать саму себя бесконечно, что может привести к переполнению стека. Например, для вычисления факториала базовым случаем будет n = 0, возвращающее 1.
  • Рекурсивные вызовы требуют дополнительной памяти для каждого уровня рекурсии, так как каждый вызов создаёт новый фрейм в стеке вызовов. При слишком глубокой рекурсии это может привести к переполнению стека. В отличие от этого, итеративные алгоритмы используют фиксированный объём памяти.
  • Рекурсия часто упрощает решение задач, которые имеют естественную рекурсивную структуру, например, обход деревьев, графов и разделение и завоевание. Рекурсивный код может быть более понятным и читаемым, особенно для сложных задач.
  • Рекурсивные функции могут быть менее эффективными по времени и памяти по сравнению с итеративными аналогами из-за накладных расходов на каждый вызов функции. Однако некоторые компиляторы и интерпретаторы могут оптимизировать хвостовую рекурсию, что позволяет уменьшить накладные расходы и предотвратить переполнение стека.

Подберите программу обучения и начните учиться бесплатно

Оставьте заявку и мы откроем бесплатный доступ к вводной части обучения

alt

Всё для учебы доступно онлайн

Расписание, зачётку и домашние задания смотрите в приложении
Подберите программу обучения

ответьте на пять вопросов и узнайте, где будете учиться

Образование для карьеры
К каким профессиям вы более склонны?
ТехническимГуманитарнымТворческимМедицинским
Какой у вас уровень образования?
Без образованияШкола 9-11 классКолледжБакалавриатМагистратураАспирантура
Какой формат обучения вам подходит?
ОчноЗаочноОнлайнПо выходным дням
Интересует ли вас кредит на образование по ставке 3% в год?
ДаНет

Мы подобрали для вас программу обучения

Заполните форму, чтобы узнать больше о программе и наших предложениях

Подобрать программу и поступить

Политика конфиденциальности

Ваша конфиденциальность очень важна для нас. Мы хотим, чтобы Ваша работа в Интернет по возможности была максимально приятной и полезной, и Вы совершенно спокойно использовали широчайший спектр информации, инструментов и возможностей, которые предлагает Интернет. Персональные данные, собранные при регистрации (или в любое другое время) преимущественно используется для подготовки Продуктов или Услуг в соответствии с Вашими потребностями. Ваша информация не будет передана или продана третьим сторонам. Однако мы можем частично раскрывать личную информацию в особых случаях, описанных в данной Политике конфиденциальности.

Рамки Политики конфиденциальности

Настоящая Политика конфиденциальности (далее — «Политика») применяется к информации, полученной через данный сайт, иные сайты, виджеты и другие используемые интерактивные средства, на которых есть ссылка на данную Политику (далее — «Сайт») от пользователей Сайта (далее — «Пользователи»).

Нижеследующие правила описывают, как Университет «Синергия» обращается с любой информацией, относящейся к прямо или косвенно определенному или определяемому физическому лицу (субъекту персональных данных) (далее — «Персональные данные»), для целей оказания услуг с использованием Сайта.

Пользователи включают в себя всех физических лиц, которые подключаются к Сайту и используют Сайт.

Пользователи прямо соглашаются на обработку своих Персональных данных, как это описано в настоящей Политике. Обработка означает любое действие (операцию) или совокупность действий (операций), совершаемых с использованием средств автоматизации или без использования таких средств с Персональными данными, включая сбор, запись, систематизацию, накопление, хранение, уточнение (обновление, изменение), извлечение, использование, передачу (распространение, предоставление, доступ), блокирование, удаление, уничтожение Персональных данных.

Настоящая Политика конфиденциальности вступает в силу с момента ее размещения на Сайте, если иное не предусмотрено новой редакцией Политики конфиденциальности.

Контролирующие и обрабатывающие лица

Пользователи соглашаются с тем, что:

  • Пользуясь Сайтом, и принимая условия использования, опубликованные на Сайте, пользователь заявляет о своем однозначном согласии с обработкой его Персональных данных способами, описанными в настоящей Политике.
  • Обработка Персональных данных Пользователей осуществляется Оператором персональных данных — Университет «Синергия» (ИНН: 7729152149, ОГРН: 1037700232558).

С какой целью собираются эти данные

Имя используется для обращения лично к вам, а ваш e-mail для отправки вам писем рассылок, новостей тренинга, полезных материалов, коммерческих предложений. Вы можете отказаться от получения писем рассылки и удалить из базы данных свои контактные данные в любой момент, кликнув на ссылку для отписки, присутствующую в каждом письме.

Сбор Персональных данных

При регистрации на Сайте Пользователи подтверждают свое согласие с условиями настоящей Политики и свое согласие на обработку своих Персональных данных в соответствии с условиями настоящей Политики, кроме того они соглашаются на обработку своих Персональных данных на серверах Университета «Синергия», расположенных на территории Российской Федерации.

Обработка Персональных данных осуществляется не дольше, чем этого требуют цели обработки Персональных данных, изложенные в настоящей Политике (за исключением случаев, предусмотренных законодательством Российской Федерации). Университет «Синергия» может обрабатывать следующие Персональные данные:

  • «Как к Вам обращаться» в форме обратной связи, в случае если посетитель указывает свои полные ФИО или только часть;
  • Электронный адрес;
  • Номер телефона;
  • Также на сайте происходит сбор и обработка обезличенных данных о посетителях (в т. ч. файлов «cookie») с помощью сервисов интернет-статистики (Яндекс Метрика и других).
  • Вышеперечисленные данные далее по тексту Политики объединены общим понятием Персональные данные.

Как эти данные используются

На сайте используются куки (Cookies) и данные о посетителях сервисов (Яндекс Метрика и других). При помощи этих данных собирается информация о действиях посетителей на сайте с целью улучшения его содержания, улучшения функциональных возможностей сайта и, как следствие, создания качественного контента и сервисов для посетителей. Вы можете в любой момент изменить настройки своего браузера так, чтобы браузер блокировал все файлы cookie или оповещал об отправке этих файлов. Учтите при этом, что некоторые функции и сервисы не смогут работать должным образом.

Как эти данные защищаются

Для защиты Вашей личной информации мы используем разнообразные административные, управленческие и технические меры безопасности. Наша Компания придерживается различных международных стандартов контроля, направленных на операции с личной информацией, которые включают определенные меры контроля по защите информации, собранной в Интернет. Наших сотрудников обучают понимать и выполнять эти меры контроля, они ознакомлены с нашим Уведомлением о конфиденциальности, нормами и инструкциями. Тем не менее, несмотря на то, что мы стремимся обезопасить Вашу личную информацию, Вы тоже должны принимать меры, чтобы защитить ее. Мы настоятельно рекомендуем Вам принимать все возможные меры предосторожности во время пребывания в Интернете. Организованные нами услуги и веб-сайты предусматривают меры по защите от утечки, несанкционированного использования и изменения информации, которую мы контролируем. Несмотря на то, что мы делаем все возможное, чтобы обеспечить целостность и безопасность своей сети и систем, мы не можем гарантировать, что наши меры безопасности предотвратят незаконный доступ к этой информации хакеров сторонних организаций.

В случае изменения данной политики конфиденциальности вы сможете прочитать об этих изменениях на этой странице или, в особых случаях, получить уведомление на свой e-mail.

Политика в отношении обработки персональных данных.pdf

В случае изменения данной политики конфиденциальности вы сможете прочитать об этих изменениях на этой странице или, в особых случаях, получить уведомление на свой e-mail.

Jivo

DMCA.com Protection Status