कम्प्यूटेशनल जटिलता ओ (n ^ 4) क्यों है?


50
int sum = 0;
for(int i = 1; i < n; i++) {
    for(int j = 1; j < i * i; j++) {
        if(j % i == 0) {
            for(int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
}

मुझे समझ में नहीं आता कि जब j = i, 2i, 3i ... आखिरी forलूप n बार चलता है। मुझे लगता है कि मुझे समझ में नहीं आ रहा है कि हम ifबयान के आधार पर उस निष्कर्ष पर कैसे पहुंचे ।

संपादित करें: मुझे पता है कि सभी छोरों के लिए जटिलता की गणना कैसे करें सिवाय इसके कि आखिरी लूप मैं मॉड ऑपरेटर के आधार पर क्यों निष्पादित करता हूं ... मैं सिर्फ यह नहीं देखता कि यह कैसे है। मूल रूप से, क्यों नहीं% j मैं i * i के बजाय i तक जा सकता हूं?


5
आप कई बड़े कारकों द्वारा इस कोड की जटिलता को कम कर सकते हैं। संकेत : संख्या 1 से n का योग ((n + 1) * n) / 2 संकेत 2 है : for (j = i; j < i *i; j += i)फिर आपको मापांक परीक्षण की आवश्यकता नहीं है (क्योंकि jविभाज्य होने की गारंटी है i)।
इलियट फ्रिश

1
O () फ़ंक्शन एक बॉल-पार्क फ़ंक्शन है इसलिए इस उदाहरण में कोई भी लूप जटिलता में जोड़ रहा है। दूसरा लूप n ^ 2 तक चल रहा है। अगर-कथनों को नजरअंदाज किया जाता है।
क्रिस्टोफ बाउर

11
@ChristophBauer के ifबयानों को बिल्कुल नजरअंदाज नहीं किया जाता है। इस ifकथन का अर्थ है कि जटिलता O (n ^ 5) के बजाय O (n ^ 4) है, क्योंकि यह दूसरे लूप के प्रत्येक पुनरावृत्ति के लिए iसमय के बजाय अंतरतम लूप को केवल निष्पादित करने का कारण बनता है i*i
काया 3

1
@ kaya3 पूरी तरह से भाग से चूक गए। k < n^2तो यह O (n ^ 5) है, लेकिन ज्ञान (समझने से if) O (n ^ 4) का सुझाव देता है।
क्रिस्टोफ बाउर

1
यदि यह सिर्फ एक क्लास एक्सरसाइज नहीं है, तो दूसरे लूप को (int j = i; j <i * i; j + = i) में बदल दें
Cristobol Polychronopolis

जवाबों:


49

चलो छोरों को A, B और C लेबल करते हैं:

int sum = 0;
// loop A
for(int i = 1; i < n; i++) {
    // loop B
    for(int j = 1; j < i * i; j++) {
        if(j % i == 0) {
            // loop C
            for(int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
}
  • लूप ए iterates O ( n ) बार।
  • लूप B , A के पुनरावृत्ति के समय O ( i 2 ) बार पुनरावृत्त करता है । इनमें से प्रत्येक पुनरावृत्तियों के लिए:
    • j % i == 0 मूल्यांकन किया जाता है, जो O (1) समय लेता है।
    • इन पुनरावृत्तियों में से 1 / i पर, लूप सी पुनरावृत्तियों को जे बार करता है, ओ (1) प्रति पुनरावृत्ति कार्य करता है। चूंकि j औसत पर O ( i 2 ) है, और यह केवल लूप B के 1 / i पुनरावृत्तियों के लिए किया जाता है , औसत लागत O ( i 2  /  i ) = O ( i ) है।

इन सभी को एक साथ गुणा करते हुए, हमें O ( n  ×  i 2  × (1 +  i )) = O ( n  ×  i 3 ) मिलता है। चूँकि मैं औसत O ( n ) पर हूं , यह O ( n 4 ) है।


इस का मुश्किल हिस्सा यह कह रहा है कि ifहालत केवल 1 / i बार सच है :

मूल रूप से, क्यों नहीं% j मैं i * i के बजाय i तक जा सकता हूं?

वास्तव में, jऊपर जाता है j < i * i, न कि केवल ऊपर तक j < i। लेकिन हालत j % i == 0सच है अगर और केवल अगर jएक से अधिक है i

के गुणकों iसीमा के भीतर हैं i, 2*i, 3*i, ..., (i-1) * i। इनमें से एक हैं i - 1, इसलिए i - 1लूप बी के दौरान कई बार लूप सी पहुंच जाता है i * i - 1


2
O (n × i ^ 2 × (1 + i)) में 1 + i क्यों?
सोइल

3
चूँकि ifलूप बी के प्रत्येक पुनरावृत्ति पर ओ (1) समय लगता है, यहाँ लूप सी का प्रभुत्व है, लेकिन मैंने इसे ऊपर गिना है, इसलिए यह सिर्फ "मेरा काम दिखा रहा है"।
काया 3

16
  • पहला लूप nपुनरावृत्तियों का उपभोग करता है ।
  • दूसरा लूप n*nपुनरावृत्तियों का उपभोग करता है । जब केस की कल्पना करो i=n, तब j=n*n
  • तीसरे पाश की खपत nहै क्योंकि यह केवल मार डाला है पुनरावृत्तियों iबार है, जहां iघिरा है nसबसे खराब स्थिति में।

इस प्रकार, कोड जटिलता O (n × n × n × n) है।

मुझे उम्मीद है कि यह आपको समझने में मदद करेगा।


6

अन्य सभी उत्तर सही हैं, मैं केवल निम्नलिखित संशोधन करना चाहता हूं। मैं देखना चाहता था, अगर आंतरिक के-लूप के निष्पादन में कमी वास्तविक जटिलता को कम करने के लिए पर्याप्त थी, O(n⁴).तो मैंने निम्नलिखित लिखा:

for (int n = 1; n < 363; ++n) {
    int sum = 0;
    for(int i = 1; i < n; ++i) {
        for(int j = 1; j < i * i; ++j) {
            if(j % i == 0) {
                for(int k = 0; k < j; ++k) {
                    sum++;
                }
            }
        }
    }

    long cubic = (long) Math.pow(n, 3);
    long hypCubic = (long) Math.pow(n, 4);
    double relative = (double) (sum / (double) hypCubic);
    System.out.println("n = " + n + ": iterations = " + sum +
            ", n³ = " + cubic + ", n⁴ = " + hypCubic + ", rel = " + relative);
}

इसे निष्पादित करने के बाद, यह स्पष्ट हो जाता है, कि वास्तव में जटिलता है n⁴। आउटपुट की अंतिम लाइनें इस तरह दिखती हैं:

n = 356: iterations = 1989000035, n³ = 45118016, n = 16062013696, rel = 0.12383254507467704
n = 357: iterations = 2011495675, n³ = 45499293, n = 16243247601, rel = 0.12383580700180696
n = 358: iterations = 2034181597, n³ = 45882712, n = 16426010896, rel = 0.12383905075183874
n = 359: iterations = 2057058871, n³ = 46268279, n = 16610312161, rel = 0.12384227647628734
n = 360: iterations = 2080128570, n³ = 46656000, n = 16796160000, rel = 0.12384548432498857
n = 361: iterations = 2103391770, n³ = 47045881, n = 16983563041, rel = 0.12384867444612208
n = 362: iterations = 2126849550, n³ = 47437928, n = 17172529936, rel = 0.1238518469862343

यह क्या दिखाता है, कि वास्तविक n⁴और इस कोड सेगमेंट की जटिलता के बीच वास्तविक अंतर एक मूल्य के आस-पास का कारक है 0.124...(वास्तव में 0.125)। हालांकि यह हमें सही मूल्य नहीं देता है, हम निम्नलिखित कटौती कर सकते हैं:

समय जटिलता वह है n⁴/8 ~ f(n)जहाँ fआपका कार्य / विधि है।

  • बिग ओ नोटेशन पर विकिपीडिया-पेज 'फैमिली ऑफ बच्चन-लैंडौ नोटेशन' की तालिका में बताता है कि ~दोनों ऑपरेंड पक्षों की सीमा को बराबर करता है। या:

    f, asymptotically g के बराबर है

(मैंने ऊपरी बंधे को छोड़कर 363 को चुना, क्योंकि n = 362अंतिम मूल्य है जिसके लिए हमें एक समझदार परिणाम मिलता है। उसके बाद, हम लंबे स्थान से अधिक हो जाते हैं और सापेक्ष मूल्य नकारात्मक हो जाता है।)

उपयोगकर्ता kaya3 निम्नलिखित का पता लगाता है:

स्पर्शोन्मुख स्थिरांक वैसे तो 1/8 = 0.125 है; यहाँ वुल्फराम अल्फा के माध्यम से सटीक सूत्र है


5
बेशक, O (n⁴) * 0.125 = O (n।)। एक सकारात्मक स्थिर कारक द्वारा रनटाइम को गुणा करने से एसिम्प्टोटिक जटिलता नहीं बदलती है।
इल्मरी करोनें

यह सच है। हालाँकि मैं वास्तविक जटिलता को प्रतिबिंबित करने की कोशिश कर रहा था, ऊपरी तौर पर अनुमान नहीं। जैसा कि मुझे ओ-नोटेशन के अलावा अन्य समय जटिलता व्यक्त करने के लिए कोई अन्य वाक्यविन्यास नहीं मिला, मैं उस पर वापस आ गया। हालाँकि इसे इस तरह लिखना 100% समझदारी नहीं है।
ट्रेफेनक्स

समय की जटिलता के बारे में कहने के लिए आप छोटे-से संकेतन का उपयोग कर सकते हैं n⁴/8 + o(n⁴), लेकिन n⁴/8 + O(n³)बड़े ओ के साथ कड़े अभिव्यक्ति देना संभव है ।
काया 3

@TreffnonX बड़ा ओह एक गणितीय ठोस अवधारणा है। तो आप जो कर रहे हैं वह fundmentally गलत / अर्थहीन है। बेशक आप गणितीय अवधारणाओं को फिर से परिभाषित करने के लिए स्वतंत्र हैं, लेकिन आपके द्वारा खोले जा रहे कीड़े का एक बड़ा कैन है। इसे कड़े संदर्भ में परिभाषित करने का तरीका क्या काया 3 वर्णित है, आप एक आदेश "कम" पर जाते हैं और इसे इस तरह परिभाषित करते हैं। (हालांकि गणित में आप आमतौर पर पारस्परिक उपयोग करते हैं)।
paul23

तुम सही हो। मैंने फिर से अपने आप को ठीक किया। इस बार, मैं एक ही सीमा के लिए स्पर्शोन्मुख वृद्धि का उपयोग करता हूं, जैसा कि बाचमन-लांडौ के परिवार में en.wikipedia.org/wiki/Big_O_notation#Little-o_notation पर परिभाषित किया गया है । मुझे उम्मीद है कि अब यह गणितीय रूप से सही है कि विद्रोह को उकसाया नहीं जाए;)
ट्रेफेनक्स

2

ifजटिलता को बदले बिना निकालें और मोडुलो करें

यहाँ मूल विधि है:

public static long f(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j = 1; j < i * i; j++) {
            if (j % i == 0) {
                for (int k = 0; k < j; k++) {
                    sum++;
                }
            }
        }
    }
    return sum;
}

आप से उलझन में रहे हैं ifऔर सापेक्ष, तो आप सिर्फ उन्हें दूर refactor कर सकते हैं, के साथ jसीधे से कूद iकरने के लिए 2*iकरने के लिए 3*i...:

public static long f2(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j = i; j < i * i; j = j + i) {
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}

जटिलता की गणना करना और भी आसान बनाने के लिए, आप एक मध्यस्थ j2चर पेश कर सकते हैं , ताकि प्रत्येक लूप चर को प्रत्येक पुनरावृत्ति में 1 से बढ़ा दिया जाए:

public static long f3(int n) {
    int sum = 0;
    for (int i = 1; i < n; i++) {
        for (int j2 = 1; j2 < i; j2++) {
            int j = j2 * i;
            for (int k = 0; k < j; k++) {
                sum++;
            }
        }
    }
    return sum;
}

आप डिबगिंग या पुराने-स्कूल का उपयोग कर सकते हैं System.out.printlnताकि यह जांच सके कि i, j, kप्रत्येक विधि में ट्रिपल हमेशा एक ही है।

बंद रूप अभिव्यक्ति

जैसा कि दूसरों ने उल्लेख किया है, आप इस तथ्य का उपयोग कर सकते हैं कि पहले n पूर्णांकों का योग बराबर है n * (n+1) / 2( त्रिकोणीय संख्या देखें )। यदि आप हर लूप के लिए इस सरलीकरण का उपयोग करते हैं, तो आपको यह मिलेगा:

public static long f4(int n) {
    return (n - 1) * n * (n - 2) * (3 * n - 1) / 24;
}

यह स्पष्ट रूप से मूल कोड के समान जटिलता नहीं है , लेकिन यह समान मूल्यों को वापस करता है।

आप पहली बार इन शब्दों को गूगल, तो आप देख सकते हैं कि 0 0 0 2 11 35 85 175 322 546 870 1320 1925 2717 3731में दिखाई देते हैं "पहली तरह का स्टर्लिंग संख्या: रों (n + 2, एन)।" , 0शुरुआत में दो एस जोड़े। इसका मतलब है कि यह पहली तरहsum की स्टर्लिंग संख्या है s(n, n-2)


0

पहले दो छोरों पर एक नजर डालते हैं।

पहला सरल है, यह 1 से n तक लूपिंग है। दूसरा एक और अधिक दिलचस्प है। यह 1 से i वर्ग तक जाता है। आइए देखते हैं कुछ उदाहरण:

e.g. n = 4    
i = 1  
j loops from 1 to 1^2  
i = 2  
j loops from 1 to 2^2  
i = 3  
j loops from 1 to 3^2  

कुल मिलाकर, i and j loopsसंयुक्त है 1^2 + 2^2 + 3^2
पहले n वर्गों के योग का एक सूत्र है n * (n+1) * (2n + 1) / 6, जो मोटे तौर पर है O(n^3)

आपके पास एक अंतिम है k loopजो 0 से jयदि और केवल यदि है तो लूप करता है j % i == 0। चूंकि j1 से जाता है i^2, समय के j % i == 0लिए सच iहै। चूंकि यह i loopखत्म हो गया है n, आपके पास एक अतिरिक्त है O(n)

तो आप O(n^3)से i and j loopsऔर एक अन्य O(n)से k loopकी महायोग के लिएO(n^4)


मैं जानता हूं कि सभी छोरों के लिए जटिलता की गणना कैसे की जाती है, सिवाय इसके कि आखिरी लूप मैं मॉड ऑपरेटर के आधार पर क्यों निष्पादित करता हूं ... मैं सिर्फ यह नहीं देखता कि मैं कैसा हूं। मूल रूप से, क्यों नहीं% j मैं i * i के बजाय i तक जा सकता हूं?
user11452926

1
@ user11452926 मान लें कि i था 5. j 2 लूप में 1 से 25 तक जाएगा। हालांकि, j % i == 0केवल जब j 5, 10, 15, 20 और 25 है। 5 बार, i के मान की तरह। यदि आप 5 x 5 वर्ग में 1 से 25 तक की संख्याएँ लिखते हैं, तो केवल 5 वें कॉलम में 5 से विभाज्य संख्याएँ होंगी। यह किसी भी संख्या में i के लिए काम करता है। संख्या 1 से n ^ 2 का उपयोग करके n का एक वर्ग ड्रा करें। Nth कॉलम में n द्वारा विभाज्य संख्याएँ होंगी। आपके पास n पंक्तियाँ हैं, इसलिए n से 1 से n ^ 2 विभाज्य से n संख्याएँ हैं।
सिलविउ बर्किया

धन्यवाद! समझ में आता है! क्या होगा अगर यह 25 की बजाय 24 की तरह एक मनमानी संख्या थी, क्या चौकोर चाल अभी भी काम करेगी?
user11452926

25 आता है जब i5 हिट, तो j1 से 25 तक छोरों, आप एक मनमाना संख्या नहीं चुन सकते हैं। यदि आपका दूसरा लूप एक निश्चित संख्या में जाता है, उदाहरण के लिए, 24 के बजाय i * i, वह एक स्थिर संख्या होगी और उससे बंधा नहीं होगा n, इसलिए यह होगा O(1)। यदि आप j < i * iबनाम के बारे में सोच रहे हैं j <= i * i, तो यह ज्यादा मायने नहीं रखेगा, क्योंकि ऑपरेशन nऔर n-1ऑपरेशन होंगे, लेकिन बिग-ओह संकेतन में, दोनों का मतलब हैO(n)
सिल्वु बर्किया
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.