冒泡排序(Bubble Sort)
通过相邻元素的比较和交换,使得每一趟循环都能找到未排数组的最大值或最小值,把它放到后面;
普通冒泡排序
let arr = [2,44,3,13,11,35,8]
function sort(arr){
for(let i=0,len=arr.length; i<len-1;i++){
// flag用来标志每一轮是否有交换数据;如果没有,则说明数组已有序
let flag = true
for(let j=0;j<len-i-1;j++){
if(arr[j]>arr[j+1]){
[arr[j],arr[j+1]] = [arr[j+1],arr[j]];
flag = false;
}
}
if(flag) return;
}
}
sort(arr)
console.log(arr) // [ 2, 3, 8, 11, 13, 35, 44 ]
双向冒泡排序
let arr = [2,44,3,13,11,35,8]
function sort(arr){
let low = 0;
let high = arr.length -1;
while(low < high){
let flag = true;
// 找到最大值放在右边
for(let i=low;i<high;i++){
if(arr[i]>arr[i+1]){
[arr[i],arr[i+1]] = [arr[i+1],arr[i]];
flag = false;
}
}
high--;
// 找到最小值放在左边
for(let j=high;j>low;j--){
if(arr[j]<arr[j-1]){
[arr[j],arr[j-1]] = [arr[j-1],arr[j]];
flag = false;
}
}
low++;
if(flag)return;
}
}
sort(arr)
console.log(arr) // [ 2, 3, 8, 11, 13, 35, 44 ]
快速排序
- 从数组中取出一个数作为基准。
- 在原数组中进行移动,将大于基准的数放到基准右边,小于基准的数放到基准左边,在基准左右形成两个子数组。
- 在左右子数组中反复执行步骤1、2,直到所有子数组只剩下一个数。
let arr = [2,44,3,13,11,35,8]
function sort(arr){
if(arr.length <=1) {
return arr;
}
// 获取基准的索引
let pivotIndex = Math.floor(arr.length/2);
// 找到基准,把基准从原数组中删除
let pivot = arr.splice(pivotIndex,1)[0];
// 定义左右数组
let left = [];
let right = [];
// 比基准小的放在left,比基准大的放在right
arr.forEach(item => {
if(item < pivot){
left.push(item);
}else {
right.push(item);
}
});
return sort(left).concat([pivot],sort(right));
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
直接插入排序
基本思想:
在已排序的数据序列从后向前扫描,找到对应的位置插入未排序的数据。
插入排序通常采用占位的形式,空间复杂度为O(1)。
因此,从后向前扫描的过程中,需要反复的把已排序的元素逐步向后挪位,为新插入元素提供插入的位置。
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到下一位置中
- 重复步骤2
let arr = [2,44,3,13,11,35,8]
function sort(arr){
for(let i=1,len=arr.length;i<len;i++){
let temp = arr[i]; // 需要排序的元素
let j=i-1; // 默认已排序元素的最后一位的索引
while(j>=0 && arr[j]>temp){ // 在已排序好的队列中从后向前扫描
arr[j+1]=arr[j]; // 已排序的元素大于新元素,将该元素移到下一个位置
j--;
}
arr[j+1]=temp; // 当已排序的元素<=新元素或到头了,即可将新元素加到该元素后面或首位
}
return arr
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
希尔排序
希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高的版本,也称为缩小增量排序,同时该算法是冲破O(n^2)的第一批算法之一。它与插入排序的不同之处在于,它会优先比较距离较远的元素。
基本思路:
根据增量来分割数组,对每一组进行直接插入排序
let arr = [2,44,3,13,11,35,8]
function sort(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;
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
简单选择排序
选择排序是一种简单直观的排序算法
基本思路:
首先在未排序序列中找到最小/最大 元素,存放在排序序列的起始位置;然后,再从剩余未排序元素中继续寻找最小/最大元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
let arr = [2,44,3,13,11,35,8]
function sort(arr){
let len = arr.length;
let minIndex,temp;
for(let i=0;i<len-1;i++){
minIndex=i;
for(let j=i+1;j<len;j++){ // 循环寻找最小的数
if(arr[j]<arr[minIndex]){
minIndex=j; // 保存最小数的索引
}
}
[arr[i],arr[minIndex]] = [arr[minIndex],arr[i]];
}
return arr;
}
console.log(sort(arr)) // [ 2, 3, 8, 11, 13, 35, 44 ]
堆排序
参考网站
堆排序可以说是一种利用堆的概念来排序的选择排序,分为两种方法:
- 大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列
- 小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列
我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
// 交换两个节点
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 = [50,45,40,20,25,35,30,10,15]
heapSort(arr);
console.log(arr) // [ 10, 15, 20, 25, 30, 35, 40, 45, 50 ]
二路归并排序
参考链接
归并排序采用的是分治的思想,首先是“分”,将一个数组反复二分为两个小数组,直到每个数组只有一个元素;其次是“治”,从最小数组开始,两两按大小顺序合并,直到并为原始数组大小,下面是图解:
// 融合两个有序数组,这里实际上是将数组 arr 分为两个数组
function mergeArray(arr, first, mid, last, temp) {
let i = first;
let m = mid;
let j = mid+1;
let n = last;
let k = 0;
while(i<=m && j<=n) {
if(arr[i] < arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while(i<=m) {
temp[k++] = arr[i++];
}
while(j<=n) {
temp[k++] = arr[j++];
}
for(let l=0; l<k; l++) {
arr[first+l] = temp[l];
}
return arr;
}
// 递归实现归并排序
function mergeSort(arr, first, last, temp) {
if(first<last) {
let mid = Math.floor((first+last)/2);
mergeSort(arr, first, mid, temp); // 左子数组有序
mergeSort(arr, mid+1, last, temp); // 右子数组有序
arr = mergeArray(arr, first, mid, last, temp);
}
return arr;
}
// example
let arr = [2,44,3,13,11,35,8]
let temp = new Array();
console.log(mergeSort(arr, 0 ,arr.length-1, temp)); // [ 2, 3, 8, 11, 13, 35, 44 ]
持续更新中。。。