一. 排序算法
1.插入排序
1.1.直接插入排序
直接插入排序的工作原理就是将未排序数据,对已排序数据序列从后向前扫描,找到对应的位置并插入。 如对于序列6 5 4 3 2
- (1).由于第一个数 6 是自然有序的,所以我们从第二个数 5 开始考察, 将 5 取出与它的前一个数 6 比较,5 < 6,将 6 复制到 5 的位置,5 向前移动继续比较,此时 5 已经处于数组第一位,第一次排序结束,将 5 放入当前位置;
- (2). 此时 [5, 6] 已经构成有序数组,考察 4,将 4 取出与它的前一个数 6 比较,4 < 6,将 6 复制到 4 的位置;4 向前移与 5 比较,4 < 5,将 5 复制到前一个 6 的位置,此时 4 处于数组第一位,第二趟排序结束。
- 如此循环重复下去 …
如图所摘:
代码实现:
function insertSort(arr) {
let len = arr.length;
for(let i = 1; i < len; i++) { // 从第二个数开始遍历(数组索引为1)
let current = arr[i]; // 保存当前位置的数据,避免伪指针断裂 -->如current = 5
let j = i - 1; // 默认已排序数据中的最后一个,从后往前遍历有序数组嘛 -->如 j = 0, arr[j] = 6
while(j >= 0 && arr[j] > current) { // 当j有效且有序元素大于当前元素时 --> arr[j] > current
arr[j + 1] = arr[j]; // 把大的元素移动到下一个位置(交换位置)--> arr[1] = arr[0] = 6
j --; // 继续从后往前遍历有序的元素 --> j = 0跳出循环
}
arr[j + 1] = current; // 否则完成交换 --> arr[0] = 5,
// 即第一次排序结束时为 5 6 4 3 2
}
}
let arr = [6, 5, 4, 3, 2];
insertSort(arr);
console.log(arr); // 2, 3, 4, 5, 6
1.2 希尔排序:
希尔排序本质上是一种插入排序,但是对数列进行了等间隔分组处理,在每一组中做插入排序,随后逐次缩小间隔,在每一个分组中做插入排序…直到间隔等于1这一优化使得原本 O(n^2) 的时间复杂度一下降为 O(nlogn)通常我们去取初始间隔为数列长度的一半:gap = length/2
,以 gap = gap/2
的方式缩小或者
通过算法动态的改变gap
的值。
代码实现
:
function shellSort(arr) {
let len = arr.length;
let gap = 1; // 初始化gap(跳跃步数)
while (gap < len / 3) { // while循环动态改变gap的值
gap = gap * 3 + 1;
}
for(gap; gap > 0; gap = Math.floor(gap / 3)) { // for循环第三项不是立即执行的而是每次循环结束的时候
for(let i = gap; i < len; i++) { // 内部开始直接插入排序
let current = arr[i]; // 保存当前的元素
let j = i - gap; // gap递减,i递增 即 i >= gap 恒成立,故只能 i - gap 而不能 gap - i
for (j; j >= 0 && arr[j] > current; j -= gap) { // 此处用while循环也是一样的
arr[j + gap] = arr[j];
}
arr[j + gap] = current; // 完成交换
}
}
return arr;
}
let arr = [2, 3, 1, 9, 6, 4, 7, 5];
shellSort(arr);
console.log(arr);
2 选择排序
2.1.简单选择排序:
function selectSort(arr) {
let len = arr.length;
for(let i = 0; i < len -1; i++) { // 每次都要和后面的元素比较,当 i = len - 1(最后一个元素)时,就没有比较的对象了
let minIndex = i; // 不妨默认第一个元素为最小的
for(let j = i+1; j < len; j++) { // 从待排序序列中找出最小值的索引
if(arr[minIndex] > arr[j]) { // 保存最小的容器的值竟然不是最小的
minIndex = j; // 那么就刷新保存最小值的索引
}
}
// 完成交换
let temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
let arr = [5, 2, 6, 4, 7, 3, 9, 1];
selectSort(arr);
console.log(arr);
2.2 堆排序:
堆排序基本思想:
- 拿到给定的待排序数组序列。
- 将数组初始化为
大根堆
或者小根堆
,这里不妨初始化为大根堆
。 - 此时根结点为最大值,将其与最后一个结点交换。
- 交换后,将该节点(最大值得节点)移除(
从堆结构中移除,不是从数组中移除
)将其余节点组成的新堆转化为大根堆
(实质上是对根节点做adjustDown
操作),此时根结点为次最大值,将其与最后一个结点交换 - 重复 4
代码实现:
// 交换两个节点
function swap(A, i, j) {
let temp = A[i];
A[i] = A[j];
A[j] = temp;
}
/**
* 将 i 结点(最后一个非叶节点)以下的堆整理为大顶堆,注意这一步实现的基础实际上是:
* 结点 i 以下的子堆已经是一个大顶堆,adjustDown函数实现的能是实际上是:找到 结点 i 在包括结点 i 的堆中的正确位置。后面
* 将写一个 for 循环,从第一个非叶子结点开始,对每一个非叶子结点执行 adjustDown操作,所以就满足了结点 i 以下的子堆已经是一大
* 顶堆
*/
function adjustDown(A, i, length) {
for(let j = 2 * i +1; j < length; j = 2 * j + 1) { //其中,2*j +1是j的左孩子 i=0 j=1
let 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 buildMaxHeap(A) { // 初始化大顶堆,从第一个非叶子结点开始
for(let i = Math.floor(A.length / 2 - 1); i >= 0; i--) {
adjustDown(A, i, A.length);
}
}
function heapSort(A) {
buildMaxHeap(A);
// 排序,每一次for循环找出一个当前最大值,堆长度减一
for(let i = Math.floor(A.length - 1); i > 0; i --) {
swap(A, 0, i); // 根节点与最后一个节点交换
adjustDown(A, 0, i); // i 为长度,从根节点开始调整,并且最后一个结点已经为当
// 前最大值,不需要再参与比较,所以第三个参数 为 i,即比较到最后一个结点前一个即可
}
}
let Arr = [6, 2, 7, 1, 9, 6, 5, 4];
heapSort(Arr);
console.log(Arr);
3. 交换排序
3.1 冒泡排序:
- 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素
第一轮结束后,序列最后一个元素一定是当前序列的最大值。
- 对序列当中剩下的n-1个元素再次执行步骤1。 对于长度为n的序列,一共需要执行n-1轮比较,利用while循环可以减少执行次数
- 假设数组中有n个数,则需要n轮,而每一轮中比较的次数都要减去已经确定的数值,即第i轮需要比较的次数为:n-i。
代码实现:
// 1.原始
function bubbleSort(arr) {
console.time('for')
let len = arr.length;
for (let i = 0; i < len; i++) {
for (let j = 0; j < len - i - 1; j++) {
if (arr[j] > arr[j+1]) {
let temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
console.timeEnd('for')
return arr;
}
let arr = [2, 1, 3, 5, 2, 9, 6, 7, 4, 8];
let a = JSON.parse(JSON.stringify(arr));
bubbleSort(a);
console.log(a);
/* 改进后的冒泡排序.
* 设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,
* 故在进行下一趟排序时只要扫描到pos位置即可。
*/
function niceBubbleSort(arr) {
console.time('改进后冒泡排序耗时');
let i = arr.length - 1; // 初始时,最后位置保持不变
while (i > 0) {
let pos = 0;
for (let j = 0; j < i; j++) {
if (arr[j] > arr[j+1]) {
pos = j;
let temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
i = pos;
}
console.timeEnd('改进后冒泡排序耗时');
return arr;
}
niceBubbleSort(arr);
console.log(arr)
3.2 快速排序:
快速排序是对冒泡排序的一种改进。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 整个排序过程只需要三步:
- 在数据集之中,选择一个元素作为"基准"(pivot)。
- 所有小于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
- 对"基准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
即每轮结束以后基准都会出现在最终的位置。
代码实现:
- 性能较高的递归实现,没有新建2个数组:
function quickSort(arr, left, right) { // left不传参默认是undefined到下面就是0
let len = arr.length, partitionIndex;
left = typeof left === "number" ? left : 0;
right = typeof right === "number" ? right : len - 1;
if(left < right) {
partitionIndex = partition(arr, left, right);
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
}
function partition(arr, left, right) {
let pivot = left;
let index = pivot + 1;
for(let i = index; i <= right; i++) {
if(arr[i] < arr[pivot]) { //满足条件则交换
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index - 1;
}
function swap(arr, i, j) { // 交换函数
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
- (双数组)算法:
// 这个方法会比上面多占用内存,不推荐
// 方法1 新建左右2个数组 效率较低
function quickSort(arr) {
if (arr.length <= 1) return arr;
let left = [], right = [];
let midIndex = Math.floor(arr.length/2); // 基准索引(位置)
let midValue = arr.splice(midIndex, 1)[0];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < midValue) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat(midValue, quickSort(right));
}
更新ing......