هياكل البيانات والخوارزميات / العقود والتسلسلات والبحث / الدرس 2
المصفوفات والمجاميع السابقة وعقود المجالات
استخدم المجالات نصف المفتوحة لتهيئة استعلامات جمع متكررة، وميّز الاستعلامات على بيانات ثابتة من تحديثات المجالات.
في هذه الصفحة
أهداف الدرس
- اختر اصطلاحا لحدود المجال والتزمه
- ابن المجاميع السابقة مع موضع إضافي واحد
- تجدي التهيئة المسبقة عندما يناسبها نمط العمل
اختر اصطلاحا لحدود المجال والتزمه
يشمل المجال نصف المفتوح [left,right) الطرف left ويستثني الطرف right. ويكون طوله right-left، وتتساوى نهايتا المجال الفارغ، وتلتقي المجالات المتجاورة دون تداخل. يوافق هذا الاصطلاح شرائح Python والتكرار باستخدام range، فيجعل الاستدلال على الحدود متسقا. تحقّق من الشرط 0 <= left <= right <= n عند حدود واجهة الاستعلام العامة. فالفهارس السالبة ميزة مشروعة في لغة Python، لكنها قد تخالف المجال الذي تقصده خوارزميتك. لا تسمح لفهرسة سالبة عرضية بتحويل مجال غير صالح إلى نتيجة تبدو معقولة. واكتب عقد المجال إلى جانب كل صيغة تستخدمها للتهيئة المسبقة.
ابن المجاميع السابقة مع موضع إضافي واحد
عرّف prefix[k] بأنه مجموع العناصر الواقعة قبل الموضع k حصرا. عندئذ يكون prefix[0] صفرا، ويكون prefix[n] مجموع العناصر كلها. ويزيل طرح prefix[left] من prefix[right] الجزء الواقع قبل المجال المطلوب بالضبط. لذلك يجيب المثال الأول عن كل استعلام ثابت صالح بعمليتي وصول إلى المصفوفة وعملية طرح، بعد تهيئة خطية واستخدام مساحة إضافية خطية. ولا تفسد القيم السالبة هذه الهوية. يفترض النموذج الحسابي في التحليل أن كلفة الجمع محدودة؛ أما أعداد Python الصحيحة ذات الحجم غير المحدود فقد تتطلب احتساب طولها بالبتات عندما تكون القيم كبيرة على نحو غير معتاد.
def prefix_sums(values):
prefix = [0]
for value in values:
prefix.append(prefix[-1] + value)
return prefix
def range_sum(prefix, left, right):
n = len(prefix) - 1
if not 0 <= left <= right <= n:
raise ValueError("Invalid half-open range")
return prefix[right] - prefix[left]
prefix = prefix_sums([4, -2, 5, 1])
print(prefix)
print(range_sum(prefix, 1, 3))
print(range_sum(prefix, 2, 2))
[0, 4, 2, 7, 8]
3
0
يشمل الاستعلام [1,3) القيمتين -2 و5. وعند تساوي الطرفين لا يُختار أي عنصر، فيكون الناتج صفرا.
تجدي التهيئة المسبقة عندما يناسبها نمط العمل
يمكن حساب مجموع واحد لمصفوفة مباشرة في زمن خطي، دون تخزين المجاميع السابقة كلها. وتصبح هذه المجاميع مفيدة عندما تشترك استعلامات مجالات كثيرة في لقطة بيانات لا تتغير: فتحل تهيئة O(n) مع q استعلامات ثابتة الكلفة محل مسح المجالات مرارا. وإذا تغيرت قيمة، فقد تتغير جميع المجاميع السابقة اللاحقة لها، فلا تعود البنية الأصلية تجيب عن الاستعلامات الحالية بصورة صحيحة. وقد تصبح إعادة البناء بعد كل تحديث الجزء الغالب من العمل. تعالج البنى الشجرية اللاحقة الاستعلامات الديناميكية؛ فلا تَعِد بتحديثات ثابتة الزمن للمجاميع السابقة الثابتة لمجرد قصر صيغة استعلامها.
استخدم مصفوفة الفروق لدفعة من إضافات المجالات
تسجل مصفوفة الفروق موضعي بدء مساهمة متراكمة وانتهائها. ولإضافة delta على المجال [left,right)، أضف delta عند left واطرحها عند right، ثم استعد القيم بمسح يحسب المجموع السابق. يطبق المثال الثاني عدة تحديثات قبل قراءة أي قيمة نهائية. وتتيح مصفوفة فروق طولها n+1 تمثيل نقطة توقف عند right=n دون معالجة خاصة لغياب موضع إضافي. هذه تقنية لمعالجة دفعة، وليست فهرسا ديناميكيا عاما لاستعلامات جمع أي مجال. يفترض عقدها تحديثات جمع وإعادة بناء واحدة؛ أما مزجها بعمليات استبدال غير مرتبطة فيحتاج إلى نموذج وبرهان جديدين.
def apply_additions(n, updates):
diff = [0] * (n + 1)
for left, right, delta in updates:
if not 0 <= left <= right <= n:
raise ValueError("Invalid half-open range")
diff[left] += delta
diff[right] -= delta
result = []
running = 0
for index in range(n):
running += diff[index]
result.append(running)
return result
print(apply_additions(5, [(1, 4, 3), (2, 5, -1)]))
[0, 3, 2, 2, -1]
يتداخل التحديث الثاني مع الموضعين 2 و3، ويمتد ليشمل الموضع 4. وتُسجّل نقطة التوقف عند 5، لكنها لا تُصدر عنصرا في الناتج.
ميّز النسخة المستقلة من لقطة مدخل مشتركة
تُشتق مصفوفة المجاميع السابقة من قيم محددة في وقت محدد. وإذا عدّل مكوّن آخر القائمة الأصلية، فلن تتحدث القيم السابقة القديمة تلقائيا. وثّق ملكية البيانات، أو خذ لقطة معتمدة منها قبل التهيئة. تحمي النسخة السطحية للقائمة عضوية العناصر فيها، لكنها لا تحمي الكائنات القابلة للتعديل المخزنة داخلها. ويكون الأمر عادة مباشرا للمصفوفات العددية؛ أما السجلات فاختر منها الحقل العددي الثابت ذي الصلة. تجنب إعادة استخدام مصفوفة سابقة مخزنة مؤقتا لمجرد أن طول القائمة الحالية لم يتغير. فالمحتوى والوحدات والترتيب اعتماديات أيضا.
افحص كل حد بمقارنته بمرجع بسيط
لمصفوفة صغيرة، عد جميع أزواج الحدود الصالحة left <= right، وقارن range_sum بجمع Python المباشر لعناصر الشريحة. أدرج المدخل الفارغ، والقيم الصفرية كلها، والقيم السالبة، والمجال الكامل، والمجالات ذات العنصر الواحد. ولإضافات الدفعة، قارن بحلقة متداخلة بسيطة تحدث كل موضع متأثر في عينات صغيرة. قد تكون هذه التنفيذات المرجعية أبطأ، لكن يسهل الوثوق بها لإجراء فحوص تفاضلية. افصل اختبارات المجالات غير الصالحة لكي يكون رفضها مقصودا، لا نتيجة IndexError عرضي. وتكمل العينات المحدودة الشاملة الهوية الجبرية للمجاميع السابقة، دون أن تحل محلها.
الأخطاء الشائعة وإصلاحها
إدراج القيمة عند الطرف الأيمن مرتين
- لماذا يحدث؟
- اختلط اصطلاح النهاية الشاملة باصطلاح النهاية نصف المفتوحة.
- كيف تصلحه؟
- التزم اصطلاحا واحدا، واشتق منه الطول وعملية الطرح.
- كيف تتجنب تكراره؟
- اختبر المجالات الفارغة والمجالات ذات العنصر الواحد.
بقاء إجابات الاستعلامات كما هي بعد تعديل المصدر
- لماذا يحدث؟
- تخص التهيئة المسبقة لقطة بيانات أقدم.
- كيف تصلحه؟
- أعد بناء البنية، أو اختر هيكل بيانات ديناميكيا مناسبا.
- كيف تتجنب تكراره؟
- تتبّع إصدار البيانات وكل اعتماد ذي صلة.
تجاوز مجال ينتهي عند n حدود مصفوفة الفروق
- لماذا يحدث؟
- لم يُخصص موضع إضافي يمثل نقطة التوقف.
- كيف تصلحه؟
- استخدم n+1 إدخالا للفروق، وأصدر n قيمة فقط في الناتج.
- كيف تتجنب تكراره؟
- أدرج تحديثات المجال الكامل وتحديثات الموضع الأخير.
اختبر فهمك
1. ما مجموع المجال [0,4) في العينة الأولى، ولماذا؟
اعرض الحل المشروح
يساوي prefix[4]-prefix[0] المقدار 8-0=8، ويشمل القيم 4 و-2 و5 و1 مرة واحدة بالضبط. يجعل الصفر الإضافي صيغة المجال الكامل مماثلة لصيغة الاستعلامات الداخلية. ولا نحتاج إلى فرع مستقل يعالج كون الطرف الأيسر صفرا.
2. لماذا ينبغي لتحديث مجال طوله صفر أن يضيف ويطرح عند الموضع نفسه؟
اعرض الحل المشروح
لأن المجال [left,left) لا يؤثر في أي قيمة. تلغي المساهمتان إحداهما الأخرى في تمثيل الفروق، فيُحفظ عقد التحديث الفارغ تلقائيا. أما تفسير النهاية على أنها شاملة فيعدّل عنصرا واحدا بالخطأ.
أهم ما تعلمته
- اختر اصطلاحا لحدود المجال والتزمه
- افحص كل حد بمقارنته بمرجع بسيط