Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2010.09.12;
Скачать: [xml.tar.bz2];

Вниз

Хочу разобраться с реализацией алгоритма Эйлерова цикла   Найти похожие ветки 

 
Виталий   (2010-06-05 11:47) [0]

Здравствуйте. Я хочу самостоятельно написать алгоритм обхода Эйлерова пути. Но не могу понять, как реализовать алгоритм: в википедии (http://ru.wikipedia.org/wiki/Эйлеров_цикл) написано:

"procedure find_all_cycles (v)
var массив cycles
1. пока есть цикл, проходящий через v, находим его
   добавляем все вершины найденного цикла в массив cycles (сохраняя порядок обхода)
   удаляем цикл из графа
2. идем по элементам массива cycles
   каждый элемент cycles[i] добавляем к ответу
   из каждого элемента рекурсивно вызываем себя: find_all_cycles (cycles[i])"

Как понять, что есть цикл, и найти его? С чего следует начать, что прочесть?
И да, я не прошу готового кода. Я хочу понять, КАК что-то нужно реализовать.
Если кому-то в это субботнее утро будет несложно потратить свое время и пояснить мне принцип, я буду очень благодарен.


 
oldman ©   (2010-06-05 12:14) [1]


> Как понять, что есть цикл, и найти его? С чего следует начать,
>  что прочесть?


На той же странице википедии, что ты привел кликни по слову "цикл" в тексте


 
Виталий   (2010-06-05 12:14) [2]

Да, я прочел его понятие, но я имею ввиду, что мне неясно, как алгоритмически найти, есть ли цикл, проходящий через v.


 
MBo ©   (2010-06-05 12:35) [3]

>как алгоритмически найти, есть ли цикл, проходящий через v
например, поиском в глубину

про эйлеров путь лучше тут почитать:
http://e-maxx.ru/algo/euler_path


 
Виталий ©   (2010-06-05 12:44) [4]

Можете пояснить несколько моментов:

while (!deg[first])  ++first;
что делает этот цикл? Простите, с C++ понимаю не вполне ясно.

if (i == n)
 {
  res.push_back (v);
  st.pop();
 }


как это соотносится с


> если степень(V) = 0, то
>   добавляем V к ответу;
>   снимаем V с вершины St;


Заранее спасибо.


 
Виталий ©   (2010-06-05 12:57) [5]

Про первое, кажется, понял - ищет элемент, с которого стартовать, то есть находит первый элемент "степени", где степень не null?


 
Виталий ©   (2010-06-05 16:04) [6]

Что же, наверное, никто не может мне помочь. Извините.


 
Sha ©   (2010-06-05 21:03) [7]

Нет смысла теорию пересказывать своими словами.
Лучше сначала почитать и понять теорию,
а потом программировать абсолютно понятные вещи.


 
Виталий ©   (2010-06-06 00:06) [8]

Я понял прочитаную теорию, но не понял моменты в реализации алгоритма. Может все-таки кто-то объяснить? Или это запретные знания?


 
Sha ©   (2010-06-06 00:48) [9]

> Виталий ©   (06.06.10 00:06) [8]

Того что написано в ссылке [3] достаточно для самостоятельной реализации.
Начинай писать, на вопросы ответим.


> Я понял прочитаную теорию

Я не совсем уверен, что это так


> но не понял моменты в реализации алгоритма.

Зачем разбираться в чужой реализации алгоритма на незнакомом тебе языке?
Это не даст ничего нового. Ты же ты прочитал теорию и понял сам алгоритм.


> Может все-таки кто-то объяснить? Или это запретные знания?

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


 
Виталий ©   (2010-06-06 09:58) [10]


> Это не даст ничего нового. Ты же ты прочитал теорию и понял
> сам алгоритм.

Я просто подумал, что условие
> if (i == n)  {   res.push_back (v);   st.pop();  }

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


 
Виталий ©   (2010-06-06 10:01) [11]

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


 
Sha ©   (2010-06-06 10:13) [12]

> Виталий ©   (06.06.10 09:58) [10]
> как я понимаю, граф нужно задать матрицей смежности,
> состоящей из единиц и нулей,
> затем для каждой вершины подсчитать ее степен


Да.

> Или, возможно, мне надо прочесть совсем не те краткие выжимки,

Достаточно разобраться в тексте по ссылке [3]


 
Виталий ©   (2010-06-06 11:02) [13]

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


 
Sha ©   (2010-06-06 11:27) [14]

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

Ну и потом, если сейчас будешь думать, как вставить рисование
в процедуру обхода, есть шанс ее вообще не написать.


 
Виталий ©   (2010-06-06 11:31) [15]

Понял. И все-таки объясните мне, нет ли в том алгоритме какой-то неточности. Я вижу несоответствие: почему в словесном описании написано "если степень вершины равно 0", а в алгоритме вообще непонятно что?


 
Sha ©   (2010-06-06 11:49) [16]

А говорил, понял прочитаную теорию [8] :)

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


 
Виталий ©   (2010-06-06 11:57) [17]

Значит, не в силах понять я прочитаную теорию, потому что то, что написали вы, никак не кореллирует с прочитаным мною текстом. То, как написали вы, теперь объясняет мне написаные тут строки:
int v = st.top();
 int i;
 for (i=0; i<n; ++i)
  if (g[v][i])
   break;
 if (i == n)
 {
  res.push_back (v);
  st.pop();
 }
 else
 {
  --g[v][i];
  --g[i][v];
  st.push (i);
 }

Но я не смог понять, как это соотносится со степенью V. И сейчас не могу. Или вы не хотите мне объяснить этот момент, или он где-то написан черным по белому и я безнадежно туп.


 
Sha ©   (2010-06-06 12:00) [18]

Не надо тут ничего понимать.
Это реализация алгоритма на другом языке.
Нафиг ее разбирать.

Словесное описание и есть алгоритм.
Требуется реализовать его на Делфи.
И все.


 
Виталий ©   (2010-06-06 12:03) [19]

Господи, да хоть на BrainFucke он будет написан, суть остается ясна: алгоритм реализован ОТЛИЧНО от его словесного описания. Да, я вижу, что он оптимизирован: на текущем шаге не подсчитывается опять число ребер, исходящих из v: если ребро вершины еще не последнее, то его конец добавляется в список, а если последнее, то уже наконец его можно добавить в результат.  Но все мои просьбы объяснить, почему сделано именно так, натыкаются почему-то на отказ.


 
Sha ©   (2010-06-06 12:06) [20]

> как это соотносится со степенью V

Степень вершины в процессе работы алгоритма уменьшается,
если мы через нее проходим, т.к. мы вычеркиваем пройденные ребра.
Степень становиться нулевой, когда идти дальше некуда.
Тут мы и начинаем вспоминать, где были, что делали.


 
Виталий ©   (2010-06-06 12:07) [21]

Да. То есть по-хорошему на каждом шаге цикла ее надо пересчитывать?


 
Sha ©   (2010-06-06 12:09) [22]

Как хочешь.
Можешь пересчитывать, можешь хранить.


 
MBo ©   (2010-06-06 12:10) [23]

Считаем, что граф проверен на эйлеричность.
В данном случае я задал граф, по виду напоминающий открытый конверт (степени вершин 2 и 4)


procedure TForm2.Button9Click(Sender: TObject);
type
 TMatr = array[0..5, 0..5] of Byte;
var
 Matr: TMatr;
 EPath: string;

 procedure InitMatrix;
 begin
   FillChar(Matr, SizeOf(Matr), 0);
   Matr[0, 1] := 1;     Matr[1, 0] := 1;
   Matr[0, 3] := 1;     Matr[3, 0] := 1;
   Matr[1, 2] := 1;     Matr[2, 1] := 1;
   Matr[1, 4] := 1;     Matr[4, 1] := 1;
   Matr[1, 5] := 1;     Matr[5, 1] := 1;
   Matr[2, 4] := 1;     Matr[4, 2] := 1;
   Matr[2, 5] := 1;     Matr[5, 2] := 1;
   Matr[2, 3] := 1;     Matr[3, 2] := 1;
 end;

 procedure EulerRec(Vertex: Integer);
 var
   i: integer;
 begin
   for i := 0 to 5 do
     if Matr[Vertex, i] <> 0 then begin
       Matr[Vertex, i] := 0;    Matr[i, Vertex] := 0;
       EulerRec(i);
     end;
   EPath := EPath + IntToStr(Vertex) + "; ";
 end;

begin
 EPath := "";
 InitMatrix;
 EulerRec(0);
 Memo1.Lines.Add(EPath);
end;


 
Виталий ©   (2010-06-06 12:12) [24]

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


 
Виталий ©   (2010-06-06 12:14) [25]

23, это как я понял, первая версия алгоритма, рекурсивная. Черт побери, а их еще назвали равными по длине кода.


 
MBo ©   (2010-06-06 12:19) [26]

Теперь можно посмотреть на рекурсивную процедуру, и заметить, что строка EPath (содержащая последовательность вершин в Эйлеровом пути) по сути аналогична стеку, в который складываются посещенные вершины в порядке, а последовательность Vertex в рекурсивных вызовах - другому стеку (это можно отследить, добавив строку     SPath := SPath + IntToStr(Vertex) + "; "; в начале рекурсивной процедуры). Так что если нужно преобразовать алгоритм в нерекурсивную форму, то фундамент для этого уже есть.

Путь:
0; 3; 2; 5; 1; 4; 2; 1; 0;

Последовательность посещения вершин алгоритмом (не путь!):
0; 1; 2; 3; 0; 4; 1; 5; 2;


 
Sha ©   (2010-06-06 12:23) [27]

> их еще назвали равными по длине кода

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


 
Виталий ©   (2010-06-06 12:24) [28]

Остался, наверное, еще важный вопрос: я опять не понимаю, почему так реализовано удаление вершин "ненужного" ребра из результата:

for (size_t i=0; i+1<res.size(); ++i)
  if (res[i] == v1 && res[i+1] == v2 || res[i] == v2 && res[i+1] == v1)
  {
   vector<int> res2;
   for (size_t j=i+1; j<res.size(); ++j)
    res2.push_back (res[j]);
   for (size_t j=1; j<=i; ++j)
    res2.push_back (res[j]);
   res = res2;
   break;
  }


Если в результате есть вершина v1 и v2, то мы вначале начнем с места, где вершина V1 или V2 и дойдем до конца результата, складывая это во временый массив, а потом с начала результата до места, где V1 или V2, также добавляя это во врем. массив, который затем присвоим результирующему. Вопрос: так ведь все равно добавятся обе этих вершины?


 
Виталий ©   (2010-06-06 12:26) [29]

27, да, но вот эта реализация внутри цикла все равно поставила меня в тупик, если бы не ваше объяснение алгоритма, а с рекурсией все как-то намного яснее.


 
Sha ©   (2010-06-06 15:04) [30]

> Виталий ©   (06.06.10 12:26) [29]
> с рекурсией все как-то намного яснее

Если как следует разобраться в алгоритме, то можно обойтись и без рекурсии и без стека.

Правда, об этом не пишут в книжках, чтобы учеба медом не казалась :)


 
Виталий ©   (2010-06-06 20:14) [31]

Не подскажете, куда копать?


 
Sha ©   (2010-06-06 20:46) [32]

Простейший вариант - аналог стека с использованием динамического массива
размером = количество ребер + 1. Изменяешь текущую позицию и заносишь туда данные или считываешь.


 
Sha ©   (2010-06-06 20:56) [33]

Например так:

unit EulerPathForm;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, Grids;

type
 TForm1 = class(TForm)
   bRecursive: TButton;
   bStack: TButton;
   Memo1: TMemo;
   StringGrid1: TStringGrid;
   procedure bFindPathClick(Sender: TObject);
 private
 public
 end;

var
 Form1: TForm1;

implementation

{$R *.dfm}

uses
 SymmetricMatrix;

var
 Matrix: TSymmetricMatrix;
 Total: integer;

procedure InitMatrix;
begin;
 SymmetricMatrixReallocate(Matrix, 7);

 SymmetricMatrixSetElement(Matrix, 0, 1, 1);
//  SymmetricMatrixSetElement(Matrix, 0, 2, 1); //закомментировать, чтобы путь существовал
 SymmetricMatrixSetElement(Matrix, 0, 3, 1);
 SymmetricMatrixSetElement(Matrix, 1, 2, 1);
 SymmetricMatrixSetElement(Matrix, 1, 4, 1);
 SymmetricMatrixSetElement(Matrix, 1, 5, 1);
 SymmetricMatrixSetElement(Matrix, 2, 3, 1);
 SymmetricMatrixSetElement(Matrix, 2, 4, 1);
 SymmetricMatrixSetElement(Matrix, 2, 5, 1);
//  SymmetricMatrixSetElement(Matrix, 3, 5, 1); //снять комментарий, чтобы путь был замкнут
 SymmetricMatrixSetElement(Matrix, 3, 6, 1);
 SymmetricMatrixSetElement(Matrix, 5, 6, 1);
 end;

function GetStartPos: integer;
var
 i, j, Count, OddCount, ZeroCount: integer;
begin;
 Result:=0;
 OddCount:=0; ZeroCount:=0; Total:=0;
 if Length(Matrix.Values)>0 then begin;
   for i:=0 to Matrix.Size-1 do begin;
     Count:=0;
     for j:=0 to Matrix.Size-1 do if SymmetricMatrixGetElement(Matrix,i,j)<>0 then inc(Count);
     inc(Total,Count);
     if Count=0 then inc(ZeroCount);
     if odd(Count) then begin;
       inc(OddCount);
       Result:=i;
       end;
     end;
   end;
 Total:=Total shr 1;
 if (Total=0) or (ZeroCount>0) or (OddCount<>0) and (OddCount<>2) then Result:=-1;
 end;

procedure EulerPathRecursive(StartPos: integer; var EPath: string);
var
 i: integer;
begin;
 for i:=0 to Matrix.Size-1 do
 if SymmetricMatrixGetElement(Matrix,StartPos,i)<>0 then begin;
   SymmetricMatrixSetElement(Matrix,StartPos,i,0);
   Dec(Total);
   EulerPathRecursive(i, EPath);
   end;
 EPath:=EPath + IntToStr(StartPos) + "; ";
 end;

procedure EulerPathStack(StartPos: integer; var EPath: string);
var
 LastCol: array of integer;
 Stack: array of integer;
 Current, i, j: integer;
begin;
 SetLength(LastCol, Matrix.Size);
 for i:=0 to Matrix.Size-1 do LastCol[i]:=Matrix.Size-1;

 SetLength(Stack, Total + 1);
 Current:=-1;
 inc(Current); Stack[Current]:=StartPos;

 while true do begin;
   i:=Stack[Current];
   j:=LastCol[i];
   while (j>=0) and (SymmetricMatrixGetElement(Matrix,i,j)=0) do dec(j);
   if j>=0 then begin;
     LastCol[i]:=j-1;
     inc(Current); Stack[Current]:=j;
     SymmetricMatrixSetElement(Matrix,i,j,0);
     dec(Total);
     end
   else begin;
     dec(Current);
     EPath:=EPath + IntToStr(i) + "; ";
     if Current<0 then break;
     end;
   end;
 end;

procedure TForm1.bFindPathClick(Sender: TObject);
var
 Comment, EPath: string;
 i, j: integer;
begin;
 InitMatrix;
 if Length(Matrix.Values)>0 then begin;
   StringGrid1.DefaultColWidth:=24;
   StringGrid1.DefaultRowHeight:=18;
   StringGrid1.ColCount:=Matrix.Size+1;
   StringGrid1.RowCount:=Matrix.Size+1;
   StringGrid1.Cols[0].Clear;
   for j:=1 to Matrix.Size do StringGrid1.Cells[0,j]:=IntToStr(j-1);
   for i:=1 to Matrix.Size do begin;
     StringGrid1.Cols[i].Clear;
     StringGrid1.Cells[i,0]:=IntToStr(i-1);
     for j:=1 to Matrix.Size do StringGrid1.Cells[i,j]:=IntToStr(SymmetricMatrixGetElement(Matrix,i-1,j-1));
     end;
   end;

 i:=GetStartPos;
 if i<0 then Comment:="Путь не существует"
 else begin;
   if Sender=bRecursive then EulerPathRecursive(i, EPath);
   if Sender=bStack     then EulerPathStack(i, EPath);
   if Total<>0 then Comment:="Граф не связен"
   else if i=0 then Comment:="Найденный путь замкнут"
   else Comment:="Найденный путь не замкнут";
   end;
 Memo1.Lines.Add(Comment);
 Memo1.Lines.Add(EPath);
 end;

end.

//Используется в EulerPath
unit SymmetricMatrix;

interface

type
 //Симметричная матрица размерами[0..Size-1, 0..Size-1]
 //Кроме экономии памяти почти в 2 раза автоматически поддерживает
 //равенство элементов, симметричных относительно главной диагонали.
 TSymmetricMatrix= record
   Size: integer;
   Values: array of integer;
   end;

procedure SymmetricMatrixReallocate(var Matrix: TSymmetricMatrix; Size: integer;
                                     Value: integer= 0; Dirty: boolean= false);
function  SymmetricMatrixGetElement(var Matrix: TSymmetricMatrix; i, j: integer): integer;
procedure SymmetricMatrixSetElement(var Matrix: TSymmetricMatrix; i, j: integer; Value: integer);

implementation

procedure SymmetricMatrixReallocate(var Matrix: TSymmetricMatrix; Size: integer;
                                     Value: integer= 0; Dirty: boolean= false);
var
 i, len: integer;
begin;
 len:=Size*(Size+1) shr 1;
 SetLength(Matrix.Values, len);
 Matrix.Size:=Size;
 if not Dirty then for i:=0 to len-1 do Matrix.Values[i]:=Value;
 end;

function SymmetricMatrixGetElement(var Matrix: TSymmetricMatrix; i, j: integer): integer;
begin;
 if i>=j then i:=(i*(i+1) shr 1)+j
         else i:=(j*(j+1) shr 1)+i;
 Result:=Matrix.Values[i];
 end;

procedure SymmetricMatrixSetElement(var Matrix: TSymmetricMatrix; i, j: integer; Value: integer);
begin;
 if i>=j then i:=(i*(i+1) shr 1)+j
         else i:=(j*(j+1) shr 1)+i;
 Matrix.Values[i]:=Value;
 end;

end.


 
Виталий ©   (2010-06-06 22:16) [34]

Мне показалось, или тут неоправданое усложнение и таки есть рекурсия? ;) За код спасибо, буду разбираться.
Но можно вернуться к изначальному нерекурсивному варианту? Скажите, вот если при каждом шаге цикла, пока стек не пуст, считать для вершины степень - это как я понимаю неоптимально? Правильнее делать так, как реализовано на C++?


 
Sha ©   (2010-06-06 22:32) [35]

> Виталий ©   (06.06.10 22:16) [34]
>Мне показалось, или тут неоправданое усложнение и таки есть рекурсия?


Тут 2 процедуры (с рекурсией и без).


> если при каждом шаге цикла, считать для вершины степень - это как я понимаю неоптимально?

Надо сначала понять нужна ли тебе степень в действительности и для чего.
Вот, допустим, ты ее узнал. Скажем она равна 2. Что мы делаем дальше?
Находим очередное ребро для перехода к следующей вершине. Что нам дало наше знание?
Мы и без него можем искать следующее ребро. Если найдем, хорошо, идем дальше.
Если нет, то понятно, что степень равна нулю.
Получается, что степень использована в описании алгоритма только для большей ясности.
Гораздо важнее помнить не степень вершины, а последнее просмотренное нами ребро.


 
Виталий ©   (2010-06-07 01:33) [36]


> последнее просмотренное нами ребро.

таки последнее просмотреное ребро а не вершину?


 
Sha ©   (2010-06-07 07:39) [37]

> Виталий ©   (07.06.10 01:33) [36]
> таки последнее просмотреное ребро а не вершину?

Вершину тоже, разумеется - это ведь переменная цикла.

А для каждой вершины хорошо бы знать,
с какого ребра начинать перебор,
чтобы не проверять каждый раз все ребра.

Посмотри, как это сделано в процедуре EulerPathStack
при помощи массива LastCol.


 
MBo ©   (2010-06-07 08:04) [38]

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

> для каждой вершины хорошо бы знать,
> с какого ребра начинать перебор,
> чтобы не проверять каждый раз все ребра.



 function EulerStackPath: string;
 var
   i, V: integer;
   VStack: TStack;
   Isolated: Boolean;
 begin
   Result := "";
   VStack := TStack.Create;
   VStack.Push(Pointer(0));
   while VStack.Count > 0 do begin
     V := Integer(VStack.Peek);
     Isolated := True;
     for i := 0 to 5 do
       if Matr[V, i] <> 0 then begin
         Matr[V, i] := 0;  Matr[i, V] := 0;
         VStack.Push(Pointer(i));
         Isolated := False;
         Break;
       end;
     if Isolated then
       Result := Result + IntToStr(Integer(VStack.Pop)) + "; ";
   end;
 end;


 
MBo ©   (2010-06-07 08:06) [39]

хе, в конце при правке потерял
VStack.Free;


 
Виталий ©   (2010-06-07 19:28) [40]

38, если я правильно понимаю, ваш вариант как раз в точности соответствуте словесному описанию, данному в сборнике алгоритмов?



Страницы: 1 2 вся ветка

Форум: "Прочее";
Текущий архив: 2010.09.12;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.58 MB
Время: 0.005 c
2-1276375219
AKE
2010-06-13 00:40
2010.09.12
Как получить координаты всех точек?


15-1276585131
brother
2010-06-15 10:58
2010.09.12
Любителям zx-spectrumа: видео...


4-1220443358
dmitry_12_08_74
2008-09-03 16:02
2010.09.12
как получить handle текущего активного окна на рабочем столе


2-1276641610
Германн
2010-06-16 02:40
2010.09.12
TMenuItem.Hint


2-1276380946
AKE
2010-06-13 02:15
2010.09.12
Принадлежит ли точка треугольнику?





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский