大发pk10_pk10下载_大发pk10下载 - 由大发pk10,pk10下载,大发pk10下载社主办的《大发pk10,pk10下载,大发pk10下载》是我国消费领域中一张全国性、全方位、大容量的综合性日报。其立足消费网投领域,依托轻工行业,面向城乡市场,最先发布相关的专业权威资讯。

JavaScript算法实现——排序

  • 时间:
  • 浏览:1

  在计算机编程中,排序算法是最常用的算法之一,本文介绍了几种常见的排序算法以及它们之间的差异和错综复杂度。

冒泡排序

  冒泡排序应该是最简单的排序算法了,在所有讲解计算机编程和数据特性的课程中,无一例外都会拿冒泡排序作为开篇来讲解排序的原理。冒泡排序理解起来也很容易,但是 一八个 嵌套循环遍历数组,对数组中的元素两两进行比较,你要前者比后者大,则交换位置(这是针对升序排序而言,你可是降序排序,则比较的原则是前者比后者小)。大伙儿来看下冒泡排序的实现:

function bubbleSort(array) {
    let length = array.length;
    for (let i = 0; i < length; i++) {
        for (let j = 0; j < length - 1; j++) {
            if (array[j] > array[j + 1]) {
                [array[j], array[j + 1]] = [array[j + 1], array[j]];
            }
        }
    }
}

  里边这段代码但是 经典的冒泡排序算法(升序排序),只不过交换一八个 元素位置的次要大伙儿这麼用传统的写法(传统写法必须引入一八个 临时变量,用来交换一八个 变量的值),这里使用了ES6的新功能,大伙儿还并能 使用这些语法特性很方便地实现一八个 变量值的交换。来看下对应的测试结果:

let array = [];
for (let i = 5; i > 0; i--) {
    array.push(i);
}

console.log(array.toString()); // 5,4,3,2,1
bubbleSort(array);
console.log(array.toString()); // 1,2,3,4,5

   在冒泡排序中,对于内层的循环而言,每一次有的是把这些轮中的最大值倒进最后(相对于升序排序),它的过程是从前的:第一次内层循环,找出数组中的最大值排到数组的最后;第二次内层循环,找出数组中的次大值排到数组的倒数第二位;第三次内层循环,找出数组中的第三大值排到数组的倒数第三位......以此类推。但是 ,对于内层循环,大伙儿还并能 无需每一次都遍历到length - 1的位置,而只必须遍历到length - 1 - i的位置就还并能 了,从前还并能 减少内层循环遍历的次数。下面是改进后的冒泡排序算法:

function bubbleSortImproved(array) {
    let length = array.length;
    for (let i = 0; i < length; i++) {
        for (let j = 0; j < length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                [array[j], array[j + 1]] = [array[j + 1], array[j]];
            }
        }
    }
}

  运行测试,结果和前面的bubbleSort()最好的土办法得到的结果是相同的。

let array = [];
for (let i = 5; i > 0; i--) {
    array.push(i);
}

console.log(array.toString()); // 5,4,3,2,1
bubbleSortImproved(array);
console.log(array.toString()); // 1,2,3,4,5

  在实际应用中,大伙儿未必推荐使用冒泡排序算法,尽管它是最直观的用来讲解排序过程的算法。冒泡排序算法的错综复杂度为O(n2)

选泽排序

  选泽排序与冒泡排序很这类,它也必须一八个 嵌套的循环来遍历数组,只不过在每一次循环中要找出最小的元素(这是针对升序排序而言,你可是降序排序,则必须找出最大的元素)。第一次遍历找出最小的元素排在第一位,第二次遍历找出次小的元素排在第二位,以此类推。大伙儿来看下选泽排序的的实现:

function selectionSort(array) {
    let length = array.length;
    let min;

    for (let i = 0; i < length - 1; i++) {
        min = i;
        for (let j = i; j < length; j++) {
            if (array[min] > array[j]) {
                min = j;
            }
        }

        if (i !== min) {
            [array[i], array[min]] = [array[min], array[i]];
        }
    }
}

  里边这段代码是升序选泽排序,它的执行过程是从前的,首先将第一八个 元素作为最小元素min,你要在内层循环中遍历数组的每一八个 元素,你要有元素的值比min小,就将该元素的值赋值给min。内层遍历完成后,你要数组的第一八个 元素和min不相同,则将它们交换一下位置。你要再将第八个元素作为最小元素min,重复前面的过程。直到数组的每一八个 元素都比较完毕。下面是测试结果:

let array = [];
for (let i = 5; i > 0; i--) {
    array.push(i);
}

console.log(array.toString()); // 5,4,3,2,1
selectionSort(array);
console.log(array.toString()); // 1,2,3,4,5

  选泽排序算法的错综复杂度与冒泡排序一样,也是O(n2)

插入排序

  插入排序与前一八个 排序算法的思路不太一样,为了便于理解,大伙儿以[ 5, 4, 3, 2, 1 ]这些数组为例,用下图来说明插入排序的整个执行过程:

  在插入排序中,对数组的遍历是从第八个元素但是开始了的,tmp是个临时变量,用来保存当前位置的元素。你要从当前位置但是开始了,取前一八个 位置的元素与tmp进行比较,你要值大于tmp(针对升序排序而言),则将这些元素的值插入到这些位置中,最后将tmp倒进数组的第一八个 位置(索引号为0)。反复执行这些过程,直到数组元素遍历完毕。下面是插入排序算法的实现:

function insertionSort(array) {
    let length = array.length;
    let j, tmp;

    for (let i = 1; i < length; i++) {
        j = i;
        tmp = array[i];
        while (j > 0 && array[j - 1] > tmp) {
            array[j] = array[j - 1];
            j--;
        }
        array[j] = tmp;
    }
}

  对应的测试结果:

let array = [];
for (let i = 5; i > 0; i--) {
    array.push(i);
}

console.log(array.toString()); // 5,4,3,2,1
insertionSort(array);
console.log(array.toString()); // 1,2,3,4,5

  插入排序比冒泡排序和选泽排序算法的性能要好。

归并排序

  归并排序比前面介绍的几种排序算法性能有的是好,它的错综复杂度为O(nlogn)

  归并排序的基本思路是通过递归调用将给定的数组不断分割成最小的两次要(每一次要必须一八个 元素),对这两次要进行排序,你要向上合并成一八个 大数组。大伙儿还是以[ 5, 4, 3, 2, 1 ]这些数组为例,来看下归并排序的整个执行过程:

  首比较慢将数组分成一八个 次要,对于非偶数长度的数组,你要自行决定将多的分到左边你要右边。你要按照这些最好的土办法进行递归,直到数组的左右两次要都必须一八个 元素。对这两次要进行排序,递归向上返回的过程中将其组成和一八个 全版的数组。下面是归并排序的算法的实现:

const merge = (left, right) => {
    let i = 0;
    let j = 0;
    const result = [];

    // 通过这些while循环将left和right中较小的次要倒进result中
    while (i < left.length && j < right.length) {
        if (left[i] < right[i]) result.push(left[i++]);
        else result.push(right[j++]);
    }

    // 你要将组合left或right中的剩余次要
    return result.concat(i < left.length ? left.slice(i) : right.slice(j));
};

function mergeSort(array) {
    let length = array.length;
    if (length > 1) {
        const middle = Math.floor(length / 2); // 找出array的里边位置
        const left = mergeSort(array.slice(0, middle)); // 递归找出最小left
        const right = mergeSort(array.slice(middle, length)); // 递归找出最小right
        array = merge(left, right); // 将left和right进行排序
    }
    return array;
}

  主函数mergeSort()通过递归调用四种 得到left和right的最小单元,这里大伙儿使用Math.floor(length / 2)将数组中较少的次要倒进left中,将数组中较多的次要倒进right中,你要使用Math.ceil(length / 2)实现相反的效果。你要调用merge()函数对这两次要进行排序与合并。注意在merge()函数中,while循环次要的作用是将left和right中较小的次要存入result数组(针对升序排序而言),励志的话 result.concat(i < left.length ? left.slice(i) : right.slice(j))的作用则是将left和right中剩余的次要加到result数组中。考虑到递归调用,假如有一天最小次要你要排好序了,这麼在递归返回的过程中只必须把left和right这两次要的顺序组合正确就能完成对整个数组的排序。

  对应的测试结果:

let array = [];
for (let i = 5; i > 0; i--) {
    array.push(i);
}

console.log(array.toString()); // 5,4,3,2,1
console.log(mergeSort(array).toString()); // 1,2,3,4,5

快速排序

  快速排序的错综复杂度也是O(nlogn),但它的性能要优于其它排序算法。快速排序与归并排序这类,其基本思路也是将一八个 大数组分为较小的数组,但它不像归并排序一样将它们分割开。快速排序算法比较错综复杂,大致过程为:

  1. 从给定的数组中选泽一八个 参考元素。参考元素还并能 是任意元素,也还并能 是数组的第一八个 元素,大伙儿这里选泽里边位置的元素(你要数组长度为偶数,则向下取一八个 位置),从前在大多数状况下还并能 提高速率。
  2. 创建一八个 指针,一八个 指向数组的最左边,一八个 指向数组的最右边。移动左指针直到找到比参考元素大的元素,移动右指针直到找到比参考元素小的元素,你要交换左右指针对应的元素。重复这些过程,直到左指针超过右指针(即左指针的索引号大于右指针的索引号)。通过这些操作,比参考元素小的元素都排在参考元素但是,比参考元素大的元素都排在参考元素但是(针对升序排序而言)。
  3. 以参考元素为分隔点,对左右一八个 较小的数组重复上述过程,直到整个数组完成排序。

  下面是快速排序算法的实现:

const partition = (array, left, right) => {
    const pivot = array[Math.floor((right + left) / 2)];
    let i = left;
    let j = right;

    while (i <= j) {
        while (array[i] < pivot) {
            i++;
        }
        while (array[j] > pivot) {
            j--;
        }
        if (i <= j) {
            [array[i], array[j]] = [array[j], array[i]];
            i++;
            j--;
        }
    }
    return i;
};

const quick = (array, left, right) => {
    let length = array.length;
    let index;
    if (length > 1) {
        index = partition(array, left, right);
        if (left < index - 1) {
            quick(array, left, index - 1);
        }
        if (index < right) {
            quick(array, index, right);
        }
    }
    return array;
};

function quickSort(array) {
    return quick(array, 0, array.length - 1);
}

  假定数组为[ 3, 5, 1, 6, 4, 7, 2 ],按照里边的代码逻辑,整个排序的过程如下图所示:

  下面是测试结果:

let array = [3, 5, 1, 6, 4, 7, 2];
console.log(array.toString()); // 3,5,1,6,4,7,2
console.log(quickSort(array).toString()); // 1,2,3,4,5,6,7

  快速排序算法理解起来这些难度,还并能 按照里边给出的示意图逐步推导一遍,以帮助理解整个算法的实现原理。

堆排序

  在计算机科学中,堆是四种 特殊的数据特性,它通常用树来表示数组。堆有以下特点:

  • 堆是一棵全版二叉树
  • 子节点的值不大于父节点的值(最大堆),你要子节点的值不小于父节点的值(最小堆)
  • 根节点的索引号为0
  • 子节点的索引为父节点索引 × 2 + 1
  • 右子节点的索引为父节点索引 × 2 + 2

  堆排序是四种 比较高效的排序算法。

  在堆排序中,大伙儿未必必须将数组元素插入到堆中,而但是 通过交换来形成堆,以数组[ 3, 5, 1, 6, 4, 7, 2 ]为例,大伙儿用下图来表示其初始状况:

  这麼,怎么还可以将其转打上去一八个 符合标准的堆特性呢?先来看看堆排序算法的实现:

const heapify = (array, heapSize, index) => {
    let largest = index;
    const left = index * 2 + 1;
    const right = index * 2 + 2;
    if (left < heapSize && array[left] > array[index]) {
        largest = left;
    }
    if (right < heapSize && array[right] > array[largest]) {
        largest = right;
    }
    if (largest !== index) {
        [array[index], array[largest]] = [array[largest], array[index]];
        heapify(array, heapSize, largest);
    }
};

const buildHeap = (array) => {
    let heapSize = array.length;
    for (let i = heapSize; i >= 0; i--) {
        heapify(array, heapSize, i);
    }
};

function heapSort(array) {
    let heapSize = array.length;
    buildHeap(array);

    while (heapSize > 1) {
        heapSize--;
        [array[0], array[heapSize]] = [array[heapSize], array[0]];
        heapify(array, heapSize, 0);
    }

    return array;
}

  函数buildHeap()将给定的数组转打上去堆(按最大堆处置)。下面是将数组[ 3, 5, 1, 6, 4, 7, 2 ]转打上去堆的过程示意图:

  在函数buildHeap()中,大伙儿从数组的尾部但是开始了遍历去查看每个节点算不算 符合堆的特点。在遍历的过程中,大伙儿发现当索引号为6、5、4、3时,其左右子节点的索引大小都超出了数组的长度,这原应分析它们有的是叶子节点。这麼大伙儿真正要做的但是 从索引号为2的节点但是开始了。我我人太好从这些点考虑,结合大伙儿利用全版二叉树来表示数组的特性,还并能 对buildHeap()函数进行优化,将其中的for循环修改为下面从前,以打上去对子节点的操作。

for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
    heapify(array, heapSize, i);
}

  从索引2但是开始了,大伙儿查看它的左右子节点的值算不算 大于另一方,你可是,则将其中最大的那个值与另一方交换,你要向下递归查找算不算 还必须对子节点继续进行操作。索引2处置完但是再处置索引1,你可是索引0,最终转换出来的堆如图中的4所示。你要发现,每一次堆转换完成但是,排在数组第一八个 位置的但是 堆的根节点,也但是 数组的最大元素。根据这些特点,大伙儿还并能 很方便地对堆进行排序,其过程是:

  • 将数组的第一八个 元素和最后一八个 元素交换
  • 减少数组的长度,从索引0但是开始了重新转换堆

  直到整个过程但是开始了。对应的示意图如下:

  堆排序的核心次要在于怎么还可以将数组转打上去堆,也但是 里边代码中buildHeap()和heapify()函数次要。

  同样给出堆排序的测试结果:

let array = [3, 5, 1, 6, 4, 7, 2];
console.log(array.toString()); // 3,5,1,6,4,7,2
console.log(heapSort(array).toString()); // 1,2,3,4,5,6,7

有关算法错综复杂度

  里边大伙儿在介绍各种排序算法的但是,提到了算法的错综复杂度,算法错综复杂度用大O表示法,它是用大O表示的一八个 函数,如:

  • O(1):常数
  • O(log(n)):对数
  • O(log(n) c):对数多项式
  • O(n):线性
  • O(n2):二次
  • O(nc):多项式
  • O(cn):指数

  大伙儿怎么还可以理解大O表示法呢?看一八个 例子:

function increment(num) {
    return ++num;
}

  对于函数increment(),无论我传入的参数num的值是什么数字,它的运行时间有的是X(相对于同一台机器而言)。函数increment()的性能与参数无关,你要大伙儿还并能 说它的算法错综复杂度是O(1)(常数)。

  再看一八个 例子:

function sequentialSearch(array, item) {
    for (let i = 0; i < array.length; i++) {
        if (item === array[i]) return i;
    }
    return -1;
}

  函数sequentialSearch()的作用是在数组中搜索给定的值,并返回对应的索引号。假设array有10个元素,你要要搜索的元素排在第一八个 ,大伙儿说开销为1。你要要搜索的元素排在最后一八个 ,则开销为10。当数组有2000个元素时,搜索最后一八个 元素的开销是2000。但是 ,sequentialSearch()函数的总开销取决于数组元素的个数和要搜索的值。在最坏状况下,这麼找到要搜索的元素,这麼总开销但是 数组的长度。你要大伙儿得出sequentialSearch()函数的时间错综复杂度是O(n),n是数组的长度。

  同理,对于前面大伙儿说的冒泡排序算法,里边有一八个 双层嵌套的for循环,你要它的错综复杂度为O(n2)。

  时间错综复杂度O(n)的代码必须一层循环,而O(n2)的代码有双层嵌套循环。你要算法有三层嵌套循环,它的时间错综复杂度但是 O(n3)。

  下表展示了各种不同数据特性的时间错综复杂度:

数据特性 一般状况 最差状况
插入 删除 搜索 插入 删除 搜索
数组/栈/队列 O(1) O(1) O(n) O(1) O(1) O(n)
链表 O(1) O(1) O(n) O(1) O(1) O(n)
双向链表 O(1) O(1) O(n) O(1) O(1) O(n)
散列表 O(1) O(1) O(1) O(n) O(n) O(n)
BST树 O(log(n)) O(log(n)) O(log(n)) O(n) O(n) O(n)
AVL树 O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n)) O(log(n))

数据特性的时间错综复杂度

节点/边的管理最好的土办法 存储空间 增加顶点 增加边 删除顶点 删除边 轮询
领接表 O(| V | + | E |) O(1) O(1) O(| V | + | E |) O(| E |) O(| V |)
邻接矩阵 O(| V |2) O(| V |2) O(1) O(| V |2) O(1) O(1)

图的时间错综复杂度  

算法(用于数组) 时间错综复杂度
最好状况 一般状况 最差状况
冒泡排序 O(n) O(n2) O(n3)
选泽排序 O(n2) O(n2) O(n2)
插入排序 O(n) O(n2) O(n2)
归并排序 O(log(n)) O(log(n)) O(log(n))
快速排序 O(log(n)) O(log(n)) O(n2)
堆排序 O(log(n)) O(log(n)) O(log(n))

排序算法的时间错综复杂度

搜索算法

  顺序搜索是四种 比较直观的搜索算法,里边介绍算法错综复杂度一小节中的sequentialSearch()函数但是 顺序搜索算法,但是 按顺序对数组中的元素逐一比较,直到找到匹配的元素。顺序搜索算法的速率比较低。

  还有四种 常见的搜索算法是二分搜索算法。它的执行过程是:

  1. 将待搜索数组排序。
  2. 选泽数组的里边值。
  3. 你要里边值正好是要搜索的值,则完成搜索。
  4. 你要要搜索的值比里边值小,则选泽里边值左边的次要,重新执行步骤2。
  5. 你要要搜索的值比里边值大,则选泽里边值右边的次要,重新执行步骤2。

  下面是二分搜索算法的具体实现:

function binarySearch(array, item) {
    quickSort(array); // 首先用快速排序法对array进行排序

    let low = 0;
    let high = array.length - 1;

    while (low <= high) {
        const mid = Math.floor((low + high) / 2); // 选泽里边位置的元素
        const element = array[mid];

        // 待搜索的值大于里边值
        if (element < item) low = mid + 1;
        // 待搜索的值小于里边值
        else if (element > item) high = mid - 1;
        // 待搜索的值但是

里边值
        else return true;
    }

    return false;
}

  对应的测试结果:

const array = [8, 7, 6, 5, 4, 3, 2, 1];
console.log(binarySearch(array, 2)); // true

   这些算法的基本思路不得劲这类于猜数字大小,每当你说什么出一八个 数字,我都会告诉你是大了还是小了,经过几轮但是,你就还并能 很准确地选泽数字的大小了。