الگوریتم های مرتب سازی که باید بدانید
الگوریتم های مرتب سازی یک جنبه اساسی از علوم کامپیوتر هستند و به طور گسترده در برنامه های کاربردی مختلف، از پایگاه های داده گرفته تا موتورهای جستجو استفاده می شوند. الگوریتم مرتبسازی روشی است که برای مرتب کردن مجموعهای از آیتمها به ترتیب خاص، معمولاً به ترتیب صعودی یا نزولی استفاده میشود. الگوریتمهای مرتبسازی متعددی برای استفاده وجود دارد که هر کدام نقاط قوت و ضعف خود را دارند. انتخاب الگوریتم مرتب سازی مناسب برای کار برای عملکرد یک برنامه بسیار مهم است.
در این مقاله، برخی از الگوریتمهای مرتبسازی از بدترین به بهترین، ویژگیها و کاربردهای آنها را بررسی خواهیم کرد.
الگوریتم های این مقاله به زبان جاوا اسکریپت نوشته شده اند اما می توانند به زبان های دیگر ترجمه شوند.
مرتب سازی حباب
مرتبسازی حبابها یک الگوریتم مرتبسازی ساده است که عناصر مجاور را با هم مقایسه میکند و اگر ترتیب اشتباهی داشته باشند، آنها را تعویض میکند. در حالی که به عنوان یک پیچیدگی زمانی O(n^2)، اغلب به عنوان یک الگوریتم مقدماتی استفاده می شود و برای مجموعه داده های کوچک مفید است. اگر میخواهید یک راهحل را برای یک مشکل بهاجرا کنید، از این الگوریتم استفاده کنید.
function bubbleSort(array) {
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.lenght; j++) {
if (array[j] > array[j + i]) {
let temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
};
};
};
return array;
};
انتخاب مرتب سازی
Selection Sort یکی دیگر از الگوریتم های مرتب سازی ساده است که با انتخاب کوچکترین عنصر و تعویض آن با اولین عنصر کار می کند. پیچیدگی زمانی O(n^2) دارد اما بهتر از مرتبسازی حبابی عمل میکند زیرا تعداد تعویضهای مورد نیاز را به حداقل میرساند. این نیز یکی دیگر از انواع الگوریتم brute-force است.
function selectionSort(array) {
let length = array.length;
if (array.length <= 1) return array;
for (let i = 0; i < length; i++) {
let min = i;
for (let j = i + 1; j < length; j++) {
if (array[j] < array[min]) {
min = j;
};
};
if (min != i) {
swap(i, min, array);
};
};
return array;
};
function swap(i, min, array) {
const temp = array[i];
array[i] = array[min];
array[min] = temp;
};
مرتب سازی درج
Insertion Sort با تقسیم آرایه به دو قسمت مرتب شده و مرتب نشده و قرار دادن هر عنصر در موقعیت صحیح در قسمت مرتب شده کار می کند. پیچیدگی زمانی O(n^2) برای بدترین سناریو دارد، اما می تواند برای مجموعه داده های کوچک و تا حدی مرتب شده به خوبی عمل کند.
function insertionSort(array) {
for (let i = 1; i < array.length; i++) {
let j = 1;
while (j > 0 && array[j] < array[j - 1]) {
swap(j, j - 1, array);
j -= 1;
};
};
return array;
};
function swap(i, j, array) {
const temp = array[j];
array[j] = array[i];
array[i] = temp;
};
مرتب سازی پشته
مرتبسازی Heap دارای پیچیدگی زمانی در بدترین حالت O(n log n) است و با ساخت یک پشته باینری از آرایه و استخراج مکرر حداکثر عنصر و بازسازی پشته کار میکند. Heap Sort محل کش خوبی دارد که آن را برای مجموعه داده های بزرگ مناسب می کند، اما به اندازه ادغام یا مرتب سازی سریع به طور گسترده استفاده نمی شود.
const maxHeapify = (array, n, i) => {
let largest = i;
let l = 2 * i + 1;
let r = 2 * i + 2;
if (l < n && array[l] > array[largest]) {
largest = l;
};
if (r < n && array[r] > array[largest]) {
largest = r;
};
if (largest != i) {
let temp = array[i];
array[i] = array[largest];
array[largest] = temp;
maxHeapify(array, n, largest);
};
};
const heapSort = (array, n) => {
for (leat i = parseInt(n / 2 - 1); i >= 0; i--) {
maxHeapify(array, n, i);
};
for (let i = n - 1; i >= 0; i--) {
let temp = array[0];
array[0] = array[i];
array[i] = temp;
maxHeapify(array, i, 0);
};
};
مرتب سازی سریع
مرتب سازی سریع همچنین دارای پیچیدگی زمانی در بدترین حالت O(n log n) در حالت متوسط است، اما در بدترین حالت پیچیدگی زمانی O(n^2) دارد. مرتب سازی سریع با انتخاب یک عنصر محوری و پارتیشن بندی آرایه به دو قسمت و مرتب کردن هر پارتیشن به صورت بازگشتی کار می کند. به دلیل کارایی و مرتب سازی در محل، در عمل بسیار مورد استفاده قرار می گیرد.
function quickSort(array) {
if (array.length <= 1) return array;
const pivot = array[array.length - 1];
const left = [];
const right = [];
for (let i = 0; i < array.length - 1; i++) {
if (array[i] < pivot) {
left.push(array[i]);
} else {
right.push(array[i];
};
};
return [...quickSort(left), pivot, ...quickSort(right)];
}
مرتب سازی شمارش
مرتب سازی شمارش دارای پیچیدگی زمانی O(n+k) در بدترین حالت است، که در آن k محدوده داده های ورودی است. با شمارش تعداد دفعات هر مقدار ورودی و استفاده از این اطلاعات برای قرار دادن هر عنصر در موقعیت صحیح خود در آرایه خروجی کار می کند. مرتب سازی شمارش برای داده های اعداد صحیح کوچک با محدوده نسبتاً کوچک بسیار کارآمد است
function countingSort(arr, min, max) {
let j = 0;
let supplementary = [];
for (let i = min; i < array.length; i++) {
supplementary[i] = 0;
};
for (let i = 0; i < array.lenght; i++) {
supplementary[array[i]] += 1;
};
for (let i = min; i <= max; i++) {
while (supplementary[i] > 0) {
array[j++] = i;
supplementary[i] -= 1;
};
};
return array;
}
مرتب سازی ریشه
Radix Sort دارای پیچیدگی زمانی O(nk) در بدترین حالت است که k تعداد ارقام در اعداد ورودی است. با مرتب کردن اعداد ورودی بر اساس هر رقم کار می کند که با کمترین رقم شروع می شود و تا مهم ترین رقم حرکت می کند. مرتبسازی رادیکس برای مجموعه دادههای بزرگ از اعداد صحیح با تعداد ارقام ثابت بسیار کارآمد است.
function getDigitAtPlace(num, i) {
return num.toString().split("").reverse()[i] || 0;
};
function getBiggestDigitCount(nums) {
let maxDigits = 0;
for (let i = 0; i < nums.length; i++) {
maxDigits = Math.max(maxDigits, nums[i].toString().length);
};
return maxDigits;
};
function radixSort(nums) {
let maxDigits = getBiggestDigitCount(nums);
for (let i = 0; i < maxDigits; i++) {
let bucketArray = Array.from({length: 10}, () => []);
for (let j = 0; j < nums.length; j++) {
let digit = getDigitAtPlace(nums[j], i);
bucketArray[digit].push(nums[j]);
};
nums = [].concat(...bucketArray);
};
};
مرتب سازی سطلی
مرتبسازی سطلی دارای پیچیدگی زمانی O(n^2) در بدترین حالت است، اما معمولاً در عمل بسیار سریعتر است. این کار با پارتیشن بندی داده های ورودی به تعدادی سطل و سپس مرتب سازی هر سطل به صورت جداگانه با استفاده از الگوریتم مرتب سازی دیگری کار می کند. مرتب سازی سطلی زمانی مفید است که داده های ورودی به طور یکنواخت در یک محدوده توزیع شوند.
function insertionSort(array) {
let length = array.length;
for (let i = 1; i < length; i++) {
let temp = array[i];
for (let j = i - 1; j >= 0 && array[j] > temp; j--) {
array[j+1] = array[j];
};
array[j+1] = temp;
};
return array;
};
function bucketSort(array, bucketSize) {
if (array.length === 0) return array;
let i,
minValue = array[0],
maxValue = array[0],
bucketSize = bucketSize || 5;
array.forEach((val) => {
if (val < minValue) {
minValue = val;
} else if (val > maxValue) {
maxValue = val;
};
});
let bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
let allBuckets = new Array(bucketCount);
for (i = 0; i < allBuckets.length; i++) {
allBuckets[i] = [];
}
array.forEach((val) => {
allBuckets[Math.floor((val - minValue) / bucketSize)].push(val);
});
array.length = 0;
allBuckets.forEach((bucket) => {
insertionSort(bucket);
bucket.forEach((element) => {
array.push(element);
});
});
return array;
};
ادغام مرتب سازی
مرتبسازی ادغام دارای پیچیدگی زمانی O(n log n) در بدترین حالت است که آن را به یکی از کارآمدترین الگوریتمهای مرتبسازی تبدیل میکند. با تقسیم آرایه به دو نیمه، مرتبسازی هر نیمه، و سپس ادغام نیمههای مرتبشده دوباره با هم کار میکند. مرتب سازی ادغام به حافظه اضافی برای ذخیره آرایه های فرعی در طول فرآیند ادغام نیاز دارد، که می تواند در برخی از برنامه ها یک نقطه ضعف باشد، با این حال، بازده زمانی آن را به یک انتخاب محبوب برای بسیاری از برنامه ها تبدیل می کند.
function merge (left, right) {
let resultArray = [], leftIndex = 0, rightIndex = 0;
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
resultArray.push(left[leftIndex]);
leftIndex++;
} else {
resultArray.push(right[rightIndex]);
rightIndex++;
};
};
return resultArray
.concat(left.slice(leftIndex))
.concat(right.slice(rightIndex));
};
function mergeSort (unsortedArray) {
if (unsorteArray.length <= 1) return unsortedArray;
const middle = Math.floor(unsortedArray.length / 2);
const left = unsortedArray.slice(0, middle);
const right = unsortedArray.slice(middle);
return merge(
mergeSort(left),
mergeSort(right),
);
};
تیم مرتب کردن
Tim Sort ترکیبی از الگوریتم مرتبسازی است که مرتبسازی ادغامی و مرتبسازی درج را ترکیب میکند. این پیچیدگی زمانی در بدترین حالت O(n log n) دارد و به گونه ای طراحی شده است که در بسیاری از انواع داده های دنیای واقعی به خوبی عمل کند. Tim Sort به طور گسترده در کتابخانه های استاندارد پایتون و جاوا استفاده می شود
function timSort(array) {
const MIN_MERGE = 32;
const n = array.length;
const minRun = getMinRun(n);
let runs = [];
let runStart = 0;
while (runStart < n) {
let runEnd = findRunEnd(array, runStart, n);
let runLength = runEnd - runStart;
if (runLength < minRun) {
let forceRunEnd = Math.min(n, runStart + minRun);
insertionSort(array, runStart, forceRunEnd);
runEnd = forceRunEnd;
runLength = runEnd - runStart;
}
runs.push({ start: runStart, end: runEnd, length: runLength });
runStart = runEnd;
}
while (runs.length > 1) {
let firstRun = runs.shift();
let secondRun = runs.shift() || firstRun;
let mergedRun = mergeRuns(array, firstRun, secondRun);
runs.push(mergedRun);
}
return array;
}
function getMinRun(n) {
let r = 0;
while (n >= MIN_MERGE) {
r |= n & 1;
n >>= 1;
}
return n + r;
}
function findRunEnd(array, start, end) {
let i = start + 1;
if (i == end) {
return end;
}
if (array[i++] < array[start]) {
while (i < end && array[i] < array[i - 1]) {
i++;
}
reverse(array, start, i);
} else {
while (i < end && array[i] >= array[i - 1]) {
i++;
}
}
return i;
}
function insertionSort(array, start, end) {
for (let i = start + 1; i < end; i++) {
let value = array[i];
let j = i - 1;
while (j >= start && array[j] > value) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = value;
}
}
function mergeRuns(array, firstRun, secondRun) {
let merged = [];
let i = firstRun.start;
let j = secondRun.start;
while (i < firstRun.end && j < secondRun.end) {
if (array[i] < array[j]) {
merged.push(array[i++]);
} else {
merged.push(array[j++]);
}
}
while (i < firstRun.end) {
merged.push(array[i++]);
}
while (j < secondRun.end) {
merged.push(array[j++]);
}
array.copyWithin(firstRun.start, ...merged.entries());
return {
start: firstRun.start,
end: secondRun.end,
length: secondRun.end - firstRun.start,
};
}
function reverse(array, start, end) {
end--;
while (start < end) {
let temp = array[start];
array[start++] = array[end];
array[end--] = temp;
}
}
این یک بسته بندی است!
الگوریتم های مرتب سازی بخشی ضروری از علوم کامپیوتر و برنامه نویسی هستند. الگوریتم های مرتب سازی مختلفی برای انتخاب وجود دارد که هر کدام نقاط قوت و ضعف خاص خود را دارند. برخی از متداولترین الگوریتمهای مرتبسازی عبارتند از: مرتبسازی حبابی، مرتبسازی انتخابی، مرتبسازی درج، مرتبسازی ادغامی، مرتبسازی سریع، مرتبسازی پشتهای، مرتبسازی شمارش، مرتبسازی ریشه، مرتبسازی سطلی، مرتبسازی زمانی، مرتبسازی پوسته، مرتبسازی شانهای، مرتبسازی گنوم، مرتبسازی چرخهای. ، و مرتب سازی کبوتر.
هنگام انتخاب یک الگوریتم مرتب سازی، مهم است که ماهیت داده های ورودی و الزامات برنامه را در نظر بگیرید. برخی از الگوریتم ها در مجموعه داده های بزرگ عملکرد بهتری دارند، در حالی که برخی دیگر برای مجموعه داده های کوچک یا جزئی مرتب شده کارآمدتر هستند. پیاده سازی برخی از الگوریتم ها آسان تر است، در حالی که برخی دیگر به کد پیچیده تری نیاز دارند.
با وجود گزینههای فراوان موجود، هیچ راهحلی برای مرتبسازی الگوریتمها وجود ندارد. انتخاب مناسبترین الگوریتم برای یک مسئله خاص، مستلزم بررسی دقیق الزامات و محدودیتهای خاص مسئله است. با این حال، با درک کامل از الگوریتمهای مرتبسازی مختلف و ویژگیهای آنها، توسعهدهندگان میتوانند تصمیمات آگاهانه بگیرند و کد کارآمدتر و مؤثرتری بنویسند.