如果出现错误,请在评论中指出,我也好自己纠正自己的错误
author: thomaszhou
排序
先定义一些变量和方法
function ArrayList() {
let arr = [];
this.insert = function(item) { // 插入
arr.push(item);
};
this.toString = function() { // 字符串形式显示数值
return arr.join();
};
复制代码
很多排序算法用到两个值的交换功能,所以我抽象出一个函数:
this.swap = function(i, j, arr) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
复制代码
直接插入排序
这个方法是排序中最简答的方法
- 思想:依次将待排序序列中的每一个值插入到一个已经排好序的序列中,直到全部记录都排好序
- 需要解决的问题:
- 如何构造初始的有序队列?
- 如何查找待插入记录的插入位置?
- 思路:如图中第四趟排序结果,从第三趟的结果开始,我们要插入值6,然后我们只跟当前要插入的值(值为6)的前一个值比较(如果当前是arr[i],那前一个就是arr[i-1])来比较,( 因为看到的用[ ]包裹的是排好序的序列,最右边是其中最大的值 ),我们将当前要排序的值存到变量current当中,如果current值比arr[i-1]小,那就继续向前移动,继续比较current和arr[i-2],依次类推,并且每次移动,都将值向后移(20移到之前6的位置,15移到之前20的位置,代码是arr[index + 1] = arr[index])。当找到current > arr[i-n]的时候,将current赋值给arr[i-n+1].
// 设置arr = [12, 15, 9, 20, 6, 31, 24]
this.InsertSort = function() {
let len = arr.length,
current,
index;
for (let i = 1; i < len; i++) {
current = arr[i]; // current存储的是当前要插入的值
for ( index = i - 1; arr[index] > current; index--) {
// index是有序序列的最后一个值,即current的前一个值
arr[index + 1] = arr[index];
}
arr[index + 1] = current;
}
};
复制代码
let arraylist = new ArrayList();
arraylist.InsertSort();
console.log(`InsertSort数组是:${arraylist.toString()}`); // InsertSort数组是:6,9,12,15,20,24,31
复制代码
简单选择排序
主要思想:每趟排序在当前待排序序列中选出最小值,和下标为i的值交换(i 初始为0,每躺排序结束后 i++)
从算法逻辑上看, 选择排序是一种简单且直观的排序算法. 它也是两层循环. 内层循环每执行一遍, 将选出本次待排序的元素中最小(或最大)的一个, 存放在数组位置i. 外层循环就是设置循环结束条件(i < n)。
选择排序每次交换的元素都有可能不是相邻的, 因此它有可能打破原来值为相同的元素之间的顺序. 比如数组[2,2,1,3], 正向排序时, 第一个数字2将与数字1交换, 那么两个数字2之间的顺序将和原来的顺序不一致, 虽然它们的值相同, 但它们相对的顺序却发生了变化. 我们将这种现象称作 不稳定性 .
this.SlectSort = function() {
let len = arr.length;
for (let i = 0; i < len; i++) {
let index = i;
for (let j = i; j < len; j++) {
if (arr[j] < arr[index]) { // 找最小值/或者最大值(< 是升序 > 是降序)
index = j;
}
}
// exchange
index !== i && this.swap(i,index,arr);
}
};
复制代码
快速排序
快排被人们认为是最快的,所以呢,面试题中也会经常考。它将数组拆分为两个子数组(也就是一次划分操作), 其中一个子数组的所有元素都比另一个子数组的元素小, 然后对这两个子数组再重复进行上述操作(递归下去,不断划分), 直到数组不可拆分, 排序完成.
如图是快排的一次h划分的过程;
伪代码表示一次划分:
- 将i和j分别指向待划分区间的最左侧和最右侧记录的位置:
- 重复下述过程,直到i = j
- 右侧扫描,直到下标 j 的值小于下标 i 的值
- 将r[j]与r[i]交换,并执行i++
- 左侧扫描,直到下标 i 的值大于下标j 的值
- 将r[i]与r[j]交换,并执行j--
- 退出循环,说明i和j指向了轴值记录的所在位置,返回该位置
//调用函数:arraylist.Partition(1, 7);但是数组下标是0-6
this.Partition = function(first, end) { // 如果提取出来会传递一个arr数组,本例是直接调用全局的arr数组
let i = first - 1, // arr下标是(first-1, end-1)
j = end - 1;
while (i < j) {
while (i < j && arr[i] <= arr[j]) { j--; }
if (i < j) {
this.swap(i, j, arr); // 交换
i++;
}
while(i < j && arr[i] <= arr[j]) { i++; }
if (i < j) {
this.swap(i, j, arr);
j--;
}
}
return i+1;
};
复制代码
如图,一次划分后,我们可以得到[19, 13, 6]和[31, 49 ,28],然后再对这两个子区间进行划分,依次类推,此处需要递归
this.QuickSort = function(first, end) {// 如果提取出来会传递一个arr数组,本例是直接调用全局的arr数组
if (first < end) {
let pivot = this.Partition(first, end);
this.QuickSort(first,pivot - 1); // 前半部分
this.QuickSort(pivot + 1, end); // 后半部分
}
};
复制代码
验证
console.log(arraylist.Partition(1, 7));
console.log(`第一个划分的结果${arraylist.toString()}`); // 第一个划分的结果19,13,6,23,31,49,28
arraylist.QuickSort(1, 7);
console.log(`最终结果是:${arraylist.toString()}`); // 最终结果是:6,13,19,23,28,31,49
复制代码
归并排序
首先将具有n个待排序的记录序列变成n个长度为1的有序序列,然后再两两合并,边合并边排序。
归并排序严格按照从左往右(或从右往左)的顺序去合并子数组, 它并不会改变相同元素之间的相对顺序, 因此它也是一种稳定的排序算法.
- 合并过程(建议结合图片和合并的代码理解!):我们看倒数第二行的右侧,[1,7]和[4,5]进行合并,创建一个数组result,先比较两个数组的首位置数值,1比4小,将1加入result,然后 7大于4,将4加入result,7大于5,将5加入result,然后原本[4,5]数组长度为空,跳出循环
拆分过程:
function mergeSort(arr) { //输入一个长度为n的数组,输出长度为1的n个数组
var length = arr.length;
if(length < 2) {
return arr;
}
var m = (length >> 1),
left = arr.slice(0, m),
right = arr.slice(m); //拆分为两个子数组
return merge(mergeSort(left), mergeSort(right));//子数组继续递归拆分,然后再合并
};
复制代码
注: x>>1 是位运算中的右移运算, 等同于x除以2再取整, 即 x>>1 == Math.floor(x/2) 合并过程:
function merge(left, right){ // 合并数组
let result = [];
while (left.length && right.length) {
// 注意:判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
let item = left[0] <= right[0] ? left.shift() : right.shift();
}
// 只要left.length和right.length其中一个不满足,就会跳出while循环
// 假设left为空,跳出循环,那么right还剩一个值,反之同理
return result.concat(left.length ? left : right); // 将最终的return返回
};
复制代码
由上, 长度为n的数组, 最终会调用mergeSort函数2n-1次.
这个算法会出现错误,栈溢出
Uncaught RangeError: Maximum call stack size exceeded
复制代码