十种经典排序算法【JavaScript】
所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。在各个领域中考虑到数据的各种限制和规范,要得到一个符合实际的优秀算法,得经过大量的推理和分析。
****此图中希尔排序有误,希尔排序最好情况为O(nlogn)最坏情况为O(n²)
冒泡排序(Bubble Sort)
算法描述
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;【此时生成序列为降序】
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
(优化部分:若在某一组比较过程中没有进行交换,则代表剩余的数已经遵循顺序排列,此时可以直接退出排列)
function swap(arr, i, j) { //交换两个下标的内容
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function bubbleSort(arr) { //冒泡排序
var flag //标记是否进行了交换
for (let i = 0; i < arr.length - 1; i++) {
flag = 0
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1)
flag = 1 //进行交换 标记为1
}
}
if (flag == 0)
break //若本轮中没有进行交换,则直接退出循环
}
return arr
}
选择排序(Select Sort)
算法描述
- 初始状态:无序区为R[1…n],有序区为空;
- 第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1…i-1]和R(i…n)。该趟排序从当前无序区中-选出关键字最小的记录
- R[k],将它与无序区的第1个记录R交换,使R[1…i]和R[i+1…n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
- n-1趟结束,数组有序化了。
例如
对于数列中[2,4,1,5,3,8],一开始的有序区长度为0,在整个数组中找出最小的数字为1,把1 和 2 的位置交换,得到 [1,4,2,5,3,8], 第一趟排序完成后,有序区长度+1,此时在无序区[4,2,5,3,8]中找出最小的为2,把2和4交换…直到所有数字比较完成
***是表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。
function selectSort(arr) {
let len = arr.length
for (let i = 0; i < len - 1; i++) {
let minIndex = i
for (let j = i + 1; j < len; j++) {
if (arr[minIndex] > arr[j])
minIndex = j
}
swap(arr, minIndex, i) //交换位置
}
return arr
}
插入排序 (Insert Sort)
算法描述
在要排序的一组数中,假定前n-1个数已经排好序,现在将第n个数插到前面的有序数列中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
function insertSort(arr) {
let len = arr.length
for (let i = 1; i < len; i++) {
let preIndex = i - 1
let current = arr[i]
while (preIndex >= 0 && arr[preIndex] > current) { //此处需要注意 不可以用arr[i]代替current,因为通过后面的置换arr[i]的值会发生改变
arr[preIndex + 1] = arr[preIndex]
preIndex--
}
arr[preIndex + 1] = current
}
return arr
}
希尔排序(Shell Sort)
算法描述
- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
- 按增量序列个数k,对序列进行k 趟排序;
- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1
时,整个序列作为一个表来处理,表长度即为整个序列的长度
(按照增量分成几个小数组来排序,增量的算法为
var gap = 1
while (gap < len / 3) {
gap = gap * 3 + 1
}
//也有直接用gap = Math.floor(len/2)的算法
function shellSort(arr) {
var len = arr.length
var gap = 1
while (gap < len / 3) {
gap = gap * 3 + 1
}
for (gap; gap > 0; gap = Math.floor(gap / 3)) {
var temp;
for (let i = gap; i < len; i++) {
temp = arr[i]
for (var j = i - gap; j >= 0 && arr[j] > temp; j -= gap) {
arr[j + gap] = arr[j]
}//此处运用到插入排序算法
arr[j + gap] = temp
}
}
return arr
}
归并排序(Merge Sort)
算法描述
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
function mergeSort(arr) {
var len = arr.length
if (len < 2) {
return arr
}
var middleIndex = Math.floor(len / 2)
var left = arr.slice(0, middleIndex)
var right = arr.slice(middleIndex)
return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
var result = []
while (left.length && right.length) {
if (left[0] < right[0])
result.push(left.shift())
else
result.push(right.shift())
}
while (left.length) {
result.push(left.shift())
}
while (right.length) {
result.push(right.shift())
}
return result
}
//push是在数组的最后插入内容
//shift为删除数组的第一个值并且返回
快速排序(Quick Sort)
算法描述
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;
// 6.快速排序 quickSort()
function quickSort(arr, left, right) {
var len = arr.length
left = typeof left != 'number' ? 0 : left //如果left值不存在则设置为0
right = typeof right != 'number' ? len - 1 : right
if (left < right) {
var partitionIndex = partition(arr, left, right)
left = quickSort(arr, left, partitionIndex - 1)
right = quickSort(arr, partitionIndex + 1, right)
}
return arr
}
function partition(arr, left, right) {
let index = left + 1
for (let i = left + 1; i <= right; i++) {
if (arr[i] < arr[left]) {
swap(arr, index, i)
index++
}
}
swap(arr, index - 1, left)
return index - 1
}
quickSort(arr)
堆排序(Heap Sort)
算法描述
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列;
小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列;
- 创建一个堆 H[0……n-1];
- 把堆首(最大值)和堆尾互换;
- 把堆的尺寸缩小 1,并调用 shift_down(0),目的是把新的数组顶端数据调整到相应位置;
- 重复步骤 2,直到堆的尺寸为 1。
// 7.堆排序
function heapSort(arr){
buildMaxHeap(arr)
var flag = arr.length
for(i=arr.length-1;i>=0;i--){
swap(arr,0,i)
heapify(arr,--flag,0)
}
return arr
}
function heapify(arr,len,i){
var maxIndex = i
var left = 2*i+1
var right = 2*i+2
if(left<len&&arr[left]>arr[maxIndex]){
maxIndex = left
}
if(right<len&&arr[right]>arr[maxIndex]){
maxIndex = right
}
if(maxIndex!=i){
swap(arr,i,maxIndex)
heapify(arr,len,maxIndex)
}
}
function buildMaxHeap(arr){
var len = arr.length
for(let i=Math.floor(len/2);i>=0;i--){
heapify(arr,len,i)
}
}
计数排序(Count Sort)
算法描述
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
// 8.计数排序
function countingSort(arr) {
var count = []
for (let i = 0; i < arr.length; i++) {
if (!count[arr[i]])
count[arr[i]] = 0
count[arr[i]]++
}
var len = arr.length
var i = 0
var flag = 0
while (len) {
while (count[i]) {
arr[flag++] = i
count[i]--
len--
}
i++
}
return arr
}
桶排序(Bucket Sort)
算法描述
先把数字放在规定的桶内
在桶内进行排序,排序完成后再依次把每个桶里面依次取出来
// 9.桶排序
function bucketSort(arr, bucketSize) {
if (arr.length === 0) {
return arr;
}
var len = arr.length
var maxValue = arr[0]
var minValue = arr[0]
//取出arr中的最大值和最小值
for (let i = 1; i < len; i++) {
if (arr[i] > maxValue) {
maxValue = arr[i]
}
else if (arr[i] < minValue) {
minValue = arr[i]
}
}
//如果没有设置桶的数量,则默认为5
bucketSize = typeof bucketSize != 'number' ? 5 : bucketSize
//每个桶的大小设置,(最大值-最小值)/桶的数量+1
var bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1
var buckets = new Array(bucketCount)
for (let i = 0; i < buckets.length; i++) {
buckets[i] = [] //二维数组的设置
}
for (let i = 0; i < len; i++) {
buckets[Math.floor((arr[i] - minValue) / bucketSize)].push(arr[i])
}
arr.length = 0 //把arr置空
for (let i = 0; i < buckets.length; i++) {
insertSort(buckets[i])
for (let j = 0; j < buckets[i].length; j++) {
arr.push(buckets[i][j]) //把桶内的值依次放回arr中
}
}
return arr
}
基数排序(Radix Sort)
算法描述
- 根据数组内的最大值来判断要根据位数来依次摆放
- 先根据个位数 0结尾的放在buckets[0]的数组中 1结尾的放在buckets[1]的数组中 …
- 把刚刚存好的buckets从[0]依次取出放到arr中
- 根据十位数 0结尾的放在buckets[0]的数组中 1结尾的放在buckets[1]的数组中 …
- 不断重复直到最大值的位数都执行完毕
//在菜鸟教程看过这一段,循环次数太多了,算是稍微改进了一下
function radixSort(arr, maxDigit) {
var mod = 10
var dev = 1
var buckets = new Array(10)
for (let i = 0; i < 10; i++) {
buckets[i] = []
}
for (mod = 10; mod < maxDigit * 10; mod *= 10, dev *= 10) {
for (let i = 0; i < arr.length; i++) {
buckets[parseInt(arr[i] % mod / dev)].push(arr[i])
}
arr.length = 0
for (let j = 0; j < 10; j++) {
while (buckets[j].length) {
arr.push(buckets[j].shift())
}
}
}
return arr
}