js排序算法
一.冒泡排序
1.规则:依次比较相邻的两个数,如果不符合排序规则,则调换两个数的位置。这样一遍比较下来,能够保证最大(或最小)的数排在最后一位。再对最后一位以外的数组,重复前面的过程,直至全部排序完成。
2.详解:
var arr = [9, 8, 7, 6, 5, 4];
0轮:
9, 8, 7, 6, 5, 4
8, 9, 7, 6, 5, 4
8, 7, 9, 6, 5, 4
8, 7, 6, 9, 5, 4
8, 7, 6, 5, 9, 4
8, 7, 6, 5, 4, 9
1轮:
8, 7, 6, 5, 4
7, 8, 6, 5, 4
7, 6, 8, 5, 4
7, 6, 5, 8, 4
7, 6, 5, 4, 8
2轮:
7, 6, 5, 4
6, 7, 5, 4
6, 5, 7, 4
6, 5, 4, 7
3轮:
6, 5, 4
5, 6, 4
5, 4, 6
4轮:
5, 4
4, 5
总结:
比较的轮数 = 数组长度 - 1;
每一轮比较的次数 = 数组长度 - 当前轮数
3.代码实现
function bubbleSort(arr) {
var len = arr.length;
//比较的轮数
for (var i = 0; i < len-1; i++) {
//每轮比较的次数
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { //相邻元素两两对比
var temp = arr[j+1]; //元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
二:选择排序
1. 规则:选择排序(Selection Sort)与冒泡排序类似,也是依次对相邻的数进行两两比较。不同之处在于,它不是每比较一次就调换位置,而是一轮比较完毕,找到最大值(或最小值)之后,将其放在正确的位置,其他数的位置不变。
2.详解:
第一轮:
9, 8, 7, 6, 5, 4
8, 9, 7, 6, 5, 4
7, 9, 8, 6, 5, 4
6, 9, 8, 7, 5, 4
5, 9, 8, 7, 6, 4
4, 9, 8, 7, 6, 5
一号位 比较出最小的数为4
第二轮:
9, 8, 7, 6, 5
8, 9, 7, 6, 5
7, 9, 8, 6, 5
6, 9, 8, 7, 5
5, 9, 8, 7, 6
二号位 比较出最小的数为5
第三轮:
9, 8, 7, 6
8, 9, 7, 6
7, 9, 8, 6
6, 9, 8, 7
三号位 比较出最小的数为6
第四轮:
9, 8, 7
8, 9, 7
7, 9, 8
四号位 比较出最小的数为7
第五轮:
8, 9
四号位 比较出最小的数为8
总结:
比较的轮数 = 数组长度 - 1
每一轮比较次数 = 数组长度 - 当前轮次
3.代码实现
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
//比较的轮数
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { //寻找最小的数
minIndex = j; //将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
三:插入排序
1.规则:插入排序(insertion sort)比前面两种排序方法都更有效率。它将数组分成“已排序”和“未排序”两部分,一开始的时候,“已排序”的部分只有一个元素,然后将它后面一个元素从“未排序”部分插入“已排序”部分,从而“已排序”部分增加一个元素,“未排序”部分减少一个元素。以此类推,完成全部排序。
2.详解
var arr = [3, 2, 4, 5, 1];
1.将数组分成[3]和[2, 4, 5, 1]两部分,前者是已排序的,后者是未排序的。
2.取出未排序部分的第一个元素“2”,与已排序部分最后一个元素“3”比较,因为2小于3,所以2排在3前面,整个数组变成[2, 3]和[4, 5, 1]两部分。
3.取出未排序部分的第一个元素“4”,与已排序部分最后一个元素“3”比较,因为4大于3,所以4排在3后面,整个数组变成[2, 3, 4]和[5, 1]两部分。
4.取出未排序部分的第一个元素“5”,与已排序部分最后一个元素“4”比较,因为5大于4,所以5排在4后面,整个数组变成[2, 3, 4, 5]和[1]两部分。
5.取出未排序部分的第一个元素“1”,与已排序部分最后一个元素“5”比较,因为1小于5,所以再与前一个元素“4”比较;因为1小于4,再与前一个元素“3”比较;因为1小于3,再与前一个元素“2”比较;因为小于1小于2,所以“1”排在2的前面,整个数组变成[1, 2, 3, 4, 5]。
0轮:
i=1;
key=2;
j=0;
比较3跟2的值 如果3>2 array[1]=3;j=-1 跳出循环
array[0] = 2
[2,3,4,5,1]
1轮:
i=2;
key=4;
j=1;
比较3跟4的值 3<4不符合条件跳出循环
array[2]= 4
[2,3,4,5,1]
2轮:
i=3;
key=5;
j=2;
比较4跟5的值 4<5不符合条件跳出循环
array[3]= 5
[2,3,4,5,1]
3轮:
i=4;
key=1;
j=3;
比较5跟1的值 5>1 array[4]=5;j=2;
比较4跟1的值 4>1 array[3]=4;j=1;
比较3跟1的值 3>1 array[2]=3;j=0;
比较2跟1的值 2>1 array[1]=2;j=-1;不符合条件 跳出循环
array[1] = 1;
排序完成 [1,2,3,4,5]
3.代码实现
function insertionSort(array) {
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
for (var i = 1; i < array.length; i++) {
var key = array[i];
var j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j--;
}
array[j + 1] = key;
}
return array;
} else {
return 'array is not an Array!';
}
}
四:希尔排序
1.规则:希尔排序是按一定的间隔对数列进行分组,然后在每一个分组中做插入排序;随后逐次缩小间隔,在每一个分组中做插入排序...直到间隔等于1,做一次插入排序后结束。
2.详解
var arr = [2,5,10,7,1,3];
1.先把数组拆分成4个间隔的 2-1 5-3
2.2-1 5-3 进行大小比较 互换位置 [1,3,10,7,2,5]
3.第二次修改gap间隔的值 改为Math.floor(gap/3)也就是1 下面流程就跟快速排序一样的流程
0轮:
gap = 4;
i=4;
temp=1;
j=0;j>=0;arr[j]>temp符合条件
arr[4] = 2; [2,5,10,7,2,3];
j-=gap;j=-4不符合条件
arr[0]=1; [1,5,10,7,2,3] //目前2跟1互换了位置
1轮:
i=5;
temp=3;
j=1;j>=0;arr[j]>temp符合条件
arr[5] = 5; [1,5,10,7,2,5];
j-=gap;j=-3不符合条件
arr[1]=3; [1,3,10,7,2,5] //目前5跟3互换了位置
2轮:
i=6;不符合i<len的条件 gap=Math.floor(gap/3) = 1;
i=1;
temp = 3;
j=0;j>=0;arr[j]<temp不符合条件跳出循环
arr[1] = 3;
3轮:
i=2;
temp = 10;
j=1;j>=0;arr[j]<temp不符合条件跳出循环
arr[2] = 10;
4轮:
i=3;
temp = 7;
j=2;j>=0;arr[j]>temp符合条件
arr[3] = 10; [1,3,10,7,2,5]
j-=gap;j=1;
j>=0;arr[j]<temp不符合条件跳出循环
arr[2] = 7 [1,3,7,10,2,5]
以此类推
3.代码实现
function shellSort(arr) {
var len = arr.length, temp, gap = 1;
while(gap < len/3) { //动态定义间隔序列
gap = gap*3+1;
}
for (gap; gap> 0; gap = Math.floor(gap/3)) {
for (var 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;
}
五:归并排序
1.规则:归并排序采用的是分治的思想,首先是“分”,将一个数组反复二分为两个小数组,直到每个数组只有一个元素;其次是“治”,从最小数组开始,两两按大小顺序合并,直到并为原始数组大小,下面是图解:
2.详解:
var arr = [3,44,38,5,47];
0轮:
middel = 2;
left = [3,44];
right = [38,5,47];
首先先执行mergeSort(left)先拆分左数组
middle = 1;
left = [3],
right = [44];
在递归拆封左数组 mergeSort(left) 因为此刻len=1;len<2;直接return [3];
在递归拆封右数组 mergeSort(right) 因为此刻len=1;len<2;直接return [44];
直接执行merge([3,44]) 执行完结果返回[3,44];
1轮:
执行mergeSort(right)先拆分右数组
middle=1;
left = [38],
right = [5,47];
在递归拆封左数组 mergeSort(left) 因为此刻len=1;len<2;直接return [38];
在递归拆封右数组
middle = 1;
left = [5];
right = [47];
mergeSort(left) 因为此刻len=1;len<2;直接return [5];
mergeSort(right) 因为此刻len=1;len<2;直接return [47];
直接执行merge([5,47]) 执行完结果返回[5,47];
2轮:
然后进行合并排序 merge[38,5,47];
执行结果为[5,38,47];
3轮:
然后进行合并排序 merge[3,44,5,38,47];
执行结果为[3,5,38,44,47];
完成排序
3.代码实现:
function mergeSort(arr) { //采用自上而下的递归方法
var len = arr.length;
if(len < 2) {
return arr;
}
var middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle);
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;
}
六:快速排序
1.规则:
- (1)在数据集之中,选择一个元素作为"基准"(pivot)。
- (2)所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
- (3)对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
2.代码实现:
var quickSort = function (arr) {
if (arr.length <= 1) {
return arr;
}
var pivotIndex = Math.floor(arr.length / 2);//中间元素的位置
var pivot = arr.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
};
七:计数排序
1.规则:
- 第一步:找出原数组中元素值最大的,记为max。
- 第二步:创建一个新数组count,其长度是max加1,其元素默认值都为0。
- 第三步:遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值。
- 第四步:创建结果数组result,起始索引index。
- 第五步:遍历count数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到result数组中去,每处理一次,count中的该元素值减1,直到该元素值不大于0,依次处理count中剩下的元素。
- 第六步:返回结果数组result。
2.代码实现
function countingSort(iArr, max) {
var n = iArr.length;
var oArr = [];
// 创建长度max的数组,填充0
var C = [];
for (var i = 0; i <= max; i++) {
C[i] = 0; //[0,0,0,0,0,0,0]
}
// 遍历输入数组,填充C
for (var j = 0; j < n; j++) {
C[iArr[j]]++; //[0,1,1,2,1,0,1]
}
// 遍历C,输出数组
for (var k = 0; k <= max; k++) {
// 按顺序将值推入输出数组,并在比较后将对应标志位减1 注意是先push后减
while (C[k]-- > 0) {
oArr.push(k); //[-1,-1,-1,-1,-1,-1,-1]
}
}
return oArr;
}
var arr = [3, 2, 1, 6, 4 , 3];
console.log(countingSort(arr, Math.max.apply(null, arr)));
八:桶排序(blog.csdn.net/m0_37477061…)
1.规则
- 设置固定空桶数
- 将数据放到对应的空桶中
- 将每个不为空的桶进行排序
- 拼接不为空的桶中的数据,得到结果
2.详解
假设一组数据(20长度)为[63,157,189,51,101,47,141,121,157,156,194,117,98,139,67,133,181,13,28,109] 现在需要按5个分桶,进行桶排序,实现步骤如下:
- 找到数组中的最大值194和最小值13,然后根据桶数为5,计算出每个桶中的数据范围为(194-13+1)/5=36.4
- 遍历原始数据,(以第一个数据63为例)先找到该数据对应的桶序列Math.floor(63 - 13) / 36.4) =1,然后将该数据放入序列为1的桶中(从0开始算)
- 当向同一个序列的桶中第二次插入数据时,判断桶中已存在的数字与新插入的数字的大小,按从左到右,从小打大的顺序插入。如第一个桶已经有了63,再插入51,67后,桶中的排序为(51,63,67) 一般通过链表来存放桶中数据,但js中可以使用数组来模拟
- 全部数据装桶完毕后,按序列,从小到大合并所有非空的桶(如0,1,2,3,4桶)
- 合并完之后就是已经排完序的数据
3.代码实现
var bucketSort = function(arr, bucketCount) {
if (arr.length <= 1) {
return arr;
}
bucketCount = bucketCount || 10;
//初始化桶
var len = arr.length,
buckets = [],
result = [],
max = arr[0],
min = arr[0];
for (var i = 1; i < len; i++) {
min = min <= arr[i] ? min: arr[i];
max = max >= arr[i] ? max: arr[i];
}
//求出每一个桶的数值范围
var space = (max - min + 1) / bucketCount;
//将数值装入桶中
for (var i = 0; i < len; i++) {
//找到相应的桶序列
var index = Math.floor((arr[i] - min) / space);
//判断是否桶中已经有数值
if (buckets[index]) {
//数组从小到大排列
var bucket = buckets[index];
var k = bucket.length - 1;
while (k >= 0 && buckets[index][k] > arr[i]) {
buckets[index][k + 1] = buckets[index][k];
k--
}
buckets[index][k + 1] = arr[i];
} else {
//新增数值入桶,暂时用数组模拟链表
buckets[index] = [];
buckets[index].push(arr[i]);
}
}
//开始合并数组
var n = 0;
while (n < bucketCount) {
if (buckets[n]) {
result = result.concat(buckets[n]);
}
n++;
}
return result;
};
//开始排序
arr = bucketSort(arr, self.bucketCount);
九:基数排序
1.规则:
- 首先确定基数为10,数组的长度也就是10.每个数都会在这10个数中寻找自己的位置。
- 不同于BinSort会直接将数放在数组的下标处,如将 [34] 放在下标为34的位置,即a[34] = 34;基数排序是将34分开为3和4,第一轮排序根据最末位放在数组的下标4处,第二轮排序根据倒数第二位放在数组的下标3处,然后遍历数组即可。
2.详解:
0轮:
按照个位数进行排序;
i = 0;
dev = 1;
mod = 10;
bucket 算出来的是个位数
couter[0] = [50];
couter[1] = [];
couter[2] = [2];
couter[3] = [3];
.....
couter[9] = [19];
接下来的循环 按照个位数进行排序的结果 [50,2,3,4,44,5,15....]
1轮:
按照十位数进行排序:
i = 1;
dev = 10;
mod = 100;
bucket 算出来的是十位数 如果没有十位数补0
根据第一轮排序的结果进行处理 按照十位数添加数组 得到结果
如下图
3.代码实现:
/**
* 基数排序适用于:
* (1)数据范围较小,建议在小于1000
* (2)每个数值都要大于等于0
* @author xiazdong
* @param arr 待排序数组
* @param maxDigit 最大位数
*/
//LSD Radix Sort
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;
}
var arr = [3, 44, 38, 5, 47];
console.log(radixSort(arr,2)); //[3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
十:堆排序
1.规则:
- 将初始二叉树转化为大顶堆(heapify)(实质是从第一个非叶子结点开始,从下至上,从右至左,对每一个非叶子结点做shiftDown操作),此时根结点为最大值,将其与最后一个结点交换。
- 除开最后一个结点,将其余节点组成的新堆转化为大顶堆(实质上是对根节点做shiftDown操作),此时根结点为次最大值,将其与最后一个结点交换。
- 重复步骤2,直到堆中元素个数为1(或其对应数组的长度为1),排序完成。
2.详解:
参考链接:segmentfault.com/a/119000001… www.cnblogs.com/chengxiao/p…
3.代码实现:
// 交换两个节点
function swap(A, i, j) {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
}
// 将 i 结点以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
// 假设 结点 i 以下的子堆已经是一个大顶堆,shiftDown函数实现的
// 功能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
// 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点
// 都执行 shiftDown操作,所以就满足了结点 i 以下的子堆已经是一大
//顶堆
function shiftDown(A, i, length) {
let temp = A[i]; // 当前父节点
// j<length 的目的是对结点 i 以下的结点全部做顺序调整
for(let j = 2*i+1; j<length; j = 2*j+1) {
temp = A[i]; // 将 A[i] 取出,整个过程相当于找到 A[i] 应处于的位置
if(j+1 < length && A[j] < A[j+1]) {
j++; // 找到两个孩子中较大的一个,再与父节点比较
}
if(temp < A[j]) {
swap(A, i, j) // 如果父节点小于子节点:交换;否则跳出
i = j; // 交换后,temp 的下标变为 j
} else {
break;
}
}
}
// 堆排序
function heapSort(A) {
// 初始化大顶堆,从第一个非叶子结点开始
for(let i = Math.floor(A.length/2-1); i>=0; i--) {
shiftDown(A, i, A.length);
}
// 排序,每一次for循环找出一个当前最大值,数组长度减一
for(let i = Math.floor(A.length-1); i>0; i--) {
swap(A, 0, i); // 根节点与最后一个节点交换
shiftDown(A, 0, i); // 从根节点开始调整,并且最后一个结点已经为当
// 前最大值,不需要再参与比较,所以第三个参数
// 为 i,即比较到最后一个结点前一个即可
}
}
let Arr = [4, 6, 8, 5, 9, 1, 2, 5, 3, 2];
heapSort(Arr);
//另一种
/*方法说明:堆排序
@param array 待排序数组*/
function heapSort(array) {
console.time('堆排序耗时');
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
//建堆
var heapSize = array.length, temp;
for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
heapify(array, i, heapSize);
}
//堆排序
for (var j = heapSize - 1; j >= 1; j--) {
temp = array[0];
array[0] = array[j];
array[j] = temp;
heapify(array, 0, --heapSize);
}
console.timeEnd('堆排序耗时');
return array;
} else {
return 'array is not an Array!';
}
}
/*方法说明:维护堆的性质
@param arr 数组
@param x 数组下标
@param len 堆大小*/
function heapify(arr, x, len) {
if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') {
var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp;
if (l < len && arr[l] > arr[largest]) {
largest = l;
}
if (r < len && arr[r] > arr[largest]) {
largest = r;
}
if (largest != x) {
temp = arr[x];
arr[x] = arr[largest];
arr[largest] = temp;
heapify(arr, largest, len);
}
} else {
return 'arr is not an Array or x is not a number!';
}
}
var arr=[91,60,96,13,35,65,46,65,10,30,20,31,77,81,22];
console.log(heapSort(arr));//[10, 13, 20, 22, 30, 31, 35, 46, 60, 65, 65, 77, 81, 91, 96]