0Pricing
Coding Interview Prep · درس

تحليل الحلقات والحلقات المتداخلة

احسب التعقيد الزمني للحلقات المفردة والمتداخلة والحلقات ذات النطاقات المتقلصة، مثل البحث الثنائي أو التكرارات المثلثية

تحليل الحلقات والحلقات المتداخلة درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

حلقة واحدة: O(n)

تنفّذ أبسط حلقة جسمها n مرة، ولذلك يكون تعقيدها O(n). قد تؤثر زيادة الخطوة في عدد التكرارات، لكنها لا تغيّر الفئة. ابدأ دائمًا بعدّ مرات تنفيذ جسم الحلقة. اطّلع على الشيفرة.

# O(n): body runs n times
def count_ops_linear(n):
    ops = 0
    for i in range(n):
        ops += 1     # constant work
    return ops

print(count_ops_linear(100))  # 100

# Still O(n): step=2 halves count but same class
def count_ops_half(n):
    ops = 0
    for i in range(0, n, 2):
        ops += 1
    return ops

print(count_ops_half(100))    # 50  => O(n)

الحلقات المتداخلة: O(n²) وما بعدها

تعطي حلقتان متداخلتان، تنفذ كل منهما n مرة، حاصل ضرب n × n = O(n^2)؛ وتعطي ثلاث حلقات O(n^3). لكن إذا نُفّذت الحلقة الداخلية عددًا ثابتًا من المرات، فسيظل التعقيد خطيًا.

def count_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(n):      # n iterations each
            ops += 1
    return ops

print(count_pairs(10))   # 100 = 10^2
print(count_pairs(100))  # 10000 = 100^2
# Doubling n quadruples ops: classic O(n^2)

الحلقة المثلثة: O(n²/2) = O(n²)

عندما تبدأ الحلقة الداخلية عند i+1، تشكّل التكرارات مثلثًا: n(n-1)/2، وهو يظل O(n^2) بعد حذف النصف. تبدو مسائل جميع الأزواج الفريدة بهذا الشكل.

def count_unique_pairs(n):
    ops = 0
    for i in range(n):          # n iterations
        for j in range(i+1, n): # n-1, n-2, ..., 0
            ops += 1
    return ops

print(count_unique_pairs(10))  # 45 = 10*9/2
print(count_unique_pairs(100)) # 4950
# Still O(n^2) -- constant factor 1/2 dropped

حلقة النطاق المتناقص: O(log n)

عندما تُنصَّف قيمة متغير الحلقة في كل خطوة، تحصل على O(log n). والسؤال الأساسي هو: هل يتناقص النطاق بشكل ضربي (log n) أم بشكل جمعي (n)؟ اطّلع على الشيفرة.

def count_log_ops(n):
    ops = 0
    i = n
    while i >= 1:
        ops += 1
        i //= 2   # halve each iteration
    return ops

import math
for n in [8, 16, 64, 1024]:
    ops = count_log_ops(n)
    print(f'n={n}, ops={ops}, log2={int(math.log2(n))}')
# ops tracks log2(n) closely

حلقة متداخلة بحلقة داخلية متناقصة: O(n log n)

تعطي حلقة خارجية تتكرر n مرة، مع حلقة داخلية بتعقيد O(log n)، تعقيدًا قدره O(n log n) — وهو شكل فرز الدمج. إن اكتشاف خطوة داخلية بتعقيد O(log n) هو المفتاح لتحليل خوارزميات الفرز.

import math

def count_n_log_n(n):
    ops = 0
    for i in range(n):    # n iterations
        j = n
        while j >= 1:     # log n iterations
            ops += 1
            j //= 2
    return ops

for n in [8, 32, 128]:
    ops = count_n_log_n(n)
    predicted = int(n * math.log2(n))
    print(f'n={n}: actual={ops}, n*log2(n)~={predicted}')

الحلقات الداخلية التابعة

عندما يعتمد نطاق الحلقة الداخلية على فهرس الحلقة الخارجية، عُدّ إجمالي التكرارات، لا التكرارات في كل خطوة. فالحلقة الداخلية التي تمتد من 0 إلى i يكون مجموع تكراراتها n(n-1)/2 = O(n^2). اطّلع على الشيفرة.

# Inner loop runs i times: total = 0+1+2+...+(n-1) = n(n-1)/2 => O(n^2)
def sum_inner_i(n):
    ops = 0
    for i in range(n):
        for j in range(i):   # runs 0,1,2,...,n-1 times
            ops += 1
    return ops

print(sum_inner_i(10))  # 45 = 10*9/2  => O(n^2)

# Inner loop runs n/i times (i doubles): sum ≈ n*log n => O(n log n)
def sum_inner_n_over_i(n):
    ops = 0
    i = 1
    while i <= n:
        for j in range(n // i):
            ops += 1
        i *= 2
    return ops
print(sum_inner_n_over_i(64))  # ~ 64*6 = 384

تحليل الفرز الفقاعي خطوة بخطوة

يقارن الفرز الفقاعي بين العناصر n(n-1)/2 مرة، ولذلك يكون تعقيده O(n^2). وحتى مع الإنهاء المبكر، يحتاج الإدخال المرتب عكسيًا إلى إجراء كل المقارنات. إنه بطيء جدًا مع المدخلات الكبيرة.

def bubble_sort(arr):
    n = len(arr)
    comparisons = 0
    for i in range(n):
        swapped = False
        for j in range(0, n - i - 1):
            comparisons += 1
            if arr[j] > arr[j+1]:
                arr[j], arr[j+1] = arr[j+1], arr[j]
                swapped = True
        if not swapped:  # early exit if sorted
            break
    return comparisons

arr = list(range(10, 0, -1))  # worst case: reversed
ops = bubble_sort(arr)
print(f'Sorted: {arr}')
print(f'Comparisons: {ops}')  # 45 = 10*9/2

الحلقات على السلاسل والسلاسل الجزئية

انتبه: إن التقطيع في Python بتعقيد O(k)، وليس مجانيًا، كما أن دمج السلاسل باستخدام + داخل حلقة بتعقيد O(n^2) لأنه ينسخ السلسلة في كل مرة. استخدم ''.join(parts) بدلًا من ذلك. اطّلع على الشيفرة.

# O(n^2): string concat in loop
def build_bad(n):
    s = ''
    for i in range(n):
        s += str(i)  # copies s each time!
    return s

# O(n): join is a single pass
def build_good(n):
    parts = []
    for i in range(n):
        parts.append(str(i))
    return ''.join(parts)

print(build_good(10))  # '0123456789'

معلمات الإدخال المتعددة

مع وجود مدخلين، قد يستخدم التعقيد كليهما: O(m + n) عند تنفيذ عمل منفصل، وO(m x n) عند التداخل. غالبًا ما تُكتب تعقيدات الرسوم البيانية على صورة O(V + E). سمِّ كل متغير بوضوح.

# O(m + n): two independent loops
def independent(m, n):
    a = sum(range(m))  # O(m)
    b = sum(range(n))  # O(n)
    return a + b       # total O(m + n)

# O(m * n): nested
def nested(m, n):
    count = 0
    for i in range(m):     # O(m)
        for j in range(n): # O(n) each
            count += 1
    return count  # O(m * n)

print(independent(5, 10))  # 10 + 45 = 55
print(nested(5, 10))       # 50

حلقة داخل حلقة مقابل الاستدعاءات المتسلسلة

استدعاء الدالة ليس مجانيًا — إذ يجب احتساب الحلقة الداخلية فيها أيضًا. إذا استدعيت دالة مساعدة بتعقيد O(n) عدد n من المرات، فستحصل على O(n^2). انظر دائمًا داخل الاستدعاءات المحجوبة عند التحليل.

# Naive string matching: O(n*m)
def naive_search(text, pattern):
    n, m = len(text), len(pattern)
    matches = []
    for i in range(n - m + 1):  # O(n)
        if text[i:i+m] == pattern:  # O(m) comparison + O(m) slice
            matches.append(i)
    return matches
# Total: O(n*m)

print(naive_search('abcabcabc', 'abc'))  # [0, 3, 6]

تطبيقي: تحديد التعقيد من النظرة الأولى

كوّن عادةً تتمثل في عدّ مستويات تداخل الحلقات، والتحقق مما إذا كانت الحلقة الداخلية تعتمد على الخارجية، والانتباه إلى التكاليف الخفية في استدعاءات الدوال والتقطيع. الشيفرة لغز يمكنك تجربته.

# What is the complexity of this function?
def mystery(nums):
    result = []
    for i in range(len(nums)):          # O(n)
        for j in range(i, len(nums)):   # O(n) worst
            if sum(nums[i:j+1]) == 0:   # O(n) slice + sum!
                result.append((i, j))
    return result
# Answer: O(n^3)  -- three nested n-proportional ops
# Outer O(n) x inner O(n) x sum/slice O(n) = O(n^3)

اختبار سريع

اختبار سريع — لنرَ مدى ثبات حيل تحليل الحلقات لديك. ثق باستدلالك هنا. 💪

مراجعة الدرس

مراجعة: الحلقات المتداخلة تُضرب والحلقات المستقلة تُجمع، والحلقة الداخلية التي تنصّف النطاق تعطي O(n log n)، كما يجب احتساب التكاليف الخفية داخل الاستدعاءات وعمليات التقطيع.

الأسئلة الشائعة

هل درس «تحليل الحلقات والحلقات المتداخلة» مجاني؟

نعم — نص درس «تحليل الحلقات والحلقات المتداخلة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «تحليل الحلقات والحلقات المتداخلة»؟

احسب التعقيد الزمني للحلقات المفردة والمتداخلة والحلقات ذات النطاقات المتقلصة، مثل البحث الثنائي أو التكرارات المثلثية تتمرن على Coding Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Coding Interview Prep؟

لا تُشترط خبرة سابقة. Coding Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «تحليل الحلقات والحلقات المتداخلة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Coding Interview Prep هذا؟

نعم. كل درس في Coding Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. ترميز Big-O من الصفر
  2. تحليل الحلقات والحلقات المتداخلة
  3. الاستدعاء الذاتي وطريقة شجرة الاستدعاء الذاتي
  4. تعقيد المساحة والمفاضلات
← العودة إلى Coding Interview Prep