Nevíte-li si rady s jakýmkoliv matematickým problémem, toto místo je pro vás jako dělané.
Nástěnka
❗22. 8. 2021 (L) Přecházíme zpět na doménu forum.matweb.cz!
❗04.11.2016 (Jel.) Čtete, prosím, před vložení dotazu, děkuji!
❗23.10.2013 (Jel.) Zkuste před zadáním dotazu použít některý z online-nástrojů, konzultovat použití můžete v sekci CAS.
Nejste přihlášen(a). Přihlásit
Stránky: 1
Zdravím, mám dotaz a je maličko náročnější ho položit.
--
1) Uvažme dvě třídy: Node, Tree.
2) Třída Tree obsahuje metodu Add.
3) Tato metoda předává jeden ze vstupních parametrů (root) jako referenci na ukazatel na objekt typu Node - tedy v hlavičce je Node*& root.
4) Nyní bych chtěl dosáhnout toho, aby pokud se do vstupu tento parametr nevyplní, automaticky se bral typu NULL.
5) Jenže to nejde, protože formálně parametr předávám jako referenci na ukazatel, kdežto NULL je předání konstantní hodnoty.
6) Takže se nabízí v paměti vyhradit místo pro jednu proměnnou typu Node* (tj. ukazatel) - pojmenujme si ji třeba "NodeNULL" - a v hlavičce mé metody Add vložit jako implicitní parametr tento NodeNULL (místo původního NULL).
7) Tohle funguje, ale je to prasárna v tom smyslu, že NodeNULL není zapouzdřen dovnitř do třídy Node.
8) Jenže při definici třídy nemůžu vytvářet rovnou její instance (tohle by zjevně měla být konstantní statická instance). Takže to dovnitř do definice třídy nezapouzdřím.
--
Doufám, že je aspoň trochu tušit, oč mi jde. Poradíte?
Offline
↑ Stýv: No, chci, aby se v průběhu hodnota ukazatele mohla měnit.
Offline
↑ Anonymystik: A nešlo by použít místo reference pointer? A když už používáš pointery, nešly by použít chytré pointery?
Offline
↑ Stýv: Šlo, ale měl bych ukazatel na ukazatel. Vzhledem k rekurzivní povaze mé metody Add se toho trochu bojím, nejsem zase tak zkušený programátor. Chytré pointery neznám. Myslíš, že by mému problému nějak pomohly? (zatím neřeším mazání)
Offline
Ukzatel na ukazatel už jsem viděl, referenci an ukazatel ještě ne. :-)
Nevím, co ti může nebo nemůže pomoct, jelikož netuším, co se to vlastně pokoušíš naprogramovat. Ale obecně je dobré se hloupým pointerům spíš vyhnout, pokud to jde.
Offline
↑ Stýv: Pokouším se objektově implementovat binární vyhledávací strom. Když se pokouším vložit nový prvek, podívám se, jestli v daném vrcholu je prázdno nebo není. Pokud je tam prázdno, uložím prvek do daného vrcholu. Pokud není, podívám se, jestli hodnota klíče je menší/větší než klíč v daném vrcholu. Podle toho pak zavolám metodu Add rekurzivně na levý nebo pravý podstrom. Přitom jeden z parametrů Add je právě ukazatel na vrchol daného podstromu. Kdyby tam ale bylo prázdno, vlastně chci tento vrchol měnit, takže to musím předat jako referenci. Proto ta reference na ukazatel.
No a ten implicitní parametr chci proto, že když volám funkci na celý strom, jako parametr se vezme kořen stromu, jehož ukazatel je jeden z atributů stromu. Jenže samozřejmě nikomu nic do žádné adresy kořene není, tam chci jen říct "přidej prvek X do stromu Y". A právě na to použiju implicitní parametr.
(Poznámka: ano, vím, že by šly použít kontejnery a iterátory, ale to není point, chci se na tom spíš učit.)
Offline
To mi přijde poněkud neobjektový. Já bych v tom případě prázdného podstromu nejprve vyrobil nový podstrom a na něm volal metodu Add.
A tady opravdu jsou na místě chytré ukazatele, konkrétně unique_ptr.
Offline
↑ Stýv: Koukám, že ty bys nevyráběl v rámci stromu nový vrchol, ale rovnou celý podstrom ... to mě nenapadlo. Na ty chytré ukazatele kouknu.
Offline
↑ Anonymystik: Ten podstrom, co bych vyráběl, by se skládal jenom z jednoho vrcholu. Jak na to znova koukám, nejsou mi jasný dvě věci:
1) Co má být ta třída Tree?
2) Na co má být ukazovat ten ukazatel z "Přitom jeden z parametrů Add je právě ukazatel na vrchol daného podstromu."?
Offline
Koukám, pojmenoval jsem tu třídu hloupě Strom, nikoliv Tree; taky jsem přejmenoval NodeNULL na Nic, aby to bylo kompaktnější - občas mám problém vymýšlet názvy pro své třídy a proměnné. Nic to ale na problému nemění.
Poznámka: metody Remove, RemoveMax, RemoveMin nejsou zatím napsané.
Můj kód zatím:
#include <iostream> #include <conio.h> using namespace std; struct Node { int obsah; Node* lSyn; Node* pSyn; }; Node* Nic; //Není zapouzdřeno! class Strom { private: Node* Koren; int PocetVrcholu; public: Strom(); bool Neprazdny(); void Add(int Cislo, Node*& root=Nic, bool Vnejsi=true); void Remove(int Cislo); bool Find(int Cislo, Node* root=Nic, bool Vnejsi=true); void List(Node* root=Nic, bool Vnejsi=true); int Min(Node* root=Nic); int Max(Node* root=Nic); void RemoveMin(); void RemoveMax(); }; Strom::Strom() { Koren = NULL; PocetVrcholu = 0; }; bool Strom::Neprazdny() { if(PocetVrcholu != 0) return true; else return false; }; void Strom::Add(int Cislo, Node*& root, bool Vnejsi) { if(Vnejsi) root=Koren; if(root==NULL) { Node* Vrchol = new Node; Vrchol->obsah = Cislo; Vrchol->lSyn = NULL; Vrchol->pSyn = NULL; root = Vrchol; PocetVrcholu++; cout << "Prave ukladam cislo " << Cislo << " do stromu!" << endl; if(PocetVrcholu==1) Koren = root; } else if(Cislo == (root->obsah)) cout << "Cislo " << Cislo << " jiz bylo ve stromu ulozeno!" << endl; else if (Cislo < (root->obsah)) Add(Cislo, root->lSyn, false); else Add(Cislo, root->pSyn, false); }; bool Strom::Find(int Cislo, Node* root, bool Vnejsi) { if(Vnejsi) root=Koren; if(root==NULL) return false; if(Cislo==(root->obsah)) return true; else if(Cislo < (root->obsah)) Find(Cislo, root->lSyn, false); else Find(Cislo, root->pSyn, false); }; int Strom::Max(Node* root) { if(root==Nic) root = Koren; if(root==NULL) { cout << "Chybne pouziti metody Max() na prazdny strom!"; exit(0); }; if(root->pSyn != NULL) return Max(root->pSyn); else return (root->obsah); }; int Strom::Min(Node* root) { if(root==Nic) root = Koren; if(root==NULL) { cout << "Chybne pouziti metody Min() na prazdny strom!"; exit(0); }; if(root->lSyn != NULL) return Min(root->lSyn); else return (root->obsah); }; void Strom::List(Node* root, bool Vnejsi) { if(Vnejsi) cout << "Tento strom obsahuje "<< PocetVrcholu << " vrcholu: "; if(root==Nic) root = Koren; if(root!=NULL) { List(root->lSyn, false); cout << root->obsah << " "; List(root->pSyn, false); }; if(Vnejsi) cout << endl; }; /* void Strom::Remove(int Cislo, Node*& root, bool Vnejsi) { }; */ int main() { Strom T; T.List(); //cout << "Maximum ve stromu: " << T.Min() << endl; T.Add(4); T.Add(4); T.Add(2); T.Add(1); T.Add(6); T.Add(5); T.Add(8); for(int i=0; i<10; i++) if(T.Find(i)) cout << "Cislo " << i << " nalezeno!" << endl; T.List(); return 0; };
Offline
Promiň, já ten příspěvek vždycky chvilku edituju a opravuju si po sobě chyby.
A ještě: kde se dělá syntax highlighting?
Offline
↑ Anonymystik: Za tím "sem" měl následovat odkaz na https://pastebin.com/ :-)
Offline
Tady vidím velký špatný...
1) Tvůj problém se vyřeší sám, když se zbavíš zbytečný třídy Strom a metody přesuneš do třídy Node.
2)
a) Vyhoď #include <conio.h>
b) Místo NULL používej nullptr.
c) Místo hloupých ukazatelů použij chytré ukazatele.
3)
a) Zvykni si používat anglické názvy. Čeština ve zdrojáku jednak vypadá divně, jednak tomu nebude skoro nikdo kromě tebe rozumět.
b) Neboj se plýtvat místem, je to pak přehlednější. Tj. např. místo
if(Cislo==(root->obsah)) return true; else if(Cislo < (root->obsah)) Find(Cislo, root->lSyn, false); else Find(Cislo, root->pSyn, false);
piš
if(Cislo==(root->obsah)) { return true; } else if(Cislo < (root->obsah)) { Find(Cislo, root->lSyn, false); } else { Find(Cislo, root->pSyn, false); }
Offline
↑ edison: Fakt to není přehlednější. Když jsem se na to podíval, viděl jsem hned větev pro případ root==NULL a divil jsem se, kde jsou ty ostatní případy. Až když jsem to začal hodně podrobně studovat, zjistil jsem, že jsou zdrcnutý tam dole.
No a co se týká jednořádkových bloků – taky jsem to dělal dřív podobně, ale co když si usmyslíš, že tam chceš přidat třeba nějakej výpis typu "Hodnota už je ve stromu." nebo cokoliv jinýho? Najednou tam ty závorky budeš muset přidat. A nebo na ně zapomeneš, a vyrobíš bug. Proto je lepší i tyhle jednořádkový bloky už od začátku obalit závorkama.
Whitespacy přidávají na přehlednosti. Pokud je nějaká metoda s tímhle formátováním příliš dlouhá (víc než třeba dvě obrazovky), tak je nejspíš blbě napsaná.
Tohle nemám z vlastní hlavy, ale z přednášky Dopručené postupy v programování.
Pokud jde o to formátování, existujou samozřejmě různý konvence, který se v detailech liší, ale ten princip je všude stejnej.
Offline
↑ Stýv: No, když těch příkazů je v bloku vícero, samozřejmě přidělávám závorky a řádkuji. Ale u těch jednořádkových jsem si navyknul to dělat bez toho (pak často dodělávám nebo naopak umazávám závorky, jak je potřeba). Tvůj přístup (nejen tvůj) jsem aplikoval dříve a ustoupil jsem od něj, protože mi přišlo, že tím hrozně nabobtnává kód a tvoří se šílené vrstvy závorek. Asi je to otázka vkusu.
--
Hodně mě překvapuje tvůj přístup "smazat třídu Strom". Když jsem vymýšlel koncepci toho programu, tak jsem si říkal, že strom bude nějaká datová struktura, do které se budou ukládat data a odkud se mohou odebírat. No a ta data se v rámci toho stromu budou uchovávat v "atomických" datových jednotkách - vrcholech ("node"). Přišlo mi to v danou chvíli jako naprosto přirozený přístup. Navíc do Stromu mohu přidat atribut jako "PocetVrcholu", který se týká stromu jako celku. Kdybych zrušil třídu strom a přidal atribut PocetVrcholu do každého vrcholu, měl bych výhodu, že bych okamžitě věděl počet vrcholů v každém podstromu, nicméně by mi asi dost nabobtnala paměťová náročnost.
--
Co se týká té češtiny, jsem dost nekonzistentní, dělám si to často dost podle nálady. Už jsem začal přemýšlet nad konvencí, že bych datové třídy a funkce pojmenovával anglicky, kdežto proměnné a instance česky (abych to nějak od sebe odlišil).
--
Chytré pointery prozkoumám, díky za tip. Asi to nebylo přesně to, co jsem chtěl, ale stejně na to kouknu.
Offline
↑ Anonymystik: Pokud ti "hrozně nabobtnává kód a tvoří se šílené vrstvy závorek", tak je problém někde jinde. Coding-style pro linuxový kernel mino jiné říká, že se odsazuje o 8 mezer (sic!) a funkce by se měla vejít na dvě stránky, které mají 80*24 znaků.
Now, some people will claim that having 8-character indentations makes the code move too far to the right, and makes it hard to read on a 80-character terminal screen. The answer to that is that if you need more than 3 levels of indentation, you’re screwed anyway, and should fix your program.
Strom = kořenový vrchol. Pokud opravdu nezbytně potřebuješ počítat vrcholy se složitostí o(1), můžeš udělat nad kořenovým vrcholem tenkou obálku, kde si to budeš pamatovat, nicméně i tak by metoda Tree.Add(n) měla v podstatě jenom volat metodu root.Add(n). Problém tohohle přístupu ovšem je, že pak podstromy toho stromu samy o sobě nejsou stromy, což je poněkud divný.
Na češtinu zapoměň. Pokud budeš někdy něco doopravdy programovat, dřív nebo později na ty zdrojáky bude koukat někdo jinej (kolega z druhýho konce světa, nezávislej vývojář opensource nebo třeba někdo, kdo ti bude chtít poradit na stackoverflow a nebude tomu rozumět). Příklad rozumné konvence je začínat typy a funkce velkým písmenem a proměnné a instance malým.
Z čeho se to C++ učíš? Zřejmě z nějakého zastaralého zdroje, jelikož C++ se zásadně změnilo ve verzi C++11 (z roku 2011).
Offline
↑ Stýv:
Už jsem použil víc než 3x odsazení a ano, souhlasím, že pak to přestává už být přehledné.
--
Ten přístup s třídami Vrchol a Strom jsem vyřešil tím, že jsem tam u metod jako Add právě zavedl ty implicitní parametry. To znamená, že pokud nikdo žádný parametr nazadá, volá se metoda na strom jako celek. Pokud naopak je zde parametr v podobě vrcholu (resp. reference na pointer na vrchol), mohu metodu zavolat pouze na příslušný podstrom. Jiný přístup by mohl být ty metody přetížit, přičemž ta verze s parametrem by byla začleněná do sekce "private", aby na ni nešlo šahat zvenčí.
--
Programuju si jen sám pro sebe, neživím se tím. Pokud tě to zajímá, rád bych si zkusil naprogramovat jednu jednoduchou hru, pak bych si rád vyzkoušel Machine learning a konečně jako třetí projekt mám v hlavě nějakou teorii her a evoluční algoritmy. Ale zatím si zkouším takové mini-projekty, na kterých se trénuju (na Stromech se člověk krásně naučí dělat s pointery, s rekurzí a s třídami, takže ideální cvičení).
--
Mám doma tyto knížky:
1) C++ výukový kurz od Davida Matouška (poměrně vydatná příručka, obsahuje i klasické Cčko)
2) Algoritmy od autora Piotr Wróblewski (psaná přímo pro jazyk C++)
3) Průvodce labyrintem algoritmů od Martina Mareše a Tomáše Válly (psaná v pseudokódu, ale velice dobrá)
Zbytek je googlení a internet.
--
Jako IDE používám Builder 6 a Dev C++.
--
V souvislosti s tím - neznáš nějakou knížku, která by se věnovala tomu, jak efektivně používat OOP? (tím nemyslím výuku nějakého konkrétního objektově orientovaného jazyka, ale spíše způsob, jak navrhovat složitější programy a rozsáhlejší projekty - tj. jak se lépe sžít s objektovým paradigmatem a naučit se tak myslet - občas mám dojem, že myslím dost postaru).
Offline
Používat něco jako Add(Cislo, root->lSyn, false) není OOP. Mělo by to vypadat zhruba jako lSyn.Add(Cislo). Až na ty český názvy.
I když si programuješ pr sebe, měl by ses snažit to dělat pořádně.;-) A stackoverflow by se ti tím spíš mohlo hodit.
Znám akorát toho Průvodce labyrintem algoritmů, podle toho se učí na matfyzu.
Z toho matouška jsem našel jenom krátičkou ukázku, ael podle obsahu je tam zmínka o chytrých ukazatelích až někde vzadu, takže to moc moderním dojmen nepůsobí (přestože je to vydané 2018).
Taky neznám, ale podle obrázků to vypadá obojí dost old-schoolově. Já používám Visual Studio, které rozhodně můžu doporučit.
Možná Code Complete: A Practical Handbook of Software Construction, podle které byla koncipovaná ta přednáška na Doporučené postupy v programování. Není jenom o OOP, vlastně ani nevím, jak moc se tam OOP reozebírá, ale určitě je tam plno užitečných informací o výrobě softwaru.
Offline
Trochu ten kód předělávám za použití přetěžování funkcí. Dospěl jsem k názoru, že to bude lepší, než používat implicitní parametry.
Offline
Stránky: 1