مرتبسازی گسترده
مرتبسازی گسترده یک الگوریتم مرتب سازی است که در سال ۲۰۰۲ توسط Steven J. Ross ابداع شد. این الگوریتم، مفاهیمی از مرتب سازیهای توزیع شده مانند مرتب سازی مبنایی و سطلی را با مفاهیم مرتب سازیهای سریع و ترکیبی تلفیق میکند. در نتایج تجربی، این الگوریتم در اجرا، بسیار کارامد تر از الگوریتمهای سنتی مانند مرتب سازی سریع به ویژه در ارائه ساختارهای توزیع شده نشان داده شدهاست.
مرتب سازی سریع یک عنصر محوری را درلیست شناسایی میکند و لیست را به دو زیر لیست تقسیم میکند:عناصر بزرگتر از عنصر محوری وعناصر کوچکتر ازعنصر محوری.
مرتب سازی گسترده این ایده را به صورت تقسیم بندی لیست به n/cپارتیشن در هر گام تعمیم میدهد، که nتعداد عناصر لیست وc ثابت کوچک است(در عمل در مقایسههای کند، معمولا بین ۴ تا ۸ است. ولی در مقایسههای سریع c مقدار بزرگتری است).
این الگوریتم برای اجرا از تکنیکهای توزیع شده استفاده میکند. ابتدا بزرگترین و کوچکترین مقادیر را وارد لیست میکند سپس ناحیه بین این دو را به n/c ظرف بااندازه مساوی تقسیم میکند. در مواقعی که ذخیره سازی یک مساله مهم است، این الگوریتم میتواند در هر مرحله از تقسیم بازگشتی به داشتن بزرگترین ظرف کمک کند و باعث میشود این روند تقسیم چندین مرحله داشته باشد، اگرچه این کار باعث تکراربیشتر میشود ولی خطاهای ذخیره سازی را کاهش میدهد و باعث میشود که الگوریتم سریع تر اجرا شود.
درمواردی که تعداد ظرفها حداقل برابر تعداد عناصر است، مرتب سازی گسترده به مرتب سازی سطلی تخریب میشود و مرتب سازی به اتمام میرسد. در غیر این صورت هرظرف به صورت بازگشتی مرتب میشود.
الگوریتم از آزمونهای اکتشافی برای تعیین اینکه ایا هر ظرف میتواند توسط این الگوریتم یا الگوریتمهای دیگر، به طور کارامدتری مرتب شود، استفاده میکند. سپس به صورت بازگشتی ظرفها را مرتب میکند.
این الگوریتم نیز مانند الگوریتمهای توزیع شده دیگر نقطه ضعفی دارد وآن این است که برنامه نویس به ابزاری برای تبدیل هرعنصربه یک کلید عددی نیاز دارد و هدف آن شناسایی این است که هرکدام ازعناصر در کدام ظرف میافتند.
اگرچه برای عناصر با طول متغیر مانند رشته ها(با توجه به اینکه هر عدد باتعداد بی نهایتی از مقادیر مینیمم دنبال میشود و برای هرنوع دادهای دارای یک ارایش کلی میباشد) این عملیات برای پیاده سازی صحیح بسیار سخت تر از یک تابع مقایسهای ساده به ویژه در ساختارهای پیچیدهاست. پیاده سازی ضعیف این تابع مقداری میتواند منجر به کلاستربندی شود که به کارایی الگوریتم آسیب میرساند.
محتویات |
کارایی [ویرایش]
بدترین حالت الگوریتم مرتب سازی گسترده بستگی به این دارد که این الگوریتم درنهایت کدام مرتب سازی را تبدیل به ظرفهای کوچک میکند. برای الگوریتمهایی که بدترین حالت شان((O(nlogn) است مانند مرتب سازی ادغامی و مرتب سازی هرمی و برای الگوریتمهایی که بدترین حالت شان O(n^۲)میباشد مانند مرتب سازی سریع، مقدار ((O(nlogn) میباشد.
در توزیعها هر جا اندازه کلید ۲k بار کوچکتر از مربع n(اندازه لیست) باشد ((۲k<log(n^۲) الگوریتم در بدترین حالت با دستیابی به بدترین زمان O(n(k - log(n)).۵)در نسخههای اصلی منتشرشده و O(n((k/s) + s)) برای نسخههای حافظه دار، بهتر عمل میکند.
آزمایشاتی که برای مقایسه نسخه بهینه مرتب سازی گسترده با مرتب سازی بهینه C++انجام شدند، با مرتب سازی درونگرا پیاده سازی شدند.
مرتب سازی گسترده در سیستم عاملهای مختلف برای دادههای تصادفی از نوع integer و float حدودا ۲-۷X برابر بهبود زمان اجرا را نشان میدهد. ازنظرحافظه موردنیاز، مرتب سازی گسترده بدتر از اکثر الگوریتمهای درجا است. این الگوریتم در ساده ترین شکلش یک الگوریتم درجا نیست که ازفضای اضافی O(n) استفاده کند. در آزمایشات ۲۰ ٪ بیشتر از مرتب سازی سریع ازحافظه استفاده میکند.
درفرم حافظه دار، از حافظه کمتری استفاده میشود و در استفاده از حافظه با حداکثرشمارش ظرفها به تعداد حداکثر بازگشتها یک کران بالا وجود دارد که تا تبدیل اندازه کلیدها از بایت به کیلوبایت تمام میشود.
اگرچه در این فرم نسبت به مرتب سازی سریع و مرتب سازی هرمی از فضای بیشتری استفاده میشود، ولی نسبت به فرمهای اولیه مرتب سازی ادغامی که از فضای کمکی برابر با فضای اشغال شده توسط لیستها استفاده میکند، به طور قابل ملاحظهای از حافظه کمتری استفاده مینماید.
مرتب سازی گسترده در مورد مسائل بسیار بزرگ که در حافظه جای نمیگیرند ونیازمند دسترسی به دیسک هستند، بسیار کارامد عمل میکند.
پیاده سازی [ویرایش]
unsigned RoughLog۲(DATATYPE input) { unsigned char cResult = 0; // The && is necessary on some compilers to avoid infinite loops; it doesn't // significantly impair performance if(input >= ۰) while((input >> cResult) && (cResult < DATA_SIZE)) cResult++; else while(((input >> cResult) < -۱) && (cResult < DATA_SIZE)) cResult++; return cResult; } SIZETYPE GetMaxCount(unsigned logRange, unsigned uCount) { unsigned logSize = RoughLog2Size(uCount); unsigned uRelativeWidth = (LOG_CONST * logRange)/((logSize > MAX_SPLITS) ? MAX_SPLITS: logSize); // Don't try to bitshift more than the size of an element if(DATA_SIZE <= uRelativeWidth) uRelativeWidth = DATA_SIZE - ۱; return ۱ << ((uRelativeWidth < (LOG_MEAN_BIN_SIZE + LOG_MIN_SPLIT_COUNT)) ? (LOG_MEAN_BIN_SIZE + LOG_MIN_SPLIT_COUNT): uRelativeWidth); } void FindExtremes(DATATYPE *Array, SIZETYPE uCount, DATATYPE & piMax, DATATYPE & piMin) { SIZETYPE u; piMin = piMax = Array[۰]; for(u = 1; u < uCount; ++u){ if(Array[u] > piMax) piMax=Array[u]; else if(Array[u] < piMin) piMin= Array[u]; } } //---------------------SpreadSort Source----------------- Bin * SpreadSortCore(DATATYPE *Array, SIZETYPE uCount, SIZETYPE & uBinCount, DATATYPE &iMax, DATATYPE &iMin) { // This step is roughly ۱۰٪ of runtime but it helps avoid worst-case // behavior and improves behavior with real data. If you know the // maximum and minimum ahead of time, you can pass those values in // and skip this step for the first iteration FindExtremes((DATATYPE *) Array, uCount, iMax, iMin); if(iMax == iMin) return NULL; DATATYPE divMin,divMax; SIZETYPE u; int LogDivisor; Bin * BinArray; Bin* CurrentBin; unsigned logRange; logRange = RoughLog2Size((SIZETYPE)iMax-iMin); if((LogDivisor = logRange - RoughLog2Size(uCount) + LOG_MEAN_BIN_SIZE) < ۰) LogDivisor = 0; // The below if statement is only necessary on systems with high memory // latency relative to processor speed (most modern processors) if((logRange - LogDivisor) > MAX_SPLITS) LogDivisor = logRange - MAX_SPLITS; divMin = iMin >> LogDivisor; divMax = iMax >> LogDivisor; uBinCount = divMax - divMin + ۱; // Allocate the bins and determine their sizes BinArray = calloc(uBinCount, sizeof(Bin)); // Memory allocation failure check and clean return with sorted results if(!BinArray) { printf("Using std::sort because of memory allocation failure\n"); std::sort(Array, Array + uCount); return NULL; } // Calculating the size of each bin; this takes roughly ۱۰٪ of runtime for(u = 0; u < uCount; ++u) BinArray[(Array[u] >> LogDivisor) - divMin].uCount++; // Assign the bin positions BinArray[۰].CurrentPosition = (DATATYPE *)Array; for(u = 0; u < uBinCount - ۱; u++) { BinArray[u + ۱].CurrentPosition = BinArray[u].CurrentPosition + BinArray[u].uCount; BinArray[u].uCount = BinArray[u].CurrentPosition - Array; } BinArray[u].uCount = BinArray[u].CurrentPosition - Array; // Swap into place. This dominates runtime, especially in the swap; // std::sort calls are the other main time-user. for(u = 0; u < uCount; ++u) { for(CurrentBin = BinArray + ((Array[u] >> LogDivisor) - divMin); (CurrentBin->uCount > u); CurrentBin = BinArray + ((Array[u] >> LogDivisor) - divMin)) SWAP(Array + u, CurrentBin->CurrentPosition++); // Now that we've found the item belonging in this position, // increment the bucket count if(CurrentBin->CurrentPosition == Array + u) ++(CurrentBin->CurrentPosition); } // If we've bucketsorted, the array is sorted and we should skip recursion if(!LogDivisor) { free(BinArray); return NULL; } return BinArray; } void SpreadSortBins(DATATYPE *Array, SIZETYPE uCount, SIZETYPE uBinCount, const DATATYPE &iMax , const DATATYPE &iMin, Bin * BinArray, SIZETYPE uMaxCount) { SIZETYPE u; for(u = 0; u < uBinCount; u++){ SIZETYPE count = (BinArray[u].CurrentPosition - Array) - BinArray[u].uCount; // Don't sort unless there are at least two items to compare if(count < ۲) continue; if(count < uMaxCount) std::sort(Array + BinArray[u].uCount, BinArray[u].CurrentPosition); else SpreadSortRec(Array + BinArray[u].uCount, count); } free(BinArray); } void SpreadSortRec(DATATYPE *Array, SIZETYPE uCount) { if(uCount < ۲) return; DATATYPE iMax, iMin; SIZETYPE uBinCount; Bin * BinArray = SpreadSortCore(Array, uCount, uBinCount, iMax, iMin); if(!BinArray) return; SpreadSortBins(Array, uCount, uBinCount, iMax, iMin, BinArray, GetMaxCount(RoughLog2Size((SIZETYPE)iMax-iMin), uCount)); }
دو سطح که از بقیه بهترند [ویرایش]
یک نتیجه جالب برای الگوریتمهای این نوع کلی(تقسیم بندی بر اساس مبنا و مرتب سازی براساس مقایسه) این است که درجه آنها برای هر عملکرد پیوسته (O(n) است. این نتیجه میتواند بدین صورت فراهم شود که اگر اندازه ظرفها بعد از اولین تکرار بیشتر از برخی مقادیر ثابت بود، الگوریتم مرتب سازی گسترده حداقل ۲ بار تکرارشود.
اگر دادهها درهمه اوقات بر روی برخی توابع پیوسته انتگرال پذیر شناخته شوند این اصلاح مرتب سازی گسترده میتواند به برخی بهبودهای کارایی بر روی الگوریتم اصلی نائل شود ومی تواند بدترین حالت بهتری داشته باشد. اگر این محدودیت نتواند وابستگی داشته باشد، این تغییر یک سربار اضافی کوچک را به الگوریتم اضافه میکند وکوچک تر میشود. الگوریتمهای مشابه دیگر Flashsort (که ساده تراست) وAdaptive Left Radix میباشند.
Adaptive Left Radix در ظاهرکاملا مشابه میباشد، تفاوت اصلی آن در وجود رفتار بازگشتی میباشد. درمرتب سازی گسترده چک کردن وضعیتهای بدترین حالت و استفاده از مرتب سازی std::sort برای جلوگیری از به وجود آمدن مشکلات کارایی (هرجا که نیاز است) و بازگشتهای مداوم در Adaptive Left Radix تا وقتی که به اتمام برسد یا داده برای استفاده در مرتب سازی درجی به قدرکافی کوچک باشد، انجام میشود.
الگوریتمهای مرتب سازی
| اصول | پیچیدگی محاسباتی، نماد گذاری o بزرگ،order کلی، لیستها، پایایی، مرتب سازی مقایسهای، مرتب سازی تطبیقی، شبکه مرتب کردن دادهها، مرتب سازی عددی |
| مرتب سازیهای معاوضهای | Bubble sort • Cocktail sort • Odd-even sort • Comb sort • Gnome sort • Quicksort |
| مرتب سازیهای انتخابی | Selection sort • Heapsort • Smoothsort • Cartesian tree sort • Tournament sort • Cycle sort |
| مرتب سازیهای درجی | Insertion sort • Shell sort • Tree sort • Library sort • Patience sorting |
| مرتب سازیهای ادغامی | Merge sort • Polyphase merge sort • Strand sort |
| مرتب سازیهای توزیعی | American flag sort • Bead sort • Bucket sort • Burstsort • Counting sort • Pigeonhole sort • Proxmap sort • Radix sort • Flashsort |
| مرتب سازیهای همزمان | Bitonic sorter • Batcher odd-even mergesort • Pairwise sorting network |
| مرتب سازیهای ترکیبی | Timsort • Introsort • Spreadsort • UnShuffle sort • JSort |
| مرتب سازیهای مقداری | Spaghetti sort |
| دیگر | Topological sorting • Pancake sorting |
| مرتب سازیهای بی فایده /مضحک | Bogosort • Stooge sort • Luckysort • Sleep sort |
منابع [ویرایش]
- Markku Tamminen: Two Levels are as Good as Any. J. Algorithms ۶(۱): ۱۳۸-۱۴۴ (۱۹۸۵)
- Steven J. Ross. The Spreadsort High-performance General-case Sorting Algorithm. Parallel and Distributed Processing Techniques and Applications، Volume ۳، pp.۱۱۰۰–۱۱۰۶. Las Vegas Nevada. ۲۰۰۲.
|
|||||||||||||||||||||||||||||||