Алгоритмы сортировки. Метод пузырька
Данный алгоритм является достаточно простым и поэтому получил широкое распространение. Вычислительная сложность алгоритма квад-ратичная – O(n2), поэтому алгоритм эффективен только на небольших массивах данных.
Алгоритм проходит все элементы массива и попарно сравнивает их друг с другом. Если порядок сравниваемых элементов неверный, алго-ритм меняет элементы местами:
// Сортировка пузырьком
105
void BubbleSort(ref int[] Array)
{
// Перебираем элементы массива (без последнего) for (int i = 0; i < Array.Length – 1; i++)
// Перебираем все элементы справа от i
for (int j = i + 1; j < Array.Length; j++)
// Правильный ли порядок элементов? if (Array[i] > Array[j])
{
// Нет – меняем порядок int t = Array[i]; Array[i] = Array[j]; Array[j] = t;
}
}
Сортировка выбором
Сортировка выбором имеет квадратичную сложность O(n2) и, как
и предыдущий метод пузырька, эффективен лишь на небольших объе-мах данных.
Алгоритм находит номер минимального значения в текущем спи-ске, меняет этот элемент со значением первой неотсортированной по-зиции (если минимальный элемент не находится на данной позиции), а затем сортирует хвост списка, исключив из рассмотрения уже отсор-тированные элементы:
// Сортировка выбором
void SelectionSort(ref int[] Array)
{
// Перебираем все элементы массива (безпоследнего)
// i – позиция первого неотсортированного элемента for (int i = 0; i < Array.Length – 1; i++)
{
// Позиция минимального элемента справа от i int min = i;
|
|
// Перебираем все элементы справа от i
for (int j = i + 1; j < Array.Length; j++)
// Меньше ли очередной элемент минимального? if (Array[j] < Array[min])
// Да – теперь это минимальный элемент min = j;
// Минимальный элемент не первый?
// Меняем местами!
if (min != i)
{
106
int t = Array[i];
Array[i] = Array[min];
Array[min] = t;
}
}
}
Быстрая сортировка
Алгоритм быстрой сортировки является одним из самых быстрых алгоритмов сортировки : в лучшем случае он имеет логарифмическую сложность, в худшем – квадратичную. Алгоритм выполняется следую-щим образом:
1. Выбирается некоторый элемент, который называется опорным.
2. Реорганизуем массив таким образом, чтобы все элементы, меньшие или равные опорному элементу, оказались слева от него, а все эле-менты, больше опорного, – справа от него.
3. Рекурсивно упорядочиваем массивы, лежащие слева и справа от опорного элемента.
// Быстрая сортировка
void QuickSort(ref int[] Array, int Left, int Right)
{
// i и j – индексы границ разделяемого массива int i = Left;
int j = Right;
// x – индекс опорного элемента
int x = Array[(Left + Right) / 2];
do
{
// Ищем элемент слева, который больше опорного while (Array[i] < x)
++i;
// Ищем элемент справа, который меньше опорного while (Array[j] > x)
|
|
‐‐j;
// Если индексы не поменялись местами,
// то обмениваем элементы
if (i <= j)
{
int t = Array[i];
Array[i] = Array[j];
Array[j] = t;
i++;
j‐‐;
}
107
} while (i <= j);
// Рекурсивно выполняем быструю сортировку
// для массивов слева и справа
if (Left < j)
QuickSort(ref Array, Left, j); if (i < Right)
QuickSort(ref Array, i, Right);
}
Поиск элемента
Алгоритмы поиска позволяют найти индекс элемента с требуемым значением.
Если массив не упорядочен, то возможен лишь простой поиск: пе-ребор всех элементов массива до тех пор, пока не встретится элемент с нужным значением или не закончится массив. Если элемент найден, по-иск должен быть прекращен, поскольку дальнейший просмотр массива не имеет смысла:
// Простой поиск элемента в массиве int IndexOf(ref int[] Array, int Value)
{
// Перебираем все элементы массива for (int i = 0; i < Array.Length; i++)
// Нашли нужное значение? Возвращаем его индекс if (Array[i] == Value)
return i;
// Перебор закончился безрезультатно – возвращаем –1 return –1;
}
Если алгоритм поиска не нашел подходящий элемент, он должен каким-то образом сигнализировать об этом вызывающей программе. Чаще всего в таком случае возвращается значение –1 – число, которое заведомо не может использоваться в качестве индекса массива.
|
|
Вычислительная сложность алгоритма простого поиска – линей-ная O(n).
Если массив упорядочен по возрастанию, то возможно использова-ние дихотомического рекурсивного алгоритма: массив каждый раз де-лится пополам, и если искомый элемент меньше среднего, то поиск продолжается в левой его половине, иначе – в правой:
// Дихотомический поиск элемента в массиве int IndexOf(ref int[] Array, int Value,
int Left, int Right)
108
{
// Находим середину диапазона int x = (Left + Right) / 2;
// Если нашли значение – возвращаем его индекс if (Array[x] == Value)
return x;
// Если середина совпадает с левой или
// правой границами – значение не найдено
if ((x == Left) || (x == Right))
return –1;
// Продолжаем поиск слева или справа от середины if (Array[x] < Value)
return IndexOf(ref Array, Value, x, Right); else
return IndexOf(ref Array, Value, Left, x);
}
Вычислительная сложность алгоритма – логарифмическая.
Индивидуальное задание
Общая часть задания: сформировать массив из 100 случайных чи-сел. Выполнить простой поиск элемента, подсчитать количество итера-ций. Отсортировать массив всеми рассмотренными методами и посчи-тать количество итерация для каждого метода. Выполнить поиск элемента методом дихотомии, подсчитать количество итераций. Сде-лать выводы.
|
|
109
ИНДИВИДУАЛЬНЫЕ ЗАДАНИЯ
ПОВЫШЕННОЙ СЛОЖНОСТИ
Для решения геометрических задач повышенной сложности необ-ходимо:
1) знать, как представляются на плоскости такие геометрические объ-екты, как точка, прямая, отрезок и окружность;
2) уметь находить уравнение прямой, соединяющей две заданные точки;
3) уметь определять координаты точки пересечения двух прямых;
4) знать, как провести перпендикуляр к прямой или определить, яв-ляются ли прямые параллельными;
5) уметь находить скалярное и векторное произведения;
6) находить площадь многоугольника;
7) уметь работать с фигурами на плоскости.
Напомним основные моменты, связанные с этими понятиями. Каждую точку плоскости можно считать вектором с началом в точ-
ке (0, 0). Обозначим через a = (x, y) вектор с координатами (x, y). Длина вектора (его модуль) вычисляется по формуле a = x 2 + y2 .
Скалярное произведение двух векторов–это число,равное про-изведению модулей этих векторов на косинус угла между ними, (a,b) = |a| · |b | · cos φ. Если вектор a имеет координаты (x1, y1), а вектор b координаты – (x2, y2), то скалярное произведение вычисляется по фор-муле ( a,b) = x1 · x2 + y1 · y2.
y
y1
a
y2
φ b
x1 x2 x
Рис . 16.1. Иллюстрация к скалярному произведению векторов
110
Заметим, что если угол φ острый, то скалярное произведение (a, b) > 0, если угол φ тупой, то (a, b) < 0. Если два вектора перпендику-лярны, то их скалярное произведение равно нулю.
Векторным произведением двух векторовaиbназывается вектор[a × b], такой, что
· длина его равна |[a × b]| = |a| · |b| · sin φ;
· вектор [a × b] перпендикулярен векторам a и b;
· вектор [a × b] направлен так, что из его конца кратчайший поворот от a к b виден происходящим против часовой стрелки.
Длина векторного произведения равна площади параллелограмма,
построенного на векторах a и b.
Через координаты векторов a и b векторное произведение выража-ется следующим образом:
i j k
[a × b] = x1 y1 z1= (y1· z2– z1· y2) i + (x1· z2– z1· x2) j + (x1· y2– y1· x2) k,
x2 y2 z2
где i, j, k – единичные вектора осей Ox, Oy, Oz соответственно.
При решении задач на плоскости координаты z1 и z2 равны нулю.
В этом случае [a × b] = (x1 · y2 – x2 · y1)· k.
Если вектор a образует с осью Ох угол α, а вектор b – угол β, то для ска-лярного произведения справедлива формула [a × b] = (|a| · |b| · sin(β – α))· k. Это означает, что для ненулевых векторов векторное произведение равно нулю тогда и только тогда, когда векторы параллельны. Если поворот от вектора а к вектору b по наименьшему углу выполняется против часовой стрелки, то [a × b] > 0, если по часовой стрелке, то [a × b] < 0.
y
b
a
β α
x
Рис. 16.2. Иллюстрация к векторному произведению
Рассмотрим задачи, при решении которых используются скалярное и векторное произведения.
111
Задача 1. «Штраф за левые повороты» [1].В городе Х водителямзапрещено выполнять левые повороты. За каждый такой поворот во-дитель должен уплатить штраф в размере М рублей. Для слежки за водителями в городе установлена компьютерная система, фикси-рующая координаты автомобиля в начале движения, в конце движения и во время поворота.
Исходные данные: N –количество зафиксированных координат авто-мобиля, (x i, y i) – координаты автомобиля в процессе движения, i = 1, 2, …, N, где (x1, y1) – точка начала движения, (x N, y N) – последняя точка маршрута автомобиля.
Требуется по заданной последовательности координат движениявычислить сумму штрафа водителя.
(x i+1, y i+1)
(x i, y i)
(x i-1, y i-1)
Рис. 16.3. Иллюстрация к задаче «Штраф за левые повороты»
Траекторию движения автомобиля можно представить в виде ло-маной, состоящей из направленных отрезков из точек (x i, y i) в точки (x i+1, y i+1), i = 1, 2,…, N–1. Поворот считается левым, если направление текущего отрезка пути a i+1 меняется относительно предыдущего отрезка a i в левую сторону,т.е.против часовой стрелки.
Таким образом, решение задачи сводится к вычислению количества пар участков пути a i и a i+1, для которых выполняется условие [a i × a i+1] > 0. Координаты векторов a i и a i +1 вычисляются через координаты точек (x i, y i): a i = (x i – x i–1, y i – y i –1), a i +1 = (x i+1 – x i, y i+1 – y i), следовательно,
[a i × a i+1] = (x i – x i–1) (y i+1 – y i) – (y i – y i–1)(x i+1 – x i), i = 2, …, N – 1.
Задача 2. «Здесь будет город -сад». Жители одного дома города Хрешили высадить у себя во дворе несколько деревьев. Так как жильцы не смогли договориться, как должны быть расположены посадки, то каждый посадил дерево в том месте двора, где ему захотелось. После проведения посадок полученный сад решили обнести забором. Но пока доски не привезли, деревья обвязали одной длинной веревкой.
Исходная информация: N –количество деревьев в саду, (x i, y i) –ко-ординаты деревьев, i = 1, 2, …, N. Так как были высажены молодые са-женцы, то их толщиной можно пренебречь.
112
Требуется определить,к каким из посаженных деревьев надо при-вязать веревку так, чтобы все деревья оказались внутри обнесенной зо-ны, а длина веревки была минимальная.
Эта и подобные ей задачи сводятся к определению для заданного множества точек на плоскости выпуклой оболочки, то есть выпуклого многоугольника с вершинами в некоторых точках из заданного множе-ства, охватывающего все его точки. В [2] приведено несколько вариан-тов решения такой задачи с учетом временных затрат на выполнение алгоритмов. Здесь мы рассмотрим способ, использующий свойства ска-лярного произведения векторов.
Будем строить выпуклую оболочку в порядке обхода участка по ча-совой стрелке. Найдем самую левую точку М0 = (x0, y0), x0 = min{x i}. Если таких точек несколько, то возьмем самую нижнюю из них. Эта точка на-верняка принадлежит искомой выпуклой оболочке. Зададим первона-чальный вектор a0 с началом в точке (x0, y0), параллельный оси Oy.
Следующей точкой оболочки будет такая точка М1, чтобы вектор a1
с началом в точке М0 и концом в точке М1 образовывал с первоначаль-ным вектором a0 минимальный угол. Если таких точек несколько, то выбирается точка, расстояние до которой максимально.
Далее процесс продолжаем, то есть ищем точку М2 с минимальным углом между вектором a1 и вектором a2 с началом в точке М1 и концом в точке М2, затем точку М3 и т. д. Процесс прекращаем, когда дойдем до первой выбранной точки или количество точек в оболочке станет равно N.
Для определения угла между векторами используется скалярное произведение. Причем сам угол можно не вычислять, так как мини-мальному углу соответствует максимальный косинус угла.
Задача 3. «Заяц» [3].Недалеко от города Х находится зоосад.Здешний житель, заяц, хаотично прыгая, оставил след в виде замкну-той самопересекающейся ломаной, охватывающей территорию его владения. Найти площадь минимального по площади выпуклого много-угольника, описанного вокруг этой территории.
В данной задаче необходимо не только найти выпуклую оболочку множества точек, но и вычислить площадь выпуклого многоугольника с заданным набором вершин.
Исходные данные: N –количество вершин выпуклого многоуголь-ника, (x i, y i) – координаты вершин, i = 1, 2, …, N.
Требуется определить площадь выпуклого N-угольника.
Площадь N-угольника может быть вычислена как сумма площадей треугольников, из которых N-угольник составлен. Для нахождения пло-щади треугольника используем векторное произведение. Длина векторно-
113
го произведения векторов, как известно, равна удвоенной площади тре-угольника, построенного на этих векторах. Пусть вершины треугольника расположены в точках A = (x 1, y 1), B = (x 2, y 2), C = (x 3, y 3). Совместим начало координат с первой точкой. Векторное произведение равно
i | j | k |
| |||||
[AB × AC] = | x2 | - x1 | y2 | - y1 | 0 | , | ||
x3 | - x1 | y3 | - y1 | 0 |
следовательно, площадь треугольника равна
SABC = 1/2 ((x2 – x1) (y3 – y2) – (y2 – y1)(x3 – x2)).
Значение величины SABC может быть как положительным, так и от-рицательным числом, так как оно зависит от взаимной ориентации век-торов AB и AC, поэтому говорят, что площадь ориентированная.
Для нахождения площади N-угольника последний требуется раз-бить на треугольники и найти сумму ориентированных площадей этих треугольников. Разбиение N-угольника на треугольники можно провес-ти так: зафиксировать одну из вершин N-угольника, например первую, A1 = (x1, y1) и рассматривать все треугольники A1Ai Ai+1, i = 2, 3,…, N – 1.
А2
А3
А1
А5 А
Рис. 16.4. Иллюстрация к задаче «Заяц»
Заметим, что аналогичным способом можно находить площадь произвольного многоугольника.
Рис. 16.5. Нахождение площади произвольного многоугольника
114
Использование свойства ориентации площади треугольника, вы-численной по векторному произведению, позволяет определить, являет-ся ли заданный многоугольник выпуклым. Для выпуклого многоуголь-ника все треугольники, образованные тройками соседних вершин в порядке их обхода, имеют одну ориентацию. Поэтому проверка много-угольника на выпуклость может быть проведена с помощью последова-тельного сравнения знаков векторных произведений для всех пар сосед-них сторон многоугольника.
Задача 4. «Тигр в загоне». Недалеко от города Х находится запо-ведник, в котором обитают уссурийские тигры. Работники заповедни-ка очень переживают, когда тигр покидает охраняемую зону. Про-грамма охраны уссурийских тигров предусматривает снабжение каждого тигра ошейником с радиомаяком. Сигнал от тигриного ра-диомаяка поступает в центр охраны и позволяет определить местопо-ложение тигра. Территория заповедника представляет собой произ-вольный многоугольник.
Исходные данные: N –количество вершин многоугольника,задаю-щего заповедник, ( x i, y i) – координаты его вершин, i = 1, 2, …, N. (X, Y) – координаты точки, в которой находится тигр.
Требуется определить,находится ли тигр на территории заповед-ника, или надо срочно снаряжать спасательную экспедицию.
P
Q
Рис. 16.6. Иллюстрация к задаче «Тигр»
Очень часто при решении задач геометрического содержания тре-буется проверить, лежит ли заданная точка внутри или вне многоуголь-ника. Таким способом можно решить, например, задачу о Бармаглоте, проверяя каждую точку Бармаглота относительно одеяла-многоугольника. Есть много способов проверки принадлежности точки многоугольнику, однако мы приведем здесь один из них, основанный на использовании произведения векторов.
Идея метода заключается в том, чтобы определить сумму углов, под которыми стороны многоугольника видны из проверяемой точки. Если точка лежит внутри многоугольника, то суммарный угол равен 2π
115
(точка Р на рисунке), если же точка лежит вне многоугольника, то сум-ма углов не равна 2π (точка Q).
Таким образом, для решения надо перебрать в цикле последова-тельно все вершины многоугольника и найти сумму углов между векто-
рами PAi и PAi+1, i = 1, 2, …, N. Не забудьте добавить угол между векто-рами PAN и PA1. Для определения величины угла между векторами нам потребуется формула скалярного произведения.
Так как стороны многоугольника должны рассматриваться после-довательно, в порядке обхода вершин, то при нахождении суммарного угла следует учитывать взаимное расположение векторов. Угол, под ко-торым сторона видна из исследуемой точки, может быть как положи-тельным, так и отрицательным. Для определения знака угла воспользу-емся векторным произведением. Знак векторного произведения и определит знак конкретного угла в общей сумме.
Задача 5. «Пересечение отрезков». Даноnотрезков.Реализоватьпрограмму, находящую все их пересечения между собой. Отобразить решение графически.
На плоскости заданы два отрезка a и b, a – точками A1(A1x,A1y)
и A2(A2x,A2y), b – точками B1(B1x,B1y) и B2(B2x,B2y). Найти и напечатать возможную точку их пересечения C(Cx,Cy). Рассмотрим первый отрезок a. Уравнение прямой, на которой он лежит, можно записать так:
xa = A1x + ta (A2x – A1x)
ya = A1y + ta (A2y – A1y)
Здесь A1x,A1y,A2x,A2y – константы, xa,ya – точки, принадлежащие отрезку, при ta изменяющемся от 0 до 1. Аналогично для отрезка b:
xb = B1x + tb (B2x – B1x)
yb = B1y + tb (B2y – B1y)
Таким образом, приравнивая соответствующие координаты, полу-чаем задачу нахождения параметров ta, tb, при которых бы выполнялись равенства:
A1x + ta (A2x – A1x) = B1x + tb (B2x – B1x)
A1y + ta (A2y – A1y) = B1y + tb (B2y – B1y)
После разрешения системы относительно ta,tb получаем:
ta (A1x – A2x) + tb (B2x – B1x) = A1x – B1x ta (A1y – A2y) + tb (B2y – B1y) = A1y – B1y
А это есть система из двух линейных уравнений относительно ta, tb. Известно, что система:
a1 x + b1 y = c1
a2 x + b2 y = c2
имеет следующее решение:
116
x = dx/d
y = dy/d,
где d – определитель матрицы,
d = a1b2 – a2b1,
dx = c1b2 – c2b1,
dy = a1c2 – a2c1.
В нашей системе относительно ta,tb:
a1 = A1x – A2x
b1 = B2x – B1x
c1 = A1x – B1x
a2 = A1y
b2 = B2y
c2 = A1y
– A2y
– B1y
– B1y
откуда легко находится d,dx,dy. Если d отличен от нуля, то система име-ет единственное решение. Правда, следует помнить, что искомые ta, tb параметрически задают отрезки, только если они лежат в диапазоне [0,1], в противном случае – точка пересечения прямых, на которых ле-жат отрезки, находится вне этих самых отрезков.
Если d равен нулю, а хотя бы один из dx,dy отличен от нуля, то от-резки лежат на параллельных прямых, или, как говорят математики, они коллинеарны. Если же все три d,dx,dy равны нулю, то это значит, что от-резки лежат на одной и той же прямой, где опять возможны три слу-чая – либо отрезки не перекрываются, либо перекрываются в одной точ-ке, либо перекрываются в бесконечном множестве точек.
Решение ряда задач повышенной сложности опирается на методы, рассмотренные в комбинаторике, а именно на возможность генерации: сочетаний, перестановок, размещений и перечислений элементов.
Одним из важных элементов комбинаторики являются переста-новки. Перестановки без повторений –комбинаторные соединения,которые могут отличаться друг от друга лишь порядком входящих в них элементов. Число таких перестановок определяется как n!.
Для числа 3 количество перестановок будет равно 3! = 3 * 2 * 1 = 6.
Для четырех: 4! = 4 * 3 * 2 * 1 = 24.
Часто для генерации перестановок используется алгоритм Дейкстры для получения всех перестановок по алфавиту. Разберем этот алгоритм.
Пусть у нас есть первая перестановка (например, 1234). Для нахо-ждения следующей перестановки выполняем три шага.
1. Двигаясь с предпоследнего элемента перестановки, ищем эле-мент a[i], удовлетворяющий неравенству a[i] < a[i + 1]. Для перестанов-
ки 1234, это число 3, т. к. (3 < 4).
117
2. Меняем местами элемент a[i] с наименьшим элементом, который: а) находится правее a[i];
б) больше, чем a[i].
В нашем случае меняем 3 и 4.
3. Все элементы, стоящие за a[i], сортируем. В нашем случае нужно отсортировать число 4, но это единственный элемент, следовательно, так его и оставляем.
В результате выполнения этих трех шагов получаем следующую по алфавиту перестановку 1243.
Выполнять эти шаги нужно циклически до тех пор, пока в переста-новке не будет находиться искомый в первом шаге элемент a[i], т. е. по-ка перестановка не станет отсортированной по убыванию: 4321.
Перестановки с повторениями –комбинаторные соединения,
в которых среди образующих элементов имеются одинаковые. В таких соединениях участвуют несколько типов объектов, причем имеется не-которое количество объектов каждого типа. Поэтому в выборках встре-чаются одинаковые элементы.
Рис. 16.7. Иллюстрация к задаче «Гномики»
Задание 6. Гномики. Гномики решили встречать гостей с разно-цветными шарами. Раздай шарики гномикам так, чтобы цвет шарика не был такой же, как цвет колпачка, и чтобы у гномиков в одинаковых
118
по цвету колпачках были шарики разного цвета и разной формы. Напи-шите программу, выводящую все возможные варианты раздачи шариков.
Задание 7. Имеются цифры от1до9,расположенные по возраста-нию (убыванию). Требуется расставить между ними произвольное ко-личество знаков <<плюс>> и <<минус>>, чтобы получилось выражение со значением 100. Например,
123 + 4 – 5 + 67 – 89 = 100
9 – 8 + 76 – 5 + 4 + 3 + 21 = 100
Найти все возможные варианты таких выражений.
Задача 8. Дан двумерный массив,заполненный нулями и единицами.
Найти прямоугольник наибольшей площади, заполненный единицами.
Площадь прямоугольников изменяется от максимальной (весь мас-сив) до минимальной (прямоугольник, состоящий из одной 1). Каждый прямоугольник конкретной площади может быть построен множеством способов. Для площади S допустимый прямоугольник – это такой, про-изведение сторон которого равно S. Мы должны для каждого значения площади перебрать все допустимые способы построения прямоугольни-ков. Каждый прямоугольник конкретной площади и формы может рас-полагаться в массиве различным образом. Точнее сказать, его левая верхняя вершина может находиться в разных точках массива. Следова-тельно, для прямоугольника определенной площади и формы мы долж-ны перебрать все возможные расположения.
Может показаться, что программа для большого массива будет ра-ботать слишком долго, но есть серьезные возможности для ее ускоре-ния. А именно:
1. Если площадь перебирать от максимальной к минимальной, то пер-вый найденный прямоугольник и будет искомым.
2. Прямоугольник конкретной площади и формы не поместится в лю-бом положении в массив.
Учет этих утверждений ведет к очень серьезному ускорению про-граммы.
Задание 9. «Вирус ». Колония клеток представляет собой квадрат-ную матрицу порядка N (N < 500). В колонию проникает M (M < 11) ви-русов, которые поражают клетки с координатами (X1, Y1), … (Xm, Ym). За одну единицу времени вирус проникает в клетки, соседние с зара-женными (соседними считаются клетки, имеющие общую сторону). Требуется написать программу, которая определит время заражения всей колонии. Графически показать процесс заражения.
119
Задание 10. «Сундук Билли Бонса». Билли Бонс положил в сун-дук некоторое количество золотых монет. На второй год он вынул из сундука сколько-то монет. Начиная с третьего года, он добавлял столько монет, сколько было в сундуке два года назад.
Требуется написать программу, определяющую количество монет
в сундуке в первый и во второй года, если в X-м году там оказалось ровно Y монет. (3 < = X < = 20) и Y (1 < = Y < = 32767).
Пояснение: если в первый год положить 5 монет, а во второй год вынуть 3 монеты, то, начиная с первого года, в сундуке будет 5, 2, 7, 9, 16, 25, … монет.
120
Дата добавления: 2020-04-08; просмотров: 628; Мы поможем в написании вашей работы! |
Мы поможем в написании ваших работ!