Idea:
Wyznaczamy najmniejszy element w ciągu (tablicy) i zamieniamy go miejscami z elementem pierwszym, następnie z pozostałego ciągu wybieramy element najmniejszy i ustawiamy go na drugie miejsce tablicy (zmieniamy), itd.
Realizacja w C++
void selekcja (int a[],int l, int r) {
for (int i=l; i<r; i++) {
int min=i;
for (int j=i+1; j<=r; j++) if (a[j]<a[min]) min=j;
zamiana(a[i],a[min]);
} }
//realizacja funkcji zamiana //przestawiajacej dwa elementy //dowolnego typu
void zamiana(int &A, int &B) {
int t=A;
A=B;
B=t;
}
Typ może być dowolny
Przykład:
S E L E K C J A 7 porównań
A E L E K C J S 6 porównań
A C L E K E J S 5 porównań
A C E L K E J S 4 porównania
A C E E K L J S 3 porównania
A C E E J K L S 2 porównania
A C E E J K L S 1 porównanie
Analiza:
Załóżmy, że l=0 i r=n-1.
W linii pierwszej przykładu mamy n-1 porównań a[j]<a[min], potem w kolejnych liniach: n-2, n-3, ….a na końcu tylko 1 porównanie. Zatem:
) ( )
1 (
1 2
...
) 2 (
) 1
( n n
1(2n1) n
n(n21)
12n
2 O n
Tmax(n) P-stwo, że w każdym z
porównań znajdziemy element najmniejszy jest jednakowe
2 ) 1 ( 2
) 1
, (
, 1 , 2 , ,
) 1 (
2
1
n n n nk
n
k
n
p n
Stąd:
)
2
(
4 2 1 4 1 4
1 2 1 4
) 1 ( 2
2 ) 1 ( 2
1 ) 1 (
2 1
) 1 (
2 )
1 (
2 1
, 1
2 ) 1 2 (
) 1 ( 2
) 1 ( 2
) 1 (
n O n
n n
k k
p k
n n
n n n
n k
n n n
n k
k n k
n n n
n n
n n
n
Policzmy teraz pesymistyczną wrażliwość tego algorytmu. Przypomnijmy, że
Ponieważ w procedurze zawsze jest wykonywany ten sam ciąg operacji, niezależnie od danych wejściowych, to Δ(n)=0.
Obliczmy na koniec miarę wrażliwości oczekiwanej algorytmu.
0 )
( n
w procedurze zawsze jest wykonywany ten sam ciąg operacji, niezależnie od danych wejściowychPonadto S(n)=O(1) Mówimy, że algorytm sortuje w miejscu
Zalety:
a) Liczba zamian w najgorszym przypadku: n-1.
b) Prostota implementacji.
c) Zadowalająca szybkość dla małych wartości n.
d) Nie wymaga dodatkowej pamięci.
Wady:
a) Nie jest stabilny.
b) Ma dużą złożoność (rzędu kwadratowego), więc nie nadaje się do sortowania długich tablic.
c) Jest mało wrażliwy na wstępne uporządkowanie.
Algorytm można uczynić stabilnym, zwiększając współczynnik proporcjonalności złożoności.
Idea:
W i-tym kroku trzeba wstawić element tab[i] na właściwe miejsce w posortowanym fragmencie tab[0]…tab[i-1], wcześniej przesuwając wszystkie elementy większe od niego w tym fragmencie w prawo o 1;
powstaje posortowany fragment tab[0]…tab[i+1].
Realizacja w C++
void InsertSort(int *tab) {
for(int i=1; i<n;i++) {
int j=i; // 0..i-1 jest już posortowane int temp=tab[j];
while ((j>0) && (tab[j-1]>temp)) {
tab[j]=tab[j-1];
j--;
}
tab[j]=temp;
} }
W S T A W I A N I E 1 porównanie
S W T A W I A N I E <=2 porównania
S T W A W I A N I E <=3 porównania
A S T W W I A N I E <= 4 porównania
A S T W W I A N I E <=5 porównań
A I S T W W A N I E …….
A A I S T W W N I E …….
A A I N S T W W I E …….
A A I N N S T W W E <=9 porównań
A A E I N N S T W W GOTOWE
Analiza:
W linii pierwszej mamy 1 porównanie, potem maksymalnie 2, itd. , aż do maksymalnie n-1 porównań na końcu. Zatem możemy policzyć
pesymistyczną złożoność :
) ( )
1 (
) 2 (
2 1 )
(
( 2 1) 2m ax
n n n n
T
n n
Ponieważ element tab[i] z równym prawdopodobieństwem może
zająć każdą z i-tej pozycji w ciągu tab[0]<tab[1]<…<tab[i-1], to w i-tym kroku mamy pij=1/i, czyli
. )
...
2 1 ( )
(
1 1 12 121 1 1
i i
i i
i
j i i
j
ij
sr
i j p j i i
T
Sumując teraz po wszystkich n-1 iteracjach, dostajemy:
).
( )
1 (
) ...
3 2
( )
1 (
) ( )
(
2 2
2 2 1
2 1 2
2 1 1
1 2 1 1
1 2
1 1
1
n n
n k
i i
T n
T
n
n
k n
i n
i i n
i
sr sr
Jest to zatem kres górny zbioru liczb, które powstają jako różnice ilości operacji dominujących. Zatem od liczby największej z
możliwych należy odjąć najmniejszą z możliwych, żeby otrzymać taki kres górny.
Ponieważ najmniejszą ilością porównań w każdym kroku n-1iteracji jest jedno porównanie, a największa ilość wyrażą się obliczoną
właśnie Tmax(n)=n(n-1)/2 to = =n(n-1)/2-(n-1)=Θ(n2).
Policzmy teraz pesymistyczną wrażliwość tego algorytmu. Przypomnijmy, że
Pesymistyczna wrażliwość złożoności czasowej jest zatem duża i możemy się spodziewać dużej zmienności złożoności
obliczeniowej.
Średnia wrażliwość (czyli miara wrażliwości oczekiwanej): w i-tym kroku mamy:
).
( ...
) 1 2
( )
( )
(
16 231 ...
1 1...
2 4
1 1
...
1
2
i j i n n
n
n
i j i
i n
i
i j i i
j
i i i
j
śr
i p
ijj j i
T j
i
...
1
2 4
1 ...
1
2 1 2 1 ...
1
2
2
( ) ( ( )) ( ) ( 2 1 )
Sumując po wszystkich n-1 iteracjach, dostajemy:
Zalety:
a) Stabilność.
b) Średnio algorytm jest 2 razy szybszy niż algorytm sortowania przez selekcję.
c) Optymalny dla ciągów prawie posortowanych.
d) Nie wymaga dodatkowej pamięci.
Udoskonalenia:
•Można przestać porównywać elementy, napotkawszy element, który jest nie większy niż wstawiany, bo podtablica z lewej strony jest posortowana – sortowanie adaptacyjne.
•W pierwszej pętli „for” wyznaczamy element najmniejszy i umieszczamy go na początku tablicy, następnie sortujemy pozostałe elementy.
•Standardowo sortuje się zamiany elementów, ale można zrobić przeniesienie większych elementów o jedną pozycję w prawo.
Ma prosty zapis.
Na czym polega to sortowanie? Przykład 7-elementowej tablicy.
Element zacieniowany w pojedynczym przebiegu głównej pętli „ulatuje” do góry jako najlżejszy.
Tablica jest przemiatana od dołu do góry (pętla i) i analizowane są dwa
sąsiadujące ze sobą
elementy (pętla j); jeśli nie są uporządkowane, to następuje ich zamiana.
void bubble(int *tab) {
for (int i=1;i<n;i++)
for (int j=n-1;j>=i;j--) if (tab[j]<tab[j-1])
{//swap
int tmp=tab[j-1];
tab[j-1]=tab[j];
tab[j]=tmp;
} }
Implementacja w C++
Analiza:
•Dość często zdarzają się puste przebiegi(nie jest dokonywana żadna wymiana, bo elementy są posortowane).
•Algorytm jest bardzo wrażliwy na konfigurację danych:
4,2,6,18,20,39,40 – wymaga jednej zamiany 4,6,18,20,39,40,2 – wymaga szesściu zamian
Algorytm jest klasy O(n2)
void bubble(int *tab) {
for (int i=1;i<n;i++)
for (int j=n-1;j>=i;j--) if (tab[j]<tab[j-1])
{//swap
int tmp=tab[j-1];
tab[j-1]=tab[j];
tab[j]=tmp;
} }
Ulepszenia: przyśpieszają, choć nie zmieniają klasy.
•Można zapamiętać indeks ostatniej zamiany (walka z pustymi przebiegami).
•Można przełączać kierunki przeglądania tablicy (walka z niekorzystnymi konfiguracjami danych). void ShakerSort(int *tab)
{
int left=1,right=n-1,k=n-1;
do {
for(int j=right; j>=left; j--) if(tab[j-1]>tab[j])
{
swap(tab[j-1],tab[j]);
k=j;
} left=k+1;
for(j=left; j<=right; j++) if(tab[j-1]>tab[j])
{
swap(tab[j-1],tab[j]);
k=j;
} right=k-1;
}
while (left<=right);
}
Algorytm poprawiony – sortowania przez wstrząsanie.
Idea:
Jest to również metoda „dziel i rządź”,
ponieważ dzieli tablicę na dwie części, które potem sortuje niezależnie.
Algorytm składa się z dwóch kroków:
Krok 1: procedura rozdzielania elementów tablicy względem wartości pewnej komórki tablicy służącej za oś podziału; proces
sortowania jest dokonywany przez tę właśnie procedurę.
Krok 2: procedura służąca do właściwego sortowania, która nie robi w zasadzie nic oprócz wywoływania samej siebie; zapewnia poskładanie wyników cząstkowych i w
konsekwencji posortowanie całej tablicy.
Sednem metody jest proces podziału, który zmienia
kolejność elementów w tablicy tak, że spełnione są trzy
warunki:
• element a[i] znajduje się dla pewnego i na właściwej pozycji w tablicy;
•Żaden z elementów a[l], …, a[i-1] nie jest większy niż a[i];
•Żaden z elementów a[i+1],
…, a[r] nie jest mniejszy niż
a[i]. W kółku mamy element
rozgraniczający, elementy mniejsze są na lewo, a większe na prawo.
Oś podziału