فرز الكتل

فرز الكتل ، أو فرز دمج الكتل ، هو خوارزمية فرز تجمع بين عمليتي دمج على الأقل مع فرز الإدراج للوصول إلى زمن فرز مستقر في مكانه O ( n log n ) (انظر ترميز Big O ) . وقد سُميت بهذا الاسم نسبةً إلى ملاحظة أن دمج قائمتين مرتبتين، A و B ، يُكافئ تقسيم A إلى كتل متساوية الحجم ، وإدراج كل كتلة من A في B وفقًا لقواعد خاصة، ثم دمج أزواج AB .

تم اقتراح خوارزمية عملية واحدة لدمج O ( n log n ) في مكانها بواسطة بوك سون كيم وأرني كوتزنر في عام 2008. [ 1 ]

ملخص

الحلقة الخارجية لفرز الكتل متطابقة مع فرز الدمج من الأسفل إلى الأعلى ، حيث يقوم كل مستوى من الفرز بدمج أزواج من المصفوفات الفرعية، A و B ، بأحجام 1، ثم 2، ثم 4، 8، 16، وهكذا، حتى تصبح المصفوفات الفرعية مجتمعة هي المصفوفة نفسها.

بدلاً من دمج A و B مباشرة كما هو الحال مع الطرق التقليدية، تقوم خوارزمية الدمج القائمة على الكتل بتقسيم A إلى كتل منفصلة بحجم A (مما ينتج عنه عدد A من الكتل أيضًا)، [ 2 ] تقوم بإدراج كل كتلة A في B بحيث تكون القيمة الأولى لكل كتلة A أقل من أو تساوي (≤) قيمة B التي تليها مباشرة، ثم تقوم بدمج كل كتلة A محليًا مع أي قيم B بينها وبين كتلة A التالية .

بما أن عمليات الدمج لا تزال تتطلب مخزنًا مؤقتًا منفصلاً بحجم كافٍ لاستيعاب كتلة A المراد دمجها، فقد تم حجز منطقتين داخل المصفوفة لهذا الغرض (تُعرفان بالمخازن المؤقتة الداخلية ). [ 3 ] وبالتالي، يتم تعديل أول كتلتين من A لاحتواء أول ظهور لكل قيمة داخل A ، مع إزاحة المحتويات الأصلية لتلك الكتل إذا لزم الأمر. ثم يتم إدراج كتل A المتبقية في B ودمجها باستخدام أحد المخزنين المؤقتين كمساحة تبديل. وتؤدي هذه العملية إلى إعادة ترتيب القيم في ذلك المخزن المؤقت.

بعد دمج كل كتلتي A و B من كل مصفوفة فرعية A و B في مستوى فرز الدمج، يجب فرز القيم في المخزن المؤقت لاستعادة ترتيبها الأصلي، لذا يجب تطبيق فرز الإدراج. ثم تُعاد توزيع القيم في المخازن المؤقتة إلى أول موضع مُرتب لها داخل المصفوفة. تتكرر هذه العملية لكل مستوى من مستويات فرز الدمج الخارجي من الأسفل إلى الأعلى، وعندها تكون المصفوفة قد استقرت في ترتيبها.

الخوارزمية

تُستخدم المعاملات التالية في أمثلة التعليمات البرمجية:

|عملية OR الثنائية
>>انعطف يمينًا
%modulo
++ و +=زيادة
[ x , y )يتراوح من ≥ x و < y
| نطاق |range.end – range.start
array[i]العنصر رقم i من المصفوفة

بالإضافة إلى ذلك، يعتمد فرز الكتل على العمليات التالية كجزء من خوارزميته الشاملة:

  • التبديل : تبديل مواقع قيمتين في مصفوفة.
  • تبديل الكتل : استبدال نطاق من القيم داخل مصفوفة بقيم في نطاق مختلف من المصفوفة.
  • البحث الثنائي : بافتراض أن المصفوفة مرتبة، يتم التحقق من القيمة الوسطى في نطاق البحث الحالي، ثم إذا كانت القيمة أصغر، يتم التحقق من النطاق الأدنى، وإذا كانت القيمة أكبر، يتم التحقق من النطاق الأعلى. يستخدم فرز الكتل نوعين: أحدهما لإيجاد أول موضع لإدراج قيمة في المصفوفة المرتبة ، والآخر لإيجاد آخر موضع.
  • البحث الخطي : إيجاد قيمة معينة في مصفوفة عن طريق فحص كل عنصر على حدة بالترتيب، حتى يتم العثور عليها.
  • فرز الإدراج : لكل عنصر في المصفوفة، قم بالتكرار للخلف وابحث عن المكان الذي يجب إدراجه فيه، ثم قم بإدراجه في ذلك الموضع.
  • تدوير المصفوفة : تحريك عناصر المصفوفة إلى اليسار أو اليمين بعدد معين من الخانات، مع التفاف القيم الموجودة على الحواف إلى الجانب الآخر. يمكن تنفيذ التدوير بثلاث عمليات عكس . [ 4 ]
تدوير (المصفوفة، المبلغ، النطاق) عكس (المصفوفة، النطاق) عكس (المصفوفة، [بداية النطاق، بداية النطاق + المبلغ)) عكس (المصفوفة، [بداية النطاق + المبلغ، نهاية النطاق))
  • التقريب إلى أقرب قوة للعدد اثنين : تقريب قيمة إلى أقرب قوة للعدد اثنين . وهكذا يصبح العدد 63 هو 32، ويبقى العدد 64 كما هو، وهكذا. [ 5 ]
قوة العدد اثنين (x) x = x | (x >> 1) x = x | (x >> 2) x = x | (x >> 4) x = x | (x >> 8) x = x | (x >> 16) إذا (كان هذا نظام 64 بت ) x = x | (x >> 32) أعد x - (x >> 1)

الحلقة الخارجية

كما ذُكر سابقاً، فإن الحلقة الخارجية لفرز الكتل تُطابق فرز الدمج من الأسفل إلى الأعلى. إلا أنها تتميز بميزة تضمن أن يكون حجم كل مصفوفة فرعية A و B متساوياً في حدود عنصر واحد.

فرز الكتل (المصفوفة) قوة_العدد_اثنان = FloorPowerOfTwo (حجم_المصفوفة) المقياس = حجم المصفوفة / قوة العدد اثنين // 1.0 ≤ المقياس < 2.0// فرز الإدراج 16-31 عنصرًا في كل مرة for (merge = 0; merge < power_of_two; merge += 16) البداية = الدمج * المقياس النهاية = البداية + 16 * المقياس فرز الإدراج (المصفوفة، [البداية، النهاية)) for (length = 16; length < power_of_two; length += length) for (merge = 0; merge < power_of_two; merge += length * 2) البداية = الدمج * المقياس المنتصف = (الدمج + الطول) * المقياس النهاية = (الدمج + الطول * 2) * المقياس إذا كان (array[end − 1] < array[start]) // النطاقان بترتيب عكسي، لذا يكفي تدويرهما لدمجهما. Rotate (array, mid − start, [start, end)) وإلا إذا كان (array[mid − 1] > array[mid]) Merge (array, A = [start, mid), B = [mid, end)) // وإلا فإن النطاقين مرتبان بشكل صحيح بالفعل

يمكن أيضًا استخدام العمليات الحسابية ذات النقطة الثابتة ، وذلك بتمثيل عامل المقياس ككسر integer_part + numerator/denominator:

فرز الكتل (المصفوفة) قوة_العدد_اثنان = FloorPowerOfTwo (حجم_المصفوفة) المقام = قوة العدد اثنين / 16 numerator_step = array.size % denominator integer_step = floor (array.size/denominator) // فرز الإدراج 16  31 عنصرًا في المرة الواحدةبينما (الخطوة_الصحيحة < حجم_المصفوفة) الجزء الصحيح = البسط = 0 بينما (الجزء_الصحيح < حجم_المصفوفة) // الحصول على نطاقات A و B البداية = الجزء_الصحيح integer_part += integer_step البسط += خطوة_البسط إذا كان (البسط ≥ المقام) البسط - = المقام integer_part++ mid = الجزء_الصحيح integer_part += integer_step البسط += خطوة_البسط إذا كان (البسط ≥ المقام) البسط −= المقام integer_part++ النهاية = الجزء_الصحيح إذا كان (array[end − 1] < array[start]) قم بتدوير (array, mid − start, [start, end)) وإلا إذا كان (array[mid − 1] > array[mid]) قم بدمج (array, A = [start, mid), B = [mid, end)) integer_step += integer_step numerator_step += numerator_step إذا كان (البسط_الخطوة ≥ المقام) خطوة_البسط −= المقام integer_step++

استخراج المخازن المؤقتة

عملية استخراج المخزن المؤقت لفرز الكتل.

يتم إنشاء المخزنين الداخليين اللازمين لكل مستوى من مستويات خطوة الدمج عن طريق نقل أول 2√A من قيمها داخل مصفوفة فرعية A إلى بداية A. في البداية ، يتم المرور على عناصر A وحساب القيم الفريدة المطلوبة، ثم يتم تطبيق تدوير المصفوفة لنقل هذه القيم الفريدة إلى البداية. [ 6 ] إذا لم تحتوي A على عدد كافٍ من القيم الفريدة لملء المخزنين (بحجم √A لكل منهما)، فيمكن استخدام B بنفس الكفاءة. في هذه الحالة، يتم نقل آخر نسخة من كل قيمة إلى نهاية B ، مع استبعاد هذا الجزء من B أثناء عمليات الدمج.

بينما (الخطوة_الصحيحة < حجم_المصفوفة) حجم الكتلة = الخطوة الصحيحة حجم المخزن المؤقت = عدد صحيح_الخطوة/حجم_الكتلة + 1 [استخراج مخزنين مؤقتين بحجم 'buffer_size' لكل منهما]

إذا لم تحتوي المجموعة B على عدد كافٍ من القيم الفريدة، فسيتم استخراج أكبر عدد ممكن من القيم الفريدة ، ثم يتم تعديل حجم المجموعتين A و B بحيث يكون عدد المجموعات A الناتجة أقل من أو يساوي عدد العناصر الفريدة المستخرجة للمخزن المؤقت. في هذه الحالة، سيتم استخدام مخزن مؤقت واحد فقط، ولن يكون هناك مخزن مؤقت ثانٍ.

حجم المخزن المؤقت = [عدد القيم الفريدة التي تم العثور عليها] حجم الكتلة = الخطوة الصحيحة / حجم المخزن المؤقت + 1 الجزء الصحيح = البسط = 0 بينما (الجزء_الصحيح < حجم_المصفوفة) [احصل على نطاقات A و B] [اضبط A و B بحيث لا تشمل النطاقات المستخدمة بواسطة المخازن المؤقتة]

كتل العلامة أ

يتم تصنيف كتل A باستخدام القيم من المخزن المؤقت الداخلي الأول. لاحظ أن حجم كتلة A الأولى وكتلة B الأخيرة غير متساويين.

بعد إنشاء المخزن المؤقت الداخلي (واحد أو اثنين)، تبدأ عملية دمج كل مصفوفة فرعية A و B لهذا المستوى من فرز الدمج. وللقيام بذلك، تُقسّم كل مصفوفة فرعية A وB إلى كتل متساوية الحجم، بالحجم المحسوب في الخطوة السابقة، مع إمكانية اختلاف حجم الكتلة A الأولى عن حجم الكتلة B الأخيرة إذا لزم الأمر. ثم تُكرر العملية على كل كتلة A متساوية الحجم، وتُبدّل القيمة الثانية بقيمة مُقابلة لها من المخزن المؤقت الداخلي الأول. تُعرف هذه العملية بترميز الكتل.

// blockA هو نطاق كتل A المتبقية، // و firstA هي كتلة A الأولى ذات الحجم غير المتساوي blockA = [A.start, A.end) firstA = [A.start, A.start + |blockA| % block_size) // تبديل القيمة الثانية لكل كتلة A مع القيمة الموجودة في المخزن المؤقت buffer1 for (index = 0, indexA = firstA.end + 1; indexA < blockA.end; indexA += block_size) Swap (array[buffer1.start + index], array[indexA]) فهرس++ lastA = firstA blockB = [B.start, B.start + minimum (block_size, |B|)) blockA.start += |firstA|

تدحرج وأسقط

تتحرك كتلتان من النوع A عبر الكتل من النوع B. بمجرد أن تسقط الكتلة الأولى من النوع A خلف الكتل الأخرى، يتم دمج الكتلة A غير المتساوية الحجم محليًا مع قيم B التي تليها.

بعد تحديد وتصنيف كتل A بهذه الطريقة، يتم تمرير كتل A عبر كتل B عن طريق تبديل أول كتلة A متساوية الحجم مع كتلة B التالية. تتكرر هذه العملية حتى تصبح قيمة أول كتلة A ذات أصغر قيمة تصنيف أقل من أو تساوي قيمة آخر كتلة B تم تبديلها للتو مع كتلة A.

عند هذه النقطة، يتم تبديل كتلة A الدنيا (كتلة A ذات أصغر قيمة علامة) إلى بداية كتل A المتداولة، وتُستعاد قيمة العلامة إلى قيمتها الأصلية من المخزن المؤقت الأول. يُعرف هذا بحذف كتلة، حيث لن يتم تداولها مع كتل A المتبقية. ثم تُدرج كتلة A هذه في كتلة B السابقة، أولاً باستخدام بحث ثنائي على B للعثور على الفهرس الذي تكون فيه القيمة الأولى لـ A أقل من أو تساوي القيمة عند ذلك الفهرس لـ B، ثم بتدوير A إلى B عند ذلك الفهرس.

 minA = blockA.start indexA = 0 بينما (صحيح) // إذا كان هناك كتلة B سابقة وكانت القيمة الأولى للكتلة A الدنيا ≤ // القيمة الأخيرة للكتلة B السابقة، فقم بإسقاط تلك الكتلة A الدنيا خلفها. // أو إذا لم تكن هناك كتل B متبقية، فاستمر في إسقاط كتل A المتبقية. إذا ((|lastB| > 0 و array[lastB.end - 1] ≥ array[minA]) أو |blockB| = 0) // حدد مكان تقسيم كتلة B السابقة، وقم بتدويرها عند نقطة التقسيم B_split = BinaryFirst (array, array[minA], lastB) B_remaining = lastB.end - B_split // تبديل كتلة A الدنيا إلى بداية كتل A المتداولة BlockSwap (array, blockA.start, minA, block_size) // استعادة القيمة الثانية للكتلة A Swap (array[blockA.start + 1], array[buffer1.start + indexA]) مؤشر A++ // تدوير الكتلة A إلى داخل الكتلة B السابقة Rotate (array, blockA.start - B_split, [B_split, blockA.start + block_size)) // دمج محليًا كتلة A السابقة مع قيم B التي تليها، // باستخدام المخزن المؤقت الداخلي الثاني كمساحة تبديل (إن وُجد) إذا كان (|buffer2| > 0) MergeInternal (array, lastA, [lastA.end, B_split), buffer2) else MergeInPlace (array, lastA, [lastA.end, B_split)) // تحديث نطاق الكتل المتبقية من النوع A، // والنطاق المتبقي من الكتلة B بعد تقسيمها lastA = [blockA.start - B_remaining, blockA.start - B_remaining + block_size) lastB = [lastA.end، lastA.end + B_remaining) // إذا لم يتبق أي كتل من النوع A، فقد انتهت هذه الخطوة blockA.start = blockA.start + block_size إذا كانت قيمة (|blockA| = 0) فاخرج من الحلقة minA = [أصغر كتلة A جديدة] (انظر أدناه) وإلا إذا كان (|blockB| < حجم الكتلة) // انقل كتلة B الأخيرة، ذات الحجم غير المتساوي، // إلى ما قبل كتل A المتبقية، باستخدام تدوير Rotate (array, blockB.start - blockA.start, [blockA.start, blockB.end)) lastB = [blockA.start, blockA.start + |blockB|) blockA.start += |blockB| blockA.end += |blockB| minA += |blockB| blockB.end = blockB.start وإلا // قم بتدوير الكتلة A الموجودة في أقصى اليسار إلى النهاية عن طريق تبديلها مع الكتلة B التالية BlockSwap (array, blockA.start, blockB.start, block_size) lastB = [blockA.start، blockA.start + block_size) إذا كان (minA = blockA.start) minA = blockA.end blockA.start += block_size blockA.end += block_size blockB.start += block_size // هذا يعادل الحد الأدنى (blockB.end + block_size, B.end)، // ولكن هذا قد يؤدي إلى تجاوز الحد الأقصى إذا (blockB.end > B.end - block_size) blockB.end = B.end آخر blockB.end += block_size // دمج كتلة A الأخيرة مع قيم B المتبقية إذا كان (|buffer2| > 0) MergeInternal (array, lastA, [lastA.end, B.end), buffer2) else MergeInPlace (array, lastA, [lastA.end, B.end))

إحدى التحسينات التي يمكن تطبيقها خلال هذه الخطوة هي تقنية الثقب العائم . [ 7 ] عندما يتم إسقاط كتلة A الدنيا خلف كتلة B السابقة، ثم تدويرها إليها، وبعد ذلك يتم تبديل محتوياتها إلى المخزن المؤقت الداخلي الثاني لعمليات الدمج المحلية، سيكون من الأسرع تبديل كتلة A إلى المخزن المؤقت مسبقًا، والاستفادة من حقيقة أن محتويات هذا المخزن المؤقت لا تحتاج إلى الاحتفاظ بأي ترتيب. لذا، بدلًا من تدوير المخزن المؤقت الثاني (الذي كان كتلة A قبل تبديل الكتل) إلى كتلة B السابقة عند الفهرس 1 ، يمكن ببساطة تبديل قيم كتلة B بعد الفهرس 1 مع آخر عناصر المخزن المؤقت.

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

عمليات الدمج المحلية

بعد تدوير الكتلة A إلى الكتلة B، تُدمج الكتلة A السابقة مع قيم B التي تليها، باستخدام المخزن المؤقت الثاني كمساحة تبديل. عندما تُسقط الكتلة A الأولى خلف الكتلة B، فهذا يعني الكتلة A غير المتساوية الحجم في البداية، وعندما تُسقط الكتلة A الثانية خلفها، فهذا يعني الكتلة A الأولى، وهكذا.

MergeInternal (array, A, B, buffer) // تبديل القيم في A مع القيم الموجودة في 'buffer' BlockSwap (array, A.start, buffer.start, |A|) عدد العناصر A = 0، عدد العناصر B = 0، عدد العناصر المُدرجة = 0 بينما (عدد عناصر A < |A| وعدد عناصر B < |B|) إذا (array[buffer.start + عدد عناصر A] ≤ array[B.start + عدد عناصر B]) قم بتبديل (array[A.start + insert], array[buffer.start + عدد عناصر A]) عدد A++ وإلا قم بتبديل (array[A.start + insert], array[B.start + B_count]) B_count++ إدراج++ // تبديل الجزء المتبقي من المخزن المؤقت مع الجزء المتبقي من المصفوفة BlockSwap (array, buffer.start + A_count, A.start + insert, |A| - A_count)

إذا لم يكن المخزن المؤقت الثاني موجودًا، فيجب إجراء عملية دمج في مكانها تمامًا، مثل نسخة تعتمد على الدوران من خوارزمية هوانغ ولين، [ 7 ] [ 8 ] خوارزمية دودزينسكي وديدك، [ 9 ] أو بحث ثنائي متكرر وتدوير.

MergeInPlace (array, A, B) while (|A| > 0 and |B| > 0) // ابحث عن أول مكان في B حيث يجب إدراج العنصر الأول في A mid = BinaryFirst (array, array[A.start], B) // قم بتدوير A إلى مكانه المبلغ = منتصف - نهاية أ. تدوير (المصفوفة، الكمية، [A.start، منتصف)) // حساب نطاقي A و B الجديدين B = [mid, B.end) أ = [أ.البداية + الكمية، المنتصف) A.start = BinaryLast (array, array[A.start], A)

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

minA = blockA.start for (findA = minA + block_size; findA < blockA.end - 1; findA += block_size) if (array[findA + 1] < array[minA + 1]) minA = findA

تستمر هذه الكتل المتبقية من النوع A في التدحرج عبر المصفوفة، حيث يتم إسقاطها وإدخالها في مكانها الصحيح. تتكرر هذه العملية حتى يتم إسقاط جميع الكتل من النوع A وتدويرها داخل الكتلة B السابقة.

بعد إسقاط آخر كتلة A متبقية وإدراجها في B حيث تنتمي، يجب دمجها مع قيم B المتبقية التي تليها. بهذا تكتمل عملية الدمج لهذا الزوج المحدد من المصفوفات الفرعية A وB. مع ذلك، يجب تكرار العملية للمصفوفات الفرعية المتبقية A وB للمستوى الحالي من فرز الدمج.

لاحظ أنه يمكن إعادة استخدام المخازن الداخلية لكل مجموعة من المصفوفات الفرعية A و B لهذا المستوى من فرز الدمج، ولا يلزم إعادة استخراجها أو تعديلها بأي شكل من الأشكال.

إعادة التوزيع

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

بعد تكرار هذه الخطوات لكل مستوى من مستويات فرز الدمج من الأسفل إلى الأعلى، يكتمل فرز الكتل.

المتغيرات

تعتمد خوارزمية فرز الكتل على استخراج مخزنين داخليين، وتقسيم المصفوفتين الفرعيتين A وB إلى كتل متساوية الحجم، ثم دمج كتل A في B (باستخدام المخزن الأول لتتبع ترتيب كتل A)، ودمج المصفوفتين محليًا باستخدام المخزن الثاني كمساحة تبديل، ثم فرز المخزن الثاني، وأخيرًا إعادة توزيع المخزنين. مع أن الخطوات ثابتة، إلا أن هذه الأنظمة الفرعية قد تختلف في طريقة تنفيذها.

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

تشمل الخيارات الجيدة لحجم المخزن المؤقت ما يلي:

مقاسملحوظات
(العدد + 1)/2يتحول إلى فرز دمج كامل السرعة لأن جميع المصفوفات الفرعية A ستناسبه
(العدد + 1)/2 + 1سيكون هذا هو حجم كتل A عند أكبر مستوى من عمليات الدمج، لذا يمكن لفرز الكتل تخطي استخدام عمليات الدمج الداخلية أو الموضعية لأي شيء.
512مخزن مؤقت ذو حجم ثابت كبير بما يكفي للتعامل مع عمليات الدمج العديدة في المستويات الأصغر من فرز الدمج
0إذا لم يتمكن النظام من تخصيص أي ذاكرة إضافية، فلن تعمل الذاكرة بشكل جيد

بدلاً من وسم كتل A باستخدام محتويات أحد المخازن المؤقتة الداخلية، يمكن استخدام مخزن مؤقت لمحاكاة الحركة غير المباشرة. [ 1 ] [ 10 ] يُعرَّف هذا المخزن المؤقت الداخلي على أنه s1 t s2 ، حيث يكون حجم كل من s1 و s2 مساويًا لعدد كتل A و B على التوالي، ويحتوي t على أي قيم تلي s1 مباشرةً وتساوي آخر قيمة لـ s1 (مما يضمن عدم ظهور أي قيمة في s2 في s1 ). يُستخدم مخزن مؤقت داخلي ثانٍ يحتوي على √A قيمة فريدة . ثم تُبدَّل أول √A من قيم s1 و s2 مع بعضها البعض لترميز معلومات في المخزن المؤقت حول أي الكتل هي كتل A وأيها كتل B. عند تبديل كتلة A في الفهرس i مع كتلة B في الفهرس j (حيث تكون أول كتلة A متساوية الحجم في البداية في الفهرس 0)، يتم تبديل s1[i] و s1[j] مع s2[i] و s2[j] على التوالي. يحاكي هذا حركة كتل المجموعة A عبر كتل المجموعة B. تُستخدم القيم الفريدة في المخزن المؤقت الثاني لتحديد الترتيب الأصلي لكتل ​​المجموعة A أثناء مرورها عبر كتل المجموعة B. بمجرد إسقاط جميع كتل المجموعة A، يُستخدم مخزن محاكاة الحركة لفك تشفير ما إذا كانت كتلة معينة في المصفوفة كتلة من المجموعة A أو كتلة من المجموعة B، ثم تُدار كل كتلة من المجموعة A إلى المجموعة B، ويُستخدم المخزن المؤقت الداخلي الثاني كمساحة تبديل لعمليات الدمج المحلية.

لا يشترط بالضرورة وسم القيمة الثانية لكل كتلة A، إذ يمكن استخدام القيمة الأولى أو الأخيرة أو أي عنصر آخر. مع ذلك، إذا وُسمت القيمة الأولى، فسيتعين قراءة القيم من المخزن المؤقت الداخلي الأول (حيث تم تبديلها) عند تحديد مكان حذف كتلة A الدنيا.

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

التطبيقات

تشمل التطبيقات المعروفة لخوارزمية فرز الكتل ما يلي:

  • تطبيق كوتزنر وكيم في لغة C++. [ 11 ]
  • Wikisort، وهو تطبيق مايك مكفادين المستند إلى ورقة كوتزنر وكيم. [ 12 ]
  • GrailSort (و HolyGrailSort المعاد كتابتها)، تطبيق أندريه أستريلين المستند إلى هوانغ ولانغستون (1992)، [ 13 ] والذي يصف في النهاية خوارزمية مشابهة جدًا. [ 14 ]
  • خوارزمية الفرز الافتراضية في المكتبة القياسية لـ Zig [ 15 ] [ 16 ] منذ الإصدار 0.2.0 [ 17 ] .

تحليل

يُعدّ فرز الكتل فئةً محددةً جيدًا وقابلةً للاختبار من الخوارزميات، مع وجود تطبيقات عملية متاحة كخوارزمية دمج وخوارزمية فرز. [ 11 ] [ 18 ] [ 12 ] وهذا يسمح بقياس خصائصها ودراستها.

تعقيد

تبدأ عملية فرز الكتل بإجراء فرز الإدراج على مجموعات من 16 إلى 31 عنصرًا في المصفوفة. فرز الإدراج عملية من رتبة O ( ) ، لذا فإن هذا يؤدي إلى زمن يتراوح بين O (16² × n / 16) و O (31² × n / 31) ، وهو O ( n ) عند حذف العوامل الثابتة . يجب أيضًا تطبيق فرز الإدراج على المخزن المؤقت الداخلي الثاني بعد اكتمال كل مستوى من مستويات الدمج. ومع ذلك، نظرًا لأن هذا المخزن المؤقت كان محدودًا بـأ{\displaystyle {\sqrt {A}}}من حيث الحجم،يا(ن2){\displaystyle O({\sqrt {n}}^{2})}وتنتهي العملية أيضًا بـ O ( n ) .

بعد ذلك، يجب استخراج مخزنين داخليين لكل مستوى من مستويات فرز الدمج. يتم ذلك من خلال المرور على عناصر المصفوفتين الفرعيتين A وB وزيادة عداد كلما تغيرت القيمة، وعند العثور على عدد كافٍ من القيم، يتم تدويرها إلى بداية A أو نهاية B. في أسوأ الأحوال، سينتهي الأمر بالبحث في المصفوفة بأكملها قبل العثور على القيمة المطلوبة.أ{\displaystyle {\sqrt {A}}}القيم الفريدة غير المتجاورة، الأمر الذي يتطلب O ( n ) من المقارنات وأ{\displaystyle {\sqrt {A}}}دورات لـأ{\displaystyle {\sqrt {A}}}القيم. وهذا يؤدي إلىيا(ن+ن×ن){\displaystyle O(n+{\sqrt {n}}\times {\sqrt {n}})}أو O ( n ) .

عندما لم تحتوي أي من المصفوفات الفرعية A أو B علىأ{\displaystyle {\sqrt {A}}}لإنشاء المخازن المؤقتة الداخلية باستخدام قيم فريدة، تُجرى عملية دمج موضعي غير مثالية عادةً، حيث تُكرر عمليات البحث الثنائي وتدوير المصفوفة A إلى B. ومع ذلك، فإن النقص المعروف في القيم الفريدة داخل أي من المصفوفات الفرعية يفرض حدًا صارمًا على عدد عمليات البحث الثنائي والتدوير التي ستُجرى خلال هذه الخطوة، وهو ما يُعد مرة أخرىأ{\displaystyle {\sqrt {A}}}تم تدوير العناصر حتىأ{\displaystyle {\sqrt {A}}}مرات، أو O ( n ) . يتم أيضًا تعديل حجم كل كتلة ليكون أصغر في الحالة التي تم العثور عليها فيهاأ{\displaystyle {\sqrt {A}}}قيم فريدة ولكن ليس 2أ{\displaystyle {\sqrt {A}}}، مما يحد بشكل أكبر من عدد القيم الفريدة الموجودة داخل أي كتلة A أو B.

يتم تنفيذ عملية وضع العلامات على الكتل Aأ{\displaystyle {\sqrt {A}}}يتم تكرار ذلك عدة مرات لكل مصفوفة فرعية A، ثم يتم تمرير كتل A وإدراجها في كتل B حتىأ{\displaystyle {\sqrt {A}}}مرات. تحتفظ عمليات الدمج المحلية بنفس تعقيد O ( n ) لعملية الدمج القياسية، وإن كان ذلك مع المزيد من عمليات الإسناد نظرًا لأنه يجب تبديل القيم بدلاً من نسخها. يتكرر البحث الخطي لإيجاد الحد الأدنى الجديد لكتلة A علىأ{\displaystyle {\sqrt {A}}}مكعباتأ{\displaystyle {\sqrt {A}}}مرات. وعملية إعادة توزيع المخزن المؤقت مطابقة لعملية استخراج المخزن المؤقت ولكن بشكل عكسي، وبالتالي فإن لها نفس التعقيد O ( n ) .

بعد استبعاد جميع مستويات التعقيد باستثناء المستوى الأعلى ، ومع الأخذ في الاعتبار وجود log( n ) مستوى في حلقة الدمج الخارجية، ينتج عن ذلك تعقيد تقاربي نهائي قدره O ( n log( n )) في أسوأ الحالات ومتوسطها. أما في أفضل الحالات، حيث تكون البيانات مرتبة بالفعل، فإن خطوة الدمج تُجري n /16 مقارنة للمستوى الأول، ثم n /32 ، ثم n /64 ، ثم n /128 ، وهكذا. هذه متسلسلة رياضية معروفة تُختزل إلى O ( n ) .

ذاكرة

بما أن فرز الكتل غير تكراري ولا يتطلب استخدام تخصيصات ديناميكية ، فإن هذا يؤدي إلى مساحة ثابتة في المكدس والكومة. يستخدم هذا الفرز ذاكرة مساعدة من رتبة O (1) في نموذج ثنائي التفرع ، والذي يقبل أن عدد البتات اللازمة لتتبع نطاقات A وB، والبالغة O (log n لا يمكن أن يتجاوز 32 بتًا على أنظمة الحوسبة 32 بت أو 64 بتًا على التوالي، وبالتالي يتبسط إلى مساحة O (1) لأي مصفوفة يمكن تخصيصها عمليًا.

استقرار

على الرغم من أن العناصر الموجودة في المصفوفة يتم نقلها خارج الترتيب أثناء فرز الكتلة، إلا أن كل عملية قابلة للعكس تمامًا وستستعيد الترتيب الأصلي للعناصر المتكافئة عند اكتمالها.

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

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

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

القدرة على التكيف

فرز الكتل هو فرز تكيفي على مستويين: أولاً، يتخطى دمج المصفوفات الفرعية A وB المرتبة أصلاً. ثانياً، عندما تحتاج A وB إلى الدمج، وتكونان مقسمتين إلى كتل متساوية الحجم، يتم تمرير كتل A عبر B بالقدر اللازم فقط، ويتم دمج كل كتلة مع قيم B التي تليها مباشرة. كلما كانت البيانات أكثر ترتيباً في الأصل، قلّ عدد قيم B التي تحتاج إلى الدمج في A.

المزايا

يُعدّ فرز الكتل خوارزمية فرز مستقرة لا تتطلب ذاكرة إضافية، وهو أمر مفيد في الحالات التي لا تتوفر فيها ذاكرة كافية لتخصيص مخزن مؤقت بحجم O ( n ) . عند استخدام صيغة المخزن المؤقت الخارجي لفرز الكتل، يمكن توسيع نطاقها من استخدام ذاكرة بحجم O ( n ) إلى مخازن مؤقتة أصغر تدريجيًا حسب الحاجة، وستظل تعمل بكفاءة ضمن هذه القيود.

العيوب

لا تستغل خوارزمية فرز الكتل نطاقات البيانات المرتبة بدقةٍ تضاهي بعض الخوارزميات الأخرى، مثل خوارزمية فرز تيم . [ 19 ] فهي تتحقق من هذه النطاقات المرتبة فقط على مستويين محددين مسبقًا: المصفوفات الفرعية A وB، والكتل A وB. كما أنها أصعب في التنفيذ والتوازي مقارنةً بخوارزمية فرز الدمج.

مراجع

  1. 1 2 كوتزنر، آرني؛ كيم، بوك-سون (2008). دمج مستقر قائم على النسبة (ملف PDF) . سلسلة محاضرات في علوم الحاسوب . المجلد  4978. سبرينغر برلين هايدلبرغ. الصفحات 246-257 . تاريخ الاسترجاع : 7 سبتمبر 2016 . 
  2. ^ مانيلا ، هيكي. أوكونين ، إيسكو (14 مايو 1984). “خوارزمية زمنية خطية بسيطة للدمج في الموقع”. خطابات معالجة المعلومات . 18 (4): 203-208 . دوى : 10.1016 / 0020-0190 (84)90112-1 .
  3. ^ كرونرود، م. ألكسندر (فبراير 1969). ""الخوارزمية المثالية للترقية دون الحاجة إلى العمل"" [ خوارزمية ترتيب مثالية بدون مجال تشغيل ] . وقائع أكاديمية العلوم في اتحاد الجمهوريات الاشتراكية السوفياتية (بالروسية). 186 (6): 1256 – 1258.
  4. بنتلي، جون (2006). لآلئ البرمجة ( الطبعة الثانية). 
  5. وارن الابن، هنري س. (2013) [2002]. متعة المخترق ( الطبعة الثانية). أديسون ويسلي - بيرسون للتعليم، المحدودة. ISBN  978-0-321-84268-8. 0-321-84268-5.
  6. باردو، لويس تراب (1977). الفرز والدمج المستقر مع حدود مثلى للمساحة والوقت . مجلة SIAM للحوسبة . المجلد 6. الصفحات 351-372 .  
  7. 1 2 جيفيرت، فيليم؛ كاتاجينن، جيركي؛ تومي باسانين (أبريل 2000). “الدمج المكاني الفعال في مكانه”. علوم الكمبيوتر النظرية . 237 ( 1– 2): 159– 181. سيتيسيركس 10.1.1.22.5750 . دوى : 10.1016/S0304-3975(98)00162-5 . 
  8. هوانغ، إف كيه؛ لين، إس. (1972). خوارزمية بسيطة لدمج مجموعتين منفصلتين مرتبتين خطيًا . مجلة SIAM للحوسبة . المجلد 1. الصفحات 31-39 . doi : 10.1137/0201004 . ISSN 0097-5397 .   
  9. دودزينسكي، كريستوف؛ ديديك، أندريه (1981). حول خوارزمية دمج تخزين مستقرة . رسائل معالجة المعلومات . المجلد 12. الصفحات 5-8 .  
  10. سيمفونيس، أنطونيوس (1995). "الدمج المستقر الأمثل". مجلة الكمبيوتر . 38 (8): 681-690 . CiteSeerX 10.1.1.55.6058 . doi : 10.1093/comjnl/38.8.681 . 
  11. 1 2 آرني كوتزنر. "أداة قياس أداء خوارزمية الدمج الموضعي" . تم الاسترجاع في 23-03-2014 .{{cite web}}: CS1 maint: deprecated archiveal service ( link )
  12. 1 2 "BonzaiThePenguin/WikiSort: تطبيقات المجال العام لفرز الكتل للغات C و C++ و Java" . GitHub . تم الاسترجاع في 23 مارس 2014 .
  13. هوانغ، بي سي؛ لانغستون، ماساتشوستس (1 ديسمبر 1992). "دمج وفرز سريع ومستقر في مساحة إضافية ثابتة". مجلة الكمبيوتر . 35 (6): 643-650 . doi : 10.1093/comjnl/35.6.643 .
  14. "ziglang/zig/lib/std/mem.zig" . كودبيرغ . تم الاسترجاع في 12-06-2026 .
  15. "ziglang/zig/lib/std/sort/block.zig" . كودبيرغ . تم الاسترجاع في 12-06-2026 .
  16. "ملاحظات الإصدار 0.2.0 · لغة برمجة Zig" . ziglang.org . تم الاطلاع عليه بتاريخ 12-06-2026 .
  17. آرني كوتزنر. "أداة قياس أداء خوارزمية الدمج الموضعي" . مؤرشف من الأصل بتاريخ 20 ديسمبر 2016. تم الاطلاع عليه بتاريخ 11 ديسمبر 2016 .
  18. تيم بيترز. "ردًا على: ويكي سورت" . تم الاسترجاع في 13 سبتمبر 2020 .