ما معنى Random Number Generator في الخوارزميات وهياكل البيانات؟ دليل عملي

شروحات تقنية

مولّد الأرقام العشوائية (Random Number Generator أو اختصارًا RNG) في سياق الخوارزميات وهياكل البيانات هو ببساطة مصدر تستدعيه شفرتك كلما احتجت رقمًا "غير متوقّع" لاتخاذ قرار داخل الخوارزمية: أي عنصر تختاره محورًا للفرز، في أي خانة تضع مفتاحًا في جدول التقطيع، أو أي عيّنة تأخذها من تدفّق بيانات ضخم. الفكرة الجوهرية التي يخطئ فيها كثيرون: معظم ما نسمّيه "عشوائيًا" في البرمجة ليس عشوائيًا حقيقيًا، بل حتميًّا (deterministic) — أي أنه يتبع صيغة رياضية، وإذا بدأت من نفس نقطة الانطلاق حصلت على نفس التسلسل تمامًا. وهذا ليس عيبًا، بل ميزة تُستغَل يوميًا.

عشوائي حقيقي أم عشوائي زائف؟

عمليًّا هناك نوعان، ولكلٍّ استخدامه:

المعيارPRNG (زائف)TRNG (حقيقي)
المصدرصيغة رياضية + بذرةظاهرة فيزيائية (ضجيج، توقيت الأجهزة)
السرعةسريع جدًّاأبطأ
قابلية التكرارنعم (نفس البذرة = نفس النتيجة)لا
الاستخدام النموذجيخوارزميات، محاكاة، ألعابمفاتيح التشفير، توليد كلمات المرور

الغالبية العظمى من دوال اللغات — مثل random في بايثون أو Math.random() في جافاسكربت أو rand() في لغة C — هي PRNG. تكفي تمامًا داخل الخوارزميات وهياكل البيانات، لكنها لا تصلح للأمان مطلقًا.

ما دور "البذرة" (Seed)؟

البذرة هي الرقم الذي ينطلق منه المولّد الزائف. مِن نفس البذرة يخرج نفس التسلسل في كل مرة. هذه النقطة هي أهم ما يميّز الـ PRNG عمليًّا:

  • لتصحيح الأخطاء: إذا انهارت خوارزميتك عند مدخل عشوائي معيّن، ثبّت البذرة لتُعيد إنتاج نفس الحالة وتلاحق العطب.
  • لإعادة إنتاج التجارب: في تعلّم الآلة والمحاكاة، تثبيت البذرة يجعل نتائجك قابلة للتكرار والمراجعة من الآخرين.

مثال بايثون: random.seed(42) قبل أي استدعاء يجعل كل تشغيلات البرنامج متطابقة. انزع هذا السطر فتعود العشوائية "الظاهرية".

أين تُستخدم داخل الخوارزميات فعليًّا؟

هنا يظهر جمال RNG، فهو ليس مجرد أداة ألعاب:

  1. الفرز السريع العشوائي (Randomized QuickSort): اختيار المحور عشوائيًّا يحوّل أسوأ حالة زمنية من O(n²) إلى أداء متوقّع مقداره O(n log n) مهما كان ترتيب المدخلات، لأنه يمنع الخصم من تصميم مدخل يُبطئك عمدًا.
  2. جداول التقطيع (Hash Tables): التقطيع الشامل (Universal Hashing) يختار دالة تقطيع عشوائيًّا وقت التشغيل ليقلّل التصادمات ويقاوم هجمات إغراق الجداول.
  3. هياكل بيانات احتمالية: قوائم التخطّي (Skip Lists) وأشجار الـ Treap تعتمد على قلبة عملة عشوائية لتحافظ على توازنها تقريبيًّا دون كود موازنة معقّد.
  4. أخذ العيّنات من التدفّق (Reservoir Sampling): اختيار k عناصر عادلة من تدفّق لا تعرف طوله مسبقًا — مستحيل بلا RNG.
  5. خوارزميات مونتي كارلو ولاس فيغاس: تقريب حلول لمسائل يصعب حلّها بدقّة، من حساب التكاملات إلى محاكاة المخاطر.

الخطأ الأخطر: استخدام PRNG في التشفير

هذه أكثر مزلقة يقع فيها المطوّرون. دالة مثل Math.random() أو rand() قابلة للتنبؤ: لو عرف المهاجم بعض المخرجات أمكنه استنتاج البذرة وتوقّع البقية. لذلك:

  • لا تولّد رموز جلسات، أو رموز إعادة تعيين كلمة المرور، أو مفاتيح تشفير عبر PRNG عادي.
  • استخدم مولّدًا آمنًا تشفيريًّا (CSPRNG): secrets في بايثون، SecureRandom في جافا، وcrypto.randomBytes() في Node.js، وjava.security عمومًا. هذه مصمّمة ليستحيل التنبؤ بها عمليًّا.

القاعدة البسيطة: إن كان الرقم سيحمي شيئًا، استخدم CSPRNG؛ وإن كان لتسريع خوارزمية أو لعبة، فالـ PRNG العادي أسرع ويكفي.

نصيحة عملية من التجربة

عند اختبار الأداء (Benchmarking) لخوارزمية عشوائية، لا تكتفِ بتشغيل واحد. شغّلها عشرات المرات ببذور مختلفة وخذ المتوسط، لأن تشغيلة واحدة قد تصادف حظًّا استثنائيًّا يخدعك في الحكم. وفي المقابل، حين تصلح خطأ، ثبّت البذرة أولًا — الجمع بين "بذرة ثابتة للتصحيح" و"بذور متعددة للقياس" هو ما يميّز الشفرة الاحترافية.

الأسئلة الشائعة

هل الأرقام من Math.random() عشوائية حقًّا؟ لا، هي زائفة العشوائية (PRNG): تبدو عشوائية لكنها محسوبة بصيغة رياضية من بذرة داخلية. كافية للألعاب والخوارزميات، غير كافية للأمان.

لماذا أحتاج تثبيت البذرة أصلًا؟ لجعل النتائج قابلة للتكرار: تصحيح الأخطاء، مراجعة التجارب، وكتابة اختبارات ثابتة لا تتغيّر نتيجتها بين تشغيل وآخر.

ما الفرق بين خوارزمية لاس فيغاس ومونتي كارلو؟ لاس فيغاس تعطي دائمًا نتيجة صحيحة لكن زمنها متغيّر (مثل QuickSort العشوائي)، ومونتي كارلو زمنها محدود لكن نتيجتها قد تحمل احتمال خطأ صغيرًا مقبولًا.

هل يمكن الوثوق بمولّد اللغة لتوليد كلمة مرور؟ ليس بالمولّد العادي. استخدم وحدة تشفير مخصّصة مثل secrets أو SecureRandom، فهي وحدها المصمّمة لمقاومة التنبؤ.