数据结构JavaScript实现回顾之排序查找

一. 排序算法

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......

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值