JS排序算法
- 本文参考来源链接
一、排序算法说明
1、排序的定义:对一序列对象根据某个关键字进行排序
输入:n个数:a1,a2,a3,…,an 输出:n个数的排列:a1’,a2’,a3’,…,an’,使得a1’<=a2’<=a3’<=…<=an’。
再讲的形象点就是排排坐,调座位,高的站在后面,矮的站在前面咯。
2、对于评述算法优劣术语的说明
稳定: 如果a原本在b前面,而a=b,排序之后a仍然在b的前面; 不稳定: 如果a原本在b的前面,而a=b,排序之后a可能会出现在b的后面;
内排序: 所有排序操作都在内存中完成; 外排序: 由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;
时间复杂度: 一个算法执行所耗费的时间。 空间复杂度: 运行完一个程序所需内存的大小。
3、排序算法图片总结
- 排序对比:

- 图片名词解释: n: 数据规模 k:“桶”的个数 In-place: 占用常数内存,不占用额外内存 Out-place: 占用额外内存
- 排序分类:

二、排序算法实现
1、冒泡排序
- 算法描述
<1>.比较相邻的元素。如果第一个比第二个大,就交换它们两个;
<2>.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
<3>.针对所有的元素重复以上的步骤,除了最后一个;
<4>.重复步骤1~3,直到排序完成。 - 代码实现
function bubbleSort(arr) {
for(let i = 0; i < arr.length; i++) {
for(let j = 0; j < arr.length - 1; j++) {
if(arr[j] > arr[j+1]) { //相邻两个元素比较
[arr[j],arr[j+1]] = [arr[j+1], arr[j]] // 元素交换
}
}
}
return arr
}
let arr = [3,8,1,4,8]
console.log(bubbleSort(arr)) // [ 1, 3, 4, 8, 8 ]
2、选择排序
- 算法描述
01、选择排序(Selection-sort)是一种简单直观的排序算法。
它的工作原理:
①、在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
②、然后从剩余未排序元素中继续寻找最小(大)元素
③、然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。 - n个记录的直接选择排序可经过n-1趟直接选择排序得到有序结果
<1>.初始状态:无序区为R[1…n],有序区为空;
<2>.第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个的新无序区;
<3>.n-1趟结束,数组有序化了。 - 代码实现:
function selectSort(arr) {
let minIndex
for (let i = 0; i < arr.length; i++) {
minIndex = i
for(j = i+1; j < arr.length; j++ ) {
if(arr[j] < arr[minIndex]) { // 寻找最小值索引
minIndex = j
}
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]
}
return arr
}
let arr = [3,8,1,4,8,2]
console.log(selectSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
3、插入排序
- 算法描述
<1>.从第一个元素开始,该元素可以认为已经被排序;
<2>.取出下一个元素,在已经排序的元素序列中从后向前扫描;
<3>.如果该元素(已排序)大于新元素,将该元素移到下一位置;
<4>.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
<5>.将新元素插入到该位置后;
<6>.重复步骤2~5。 - 代码实现:
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let key = arr[i]
let j = i - 1
while(j >= 0 && arr[j] > key) {
arr[j+1] = arr[j]
j--
}
arr[j + 1] = key
}
return arr
}
let arr = [3,8,1,4,8,2]
console.log(insertSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
4、希尔排序
- 算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
<1>. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
<2>.按增量序列个数k,对序列进行k 趟排序;
<3>.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
function shellSort(arr) {
let len = arr.length
// gap 为增量
for(let gap = Math.floor(len/2); gap > 0; gap = Math.floor(gap/2)) {
for ( let i = gap; i < len; i++ ) {
let j = i
let current = arr[i]
while(j-gap >= 0 && current < arr[j-gap]) {
arr[j] = arr[j-gap]
j = j - gap
}
arr[j] = current
}
}
return arr
}
let arr = [3,8,1,4,8,2]
console.log(shellSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]

5、归并排序
-
算法描述
<1>.把长度为n的输入序列分成两个长度为n/2的子序列;
<2>.对这两个子序列分别采用归并排序;
<3>.将两个排序好的子序列合并成一个最终的排序序列。

-
代码实现:
//采用自上而下的递归方法
function mergeSort(arr) {
let len = arr.length
if(len < 2) {
return arr
}
let middle = Math.floor(len / 2)
let left = arr.slice(0, middle)
let right = arr.slice(middle)
// console.log(left, right)
return merge(mergeSort(left),mergeSort(right))
}
function merge(left, right) {
let result = []
// 当拆分的数组都有元素时
while(left.length && right.length) {
if(left[0] <= right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
// 当只有 left 数组有元素时
while(left.length) {
result.push(left.shift())
}
// 当只有 right 数组中有值时
while(right.length) {
result.push(right.shift())
}
// console.log("result:"+result)
return result
}
let arr = [3,8,1,4,8,2]
console.log(mergeSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
6、快速排序
- 算法描述
<1>.从数列中挑出一个元素,称为 “基准”(pivot);
<2>.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
<3>.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。 - 代码实现
function quickSort(arr) {
// 判断传入进来的是否是数组
if(!Array.isArray(arr)) return
// 判断数组长度是否大于等于2
if(arr.length <= 1) return arr
// 在数组中获取一个基准
let pivotIndex = Math.floor(arr.length/2)
let pivot = arr.splice(pivotIndex, 1)[0]
// 创建两个数组,放置大于或者小于基准的元素
let left = [], right = []
for(let i = 0; i < arr.length; i++) {
// 判断数组中的每一个元素是否比基准大
if(arr[i] > pivot) {
right.push(arr[i])
} else {
left.push(arr[i])
}
}
// 递归连接两个数组和基准值,返回一个排序完整的数组
return quickSort(left).concat([pivot],quickSort(right))
}
let arr = [3,8,1,4,8,2]
console.log(quickSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
7、堆排序来源
- 算法描述
<1>.将初始待排序关键字序列(R1,R2…Rn)构建成大顶堆,此堆为初始的无序区;
<2>.将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,…Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
<3>.由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,…Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2…Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。 - 代码实现
//生成大顶堆
function adjustHeap(arr, i, len) {
//将当前值保存
var temp = arr[i];
//从i结点的左子结点开始,也就是2i+1处开始
for (var j = 2 * i + 1; j < len; j = 2 * j + 1) {
//如果左子结点小于右子结点,j指向右子结点
if ((j + 1 < len) && arr[j] < arr[j + 1]) {
j++;
}
//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)值和索引都赋值
if (arr[j] > temp) {
arr[i] = arr[j];
i = j;
} else {
break;
}
}
arr[i] = temp; //将temp值放到最终的位置
}
function heapSort(data) {
//构造大顶堆
//此时我们从最后一个非叶子结点开始,叶结点自然不用调整
////从第一个非叶子结点从下至上,从右至左调整结构
for (var i = Math.floor(data.length / 2 - 1); i >= 0; i--) {
adjustHeap(data, i, data.length);
}
// console.log(data);
//交换堆顶元素与末尾元素;不算最后一个元素,重新调整堆
for (var k = data.length - 1; k > 0; k--) {
//将堆顶元素与末尾元素进行交换
[data[0], data[k]] = [data[k], data[0]];
// console.log(data);
//不算最后一个元素,重新对堆进行调整
adjustHeap(data, 0, k);
//此处不用向上面调整一样for循环,因为此处只需要调整顶点,其他点已在上一步调整好,从顶点再往下调整,
}
return data;
}
let arr = [3, 8, 1, 4, 8, 2]
console.log(heapSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
8、计数排序
- 算法描述
原版计数排序,桶的容积需要一个可以包含最小值到最大值所有可能出现的数字。这里我们可以将桶换成对象,利用对象的自动排序与不能出现相同属性名的键值对这两个特点,不需要一个有序容积的桶,随意新增键值对即可 - 代码实现
function countSort(arr) {
let obj = {};
//遍历原数组,给对象新增键值对,如果已经存在就对应的属性值++,如果不存在则新增键值对
for (let i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
obj[arr[i]] = 1;
} else {
obj[arr[i]]++;
}
}
let index = 0;
//遍历对象属性名,按顺序放回覆盖原数组
for (let key in obj) {
while (obj[key] > 0) {
arr[index] = Number(key);
obj[key]--;
index++
}
}
return arr;
}
let arr = [3, 8, 1, 4, 8, 2]
console.log(countSort(arr)) // [ 1, 2, 3, 4, 8, 8 ]
9、桶排序
- 算法描述
<1>.设置一个定量的数组当作空桶;
<2>.遍历输入数据,并且把数据一个一个放到对应的桶里去;
<3>.对每个不是空的桶进行排序;
<4>.从不是空的桶里把排好序的数据拼接起来。 - 代码实现
// arr 排序数组,bucketCount桶的数量
function bucketSort(arr, bucketCount) {
result = []
minValue = arr[0]
maxValue = arr[0]
// 找出最大值和最小值,为给每个桶分配大小做准备
for (let i = 0; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i]
}
if (arr[i] > maxValue) {
maxValue = arr[i]
}
}
// 求得每个桶的size
bucketSize = Math.floor((maxValue - minValue) / bucketCount) + 1
bucket = new Array(bucketCount)
for (let i = 0; i < bucketCount; i++) {
bucket[i] = []
}
// 往桶里放数据
for (let i = 0; i < arr.length; i++) {
bucket[Math.floor((arr[i] - minValue) / bucketCount)].push(arr[i])
}
// 对每个桶进行单独排序,放进结果数组中
for (let i = 0; i < bucketCount; i++) {
bucket[i].sort()
for (let j = 0; j < bucket[i].length; j++) {
result.push(bucket[i][j])
}
}
return result
}
let arr = [3, 8, 1, 4, 8, 2]
console.log(bucketSort(arr, 3)) // [ 1, 2, 3, 4, 8, 8 ]
10、基数排序
- 算法描述
基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
<1>.取得数组中的最大数,并取得位数;
<2>.arr为原始数组,从最低位开始取每个位组成radix数组;
<3>.对radix进行计数排序(利用计数排序适用于小范围数的特点); - 代码实现
// arr 待排序数组 maxDigit 最大位数
function radixSort(arr, maxDigit) {
var mod = 10;
var dev = 1;
var counter = [];
for (var i = 0; i < maxDigit; i++, dev *= 10, mod *= 10) {
for (var j = 0; j < arr.length; j++) {
var bucket = parseInt((arr[j] % mod) / dev);
if (counter[bucket] == null) {
counter[bucket] = [];
}
counter[bucket].push(arr[j]);
}
var pos = 0;
for (var j = 0; j < counter.length; j++) {
var value = null;
if (counter[j] != null) {
while ((value = counter[j].shift()) != null) {
arr[pos++] = value;
}
}
}
}
return arr;
}
let arr = [3, 8, 1, 4, 8, 20, 600]
console.log(radixSort(arr, 3)) // [ 1, 2, 3, 4, 8, 8, 20, 600 ]
本文详细介绍了排序算法,包括冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序和基数排序。每种排序算法都附带了具体的描述、优劣术语解释、时间复杂度和空间复杂度分析,并提供了JavaScript实现代码。
604

被折叠的 条评论
为什么被折叠?



