محتويات الدورة

هياكل البيانات والخوارزميات / براهين التحسين والتجميعات المحفوظة / الدرس 18

الكلف المستهلكة واختيار الخوارزمية

حد كلفة تسلسلات العمليات دون خلط التحليل المستهلك بالعشوائية أو تأخر الحالة الأسوأ أو سلوك التنفيذ المقاس.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • قد تنسجم عملية مكلفة مع تسلسل منخفض الكلفة إجمالا
  • نمذج المضاعفة صراحة بدلا من تخمين سعة Python
  • حاسب كل عنصر في الطابور على نقله مرة واحدة

قد تنسجم عملية مكلفة مع تسلسل منخفض الكلفة إجمالا

يحدد التحليل المستهلك الكلفة الكلية عبر تسلسل عمليات، بما فيها عمليات مكلفة تحدث أحيانا. وهو لا يأخذ متوسطا على مدخل عشوائي، ما لم تكن العشوائية جزءا مستقلا من النموذج. قد تنسخ مصفوفة ديناميكية تضاعف سعتها جميع عناصرها المخزنة أحيانا، لكن مجموع المنسوخ عبر إلحاقات كثيرة محدود بمجموع هندسي. لا يَعِد ذلك بأن يكون لكل إلحاق زمن استجابة ثابت في الحالة الأسوأ. وإذا احتاجت مهمة وقت حقيقي إلى مهلة صارمة لكل عملية، فقد لا يكفي الضمان المستهلك وحده. سم التسلسل والحالة الابتدائية والعمل المحسوب والعمليات المسموحة، قبل تقديم رقم يبدو متوسطا.

نمذج المضاعفة صراحة بدلا من تخمين سعة Python

يعد البرنامج الأول الكتابات والعناصر المنسوخة في مصفوفة مضاعفة مجردة. وهو لا يفحص سياسة تخصيص CPython الفعلية. تبدأ السعة من الصفر، وينشئ التخصيص الأول موضعا واحدا، ثم تتضاعف السعات اللاحقة عندما تمتلئ. تشكل النسخ قبل بلوغ n إدخالا السلسلة 1+2+4 وهكذا، وهي أقل من 2n عندما يكون n موجبا. وتؤدي إضافة n كتابة للعناصر إلى حد إجمالي خطي. يسجل المثال أحداث تغيير السعة لكي تبقى الخطوات الفردية المكلفة ظاهرة. وقد يختار تنفيذ Python تفاصيل نمو مختلفة مع دعمه عقد إلحاق مستهلكا مناسبا.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
def doubling_model(n):
    if type(n) is not int or n < 0:
        raise ValueError("Count must be a nonnegative integer")
    size = capacity = cost = 0
    resizes = []
    for _ in range(n):
        if size == capacity:
            new_capacity = max(1, 2 * capacity)
            cost += size
            resizes.append((size, new_capacity))
            capacity = new_capacity
        size += 1
        cost += 1
    return cost, resizes

print(doubling_model(8))
print(doubling_model(0))
الناتج المتوقع
(15, [(0, 1), (1, 2), (2, 4), (4, 8)])
(0, [])

تكلف ثماني كتابات وسبعة عناصر منسوخة 15 في هذا النموذج. ولا يُقاس حجم تخصيص أثناء التشغيل أو زمن اختبار معياري.

حاسب كل عنصر في الطابور على نقله مرة واحدة

ينفذ البرنامج الثاني FIFO باستخدام مكدس وارد وآخر صادر. تدخل العناصر الجديدة incoming. وعندما يفرغ outgoing، يعكس نقل جميع عناصر incoming ترتيبها، فيصل الأقدم إلى قمة outgoing. يعبر كل عنصر من الوارد إلى الصادر مرة واحدة على الأكثر قبل إزالته. وعبر n عملية دفع وn عملية سحب ناجحة على الأكثر، لا تتجاوز عمليات النقل n، حتى لو نقل سحب واحد عناصر كثيرة. يفترض هذا البرهان الإجمالي أن عمليات المكدس الأساسية لها الكلف المعلنة؛ ومع مكدسات قائمة على مصفوفات ديناميكية يجب إدراج حدودها المستهلكة أيضا بصورة متسقة.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
class TwoStackQueue:
    def __init__(self):
        self.incoming, self.outgoing = [], []
        self.moves = 0
    def push(self, value):
        self.incoming.append(value)
    def pop(self):
        if not self.outgoing:
            while self.incoming:
                self.outgoing.append(self.incoming.pop())
                self.moves += 1
        if not self.outgoing:
            raise IndexError("Queue is empty")
        return self.outgoing.pop()

queue = TwoStackQueue()
queue.push("A")
queue.push("B")
first = queue.pop()
queue.push("C")
second, third = queue.pop(), queue.pop()
print(first, second, third)
print(queue.moves)
الناتج المتوقع
A B C
3

تنتظر C بينما توجد B بالفعل في المكدس الصادر. وتنتقل كل من A وB وC مرة واحدة، فيُحفظ ترتيب FIFO.

يشرح الجهد والمحاسبة العمل المحفوظ نفسه

يمكن لبرهان المحاسبة تحميل كل دفع رصيدا إضافيا، ثم إنفاق ذلك الرصيد عند نقل عنصره. ويمكن لدالة جهد بدلا من ذلك قياس العمل المستقبلي المخزن، مثل مضاعف لحجم المكدس الوارد. الكلفة المستهلكة هي الكلفة الفعلية مضافا إليها تغير الجهد. ويجب أن يظل الجهد محدودا من أسفل وفقا للحالة الابتدائية المختارة، حتى يمكن ضبط مجموع العمل الفعلي. لا تخترع رصيدا سالبا يجعل كل عملية تبدو مجانية دون دفع الالتزامات الابتدائية أو النهائية. الهدف متراجحة صحيحة للتسلسل، وليس إسنادا شكليا لأعداد أصغر إلى العمليات المكلفة.

اختر الخوارزمية من القيود والنواتج المطلوبة

تختلف افتراضات الحدود الدنيا لفرز كامل قائم على المقارنات عن طرق عد الأعداد الصحيحة أو الفرز الجذري. قد يتطلب إنتاج كل زوج مطابق ناتجا تربيعي الحجم، حتى عندما يكون إيجاد زوج واحد خطيا بافتراضات التجزئة. ويختلف استعلام أكبر k أثناء التدفق عن فرز جميع المدخلات، كما تختلف استعلامات المجالات الثابتة عن التحديثات أثناء العمل. اكتب الناتج ومزيج العمليات قبل مقارنة التسميات التقاربية. أدرج الذاكرة والتهيئة وكلفة المفتاح والتمثيل العددي ومتطلبات الحالة الأسوأ وسياسة الفشل. لا يبرر حد نظري جذاب شرطا مسبقا غير صالح أو عقد نتيجة أضعف من مهمة المستخدم.

استخدم عدادات محددة ومراجع مستقلة

لنموذج المضاعفة، افحص نمو الكلفة الكلية داخل الغلاف الخطي المبرهن لقيم n صغيرة، وافحص حدود تغيير السعة مستقلة. وللطابور، قارن تسلسلات محدودة مختلطة من الدفع والسحب بـ collections.deque، وتأكد من عدم نقل أي عنصر مرتين. سجّل أعداد العمليات منفصلة عن الزمن الفعلي؛ فقد تعكس نتائج توقيت صغيرة كلفة المفسر في معظمها. يثبت تنفيذ Python المعزول والفحوص التفاضلية المحدودة في الدورة النتائج الدقيقة والخصائص المختبرة، وليس معيارا لزمن استجابة إنتاجي أو برهانا لكل مدخل ممكن. أبق هذه الحدود ظاهرة عند الإبلاغ عن تحسينات الخوارزميات.

الأخطاء الشائعة وإصلاحها

الوعد بكلفة مستهلكة ثابتة على أنها زمن استجابة ثابت

لماذا يحدث؟
طُبق حد التسلسل الكامل على كل عملية منفردة.
كيف تصلحه؟
اذكر الحالة الأسوأ التي تحدث أحيانا والحد الإجمالي معا.
كيف تتجنب تكراره؟
افحص العمليات التي تطلق تغيير السعة أو نقل العناصر.

عرض نموذج المضاعفة على أنه قياس لتنفيذ CPython

لماذا يحدث؟
اختلط العمل المجرد المعدود بملاحظة التنفيذ الفعلي.
كيف تصلحه؟
سم السياسة التي يمثلها النموذج، وقِس التنفيذات مستقلة إذا لزم الأمر.
كيف تتجنب تكراره؟
أرفق افتراضات النموذج بكل نتيجة للكلفة.

احتساب حجم المدخل فقط رغم ضخامة الناتج

لماذا يحدث؟
أُغفل الحد الأدنى الذي يفرضه حجم ناتج العقد.
كيف تصلحه؟
أدرج العمل والمساحة الحساسين لحجم الناتج في التحليل.
كيف تتجنب تكراره؟
قارن مهام إيجاد حل واحد بمهام تعداد الحلول كلها.

اختبر فهمك

1. لماذا يمكن أن يكون سحب واحد من الطابور خطيا، بينما يبقى التسلسل كله خطيا؟

اعرض الحل المشروح

قد ينقل ذلك السحب جميع العناصر المنتظرة في المكدس الوارد. ثم يبقى كل عنصر منقول في الصادر حتى إزالته، ولا يُنقل مجددا. يحاسب الجمع عبر التسلسل كل عنصر مرة واحدة، فلا تتضاعف الدفعات المكلفة العرضية إلى عمل كلي تربيعي.

2. هل يعني أداء التجزئة المتوقع الشيء نفسه الذي تعنيه كلفة تغيير السعة المستهلكة؟

اعرض الحل المشروح

لا. يستخدم التحليل المتوقع نموذجا احتماليا، بينما يحد التحليل المستهلك تسلسلا حتى دون مدخل عشوائي، وفقا لافتراضاته عن العمليات. قد يتضمن التنفيذ الواحد كليهما. اذكر مصدر كل ضمان، بدلا من استخدام «المتوسط» بديلا غير محدد.

أهم ما تعلمته

  • قد تنسجم عملية مكلفة مع تسلسل منخفض الكلفة إجمالا
  • استخدم عدادات محددة ومراجع مستقلة

المراجع الرسمية