Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

[C++] Liczba narcystyczna - optymalność mojego kodu.

Ostatnio zmodyfikowano 2015-02-22 23:01
Autor Wiadomość
Toreno96
Temat założony przez niniejszego użytkownika
[C++] Liczba narcystyczna - optymalność mojego kodu.
» 2015-02-20 17:32:33
Witam. Muszę wykonać zadanie, polegające na napisaniu w C++ algorytmu, który sprawdzi, czy podana liczba jest liczbą Armstronga(narcystyczną), czyli taką, której cyfry podniesione do potęgi n, gdzie n = ilość tych cyfr, po zsumowaniu dadzą tę liczbę. Na przykład: 153 = 1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153.
Mam narzucone dane:
k - liczba całkowita dodatnia,
n - liczba cyfr(całkowita dodatnia) w zapisie dziesiętnym liczby k,
tab_cyfr[] - tablica zawierająca kolejne cyfry zapisu dziesiętnego liczby k, w kolejności od najmniej znaczącej do najbardziej znaczącej,
potega( a, n ) - funkcja zwracająca n-tą potęgę liczby a.

Zdaję sobie sprawę z tego, że jest to zadanie prymitywne i wystarczy ledwie taki fragment kodu:
C/C++
unsigned int suma_pot = 0;

for( unsigned int i = 0; i < n; i++ )
     suma_pot += potega( tab_cyfr[ i ], n );

cout << "Wynik: ";
if( suma_pot == k )
     cout << "PRAWDA\n";
else
     cout << "FALSZ\n";


Jednak w przypadku, gdyby podana liczba miała kilkaset cyfr, to program musiałby każdą z tych kilkuset cyfr potęgować i dodawać, by dojść do wyniku, co nie jest wymagane.
Dla przykładu liczba 2872. Moglibyśmy każdą cyfrę z osobna potęgować do potęgi 4-tej, ale po co, gdy wystarczy zauważyć, że 8^4 = 4096. 4096 > 2872, czyli nie może być to liczba narcystyczna. W związku z tym, zważywszy też na to, że jestem z natury perfekcjonistą, postanowiłem spróbować napisać bardziej optymalny kod, rozwiązujący ten problem:
C/C++
if( n == 1 ) {
    cout << "Wynik: PRAWDA\n";
    system( "PAUSE" );
    return 0;
}

unsigned int suma_pot = 0, i, j;
bool wynik = true, czy_swap;

for( i = n; i > 1; i-- ) {
    czy_swap = false;
    for( j = 0; j + 1 < i; j++ )
    if( tab_cyfr[ j ] > tab_cyfr[ j + 1 ] ) {
        swap( tab_cyfr[ j ], tab_cyfr[ j + 1 ] );
        czy_swap = true;
    }
    suma_pot += potega( tab_cyfr[ j ], n );
    if( suma_pot > k ) {
        wynik = false;
        break;
    }
    if( !czy_swap )
         break;
   
}

if( wynik ) {
    for( i = 0; i < j; i++ )
         suma_pot += potega( tab_cyfr[ i ], n );
   
    if( suma_pot != k )
         wynik = false;
   
}

cout << "Wynik: ";
if( wynik )
     cout << "PRAWDA\n";
else
     cout << "FALSZ\n";


Jak myślicie, czy jest to dobre rozwiązanie?
Kod jest dłuższy, a w najbardziej pesymistycznym wypadku, program wykona wszystko to co wcześniejszy, plus kilka dodatkowych operacji w pętlach odpowiedzialnych za sortowanie. Z drugiej jednak strony, w pozostałych przypadkach, a szczególnie w takim jak przytoczony przykład z 2872, komputer ominie konieczność niepotrzebnego potęgowania i dodawania wszystkich cyfr.
P-126897
aksen
» 2015-02-20 17:51:26
1. unsigned int  - to bardzo mały zakres obliczeń
2. nie podałeś całego kodu
P-126898
Toreno96
Temat założony przez niniejszego użytkownika
» 2015-02-20 18:11:22
1. Sugerujesz posłużyć się unsigned long long?
2. Uznałem, że istotny będzie tylko fragment odpowiedzialny za sam algorytm sprawdzania, czy podana liczba jest liczbą narcystyczną, zważywszy że większość potrzebnych zmiennych mam podaną i narzuconą w treści zadania. Ale o to całość:
C/C++
#include <iostream>
#include <cstdlib>

using namespace std;
// FUNKCJE------------

unsigned int potega( unsigned int a, unsigned int n )
{
    unsigned int wynik = 1;
   
    while( n > 0 ) {
        wynik *= a;
        n--;
    }
   
    return wynik;
} // end of potega()

// -------------------
int main( int argc, char ** argv )
{
    // zad. 1.2
    unsigned int k; cin >> k;
    unsigned int k_zap = k, n = 0;
   
    while( k_zap > 0 ) {
        n++;
        k_zap /= 10;
    }
    k_zap = k;
   
    unsigned int tab_cyfr[ n ];
    for( int i = 0; k_zap > 0; i++ ) {
        tab_cyfr[ i ] = k_zap % 10;
        k_zap /= 10;
    }
   
    // zad. 1.3
    if( n == 1 ) {
        cout << "Wynik: PRAWDA\n";
        system( "PAUSE" );
        return 0;
    }
   
    unsigned int suma_pot = 0, i, j;
    bool wynik = true, czy_swap;
   
    for( i = n; i > 1; i-- ) {
        czy_swap = false;
        for( j = 0; j + 1 < i; j++ )
        if( tab_cyfr[ j ] > tab_cyfr[ j + 1 ] ) {
            swap( tab_cyfr[ j ], tab_cyfr[ j + 1 ] );
            czy_swap = true;
        }
        suma_pot += potega( tab_cyfr[ j ], n );
        if( suma_pot > k ) {
            wynik = false;
            break;
        }
        if( !czy_swap )
             break;
       
    }
   
    if( wynik ) {
        for( i = 0; i < j; i++ )
             suma_pot += potega( tab_cyfr[ i ], n );
       
        if( suma_pot != k )
             wynik = false;
       
    }
   
    cout << "Wynik: ";
    if( wynik )
         cout << "PRAWDA\n";
    else
         cout << "FALSZ\n";
   
    system( "PAUSE" );
    return 0;
}
P-126899
aksen
» 2015-02-20 19:07:03
Nie piszesz komentarzy co robią poszczególne fragmenty programu.
Masz tu chyba sortowanie bąbelkowe tablicy z cyframi?


potęgowanie możesz zoptymalizować np. tak (inline!!!):
C/C++
inline unsigned int potega( unsigned int a, unsigned int n )
{
    unsigned int wynik = 1;
   
    while( n-- > 0 )
         wynik *= a;
   
    return wynik;
}


PS.
Cyfry masz podane w tablicy i program sprawdza tylko jedną liczbę (nawet nie przekracza ona zakresu unsigned int).
Taki program wykona się bardzo szybko i jego optymalizacja raczej nie ma sensu.

Optymalizacja miałaby sens gdybyś takich liczb do sprawdzenia miał miliony (np wyszukanie wszystkich liczb narcystycznych od 1 do miliona, miliarda czy jeszcze dalej). Wtedy można optymalizować program i porównywać czasy  wykonania obliczeń.
P-126900
Toreno96
Temat założony przez niniejszego użytkownika
» 2015-02-20 19:39:11
Tak, zastosowałem sortowanie bąbelkowe tablicy z cyframi oraz tę jego właściwość, że sortując, umieszcza ono elementy od największego na końcu tablicy. Czyli tak jak mówiłem, w przypadku 2872, ustawia mi to w 2728. Wtedy następuje fragment:
C/C++
suma_pot += potega( tab_cyfr[ j ], n );
if( suma_pot > k ) {
    wynik = false;
    break;
}
Który sprawdza, czy j-ty element tablicy(czyli ta ostatnia z liczb, ustawionych na końcu w pętli wewnętrznej sprzed chwili), po odpowiednim podniesieniu do potęgi i dodaniu do dotychczasowej sumy spotęgowanych cyfr jest większy, niż podana liczba do sprawdzenia. Jeśli jest(Jak w przypadku 2872, gdzie 8^4 = 4096, czyli > 2872), to przerywa pętlę sortującą i podaje wynik = false. Oczywiście, w przypadku gdyby liczbą k było np. 8727, 8^4 nie byłoby większe od k, więc sortowałoby dalej, przy następnej iteracji dodając do sumy_pot liczbę 7^4, potem 2^4 i tak aż do posortowania tablicy lub ewentualnego otrzymania wyniku false.

Natomiast w przypadku, gdy podczas sortowania nie otrzymamy wyniku false, dalsza część kodu:
C/C++
if( wynik ) {
    for( i = 0; i < j; i++ )
         suma_pot += potega( tab_cyfr[ i ], n );
   
    if( suma_pot != k )
         wynik = false;
   
}
I tu już doda spotęgowaną każdą cyfrę, która do tej pory nie była potęgowana.

Jeśli mój algorytm nadal jest niezrozumiały, to później postaram się wkleić wersję z wprowadzonymi komentarzami.

A o istnieniu inline, przyznam szczerze, że nie miałem pojęcia. Jak dotąd moja wiedza na temat programowania w C++ ogranicza się głównie do wiedzy wyniesionej ze szkoły oraz części kursu z tego portalu, więc nie jest za bogata, co zapewne widać po moim kodzie ^^'

Edit:
Nie zauważyłem przed napisaniem wiadomości, że dodałeś "PS. [...]".
Chodziło mi o to, czy ten, powiedzmy że długi kod, jest optymalniejszy od krótszego i prostszego:
C/C++
#include <iostream>
#include <cstdlib>

using namespace std;
// FUNKCJE------------

unsigned int potega( unsigned int a, unsigned int n )
{
    unsigned int wynik = 1;
   
    while( n > 0 ) {
        wynik *= a;
        n--;
    }
   
    return wynik;
} // end of potega()

// -------------------
int main( int argc, char ** argv )
{
    // zad. 1.2
    unsigned int k; cin >> k;
    unsigned int k_zap = k, n = 0;
   
    while( k_zap > 0 ) {
        n++;
        k_zap /= 10;
    }
    k_zap = k;
   
    unsigned int tab_cyfr[ n ];
    for( int i = 0; k_zap > 0; i++ ) {
        tab_cyfr[ i ] = k_zap % 10;
        k_zap /= 10;
    }
   
    // zad. 1.3
    unsigned int suma_pot = 0;
   
    for( unsigned int i = 0; i < n; i++ )
         suma_pot += potega( tab_cyfr[ i ], n );
   
    cout << "Wynik: ";
    if( suma_pot == k )
         cout << "PRAWDA\n";
    else
         cout << "FALSZ\n";
   
    system( "PAUSE" );
    return 0;
}

Tego dotyczyło od samego początku moje pytanie i jak dotąd, nadal nie otrzymałem na nie odpowiedzi.
P-126902
aksen
» 2015-02-20 19:46:15
Program z sortowaniem tablicy może wykonywać się dłużej niż program bez sortowania.
Warto zrobić obliczenia dla większej ilości liczb i porównać czasy. Można się bawić godzinami ;)
 
P-126904
Toreno96
Temat założony przez niniejszego użytkownika
» 2015-02-21 16:40:51
Porównałem czasy i o ile wszystko zrobiłem dobrze, to... Nie ma większego znaczenia, którego algorytmu się tutaj użyje. Przeważnie czas wykonania i tak był za mały, by dało się wykonać porównanie, więc wpakowałem poszczególne algorytmy w pętle, żeby dla pojedynczej liczby zarówno ten z sortowaniem, jak i bez wykonywały się po kilka tysięcy razy. Ostatecznie okazało się, że ten bez sortowania częściej okazywał się szybszy. Czyli niepotrzebnie przekombinowałem, skazując siebie tylko na klepanie dłuższego kodu - trochę szkoda.
Z drugiej strony, słyszałem raz od znajomego informatyka, że niekoniecznie szybkość ma największe znaczenie, bo jest jeszcze kwestia zasobożerności etc. Z tego względu właśnie dodałem między innymi sortowanie, żeby - jak tłumaczyłem na początku - program nie musiał liczyć zawsze każdej cyfry i niepotrzebnie zawalać pamięci komputera kolejnymi wynikami, tylko w najlepszym przypadku spotęgować ledwie jedną cyfrę w całej liczbie.

Mógłby ktoś mnie oświecić, czym ostatecznie powinienem kierować się przy tworzeniu algorytmów?

A także, czy moja idea była choć trochę słuszna, czy przy tak prymitywnych zadaniach do rozwiązania jak to, lepiej jednak zdecydować się na prostszy i krótszy algorytm, niż niepotrzebnie to wszystko komplikować?
P-126982
pekfos
» 2015-02-21 16:50:46
Mógłby ktoś mnie oświecić, czym ostatecznie powinienem kierować się przy tworzeniu algorytmów?
Zależy od konkretnego celu, jaki algorytm ma osiągnąć i od zastosowania dla tego celu.

czy przy tak prymitywnych zadaniach do rozwiązania jak to, lepiej jednak zdecydować się na prostszy i krótszy algorytm, niż niepotrzebnie to wszystko komplikować?
Lepiej prościej, nie zawsze warto się spieszyć z optymalizacjami. Tutaj (nie wnikałem za bardzo) można by zrobić tablicę dwuwymiarową Nx10 wypełnioną liczbami 0-9 w kolejnych potęgach. Wartości wpisane na sztywno w kod, lub raz obliczane na początku programu - wsio rawno. Cały algorytm to sumowanie odpowiednich wartości z tablicy ze sprawdzeniem, czy suma nie jest już przypadkiem większa od testowanej liczby. Algorytm prosty i szybki. N można ustalić jako największą wartość, dla której N*9^N ma sens w zastosowanym typie danych.
P-126983
« 1 » 2 3
  Strona 1 z 3 Następna strona