3-os paskaitos skaidrės

Download Report

Transcript 3-os paskaitos skaidrės

Algoritmai ir duomenų struktūros
(ADS)
3 paskaita
Saulius Ragaišis, VU MIF
[email protected]
2011-02-21
Dvejetainis paieškos medis
Dvejetainis paieškos medis (binary search tree) – tai
dvejetainis medis, kurio viršūnėse saugomos reikšmės
sutvarkytos taip:
Viršūnės reikšmė yra didesnė už visas reikšmes jos
kairiajame pomedyje.
Viršūnės reikšmė yra mažesnė už visas reikšmes jos
dešiniajame pomedyje.
Kairysis ir dešinysis pomedžiai yra dvejetainiai paieškos
medžiai.
Įterpimas
Duomenų įterpimo į dvejetainį paieškos medį algoritmas:
Patikrinti viršūnę.
Jei įterpiama reikšmė lygi viršūnės reikšmei, baigti darbą "tokia reikšmė
jau yra".
Jei įterpiama reikšmė mažesnė už viršūnės reikšmę,
- jei kairysis pomedis netuščias, pereiti į jį ir kartoti nuo 1-o žingsnio
- jei kairysis pomedis tuščias, sukurti jame naują viršūnę su įterpiamais
duomenimis
Jei įterpiama didesnė už viršūnės reikšmę,
- jei dešinysis pomedis netuščias, pereiti į jį ir kartoti nuo 1-o žingsnio
- jei dešinysis pomedis tuščias, sukurti jame naują viršūnę su įterpiamais
duomenimis
Šis algoritmas yra skirtas reikšmių aibės saugojimui, bet jį (arba pačia duomenų struktūrą)
galima modifikuoti reikšmių rinkinio saugojimui.
Išmetimas
Duomenų išmetimo iš dvejetainio paieškos medžio algoritmas:
1. Rasti viršūnę su išmetamais duomenimis.
2. Jei viršūnė yra lapas, panaikinti viršūnę ir baigti darbą.
3. Jei viršūnė nėra lapas, sukeisti jos reikšmę su tinkamos viršūnės reikšme ir
išmesti tą viršūnę (tęsti algoritmą nuo 2-o žingsnio).
Tinkama sukeitimui viršūnė yra
- turinti didžiausią reikšmę iš viršūnės kairiojo pomedžio
- turinti mažiausią reikšmę iš viršūnės dešiniojo pomedžio
Dvejetainių paieškos medžių optimalumas
Tarkime, kad reikšmės į medį ateina atsitiktine tvarka.
Norime išsiaiškinti,kiek vidutiniškai daugiau palyginimo operacijų prireiktų,
ieškant elemento gautame medyje negu ieškant elemento visiškai
subalansuotame medyje.
Iš pradžių paverskime medį į 2-medį. Visas medžio viršūnes laikykime
skrituliais, o visus tuščius pomedžius (nulines rodykles) pavaizduokime
kvadratėliais.
Tampa
Visos duoto medžio viršūnės tampa naujo 2-medžio vidinėmis viršūnėmis, o
naujos – išorinėmis (lapais).
Sėkminga paieška sustos prie vidinės 2-medžio viršūnės, nesėkminga – prie
lapo.
Remiantis tokia duomenų struktūra galima įrodyti teoremą.
Teorema
Teorema: Vidutinis palyginimo operacijų skaičius vidutiniame dvejetainiame
paieškos medyje su n viršūnių apytiksliai lygus
2 ln n  (2 ln 2)(log2n)
Išvada: Vidutiniškam dvejetainiam paieškos medžiui apytiksliai reikia
2 ln 2  1.39
karto daugiau palyginimo operacijų negu visiškai subalansuotam medžiui.
Kitaip tariant, vidutinė paieškos nesubalansuotame dvejetainės paieškos medyje
kaina yra 39 procentais didesnė.
Dvejetainio medžio realizacijos
1. Dinaminis:
elementai su 2 rodyklėmis (tokie patys kaip dvipusiam sąrašui).
2. Masyve:
šaknis saugoma elemente su indeksu 1;
viršūnės, saugomos elemente su indeksu N:
- vaikai saugomi elementuose su indeksais 2N ir 2N+1;
- tėvas saugomas elemente su indeksu N div 2.
Aptarti privalumus ir trūkumus.
Duomenų struktūra Prioritetinė eilė
Gyvenime ne kartą esame susidūrę su situacija, kai yra eilė, bet atsiranda
žmonių, kurie aptarnaujami be eilės. Jei tokių atsiranda daug, tai
susidaro eilė “be eilės”...
Apibendrinę turime tokią situaciją:
- gali būti daugiau nei viena eilė;
- kiekvienos eilės elementai turi vienodus prioritetus;
- pirmiausia aptarnaujami elementai iš eilės su didžiausiu prioritetu;
- elementai su vienodu prioritetu aptarnaujami eilės tvarka.
Duomenų struktūra, užtikrinanti tokį elementų aptarnavimo eiliškumą,
vadinama prioritetine eile (angl. Priority Queue).
Kiekvienas elementas ne tik saugo tam tikrus duomenis, bet ir turi prioritetą,
kuris ir apibrėžia elemento aptarnavimo tvarką (žinoma, svarbi ir
elementų “pasirodymo” tvarka).
Prioritetinės eilės operacijos
Sukurti tuščią prioritetinę eilę
Patikrinti, ar prioritetinė eilė tuščia
Patikrinti, ar prioritetinė eilė pilna
Įdėti naują elementą į prioritetinę eilę
Išimti elementą iš prioritetinės eilės
Sunaikinti prioritetinę eilę
Toliau aptarsime prioritetinės eilės realizacijas jau žinomomis
duomenų struktūromis. Svarbu atkreipti dėmesį, kad
realizacijų aptarime vaizduojami tik elementų prioritetai, o
pačios elementų reikšmės nevaizduojamos.
Prioritetinės eilės realizacija masyvu
Realizacijoje masyvu elementai dažniausiai išdėstomi prioritetų didėjimo
tvarka. Tai užtikrina, kad elementas su didžiausia prioriteto reikšme bus
masyvo gale.
5
min
7
7
10
...
15
15
max
Išėmimas: išimamas paskutinis elementas.
Įdėjimas: randama teisinga pozicija – elementas turi būti įterptas pagal savo
prioritetą o tarp turinčių tokį prioritetą kaip pirmasis – o po to
perstumiami visi elementai, kad atsirastų vietos naujam elementui.
Prioritetinės eilės realizacija tiesiniu sąrašu
Realizacijoje tiesiniu sąrašu elementai dažniausiai išdėstomi prioritetų
mažėjimo tvarka, t.y. elementas su didžiausia prioriteto reikšme yra
sąrašo pradžioje.
15
15
10
7
7
5
Išėmimas: išimamas pirmas elementas.
Įdėjimas: einama sąrašu tol, kol randama elementui tinkama vieta – naujas
elementas turi būti įterptas prieš pirmą elementą, turintį mažesnį
prioritetą už naują elementą arba sąrašo gale.
Prioritetinės eilės realizacija dvejetainiu paieškos medžiu
7
9
4
1
6
Elementai dėstomi kaip tradiciniame dvejetainiame paieškos medyje tik ne
pagal raktus, bet pagal prioritetus.
Tokios realizacijos ypatumas yra tame, kad elementas su didžiausia prioriteto
reikšme bus pačioje dešinėje viršūnėje. Taigi reikia sekti dešiniuosius
vaikus, kol atsiras viršūnė be dešiniojo vaiko.
Būtina atkreipti dėmesį, kad viršūnėje saugomas ne 1 elementas, bet eilė
elementų, turinčių viršūnėje nurodytą prioritetą.
Duomenų struktūra Piramidė
Piramidė (angl. Heap) yra duomenų struktūra, panaši į dvejetainį
paieškos medį, bet skiriasi nuo pastarojo dviem esminiais
aspektais.
Visų pirma paieškos medžiuose duomenys yra surikiuoti, o
piramidėje – ne. Tačiau tai, kaip duomenys yra organizuoti
piramidėje, leidžia efektyviai realizuoti prioritetinės eilės
operacijas, tokias kaip: sukurti; patikrinti, ar eilė yra tuščia;
įterpti ir išmesti.
Kitas esminis skirtumas yra tas, kad dvejetainiai paieškos medžiai
gali būti įvairūs (pvz., AVL medis, Raudonai-juodas medis).
Piramidė visada yra užbaigtas (angl. complete) dvejetainis
medis, todėl jei jo maksimalus dydis yra žinomas iš anksto,
tai realizacija masyvu yra labai efektyvi.
Duomenų struktūra Piramidė (2)
Apibrėžimas. Duomenų struktūra Piramidė (angl. Heap)
- užbaigtas dvejetainis medis, kurio šaknies
prioriteto reikšmė yra didesnė arba lygi kiekvieno
jos vaiko prioriteto reikšmei ir abu šaknies
pomedžiai yra duomenų struktūros piramidės.
Kitaip nei dvejetainiame paieškos medyje, nėra jokio sąryšio tarp
vaikų reikšmių, t.y. nežinoma, kurio iš vaikų reikšmė
didesnė.
Operacijos:
elemento išmetimas;
elemento įdėjimas.
Būtina atkreipti dėmesį, kad jei piramidė naudojama prioritetinės
eilės realizacijai, tai viršūnėje saugomas ne 1 elementas, bet
eilė elementų, turinčių viršūnėje nurodytą prioritetą.
Duomenų struktūra Vektorius
Java klasė Vector: dinaminis masyvas, į kurį galima dėti bet
kokius objektus... Pagrindinės operacijos:
Sukurti: Vector()
Patikrinti, ar tuščias: boolean isEmpty()
Išvalyti: void removeAllElements()
Išimti elementą: void removeElementAt(int index)
Įterpti elementą nurodytoje vietoje: void insertElementAt(Object
obj, int index)
-
Pridėti elementą gale: void addElement(Object obj)
Pakeisti elemento reikšmę: void setElementAt(Object obj, int index)
Gauti elemento reikšmę: Object elementAt(int index)
Sužinoti dydį: int size()
Klasė turi ir daug kitų naudingų operacijų.
Hanojaus bokštų uždavinys
Duoti trys stulpai ir N diskų. Ant vieno (pradinio)
stulpo sumauti diskai didėjimo tvarka, einant iš
viršaus į apačią. Reikia visus diskus perkelti
nuo pradinio stulpo ant laisvo (tikslo) stulpo,
pasinaudojant atsarginiu stulpu.
Apribojimai:
1. Per vieną ėjimą galima nuimti tik vieną diską ir jį
būtina iš karto uždėti ant kito stulpo.
2. Didesnio disko negalima dėti ant mažesnio.
Rekursinis Hanojaus bokštų uždavinio sprendimo algoritmas
Jei diskas tik vienas (N=1), uždavinys elementarus – tiesiog
perkeliame šį 1 diską nuo pradinio stulpo ant tikslo
stulpo.
Tarkime, kad mes mokame išspręsti uždavinį su N-1 disku.
Tada norėdami perkelti N diskų elgiamės taip:
N-1 diską nuo pradinio stulpo perkeliame ant
atsarginio stulpo (pagal prielaidą tai mes jau mokame
padaryti);
tada ant pradinio stulpo likusį 1 diską (didžiausią)
tiesiog perkeliame ant tikslo stulpo;
N-1 diską nuo atsarginio stulpo perkeliame ant tikslo
stulpo (pagal prielaidą tai mes jau mokame padaryti).
Rekursinės procedūros pseudokodas
Towers (count, source, dest, spare)
if count = 1 then tiesiog perkelti 1 diską nuo pradinio stulpo (source) ant tikslo stulpo (dest)
else
begin
Towers (count-1, source, spare, dest)
Towers (1, source, dest, spare)
Towers (count - 1, spare, dest, source)
end
arba
Towers (count, source, dest, spare)
if (count > 0)
begin
Towers (count - 1, source, spare, dest)
perkelti 1 diską nuo pradinio stulpo (source) ant tikslo stulpo (dest)
Towers (count - 1, spare, dest, source)
end
pažymėjimai:
count - diskų skaičius,
source - pradinis stulpas, dest - tikslo stulpas, spare - tarpinis stulpas
Žingsnių skaičiaus įvertinimas
Pažymėkime žingsniai(N) žingsnių skaičių, reikalingą perkelti N diskų.
Kai N = 1, tai žingsniai(1) = 1.
Kai N > 1, procedūra Towers iškviečiama 3 kartus: perkelti N-1 diską, perkelti 1
diską ir vėl perkelti N-1 diską. Taigi galime apskaičiuoti žingsniai(N)
pagal tokią rekurentinę formulę:
žingsniai(N) = žingsniai(N-1) + žingsniai(1) + žingsniai(N-1) =
2 * žingsniai(N-1) + 1
Žingsnių skaičių galima apskaičiuoti pagal tokią formulę:
žingsniai(N) = 2N - 1 (N1)
Šios formulės teisingumą įrodysime matematinės indukcijos metodu.
Patikriname jos teisingumą, kai N=1.
žingsniai(1) = 21 - 1 = 2 - 1 = 1
Tarkime, kad ji teisinga su N-1 (t.y. žingsniai(N-1) = 2N-1 – 1), tada:
žingsniai(N) = 2 * žingsniai(N-1) + 1 = 2 * (2N-1 – 1) + 1 = 2N – 2 + 1 =
2N - 1
Įrodymas baigtas.
Perrinkimas
Gyvenime tenka susidurti su užduotimis, kuriose reikia rasti
sprendinį iš daug galimų variantų. Vienas iš sprendimo
būdų yra perrinkti (patikrinti) galimus variantus, bet
variantų dažnai būna tiek daug, kad perrinkimas visų
galimų variantų yra praktiškai neįmanomas.
Reikia kažkaip apriboti nagrinėjamus variantus, tačiau
apribojant negalima prarasti egzistuojančio sprendinio.
Dažnai sprendinio radimui pakanka perrinkti tik tam tikrus
variantus.
Šios problemos sprendimo būdą detaliau panagrinėsime su
tokiu uždaviniu: šachmatų lentoje sustatyti 8 karalienes
taip, kad nei viena iš jų nekirstų kitos.
8 karalienių uždavinys
Viena iš strategijų yra perrinkti visus galimus variantus ir iš
jų išrinkti tinkamą.
Viso variantų yra 4 426 165 368.
Galima pastebėti, kad yra ypatybė, kuri žymiai sumažina
perrinkimų skaičių: vienoje eilutėje ir viename stulpelyje
negali būti pastatytos dvi karalienės. Kitaip sakant,
kiekvienas stulpelis ir kiekviena eilė gali turėti tik vieną
karalienę.
Perrinkimų skaičius sumažėja iki 8!=40 320.
Prieš tolimesnį nagrinėjimą reikėtų susipažinti su tokia
strategija kaip valdymas su grįžimais (angl.
backtracking).
Klausimai
?