Faktorizace

Faktorizace je proces převedení čísla na součin jeho faktorů, čili rozklad na prvočísla. Rozklad je dle základní věty aritmetiky jednoznačný. Předpokládá se, že faktorizace není jednoduchý problém, na čemž je založena bezpečnost některých šifrovacích algoritmů (například RSA). Prvočíselný rozklad má také využití při výpočtu největšího společného dělitele a nejmenšího společného násobku. Obě tyto hodnoty lze ovšem lépe vypočíst pomocí Euklidova algoritmu.

Příklady

15 = 3 \cdot 5
105 = 3 \cdot 5 \cdot 7
525 = 3 \cdot 5 \cdot 5 \cdot 7
1025 = 5 \cdot 5 \cdot 41
4352= 2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 \cdot 2 \cdot 17

Složitost faktorizace

Častou nejasností ohledně faktorizace je její složitost. Pokud se podíváme na klasický algoritmus, který postupně zkouší dělit číslo až do odmocniny, tak by se zdálo, že je složitost polynomiální (odmocnina). Toto ovšem není pravda, protože se složitost počítá na základě velikosti dat, což není velikost čísla v desítkové soustavě, ale počet míst nutných k zakódování čísla v binární soustavě. Skutečná složitost algoritmu je proto exponenciální.

Kód

    /**
     * Rozloží číslo n >= 2 na prvočíselné součinitele.
     * Součinitele vypíše na obrazovku (do konzole).
     *
     * @param n číslo, které chceme faktorizovat, n >= 2
     */
    public static void faktorizuj(int n) {

        int zbytek = n;
        for (int i = 2; i <= Math.sqrt(zbytek); i++) {
            // Pokud jsme vyzkoušeli všechna čísla menší než odmocnina ze zbytku
            //  a žádné z nich nedělilo zbytek, je zbytek prvočíslo a můžeme
            //  ho rovnou vypsat.

            // Jinak zkoušíme dělit dalším potenciálním prvočinitelem, dokud
            //  se to daří.
            while (zbytek % i == 0) {
                zbytek = zbytek / i;
                System.out.print(i + " ");
            }
        }
        // Pokud je zbytek větší než 1, pak je to prvočíslo,
        //  které je třeba vypsat také.
        if (zbytek > 1) {
            System.out.print(zbytek);
        }
    }
 
/**
 * Rozlozi cislo n >= 2 na prvociselne soucinitele.
 * Soucinitele vypise na obrazovku (do konzole).
 *
 * @param n císlo, ktere chceme faktorizovat, n >= 2
 */
void faktorizuj(int n) {

    int zbytek = n;
    for (int i = 2; i <= sqrt(zbytek); i++) {
        // Pokud jsme vyzkouseli vsechna cisla mensi nez odmocnina ze zbytku
        //  a zadne z nich nedelilo zbytek, je zbytek prvocislo a muzeme
        //  ho rovnou vypsat.

        // Jinak zkousime delit dalsim potencialním prvoèinitelem, dokud
        //  se to dari.
        while (zbytek % i == 0) {
            zbytek = zbytek / i;
            cout << i << " ";
        }
    }
    // Pokud je zbytek vetsi nez 1, pak je to prvocislo,
    //  ktere je treba vypsat take.
    if (zbytek > 1) {
        cout << zbytek;
    }
}
 
/**
 * Rozlozi cislo n >= 2 na prvociselne soucinitele.
 * Soucinitele vypise na obrazovku (do konzole).
 *
 * @param $n cislo, ktere chceme faktorizovat, n >= 2
 */
function faktorizuj($n) {

    $zbytek = $n;
    for ($i = 2; $i <= sqrt($zbytek); $i++) {
        // Pokud jsme vyzkouseli vsechna cisla mensi nez odmocnina ze zbytku
        //  a zadne z nich nedelilo zbytek, je zbytek prvocislo a muzeme
        //  ho rovnou vypsat.

        // Jinak zkousime delit dalsim potencialním prvoèinitelem, dokud
        //  se to dari.
        while ($zbytek % $i == 0) {
            $zbytek = $zbytek / $i;
            echo $i . " ";
        }
    }
    // Pokud je zbytek vetsi nez 1, pak je to prvocislo,
    //  ktere je treba vypsat take.
    if ($zbytek > 1) {
        echo $zbytek;
    }
}
{ zpětná vazba }
Delicious Delicious
Sdílet
Hodnocení (0): 0

Přečtěte si také

Diskuse





Pavel Mička14.1.2012
Na stránky jsem umístil kód od Martina, který jsem navíc portnul do C++ a PHP.
Pavel Mička5.1.2012
> Martin

Tvůj kód vypadá poměrně racionálně a nevidím důvod, proč by to nemělo fungovat. Bohužel to aktuáně nemám příliš čas zkoušet. Pokud se Ti do toho chce, tak ten kód zkus napsat (v nějakém normálním jazyce (C, Cpp, C#, Java ideálně)) a pošli mi ho na mejl (info@algoritmy.net). Do komentáře si klidně napiš, že jsi to psal ty, s tím není problém a pokud to projde mejma testama, tak to uveřejním.

S tím Erastothenovým sítem máš pravdu tak napůl. Ono je použitelný myslím do řádu milionů, než to začne drhnout. Takové číslo imho v pohodě zvládne zfaktorizovat i tahle procedurka. Na druhou stranu se velká složená čísla používají v kryptologii a zde se bavíme o několika tisících bitech a s tím si to stejně nemá šanci poradit...
Martin5.1.2012
Rekurzivní volání mi připadá neefektivní, opakovaně zkoušíme dělit číslem menším než aktuální i. Pokud číslem j: j < i nebylo dělitelné číslo number, nebude jím dělitelné ani (number/i). Navíc v okamžiku, kdy najdeme i: number % i == 0, pak i je prvočíslo (kdyby nebylo, existovalo by k < i, které dělí i, ale tím by dělilo i number a museli bychom ho najít před i) a není třeba ho faktorizovat!

Navrhuji raději:

Pozn, kterou použijeme dále: V rozkladu je nejvýše jedno číslo větší než sqrt(n), protože součin dvou čísel větších než sqrt(n) by dal číslo větší než n!

z := n; //Zbytek čísla, který ještě musíme faktorizovat
// Vypíšeme prvky rozkladu menší než sqrt(n):
for i := 2 to sqrt(n) do
// Když 'i' dělí 'z' beze zbytku, vypíšeme ho a jako další zbytek vezmeme 'z/i'.
// 'i' může dělit i další zbytek, ale žádné číslo menší než 'i' určitě zbytek nedělí!
while (z mod i = 0) {
print i;
// i je prvočíslo
// kdyby nebylo, existovalo by j: 2 <= j < i, které dělí i a tedy i z, ale takové bychom našli dříve než i
z := z / i;
} end while
} end for
// Pokud je v rozkladu nějaké prvočíslo větší než sqrt(n), pak zůstalo jako zbytek 'z' (může být nejvýše jedno viz poznámka. Jinak je zbytek z roven 1.
if (z > 1)
print z;
end if

Stálo by možná za zmínku (?), že máme-li ,,dost paměti", můžeme využít princip Eratosthenova síta a ušetřit tak část operací dělení, zkoušet pouze prvočísla?

Nebo se pletu?

PS: Nepíši hvězdičky mezi čísla. Pokud by byly nutné, stačí zavést proměnnou jePrvni, která udává, jestli píši první číslo.
Pavel Mička23.4.2011
Nic nepředpokládám, počet operací Turingova stroje a tranzitivně složitost algoritmu je definována nad vstupem. Vstupem je v tomto případě číslo v binární soustavě, dvojnásobným vstupem je dvojnásobně dlouhé binární číslo.

To, že je to fakticky implementováno nad čísly fixní délky je sice pravda, ale na složitosti to nic nemění, ta je exponenciální vzhledem k velikosti vstupu.
adam7222.4.2011
To ale implic. předpokládáte různou délku zápisu čísla, což není moc prakticky použitelné kvůli "stop markerům" a nebo byste musel neustále předělávat přechodovou funkci abyste věděl kdy vstup končí. Z pohledu praxe, resp. implemetace, byste ztratil na režii okolo "proměnné délky vstupu" více času než ušetříte kdekoliv jinde.
Pavel Mička22.4.2011
Tak k ty slozitosti. Mejme Turinguv stroj s jednou paskou. Na vstupu zakodujme cislo v jeho binarni podobe. Zdvojnasobeni velikosti vstupu na pasce znamena exponencialni narust poctu operace deleni.

Cili vystup: Vami uvedeny algoritmus na hledani delitelu je samozrejme //exponencialni//, presneji receno pseudopolynomialni (jevi se byt polynomialnim vuci unarne kodovanemu vstupu), vuci binarne kodovanemu vstupu (nebo jakkoliv jinak kodovanemu) je exponencialni.

Hledani prvocisla cyklem je taktez exponencialni algoritmus. Z testu prvociselnosti je polynomialni pouze AKS test nebo deterministicky Rabin-Milleruv test, jenz vsak stoji na nedokazane Riemannove hypoteze.

Jinak o faktorizaci se nevi, jestli je v NP. :-)
adam7222.4.2011
No podáváte problém složitosti faktorizace opravdu svérázně, nevím jestli je to z toho čtenářům patrné. Příjde mi, že do toho motáte rozklad na všechny dělitele a nevím jestli si to nepletete Vy sám. Doporučuji zdůraznit fakt, že test zda je číslo "N" složeným číslem a test, zda "k" dělí "N" není NP-úloha, ale faktorizace "N" je. Mezi tím je rozdíl.
Vypsat VŠECHNY dělitele N je totiž skutečně problém s polyn. složitostí O((n^2-n)/2) kde n=sqrt(N). Jen konstanta úměrnosti skrytá v notaci O() je velmi velmi vysoká. Pokud ale chcete rozložit "N" na PRVOČÍSELNÉ dělitele, které předem nemáte, tak to je úplně jiný problém a je NP-úplný.
Počet bitů kódujících číslo do toho vůbec nemotejte, to s tím nemá nic společného.