ترميز Big-O من الصفر
افهم سبب اهتمامنا بالنمو التقاربي، وكيفية حذف الثوابت والحدود ذات الرتب الأدنى، وكيفية قراءة Big-O بسرعة
ترميز Big-O من الصفر درس مجاني في DSA Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في DSA Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
لماذا نقيس كفاءة الخوارزميات؟
قد يكون برنامجان صحيحين معًا، لكن أحدهما ينتهي في لمح البصر بينما يستغرق الآخر ساعات. يصف التعقيد الزمني كيفية نمو زمن التنفيذ مع ازدياد حجم الإدخال.
# O(n) approach
def find_max_linear(nums):
m = nums[0]
for n in nums:
if n > m: m = n
return m
# O(n^2) approach (unnecessary double loop)
def find_max_quadratic(nums):
for i in range(len(nums)):
is_max = all(nums[i] >= nums[j] for j in range(len(nums)))
if is_max: return nums[i]
print(find_max_linear([3, 1, 4, 1, 5, 9])) # 9Big-O: الحد الأعلى التقاربي
يصف Big-O الحد الأعلى في أسوأ الحالات لمدى سرعة نمو التكلفة. والحيلة هي حذف الثوابت والحدود الأصغر، لأن الحد المسيطر وحده هو المهم عند التوسع. راجعوا الكود.
# T(n) = 3n^2 + 5n + 100 is O(n^2)
# because the n^2 term dominates for large n
# T(n) = 2n + 1000 is O(n)
# the constant 1000 becomes negligible
# Rule: drop constants and lower-order terms
# 5n^3 + 2n^2 + n + 1 => O(n^3)
# 100 * log(n) + n => O(n)
print('O(n^2) example: counting iterations')
n = 1000
count = sum(1 for i in range(n) for j in range(n))
print(count) # 1_000_000 = n^2فئات التعقيد الشائعة
من الأسرع إلى الأبطأ: O(1)، O(log n)، O(n)، O(n log n)، O(n^2)، O(2^n)، O(n!). تتيح لكم معرفة هذه الفئات اختيار النهج المناسب قبل كتابة سطر واحد.
import math
n = 1000
print(f'O(1): {1}')
print(f'O(log n): {int(math.log2(n))}')
print(f'O(n): {n}')
print(f'O(n log n): {int(n * math.log2(n))}')
print(f'O(n^2): {n**2}')
# O(2^n) for n=1000 is astronomically large
# O(n!) even largerحذف الثوابت: لماذا يهم ذلك
إن تنفيذ 5n خطوة أو 2n خطوة يُعدّ في الحالتين O(n) — فالثوابت تعتمد على العتاد، لا على الخوارزمية. يحذف Big-O هذه الثوابت لكي تقارن معدل التوسع على أسس متكافئة.
# Both are O(n) — different constants
def count_a(n):
total = 0
for i in range(n): # n ops
total += 1
for i in range(n): # n ops
total += 1
return total # T(n) = 2n => O(n)
def count_b(n):
total = 0
for i in range(5 * n): # 5n ops
total += 1
return total # T(n) = 5n => O(n)
print(count_a(10), count_b(10)) # 20 50أفضل الحالات ومتوسطها وأسوأها
يمثل Big-O أسوأ حالة؛ ويمثل Omega أفضل حالة؛ أما Theta فيمثل حدًا محكمًا للحالتين. عندما يسأل المحاوِر عن «التعقيد»، فهو يقصد تقريبًا دائمًا أسوأ حالة.
def linear_search(nums, target):
for i, n in enumerate(nums):
if n == target:
return i # best case: target at index 0 => O(1)
return -1 # worst case: not found => O(n)
# Best case O(1): target is first element
print(linear_search([5,1,2,3], 5)) # 0
# Worst case O(n): target not in list
print(linear_search([1,2,3,4], 9)) # -1O(log n): تنصيف مساحة البحث
تكون الخوارزمية O(log n) عندما تنصّف حجم الإدخال في كل خطوة، مثل البحث الثنائي. حتى مع مليار عنصر، لا يتطلب الأمر سوى نحو 30 خطوة — وهذا سريع للغاية. اطّلع على الشيفرة.
def binary_search(arr, target):
lo, hi = 0, len(arr) - 1
steps = 0
while lo <= hi:
steps += 1
mid = (lo + hi) // 2
if arr[mid] == target:
return mid, steps
elif arr[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1, steps
import math
arr = list(range(1000))
idx, s = binary_search(arr, 999)
print(f'Found at {idx} in {s} steps (log2(1000)~={math.log2(1000):.1f})')O(n log n): الحد الأدنى لتعقيد الفرز
يحتاج أي فرز بالمقارنة إلى O(n log n) على الأقل في أسوأ حالة — وهذا حد أدنى رياضي حقيقي. لذلك يكون الفرز ثم المسح بتعقيد إجمالي O(n log n)، وليس O(n^2). توضّح الشيفرة فرز الدمج.
# Merge sort: O(n log n)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(a, b):
res, i, j = [], 0, 0
while i < len(a) and j < len(b):
if a[i] <= b[j]: res.append(a[i]); i+=1
else: res.append(b[j]); j+=1
return res + a[i:] + b[j:]
print(merge_sort([5,2,8,1,9,3])) # [1,2,3,5,8,9]التعقيد المُوزَّع
يحسب التحليل المُوزَّع متوسط التكلفة على عدد كبير من العمليات. تُعدّ append في Python بتعقيد O(1) موزَّع: فهي فورية عادةً، مع عملية إعادة تحجيم نادرة بتعقيد O(n) تُوزَّع تكلفتها على جميع عمليات الإلحاق.
# Dynamic array append is O(1) amortised
import sys
lst = []
capacities = []
for i in range(16):
lst.append(i)
capacities.append(sys.getsizeof(lst))
# Size jumps show reallocation events
for i, c in enumerate(capacities):
if i > 0 and capacities[i] != capacities[i-1]:
print(f'Realloc at i={i}, new size={c} bytes')التعرّف على التعقيد في الشيفرة
قاعدة سريعة: عُدّ الحلقات. الحلقة الواحدة تعني O(n)، وحلقتان متداخلتان تعنيان O(n^2)، وحلقة تنصّف النطاق تعني O(log n). عمليات المرور المستقلة تُجمع؛ أما الحلقات المتداخلة فقط فتُضرب. اطّلع على الشيفرة.
# Two independent passes: O(n) + O(n) = O(n)
def two_passes(nums):
total = sum(nums) # O(n)
mean = total / len(nums)
diffs = [abs(n - mean) for n in nums] # O(n)
return max(diffs) # O(n)
# Overall: O(n) -- NOT O(n^2)
# Nested loops: O(n) * O(n) = O(n^2)
def all_pairs(nums):
pairs = []
for i in range(len(nums)): # O(n)
for j in range(i+1, len(nums)): # O(n)
pairs.append((nums[i], nums[j]))
return pairs # O(n^2)أساسيات التعقيد المكاني
يتتبع التعقيد المكاني الذاكرة الإضافية التي تستخدمها باستثناء الإدخال. يكون العكس داخل المكان بتعقيد O(1)، بينما تكون خريطة التجزئة بتعقيد O(n). عند مبادلة الزمن بالمساحة، اذكر دائمًا كليهما.
# O(1) space: reverse in-place
def reverse_inplace(arr):
l, r = 0, len(arr) - 1
while l < r:
arr[l], arr[r] = arr[r], arr[l]
l += 1; r -= 1
# O(n) space: create reversed copy
def reverse_copy(arr):
return arr[::-1]
a = [1, 2, 3, 4, 5]
reverse_inplace(a)
print(a) # [5, 4, 3, 2, 1]الحديث عن التعقيد في المقابلات
اذكر التعقيد دائمًا من تلقاء نفسك دون أن يُطلب منك ذلك: «الزمن O(n log n)، والمساحة O(n)». ثم اقترح خيارًا أسرع. هذه العادة تدل على خبرة حقيقية.
# Example of explaining complexity step by step
def two_sum(nums, target):
# O(n) time: one pass through nums
# O(n) space: hash map stores up to n elements
seen = {} # value -> index
for i, n in enumerate(nums):
complement = target - n
if complement in seen: # O(1) lookup
return [seen[complement], i]
seen[n] = i
return []
print(two_sum([2, 7, 11, 15], 9)) # [0, 1]اختبار سريع
اختبار سريع — لنعرف ما استوعبته عن Big-O وفئات التعقيد. سؤال واحد فقط، أنت قادر على ذلك. 🎯
مراجعة الدرس
مراجعة: يعبّر Big-O عن معدل النمو في أسوأ حالة بعد حذف الثوابت، وقد تعرّفت إلى الفئات من O(1) إلى O(n!)، كما أن الحلقات المستقلة تُجمع بينما الحلقات المتداخلة تُضرب.
الأسئلة الشائعة
هل درس «ترميز Big-O من الصفر» مجاني؟
نعم — نص درس «ترميز Big-O من الصفر» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة DSA Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة DSA Interview Prep 4 دروس في المجموع.
ماذا ستتعلم في «ترميز Big-O من الصفر»؟
افهم سبب اهتمامنا بالنمو التقاربي، وكيفية حذف الثوابت والحدود ذات الرتب الأدنى، وكيفية قراءة Big-O بسرعة تتمرن على DSA Interview Prep مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ DSA Interview Prep؟
لا تُشترط خبرة سابقة. DSA Interview Prep على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.
كم من الوقت يستغرق درس «ترميز Big-O من الصفر»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس DSA Interview Prep هذا؟
نعم. كل درس في DSA Interview Prep يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- ترميز Big-O من الصفر
- تحليل الحلقات والحلقات المتداخلة
- الاستدعاء الذاتي وطريقة شجرة الاستدعاء الذاتي
- تعقيد المساحة والمفاضلات