Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритмы и структуры данных.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
1.14 Mб
Скачать

2.3Сортировка простым выбором

Метод сортировки простым выбором (СПВыб) основан на следующем правиле.

При i, принимающем значения от 1 до n-1, выполняется:

  1. Среди всех элементов аi ,....., аn отыскивается минимальный элемент.

  2. Минимальный элемент меняется местами с элементом аi.

Снова имеем два цикла, но оба типа “for”.

Внешний цикл i=1 до N-1 и внутренний j=i+1 до N.

Процедура имеет следующий вид:

type TArr = array of Item ;

var A: TArr;

procedure StraightsSelection (var A: TArr);

var I, J, K: integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

begin

N := Length (A);

for I := 0 to N-2 do

begin

K :=I;

X := A[I];

for J := I+1 to N-1 do

if A[J].Key < X.Key then

begin

K:=J;

X:=A[J];

end;

A[K]:=A[I];

A[I]:=X;

end;

end; {StraightSelection}

begin

{формирование и заполнение массива А}

ShellSort (A); {сортировка массива}

···

end.

Здесь число сравнений ключей не зависит от начального порядка ключей. В результате

С = 1/2(n2 - n).

Анализ показал, что число пересылок:

Mmin = 3(n - 1)

Mmax = tranc(n2/4) + 3(n - 1)

Mavg тяжело определить, несмотря на простоту алгоритма.

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

2.4Сортировка простым обменом

И в предыдущих методах обмен имел место, но в этом случае обмен является основным действием.

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

Этот метод получил название метод пузырька. Это очевидно если располагать массивы вертикально.

начальн. состоян.

i=2

i=3

i=4

i=5

i=6

i=7

i=8

4 4

01

01

01

01

01

01

01

55

44

11

11

11

11

11

11

11

55

44

22

22

22

22

22

33

11

55

44

33

33

33

33

99

33

33

55

44

44

44

44

22

99

99

33

55

55

55

55

01

22

22

99

99

99

99

77

77

77

77

77

77

77

77

99

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

type TArr = array of Item ;

var A: TArr;

procedure BubbleSortMin (var A: TArr);

var I, J: integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

begin

N := Length (A);

for I := 1 to N-1 do

begin

for J := N-1 downto I do

if A[J-1].Key > A[J].Key then

begin

X:=A[J-1] ;

A[J-1]:=A[J] ;

A[J]:=X;

end;

end;

end; {BubbleSortMin}

begin

{формирование и заполнение массива А}

ShellSort (A); {сортировка массива}

···

end.

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

procedure BubbleSortMax (var A: TArr);

var I, J: integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

begin

N := Length (A);

for I := 1 to N-1 do

begin

for J:=0 downto N - I do

if A[J].Key > A[J+1].Key then

begin

X:=A[J+1];

A[J+1]:=A[J];

A[J]:=X;

end;

end;

end; {BubbleSortMax}

В алгоритме "пузырька":

С=1/2(n2-n)

Mmin = 0

Mmax = 1/2(n2-n)

Mavg = 3/4(n2-n)

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

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

procedure BubbleSortMax1 (var A: TArr);

var I, J: integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

F: boolean;

begin

N := Length (A);

for I := 0 to N-2 do

begin

F := false;

for J := 0 to N-I do

if A[J].Key > A[J+1] .Key then

begin

X := A[J+1];

A[J+1] := A[J];

A[J] := X;

F := true;

end;

if not F then Exit;

end;

end; {BubbleSortMax1}

Другой вариант реализации этой же процедуры представляется более компактным. Здесь совмещены особенности оператора и контроль на отсутствие перестановки элементов.

procedure BubbleSortMax11 (var A: TArr);

var I, J: integer;

Changed: Boolean;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

repeat

Changed := false;

N := Length (A);

for i := 0 to N-1 do

begin

if A[i].Key > A[i+1].Key then

begin

{обменяем i-ый и i+1-ый элементы}

X := A[i];

A[i] := A[i+1];

A[i+1] := X;

Changed := true;

end;

end;

until not Changed;

end; {BubbleSortMax11}

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

Таким образом, на каждой итерации нижняя граница будет смещаться не на 1 , как следует из оператора цикла, а, в общем случае, на величину, значительно больше 1. Это автоматически приводит к отказу от последующих итераций по индексу I.

Например, пусть имеем такую последовательность ключей:

01, 11, 22, 77, 55, 44, 99, 33

Тогда процесс сортировки будет иметь следующий вид:

начальное состояние

i=2; k=2

i=3; k=5

i=4; k=6

i=5; k=6

01

01

01

01

01

11

11

11

11

11

22

22

22

22

22

7 7

33

33

33

33

55

77

44

44

44

44

55

77

55

55

99

44

55

77

77

33

99

99

99

99

Таким образом, вместо 7-ми итераций потребовалось только 4-ре, причем последняя итерация - только для определения признака “перестановок не было”.

procedure BubbleSortMin1 (var A: TArr);

var I, J, K, L: integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

begin

N := Length (A);

K := N-1;

for I := 0 to N-2 do

begin

L := 0;

for J := N-1 downto K do

if A[J-1].Key > A[J].Key then

begin

X:=A[J-1] ;

A[J-1]:=A[J] ;

A[J]:=X;

L := J;

end;

if L = 0 then Exit;

K := L;

end;

end; {BubbleSortMin1}

Здесь можно заметить интересную асимметрию. Пусть имеем такую последовательность.

начальн. состоян.

i=2

i=3

i=4

i=5

i=6

i=7

i=8

9 9

01

01

01

01

01

01

01

11

99

11

11

11

11

11

11

22

11

99

22

22

22

22

22

33

22

22

99

33

33

33

33

44

33

33

33

99

44

44

44

55

44

44

44

44

99

55

55

77

55

55

55

55

55

99

77

01

77

77

77

77

77

77

99

Из примера видно, что:

  • малый элемент 01 “пузырек”, расположенный изначально в конце последовательности, среди потенциально больших элементов, всплывает на свое место за один проход;

  • большой элемент 99 “камешек”, расположенный изначально в начале последовательности среди потенциально меньших элементов опускается на свое место только на один шаг на каждом проходе.

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

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

l=2 l=3 l=3 l=4 l=4

r=8 r=8 r=7 r =7 r =4

44 01 01 01 01

55 44 44 11 11

11 55 11 44 22

33 11 33 22 33

99 33 55 33 44

22 99 22 55 55

01 22 77 77 77

77 77 99 99 99

Процедура шейкер - сортировки имеет вид:

procedure ShakerSoft (var A: TArr);

var J, K, L, R : integer;

N: integer; {количество элементов сортируемой последовательности}

X: Item;

begin

L := 1;

N := Length (A);

R := N-1;

K := N;

repeat

{определяется нижняя, согласно рисунка, граница неотсортированной последовательности}

for J:= R downto L do

{погружение “камешка”}

if A[J-1].Key > A[J].Key then

begin

X := A[J-1];

A[J-1] := A[J];

A[J] := X;

K := J; {запоминается место последней перестановки}

end;

L := K + 1;

for J := L to R do

{всплытие "пузырька"}

if A[J-1].Key > A[J].Key then

begin

X := A[J-1];

A[J-1] := A[J];

A[J] := X;

K := J; {запоминается место последней перестановки}

end;

R := K - 1;

{определяется верхняя, согласно рисунка, граница неотсортированной последовательности}

until L > R;

end; {ShakerSoft}

Но даже модифицированный алгоритм СПО всё равно уступает алгоритмам СПВст и СПВкл. Собственно шейкер-сортировку выгодно использовать в тех случаях, когда известно, что элементы уже почти упорядочены, что встречается крайне редко.

Все простые методы сортировки, особенно СПВст и СПО в принципе, перемещают каждый элемент на одну позицию на каждом элементарном шаге. Поэтому все они требуют порядка n2 шагов.

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

Анализ показал, что в среднем каждый элемент во время сортировки перемещается на n/3 мест. Это число даёт ключ к поиску более совершенных методов сортировки.

Далее будут рассмотрены три усовершенствованных метода сортировки - по одному для каждого рассмотренного простого метода сортировки.