تحليل الحلقات والحلقات المتداخلة
احسب التعقيد الزمني للحلقات المفردة والمتداخلة والحلقات ذات النطاقات المتقلصة، مثل البحث الثنائي أو التكرارات المثلثية
تحليل الحلقات والحلقات المتداخلة درس مجاني في 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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- ترميز Big-O من الصفر
- تحليل الحلقات والحلقات المتداخلة
- الاستدعاء الذاتي وطريقة شجرة الاستدعاء الذاتي
- تعقيد المساحة والمفاضلات