تقدير التوزيع العد - في الغالب اختراع الفرز


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

تم العثور على مثل هذه الفكرة الحسابية في كثير من الأحيان أكثر من غيرها.

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

إديسون البرمجيات - تطوير الشبكة
كُتِب هذا المقال بدعم من EDISON ، الذي يطور مجموعة واسعة من الحلول لمجموعة متنوعة من المهام: من البرامج للمحاولة عبر الإنترنت على ملابس المتاجر متعددة العلامات التجارية إلى نظام نقل LED بين السفن النهرية والبحرية .

نحن نحب نظرية الخوارزميات! ؛-)
لتقييم المكان الذي تريد وضع العنصر فيه تقريبًا ، تحتاج إلى معرفة مقدار اختلافه عن العنصر المتوسط ​​للصفيف. للقيام بذلك ، تحتاج إلى معرفة قيم الحد الأدنى والحد الأقصى للعناصر ، وكذلك ، وحجم الصفيف.

من المفترض أن تحتوي المجموعة المصنفة على بيانات عشوائية حقًا. جميع المخترعين من هذه الطريقة يأتون إلى نفس الصيغة:


k هو المكان التقريبي في الصفيف حيث يجب أن يكون العنصر A ( i )
min ، max - قيم الحد الأدنى والحد الأقصى للعناصر في الصفيف A
الحجم - عدد العناصر في الصفيف أ

هذه هي الفكرة العامة. دعونا نرى في الاختلافات التي ولدت هذه الخوارزمية مرارا وتكرارا.

فرز الملك سليمان :: سليمان الفرز



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

نوع الاستيفاء


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

الصفيف المرتب هو مجموعة بيانات منفصلة يمكن اعتبارها مجموعة محدودة من القيم المعروفة لوظيفة معينة غير معروفة. في الواقع ، توزيع تقريبي من وجهة نظر الرياضيات الحسابية - وهذا هو الاستيفاء.

جافا سكريبت نوع الاستيفاء - الاسترجاع
Array.prototype.interpolationSort = function() { var divideSize = new Array(); var end = this.length; divideSize[0] = end; while(divideSize.length > 0) {divide(this);} function divide(A) { var size = divideSize.pop(); var start = end - size; var min = A[start]; var max = A[start]; var temp = 0; for(var i = start + 1; i < end; i++) { if(A[i] < min) { min = A[i]; } else { if(A[i] > max) {max = A[i];} } } if(min == max) { end = end - size; } else { var p = 0; var bucket = new Array(size); for(var i = 0; i < size; i++) {bucket[i] = new Array();} for(var i = start; i < end; i++) { p = Math.floor(((A[i] - min) / (max - min)) * (size - 1)); bucket[p].push(A[i]); } for(var i = 0; i < size; i++) { if(bucket[i].length > 0) { for(var j = 0; j < bucket[i].length; j++) {A[start++] = bucket[i][j];} divideSize.push(bucket[i].length); } } } } }; 

جافا سكريبت نوع الاستيفاء - نسخة العودية
 Array.prototype.bucketSort = function() { var start = 0; var size = this.length; var min = this[0]; var max = this[0]; for(var i = 1; i < size; i++) { if (this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } if(min != max) { var bucket = new Array(size); for(var i = 0; i < size; i++) {bucket[i] = new Array();} var interpolation = 0; for(var i = 0; i < size; i++){ interpolation = Math.floor(((this[i] - min) / (max - min)) * (size - 1)); bucket[interpolation].push(this[i]); } for(var i = 0; i < size; i++) { if(bucket[i].length > 1) {bucket[i].bucketSort();} //Recursion for(var j = 0; j < bucket[i].length; j++) {this[start++] = bucket[i][j];} } } }; 

الرسم البياني الفرز :: فرز الرسم البياني


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

شريط جافا سكريبت فرز
 Array.prototype.histogramSort = function() { var end = this.length; var sortedArray = new Array(end); var interpolation = new Array(end); var hitCount = new Array(end); var divideSize = new Array(); divideSize[0] = end; while(divideSize.length > 0) {distribute(this);} function distribute(A) { var size = divideSize.pop(); var start = end - size; var min = A[start]; var max = A[start]; for(var i = start + 1; i < end; i++) { if (A[i] < min) { min = A[i]; } else { if (A[i] > max) {max = A[i];} } } if (min == max) { end = end - size; } else { for(var i = start; i < end; i++){hitCount[i] = 0;} for(var i = start; i < end; i++) { interpolation[i] = start + Math.floor(((A[i] - min) / (max - min)) * (size - 1)); hitCount[interpolation[i]]++; } for(var i = start; i < end; i++) { if(hitCount[i] > 0){divideSize.push(hitCount[i]);} } hitCount[end - 1] = end - hitCount[end - 1]; for(var i = end - 1; i > start; i--) { hitCount[i - 1] = hitCount[i] - hitCount[i - 1]; } for(var i = start; i < end; i++) { sortedArray[hitCount[interpolation[i]]] = A[i]; hitCount[interpolation[i]]++; } for(var i = start; i < end; i++) {A[i] = sortedArray[i];} } } }; 

نوع الاستيفاء


من أجل زيادة تحسين الحمل ، يُقترح هنا ألا نتذكر عدد العناصر ذات الأهمية المتشابهة في المجموعات التي لم يتم فرزها ، ولكن ضع علامة على بداية هذه المجموعات باستخدام علامات True / False. يعني True أن المجموعة الفرعية مصنفة بالفعل ، ويعني False أنه لم يتم فرزها بعد.

جافا سكريبت الموسومة نوع الاستيفاء
 Array.prototype.InterpolaionTagSort = function() { var end = this.length; if(end > 1) { var start = 0 ; var Tag = new Array(end); //Algorithm step-1 for(var i = 0; i < end; i++) {Tag[i] = false;} Divide(this); } //Algorithm step-2 while(end > 1) { while(Tag[--start] == false){} //Find the next bucket's start Divide(this); } function Divide(A) { var min = A[start]; var max = A[start]; for(var i = start + 1; i < end; i++) { if(A[i] < min) { min = A[i]; } else { if(A[i] > max ) {max = A[i];} } } if(min == max) { end = start; } else { //Algorithm step-3 Start to be the next bucket's end var interpolation = 0; var size = end - start; var Bucket = new Array(size);//Algorithm step-4 for(var i = 0; i < size; i++) {Bucket[i] = new Array();} for(var i = start; i < end; i++) { interpolation = Math.floor(((A[i] - min) / (max - min)) * (size - 1)); Bucket[interpolation].push(A[i]); } for(var i = 0; i < size; i++) { if(Bucket[i].length > 0) {//Algorithm step-5 Tag[start] = true; for(var j = 0; j < Bucket[i].length; j++) {A[start++] = Bucket[i][j];} } } } }//Algorithm step-6 }; 

فرز علامة الاستيفاء (في الموضع)


إذا لم يتم تكرار قيم العناصر في المصفوفة وتوزيعها بشكل متساوٍ (بمعنى تقريبًا - إذا كانت البيانات في شكل مفروزة تشبه تقدمًا حسابيًا) ، فيمكنك الفرز في مسار واحد ، والفرز في المكان الصحيح ، دون نقل العناصر إلى المصفوفات الوسيطة.

الترتيب حسب الاستيفاء مع التسميات (في المكان) في جافا سكريبت
 Array.prototype.InPlaceTagSort = function() { var n = this.length; var Tag = new Array(n); for(i = 0; i < n; i++) {Tag[i] = false;} var min = this[0]; var max = this[0]; for(i = 1; i < n; i++) { if(this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } var p = 0; var temp = 0; for(i = 0; i < n; i++) { while(Tag[i] == false) { p = Math.floor(((this[i] - min) / (max - min)) * (n - 1)); temp = this[i]; this[i] = this[p]; this[p] = temp; Tag[p] = true; } } }; 

ترتيب الفلاش :: Flashsort


ذات مرة ، كتبت عن الفرز الذي اخترعه أستاذ الفيزياء الحيوية نيوبرت في عام 1998.

اقترح الأستاذ توزيع العناصر في عدة فصول منفصلة (يتم تحديد عضوية الفصل حسب حجم العنصر). مع وضع ذلك في الاعتبار ، تبدو الصيغة كما يلي:


بدلاً من الحجم (حجم الصفيف) ، تشير الصيغة إلى m - عدد الفئات التي نوزع بها عناصر المصفوفة. لا تحسب الصيغة المفتاح في الصفيف حيث يجب إلقاء العنصر ، ولكن رقم الفئة الذي ينتمي إليه العنصر.

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

حسنا ، الباقي هو نفس الأغنية.


ترتيب فلاش في جافا
 /** * FlashSort.java - integer version * Translation of Karl-Dietrich Neubert's algorithm into Java by * Rosanne Zhang */ class FlashSort { static int n; static int m; static int[] a; static int[] l; /* constructor @param size of the array to be sorted */ public static void flashSort(int size) { n = size; generateRandomArray(); long start = System.currentTimeMillis(); partialFlashSort(); long mid = System.currentTimeMillis(); insertionSort(); long end = System.currentTimeMillis(); // print the time result System.out.println("Partial flash sort time : " + (mid - start)); System.out.println("Straight insertion sort time: " + (end - mid)); } /* Entry point */ public static void main(String[] args) { int size = 0; if (args.length == 0) { usage(); System.exit(1); } try { size = Integer.parseInt(args[0]); } catch (NumberFormatException nfe) { usage(); System.exit(1); } FlashSort.flashSort(size); } /* Print usage */ private static void usage() { System.out.println(); System.out.println("Usage: java FlashSort n "); System.out.println(" n is size of array to sort"); } /* Generate the random array */ private static void generateRandomArray() { a = new int[n]; for(int i=0; i < n; i++) { a[i] = (int)(Math.random() * 5 * n); } m = n / 20; l = new int[m]; } /* Partial flash sort */ private static void partialFlashSort() { int i = 0, j = 0, k = 0; int anmin = a[0]; int nmax = 0; for(i=1; i < n; i++) { if (a[i] < anmin) anmin=a[i]; if (a[i] > a[nmax]) nmax=i; } if(anmin == a[nmax]) return; double c1 = ((double)m - 1) / (a[nmax] - anmin); for(i=0; i < n; i++) { k= (int) (c1 * (a[i] - anmin)); l[k]++; } for(k=1; k < m; k++) { l[k] += l[k - 1]; } int hold = a[nmax]; a[nmax] = a[0]; a[0] = hold; int nmove = 0; int flash; j = 0; k = m - 1; while(nmove < n - 1) { while(j > (l[k] - 1)) { j++; k = (int) (c1 * (a[j] - anmin)); } flash = a[j]; while(!(j == l[k])) { k = (int) (c1 * (flash - anmin)); hold = a[l[k] - 1]; a[l[k] - 1] = flash; flash = hold; l[k]--; nmove++; } } } /* Straight insertion sort */ private static void insertionSort() { int i, j, hold; for(i = a.length - 3; i >= 0; i--) { if(a[i + 1] < a[i]) { hold = a[i]; j = i; while (a[j + 1] < hold) { a[j] = a[j + 1]; j++; } a[j] = hold; } } } /* For checking sorting result and the distribution */ private static void printArray(int[] ary) { for(int i=0; i < ary.length; i++) { if((i + 1) % 10 ==0) { System.out.println(ary[i]); } else { System.out.print(ary[i] + " "); } System.out.println(); } } } 

فرز تقريبي :: فرز Proxmap


هذا الفرز هو الأقدم من تلك المذكورة هنا ؛ وقد قدمه في عام 1980 البروفيسور توماس ستانديش من جامعة كاليفورنيا. في المظهر ، يبدو الأمر مختلفًا تمامًا ، ولكن إذا نظرت عن كثب ، فكل شيء على ما هو عليه.

تعمل الخوارزمية بمفهوم مثل الضرب - وهو رقم معين قريب من بعض عناصر المصفوفة.
لتحديد ما إذا كان عنصر الصفيف عبارة عن ضربة ، يتم تطبيق دالة تقريبية على العنصر.

البروفيسور ستانديش رتبت صفائف من الأرقام الحقيقية. كانت الدالة التقريبية تقريب الأرقام الحقيقية لأسفل إلى عدد صحيح.
هذا ، على سبيل المثال ، إذا كان الصفيف يحتوي على عناصر 2.8 ، 2 ، 2.1 ، 2.6 ، إلخ. ثم ضرب لهذه الأرقام سيكون شيطان.



الإجراء العام:

  1. نحن نطبق دالة تقريبية على كل عنصر ، وتحديد أي ضرب يتوافق مع العنصر التالي.
  2. وبالتالي ، لكل ضربة ، يمكننا حساب عدد العناصر المطابقة لهذه الضربة.
  3. مع العلم بعدد العناصر لجميع الزيارات ، نحدد توطين الزيارات (الحدود على اليسار) في الصفيف.
  4. معرفة توطين الزيارات ، نحدد توطين كل عنصر.
  5. بعد تحديد توطين العنصر ، نحاول إدخاله في مكانه في المصفوفة. إذا كان المكان مأخوذ بالفعل ، فنحن ننقل الجيران إلى اليمين (إذا كان العنصر أصغر منهم) لإفساح المجال للعنصر. أو إلى اليمين ، نقوم بإدراج العنصر نفسه (إذا كان أكثر من جيران).

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

جافا سكريبت تقريب فرز
 Array.prototype.ProxmapSort = function() { var start = 0; var end = this.length; var A2 = new Array(end); var MapKey = new Array(end); var hitCount = new Array(end); for(var i = start; i < end; i++) {hitCount[i] = 0;} var min = this[start]; var max = this[start]; for (var i = start+1; i < end; i++){ if (this[i] < min) { min = this[i]; } else { if(this[i] > max) {max = this[i];} } } //Optimization 1.Save the MapKey[i]. for (var i = start; i < end; i++) { MapKey[i] = Math.floor(((this[i] - min ) / (max - min)) * (end - 1)); hitCount[MapKey[i]]++; } //Optimization 2.ProxMaps store in the hitCount. hitCount[end-1] = end - hitCount[end - 1]; for(var i = end-1; i > start; i--){ hitCount[i-1] = hitCount[i] - hitCount[i - 1]; } //insert A[i]=this[i] to A2 correct position var insertIndex = 0; var insertStart = 0; for(var i = start; i < end; i++){ insertIndex = hitCount[MapKey[i]]; insertStart = insertIndex; while(A2[insertIndex] != null) {insertIndex++;} while(insertIndex > insertStart && this[i] < A2[insertIndex - 1]) { A2[insertIndex] = A2[insertIndex - 1]; insertIndex--; } A2[insertIndex] = this[i]; } for(var i = start; i < end; i++) {this[i] = A2[i];} }; 

تجزئة الفرز الإدراج الفرز :: فرز التجزئة


حسنًا ، سننتهي من مراجعة الخوارزمية التي اقترحها haraiser bobbyKdas منذ 6 سنوات. هذه خوارزمية هجينة تُضاف فيها الدمج ، بالإضافة إلى التوزيع والإدراج.

  1. يتم تقسيم الصفيف بشكل متكرر إلى نصفين ، حتى تصل في بعض الخطوات أحجام المصفوفات الفرعية إلى الحد الأدنى للحجم (المؤلف ليس له أكثر من 500 عنصر).
  2. عند أدنى مستوى من العودية ، يتم تطبيق خوارزمية مألوفة على كل نصف مصفوفة - باستخدام نفس الصيغة ، يحدث توزيع تقريبي داخل الطبقة الفرعية ، مع الفرز حسب إدراج الأقسام غير المصنفة المحلية.
  3. بعد ترتيب نصفي المصفوفات الفرعية ، يندمجان.
  4. يتم تكرار النقطة 3 (دمج المصفوفات الفرعية للنصف) عند التسلق عبر مستويات العودية إلى الأعلى ، عندما يتم دمج المصفوفة الأصلية من نصفي.

فرز حسب الإدراج التجزئة في جافا
 import java.util.Arrays; import java.util.Date; import java.util.Random; public class HashSort { //    static int SOURCELEN = 1000000; int source[] = new int[SOURCELEN]; //        int quick[] = new int[SOURCELEN]; //     static int SORTBLOCK = 500; static int k = 3; //  static int TMPLEN = (SOURCELEN < SORTBLOCK * k) ? SORTBLOCK * k : SOURCELEN; int tmp[] = new int[TMPLEN]; //    static int MIN_VAL = 10; static int MAX_VAL = 1000000; int minValue = 0; int maxValue = 0; double hashKoef = 0; //      public void randomize() { int i; Random rnd = new Random(); for(i=0; i<SOURCELEN; i++) { int rndValue = MIN_VAL + ((int)(rnd.nextDouble()*((double)MAX_VAL-MIN_VAL))); source[i] = rndValue; } } //         - public void findMinMax(int startIndex, int endIndex) { int i; minValue = source[startIndex]; maxValue = source[startIndex]; for(i=startIndex+1; i<=endIndex; i++) { if( source[i] > maxValue) { maxValue = source[i]; } if( source[i] < minValue) { minValue = source[i]; } } hashKoef = ((double)(k-1)*0.9)*((double)(endIndex-startIndex)/((double)maxValue-(double)minValue)); } // (  - )      public void stickParts(int startIndex, int mediana, int endIndex) { int i=startIndex; int j=mediana+1; int k=0; //      -    while(i<=mediana && j<=endIndex) { if(source[i]<source[j]) { tmp[k] = source[i]; i++; } else { tmp[k] = source[j]; j++; } k++; } //     -      if( i>mediana ) { while(j<=endIndex) { tmp[k] = source[j]; j++; k++; } } //     -      if(j>endIndex) { while(i<=mediana) { tmp[k] = source[i]; i++; k++; } } System.arraycopy(tmp, 0, source, startIndex, endIndex-startIndex+1); } //        //         boolean shiftRight(int index) { int endpos = index; while( tmp[endpos] != 0) { endpos++; if(endpos == TMPLEN) return false; } while(endpos != index ) { tmp[endpos] = tmp[endpos-1]; endpos--; } tmp[endpos] = 0; return true; } //-    public int hash(int value) { return (int)(((double)value - (double)minValue)*hashKoef); } //        public void insertValue(int index, int value) { int _index = index; //  ,    //            - while(tmp[_index] != 0 && tmp[_index] <= value) { _index++; } //       ,    if( tmp[_index] != 0) { shiftRight(_index);//      } tmp[_index] = value;//  -   } //        public void extract(int startIndex, int endIndex) { int j=startIndex; for(int i=0; i<(SORTBLOCK*k); i++) { if(tmp[i] != 0) { source[j] = tmp[i]; j++; } } } //   public void clearTMP() { if( tmp.length < SORTBLOCK*k) { Arrays.fill(tmp, 0); } else { Arrays.fill(tmp, 0, SORTBLOCK*k, 0); } } //  public void hashingSort(int startIndex, int endIndex) { //1.          findMinMax(startIndex, endIndex); //2.    clearTMP(); //3.       - for(int i=startIndex; i<=endIndex; i++) { insertValue(hash(source[i]), source[i]); } //4.         extract(startIndex, endIndex); } //         public void sortPart(int startIndex, int endIndex) { //    500,   - if((endIndex - startIndex) <= SORTBLOCK ) { hashingSort(startIndex, endIndex); return; } //  > 500         int mediana = startIndex + (endIndex - startIndex) / 2; sortPart(startIndex, mediana);//    sortPart(mediana+1, endIndex);//    stickParts(startIndex, mediana, endIndex);//   -   } //       public void sort() { sortPart(0, SOURCELEN-1); } public static void main(String[] args) { HashSort hs = new HashSort(); hs.randomize(); hs.sort(); } } 

تسمى الصيغة نفسها دالة التجزئة ، وتسمى المصفوفة المساعدة للتوزيع التقريبي بجدول التجزئة.

مراجع


الاستيفاء والرسم البياني ، فلاش ، Proxmap

سليمان ، تجزئة الجدول ، فلاش

سلسلة المقالات:



ظهر فرز التقريب في تطبيق AlgoLab Excel (في هذه الحالة ، في الصفيف الأولي غير المصنف ، يتم إلحاق الجزء الكسري العشوائي بالأعداد الصحيحة). كان سليمان وفلاش موجودين هناك لفترة طويلة ، لكنهما لم يطبقا بعد الاستيفاء والتجزئة والرسم البياني.

Source: https://habr.com/ru/post/ar478654/


All Articles