手写数据结构 排序算法 篇(下)

简介: 手写数据结构 排序算法 篇(下)

手写数据结构 排序算法 篇(上)https://developer.aliyun.com/article/1392187

快速排序

// 快速排序入口
function quickSort(arr, left = 0, right = arr.length - 1) {
    // 定义递归边界,若数组只有一个元素,则没有排序必要
    if (arr.length > 1) {
        // lineIndex表示下一次划分左右子数组的索引位
        const lineIndex = partition(arr, left, right)
        // 如果左边子数组的长度不小于1,则递归快排这个子数组
        if (left < lineIndex - 1) {
            // 左子数组以 lineIndex-1 为右边界
            quickSort(arr, left, lineIndex - 1)
        }
        // 如果右边子数组的长度不小于1,则递归快排这个子数组
        if (lineIndex < right) {
            // 右子数组以 lineIndex 为左边界
            quickSort(arr, lineIndex, right)
        }
    }
    return arr
}
// 以基准值为轴心,划分左右子数组的过程
function partition(arr, left, right) {
    // 基准值默认取中间位置的元素
    let pivotValue = arr[Math.floor(left + (right - left) / 2)]
    // 初始化左右指针
    let i = left
    let j = right
    // 当左右指针不越界时,循环执行以下逻辑
    while (i <= j) {
        // 左指针所指元素若小于基准值,则右移左指针
        while (arr[i] < pivotValue) {
            i++
        }
        // 右指针所指元素大于基准值,则左移右指针
        while (arr[j] > pivotValue) {
            j--
        }
        // 若i<=j,则意味着基准值左边存在较大元素或右边存在较小元素,交换两个元素确保左右两侧有序
        if (i <= j) {
            swap(arr, i, j)
            i++
            j--
        }
    }
    // 返回左指针索引作为下一次划分左右子数组的依据
    return i
}
// 快速排序中使用 swap 的地方比较多,我们提取成一个独立的函数
function swap(arr, i, j) {
    [arr[i], arr[j]] = [arr[j], arr[i]]
}
let arr = [3, 4, 2, 8, 5, 5, 2, 0, 6, 6, 3]
console.log(quickSort(arr));

时间复杂度

快速排序的时间复杂度分析
快速排序的时间复杂度的好坏,是由基准值来决定的。
最好时间复杂度:它对应的是这种情况——我们每次选择基准值,都刚好是当前子数组的中间数。这时,可以确保每一次分割都能将数组分为两半,进而只需要递归 log(n) 次。这时,快速排序的时间复杂度分析思路和归并排序相似,最后结果也是 O(nlog(n))。
最坏时间复杂度:每次划分取到的都是当前数组中的最大值/最小值。大家可以尝试把这种情况代入快排的思路中,你会发现此时快排已经退化为了冒泡排序,对应的时间复杂度是 O(n^2)。
平均时间复杂度: O(nlog(n))

计数排序

function countingSort(array) {
    // 如果待排序的数组为空或只有一个元素,则不需要运行排序算法。
    if (array.length < 2) {
        return array;
    }
    // 数组中的最大值
    const maxValue = findMaxValue(array);
    // 对于计数排序算法,我们需要创建计数数组,从索引 0 开始直到最大值索引 value + 1
    const counts = new Array(maxValue + 1);
    array.forEach(element => {
        // 如果 counts 数组中用来计数某个元素的位置一开始没有用 0 初始化的话,我们将其赋值为 0
        if (!counts[element]) {
            counts[element] = 0;
        }
        // 迭代数组中的每个位置并在 counts 数组中增加元素计数值
        counts[element]++; 
    });
    // 从小到大挨个取出数组元素
    let sortedIndex = 0;
    counts.forEach((count, i) => {
        while (count > 0) {
            array[sortedIndex++] = i;
            count--;
        }
    });
    return array;
}
// 遍历查找数组最大元素
function findMaxValue(array) {
    let max = array[0];
    for (let i = 1; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    return max;
}
let arr = [3, 4, 2, 8, 5, 5, 2, 0, 6, 6, 3]
console.log(countingSort(arr));

时间复杂度

它是用来排序整数的优秀算法(它是一个整数排序算法),时间复杂度为 O(n+k),其中 k 是
临时计数数组的大小;但是,它确实需要更多的内存来存放临时数组。

桶排序

function bucketSort(array, bucketSize = 5) { // 指定需要多少桶来排序各个元素
  if (array.length < 2) {
    return array;
  }
  const buckets = createBuckets(array, bucketSize); // 创建桶并将元素分布到不同的桶中
  return sortBuckets(buckets); // 对每个桶执行插入排序算法和将所有桶合并为排序后的结果数组
}
function createBuckets(array, bucketSize) {
  let minValue = array[0];
  let maxValue = array[0];
  for (let i = 1; i < array.length; i++) { // 迭代原数组并找到最大值和最小值
    if (array[i] < minValue) {
      minValue = array[i];
    } else if (array[i] > maxValue) {
      maxValue = array[i];
    }
  }
  const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1; // 计算每个桶中需要分布的元素个数
  const buckets = [];
  for (let i = 0; i < bucketCount; i++) { // 初始化每个桶
    buckets[i] = [];
  }
  for (let i = 0; i < array.length; i++) { // 迭代数组中的每个元素
    const bucketIndex = Math.floor((array[i] - minValue) / bucketSize); // 计算要将元素放到哪个桶中 对比bucketCount
    buckets[bucketIndex].push(array[i]);
  }
  return buckets;
}
function sortBuckets(buckets) {
  const sortedArray = []; // 创建一个用作结果数组的新数组
  for (let i = 0; i < buckets.length; i++) { // {10}
    if (buckets[i] != null) {
      insertSort(buckets[i]); // 迭代每个可迭代的桶并应用插入排序
      sortedArray.push(...buckets[i]); // 将排好序的桶中的所有元素加入结果数组中
    }
  }
  return sortedArray;
}
function insertSort(arr) {
  // 缓存数组长度
  const len = arr.length
  // temp 用来保存当前需要插入的元素
  let temp
  // i用于标识每次被插入的元素的索引
  for (let i = 1; i < len; i++) {
    // j用于帮助 temp 寻找自己应该有的定位
    let j = i
    temp = arr[i]
    // 判断 j 前面一个元素是否比 temp 大
    while (j > 0 && arr[j - 1] > temp) {
      // 如果是,则将 j 前面的一个元素后移一位,为 temp 让出位置
      arr[j] = arr[j - 1]
      j--
    }
    // 循环让位,最后得到的 j 就是 temp 的正确索引
    arr[j] = temp
  }
  return arr
}
let arr = [3, 4, 2, 8, 5, 5, 2, 0, 6, 6, 3]
console.log(bucketSort(arr));

时间复杂度

1. 时间复杂度:O(N + C),C=N*(logN-logM)
对于待排序序列大小为 N,共分为 M 个桶,主要步骤有:
N 次循环,将每个元素装入对应的桶中
M 次循环,对每个桶中的数据进行排序(平均每个桶有 N/M 个元素)
一般使用较为快速的排序算法,时间复杂度为 O(NlogN),实际的桶排序过程是以链表形式插入的。
整个桶排序的时间复杂度为:
O(N)+O(M∗(N/M∗log(N/M)))  = O(N)+O(N∗(log(N/M)) = O(N)+O(C)= O(N∗(log(N/M)+1))
当 N = M 时,复杂度为 O(N)
2. 额外空间复杂度:O(N + M)
3、稳定性分析
桶排序的稳定性取决于桶内排序使用的算法。

基数排序

function radixSort(array, radixBase = 10) {
    if (array.length < 2) {
        return array;
    }
    const minValue = findMinValue(array);
    const maxValue = findMaxValue(array);
    let significantDigit = 1; // 基数排序也用来排序整数,我们就从最后一位开始排序所有的数
    while ((maxValue - minValue) / significantDigit >= 1) { // {2}
        array = countingSortForRadix(array, radixBase, significantDigit, minValue); // {3}
        significantDigit *= radixBase; // {4}
    }
    return array;
}
function countingSortForRadix(array, radixBase, significantDigit, minValue) {
    let bucketsIndex;
    const buckets = [];
    const aux = [];
    for (let i = 0; i < radixBase; i++) { // 基于基数初始化桶
        buckets[i] = 0;
    }
    // 基于数组中(行{6})数的有效位(行{7})进行计数排序(行{8})
    for (let i = 0; i < array.length; i++) { // {6}
        bucketsIndex = Math.floor(((array[i] - minValue) / significantDigit) %
            radixBase); // {7}
        buckets[bucketsIndex]++; // {8}
    }
    for (let i = 1; i < radixBase; i++) { // 计算累积结果来得到正确的计数值
        buckets[i] += buckets[i - 1];
    }
    for (let i = array.length - 1; i >= 0; i--) { // 使用一个临时数组(aux)来帮助我们。对原始数组中的每个值
        // 对原始数组中的每个值(行{10}),我们会再次获取它的有效位(行{11})并将它的值移动到
        // aux 数组中(从 buckets 数组中减去它的计数值——行{12})。最后一步是可选的(行{13}),
        // 我们将 aux 数组中的每个值转移到原始数组中
        bucketsIndex = Math.floor(((array[i] - minValue) / significantDigit) %
            radixBase); // {11}
        aux[--buckets[bucketsIndex]] = array[i]; // {12}
    }
    for (let i = 0; i < array.length; i++) { // {13}
        array[i] = aux[i];
    }
    return array;
}
function findMaxValue(array) {
    let max = array[0];
    for (let i = 1; i < array.length; i++) {
        if (array[i] > max) {
            max = array[i];
        }
    }
    return max;
}
function findMinValue(array) {
    let min = array[0];
    for (let i = 1; i < array.length; i++) {
        if (array[i] < min) {
            min = array[i];
        }
    }
    return min;
}
let arr = [3, 4, 2, 8, 5, 5, 2, 0, 6, 6, 3]
console.log(radixSort(arr));

时间复杂度

目录
相关文章
|
2月前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
58 1
|
3月前
|
存储 人工智能 算法
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
106 3
数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
|
1天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
14 2
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
123 23
|
2月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
64 20
|
2月前
|
机器学习/深度学习 算法 C++
数据结构之鲸鱼算法
鲸鱼算法(Whale Optimization Algorithm,WOA)是由伊朗研究员Seyedali Mirjalili于2016年提出的一种基于群体智能的全局优化算法,灵感源自鲸鱼捕食时的群体协作行为。该算法通过模拟鲸鱼的围捕猎物和喷出气泡网的行为,结合全局搜索和局部搜索策略,有效解决了复杂问题的优化需求。其应用广泛,涵盖函数优化、机器学习、图像处理等领域。鲸鱼算法以其简单直观的特点,成为初学者友好型的优化工具,但同时也存在参数敏感、可能陷入局部最优等问题。提供的C++代码示例展示了算法的基本实现和运行过程。
64 0
|
3月前
|
机器学习/深度学习 存储 缓存
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
56 1
数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
|
2月前
|
算法 vr&ar 计算机视觉
数据结构之洪水填充算法(DFS)
洪水填充算法是一种基于深度优先搜索(DFS)的图像处理技术,主要用于区域填充和图像分割。通过递归或栈的方式探索图像中的连通区域并进行颜色替换。本文介绍了算法的基本原理、数据结构设计(如链表和栈)、核心代码实现及应用实例,展示了算法在图像编辑等领域的高效性和灵活性。同时,文中也讨论了算法的优缺点,如实现简单但可能存在堆栈溢出的风险等。
70 0
下一篇
开通oss服务