0Pricing
Coding Interview Prep · درس

ترميز Big-O من الصفر

افهم سبب اهتمامنا بالنمو التقاربي، وكيفية حذف الثوابت والحدود ذات الرتب الأدنى، وكيفية قراءة Big-O بسرعة

ترميز Big-O من الصفر درس مجاني في Coding Interview Prep على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Coding Interview Prep، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Coding 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]))  # 9

Big-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))   # -1

O(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) وفتح باقي دورة Coding Interview Prep، انتقل إلى CoddyKit PRO. تتضمن دورة Coding Interview Prep 4 دروس في المجموع.

ماذا ستتعلم في «ترميز Big-O من الصفر»؟

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

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

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

كم من الوقت يستغرق درس «ترميز Big-O من الصفر»؟

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

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

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

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

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