js 常用排序算法

1 篇文章 0 订阅

一、冒泡排序

1、基本思想

一个数组arr=[9,5,8,4,7,3,2],冒泡就是从数组第一个值开始与依次与之后的值比较,如果是从小到大排序,那么9先和5比较,9大就换与5交换位置,再和8比较还大,再和8交换位置,继续。。。直到2还大,那么9放在了数组的最后,下一次比较的数组变为arr=[5,8,4,7,3,2,9],这样再来一轮5和其他值比较形成arr=[4,8,3,7,2,5,9],这样继续循环直到完成从小到大排序,当然反过来也是一样的如果是从大到小,那么如果大就不换位置。

2、 冒泡排序代码

function bubblingSort(){
	for (let i = 0; i < arr.length - 1; i++) {//外层循环控制跑几轮
	  for (let j = 0; j < arr.length - i - 1; j++) {//里面的循环 负责每轮交换的次数
	  //内部交换两个变量的值 前一个和后一个元素相比较
		if (arr[j] > arr[j + 1]) {
			let temp = arr[j]
			arr[j] = arr[j + 1]
			arr[j + 1] = temp
		}
	  }
	}
	return arr
}
let arr = [9, 5, 8, 4, 7, 3, 2]
console.log(bubblingSort(arr))

二、选择排序

1、基本思想

  • 首先在未排序的数列中找到最小(大)元素,然后将其存放到数列的起始位置
  • 接着,再从剩余未排序的元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  • 以此类推,直到所有元素均排序完毕。

选择排序的主要优点与数据移动有关:

  • 如果某个元素位于正确的最终位置,则它不会被移动
  • 选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。
  • 在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

2、流程:

1、首先将要排序的数组复制到一个新数组中,这样原数组不会被改变。
2、初始化最小数字的索引值为0,然后在数组中循环,在当前索引后面的元素中找到最小的数字的索引。
3、如果当前索引位置的数字不是最小数字,那么将这两个数字互换。
继续寻找下一个数字,直到索引到最后一个元素,此时整个数组已经是从小到大排序的了。
4、重复上面的步骤,每次排序的范围都会减少一个,直到整个数组排序完毕。

3、选择排序代码

function selectionSort() {
  // 循环遍历整个数组
  for (let i = 0; i < arr.length; i++) {
    // 预设最小数的索引为当前循环的索引
    let minIndex = i;
    // 在后面的数中寻找更小的数
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        // 如果找到更小的数,记录它的索引
        minIndex = j;
      }
    }
    // 如果当前循环的索引不是最小数的索引,交换它们
    if (i !== minIndex) {
      [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
    }
  }
  // 返回排序后的数组
  return arr;
}

// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = selectionSort(testArr);
// 打印结果
console.log(sortedArr);

4、代码的详细说明

  • 1、首先循环遍历整个数组。
  • 2、在每一次循环中,预设最小数的索引为当前循环的索引。
  • 3、在后面的数中寻找更小的数,如果找到更小的数,记录它的索引。
  • 4、如果当前循环的索引不是最小数的索引,交换它们。
  • 5、重复步骤2-4,直到遍历完整个数组。
  • 6、返回排序后的数组。

5、时间复杂度

计算选择排序算法的时间复杂度,通常是通过分析算法中每一步的执行次数来确定的。

我们分析选择排序中的每一步,再将每一步的时间复杂度加起来,最后得到的就是选择排序的时间复杂度。

在选择排序中,最多的操作是内层循环,其执行了N-1次,并且每次执行内层循环都要花费O(N)的时间。

因此,内层循环的时间复杂度是O(N^2)。

外层循环也要执行N-1次,因此,它的时间复杂度也是O(N^2)。

所以,整个选择排序算法的时间复杂度是O(N^2)。

6、选择排序的总结

  • 选择排序是一种简单易懂的排序算法。
  • 它的基本思想是遍历整个列表,每次找出最小的元素,并且将它移到列表的最左边,重复这个过程直到整个列表都有序排列。
  • 在平均情况下,选择排序的时间复杂度为 O(n^2),在最坏情况下与最好情况下都为 O(n^2)。
  • 选择排序在数据规模较小时非常适用,在数据规模较大时不够高效。

三、插入排序

1、基本思想

插入排序就像是你打扑克牌,你从牌堆顶取一张牌,找到合适的位置插入到已有牌的顺序中,并不断重复这一步骤直到所有的牌都被插入到合适的位置,最终使得整副牌有序。

与打牌类似,插入排序(Insertion sort)的实现方法是:

  • 首先假设第一个数据是已经排好序的,接着取出下一个数据,在已经排好序的数据中从后往前扫描,找到比它小的数的位置,将该位置之后的数整体后移一个单位,然后再将该数插入到该位置。
  • 不断重复上述操作,直到所有的数据都插入到已经排好序的数据中,排序完成。

插入排序的优势在于它的性能表现在已经有序的序列上比冒泡排序、选择排序两种算法要好。

  • 它的时间复杂度为O(n),因此,如果序列已经被排好,插入排序将会比冒泡排序和选择排序快得多。
  • 另外,插入排序空间复杂度为O(1),因此,对于内存限制较小的情况,插入排序也是一个更优的选择。

2、插入排序的流程

1、首先,假设数组的第一个元素已经排好序了,因为它只有一个元素,所以可以认为是有序的。
2、然后,从第二个元素开始,不断与前面的有序数组元素进行比较。
3、如果当前元素小于前面的有序数组元素,则把当前元素插入到前面的合适位置。
4、否则,继续与前面的有序数组元素进行比较。
5、以此类推,直到整个数组都有序。
6、循环步骤2~5,直到最后一个元素。
7、完成排序。

3、插入排序代码

function insertionSort(arr){
  // 对于数组的每一个元素,从它开始到0位置,比较该元素和前一个元素的大小
  for (let i = 1; i < arr.length; i++) {
    let current = arr[i];
    let j = i - 1;
    // 如果该元素小于前一个元素,那么前一个元素向后移动,并继续向前比较
    while (j >= 0 && arr[j] > current) {
      arr[j + 1] = arr[j];
      j--;
    }
    // 如果该元素大于前一个元素,那么它将放到合适的位置
    arr[j + 1] = current;
  }
  // 返回排序后的数组
  return arr;
}

// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = insertionSort(testArr);
// 打印结果
console.log(sortedArr);

4、代码的详细说明

1、首先我们定义了一个 insertSort 函数,并传入一个数字数组作为参数。
2、接着我们定义一个变量 current,它将存储当前需要比较的数字。
3、然后我们使用一个循环,将数组的第二项到最后一项依次与前面的数字进行比较。
4、在内层循环中,我们首先将 j 定义为 i-1,然后每次执行循环时,如果 j 大于等于 0 并且 arr[j] 大于 current,我们就交换 arr[j] 和 arr[j + 1] 的值。
5、在循环结束后,我们将 current 插入到正确的位置,并继续比较下一个数字。
6、当所有数字都被比较过后,我们就可以返回最终排序好的数组。

5、时间复杂度

1、插入排序的时间复杂度在最好的情况下为O(n),在最坏的情况下为O(n2),平均时间复杂度为O(n2)。

2、当数据已经有序时,插入排序只需要做n-1次比较和0次移动,运行时间为O(n);

3、当数据完全逆序时,插入排序需要做n-1趟比较和3/2*(n-1)2/2次移动,运行时间为O(n2)。

4、由于插入排序的最好时间复杂度与最坏时间复杂度都接近O(n^2),所以插入排序适用于数据规模不大的场合,如果数据规模很大,通常使用其他算法。

6、插入排序的总结

  • 插入排序是一种简单而直观的排序算法,它可以快速地对部分有序的数组进行排序。

  • 插入排序通过比较相邻的元素并在需要时将其交换,来实现从小到大的排列。

  • 插入排序的时间复杂度在最好情况下是线性O(n),最坏情况下是O(n^2)。
    总而言之,如果数组部分有序,插入排序可以比冒泡排序和选择排序更快。

  • 但是如果数组完全逆序,则插入排序的时间复杂度比较高,不如快速排序或归并排序。

  • 因此,在选择排序算法时,应该根据需要选择合适的算法。

四、归并排序

1、基本思想

  • 将待排序数组分成若干个子数组。
  • 然后将相邻的子数组归并成一个有序数组。
  • 最后再将这些有序数组归并(merge)成一个整体有序的数组。

这个算法最早出现在1945年,由约翰·冯·诺伊曼(John von Neumann)(又一个天才,现代计算机之父,冯·诺依曼结构、普林斯顿结构)首次提出。
当时他在为美国政府工作,研究原子弹的问题。
由于当时计算机,他在研究中提出了一种高效计算的方法,这个方法就是归并排序。

归并排序的基本思路是先将待排序数组递归地拆分成两个子数组,然后对每个子数组进行排序,最后将两个有序子数组合并成一个有序数组。

在实现中,我们可以使用“分治法”来完成这个过程,即将大问题分解成小问题来解决。 归并排序的算法复杂度为 O(nlogn),是一种比较高效的排序算法,因此在实际应用中被广泛使用。
虽然归并排序看起来比较复杂,但是只要理解了基本思路,实现起来并不困难,而且它还是一个非常有趣的算法。

2、归并排序的流程

归并排序是一种基于分治思想的排序算法,其基本思路可以分为三个步骤:

  • 步骤一:分解(Divide):归并排序使用递归算法来实现分解过程,具体实现中可以分为以下几个步骤:

    如果待排序数组长度为1,认为这个数组已经有序,直接返回;
    将待排序数组分成两个长度相等的子数组,分别对这两个子数组进行递归排序;
    将两个排好序的子数组合并成一个有序数组,返回这个有序数组。

  • 步骤二:合并(Merge):合并过程中,需要比较每个子数组的元素并将它们有序地合并成一个新的数组:
    可以使用两个指针 i 和 j 分别指向两个子数组的开头,比较它们的元素大小,并将小的元素插入到新的有序数组中。
    如果其中一个子数组已经遍历完,就将另一个子数组的剩余部分直接插入到新的有序数组中。
    最后返回这个有序数组。

  • 步骤三:归并排序的递归终止条件:

归并排序使用递归算法来实现分解过程,当子数组的长度为1时,认为这个子数组已经有序,递归结束。总体来看,归并排序的基本思路是分治法,分成子问题分别解决,然后将子问题的解合并成整体的解。

3、归并排序代码

// 定义函数mergeSort,参数是待排序数组arr
function mergeSort(arr) {
    // 计算数组长度
    const n = arr.length;
    // 如果数组长度小于等于1,则直接返回该数组
    if (n <= 1) {
        return arr;
    }
    // 计算中间位置
    const middle = Math.floor(n / 2);
    // 对左边的数组进行归并排序
    const left = mergeSort(arr.slice(0, middle));
    // 对右边的数组进行归并排序
    const right = mergeSort(arr.slice(middle));
    // 合并两个排好序的数组
    return merge(left, right);
}

// 定义函数merge,参数是两个排好序的数组left和right
function merge(left, right) {
    // 定义指针变量,分别指向两个数组的开头
    let i = 0, j = 0;
    // 定义一个空数组,用来存放合并后的数组
    const result = [];
    // 比较两个数组的第一个元素,将较小的放入result数组
    while (i < left.length && j < right.length) {
        if (left[i] < right[j]) {
            result.push(left[i++]);
        } else {
            result.push(right[j++]);
        }
    }
    // 将没有比较完的剩余元素放入result数组
    while (i < left.length) {
        result.push(left[i++]);
    }
    while (j < right.length) {
        result.push(right[j++]);
    }
    // 返回合并后的数组
    return result;
}


// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = mergeSort(testArr);
// 打印结果
console.log(sortedArr);

4、代码执行的过程

1、mergeSort 函数实现归并排序的递归调用,在该函数内,如果数组的长度小于等于1,直接返回该数组。

2、如果数组的长度大于1,那么执行以下代码:

	先计算数组的中点,并将数组分为左右两半。
	递归调用左边和右边的数组,最终得到两个有序的数组。

3、merge 函数实现将两个有序的数组合并为一个有序的数组。

5、时间复杂度

复杂度的分析过程:

假设数组长度为 n,需要进行 logn 次归并操作;
每次归并操作需要 O(n) 的时间复杂度;
因此,归并排序的时间复杂度为 O(nlogn)。

最好情况: O(log n)

最好情况下,待排序数组已经是有序的了,那么每个子数组都只需要合并一次,
即只需要进行一次归并操作。
因此,此时的时间复杂度是 O(log n)。

最坏情况: O(nlogn)

最坏情况下,待排序数组是逆序的,那么每个子数组都需要进行多次合并。
因此,此时的时间复杂度为 O(nlogn)。

平均情况: O(nlogn)

在平均情况下,我们假设待排序数组中任意两个元素都是等概率出现的。
此时,可以证明归并排序的时间复杂度为 O(nlogn)。

6、归并排序的总结

归并排序是一种分治策略的排序算法,是利用分治的思想将一个大问题分成小问题,并在适当的地方合并它们以解决该问题的方法。

它是一种稳定的排序算法,时间复杂度为O(nlogn)。

归并排序使用了额外的空间,因此更适合处理大数据。

归并排序的基本流程是通过递归将数组分成两半,
分别进行递归排序,最终再进行合并。
具体来说,将数组的中间元素作为分界点,
分别对左右两边的数组进行排序,并在排序完成后进行合并。

归并排序的代码实现较为简单,但要注意关于递归函数和合并函数的实现。

归并排序是一种不需要过多研究的算法,适合于所有的排序场景。

五、 快速排序

1、基本思想

又称分区交换排序(partition-exchange sort),简称快排

  • 由Tony Hoare在1959年发明。
  • 快速排序使用了分治的思想,将数组划分为两个子数组,每个子数组再分别进行排序,最终实现整个数组的排序。
  • 快速排序的特点是时间复杂度较好,平均情况下为O(nlogn)。
  • 另外,快速排序是一种原地排序算法,不需要额外的存储空间。

快速排序是一种非常流行的排序算法,因为它的时间复杂度和实现方式非常优秀。

快速排序广泛应用于各种场景,如数据库、搜索引擎等,其高效的排序速度和低空间复杂度使得它成为了一种非常重要的排序算法。

2、排序的流程

快速排序的思路可以分解成以下几个步骤:

1、首先,我们需要选择一个基准元素,通常选择第一个或最后一个元素作为基准元素。
2、然后,我们定义两个指针 i 和 j,分别指向数组的左右两端。
3、接下来,我们从右侧开始,向左移动 j 指针,直到找到一个小于或等于基准元素的值。
4、然后,我们从左侧开始,向右移动 i 指针,直到找到一个大于或等于基准元素的值。
5、如果 i 指针小于或等于 j 指针,交换 i 和 j 指针所指向的元素。
6、重复步骤 3-5,直到 i 指针大于 j 指针,这时,我们将基准元素与 j 指针所指向的元素交换位置,将基准元素放到中间位置。
7、接着,我们将数组分为两部分,左侧部分包含小于或等于基准元素的元素,右侧部分包含大于基准元素的元素。
8、然后,对左右两部分分别进行递归调用快速排序,直到左右两部分只剩下一个元素。
9、最终,整个数组就变得有序了。

3、快速排序代码

// 定义快速排序函数,参数为待排序的数组
function quickSort(array) {
    // 定义辅助函数,用于排序
    function sort(left, right) {
        // 如果左边的索引比右边的索引大,说明区间内已经没有数据,退出函数
        if (left >= right) {
            return;
        }
        // 取出基准数
        let pivot = array[left];
        // 定义两个指针
        let i = left;
        let j = right;
        // 开始排序
        while (i < j) {
            // 从右边开始搜索,直到找到比基准数小的数
            while (i < j && array[j] >= pivot) {
                j--;
            }
            // 如果找到了,则将该数存放在左边
            if (i < j) {
                array[i] = array[j];
                i++;
            }
            // 从左边开始搜索,直到找到比基准数大的数
            while (i < j && array[i] <= pivot) {
                i++;
            }
            // 如果找到了,则将该数存放在右边
            if (i < j) {
                array[j] = array[i];
                j--;
            }
        }
        // 将基准数存放在最终的位置上
        array[i] = pivot;
        // 递归处理基准数左边的数据
        sort(left, i - 1);
        // 递归处理基准数右边的数据
        sort(i + 1, right);
    }
    // 调用辅助函数,开始排序
    sort(0, array.length - 1);
    // 返回排序后的数组
    return array;
}


// 测试数据
const testArr = [5, 2, 9, 1, 5, 6];
// 调用插入排序函数
const sortedArr = quickSort(testArr);
// 打印结果
console.log(sortedArr);

4、代码执行的过程

1、在数组中选择一个元素作为基准元素(通常是数组的第一个元素)
2、将小于等于基准元素的元素移到数组的左边,大于基准元素的元素移到数组的右边
3、递归地对左半部分数组和右半部分数组进行快速排序

5、时间复杂度

快速排序的复杂度分析需要从时间复杂度和空间复杂度两个方面来考虑。

时间复杂度: 快速排序是一种分治思想的排序算法,它的时间复杂度取决于基准数的选取。

最坏情况下,每次选取的基准数为序列中的最大或最小数,
导致划分的两部分长度不平均,递归的次数将会达到O(n),
因此时间复杂度为O(n^2)。
最优情况下,每次选取的基准数将整个序列划分成两个长度大致相等的部分,
递归的次数将会达到log2n,因此时间复杂度为O(nlogn)。
平均情况下,时间复杂度为O(nlogn)。
空间复杂度: 快速排序是一种原地排序算法,它不需要额外的存储空间。

在递归过程中,空间复杂度仅为递归调用的栈空间,因此空间复杂度为O(logn)。
总结: 快速排序的时间复杂度为O(nlogn),空间复杂度为O(logn),是一种高效的排序算法。

6、快速排序的总结

快速排序是一种分治算法,它的思想是通过选定一个基准值,将数组分成两个部分,左边部分的元素都小于等于基准值,右边部分的元素都大于基准值,然后再递归地对左右两个部分进行快速排序。

快速排序的时间复杂度为 O(nlogn)

在最坏情况下,时间复杂度可能退化为O(n^2)。
但是它的平均时间复杂度是O(nlogn)。

快速排序的空间复杂度为 O(logn)

因为它需要递归调用,但是它的空间复杂度与数据的大小并不相关,
所以它不会导致内存使用方面的困难。

总的来说,快速排序是一种高效的排序算法,适用于大多数的排序场景。

六、 …排序

1、基本思想

2、排序的流程

3、排序代码

4、代码执行的过程

5、时间复杂度

6、排序的总结

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值