用javascript分类刷leetcode14.排序算法(图文视频讲解)

news2024/11/17 2:31:12

常见排序算法复杂度

d s

n^2除nlogn在不同数据规模下的结果

ds_114

常见排序算法

算法可视化来源:http://visualgo.net/

冒泡排序:时间复杂度O(n^2)

  • 比较相邻元素,如果第一个比第二个大,则交换他们
  • 一轮下来,可以保证最后一个数是最大的
  • 执行n-1轮,就可以完成排序

ds_192

function bubbleSort(arr) {
    var len = arr.length;
    for (var i = 0; i < len; i++) {
        for (var j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j+1]) {        //相邻元素两两对比
                var temp = arr[j+1];        //元素交换
                arr[j+1] = arr[j];
                arr[j] = temp;
            }
        }
    }
    return arr;
}

选择排序:时间复杂度O(n^2)

  • 找到数组中的最小值,将它放在第一位
  • 接着找到第二小的值,将它放在第二位
  • 依次类推,执行n-1轮

ds_193

function selectionSort(arr) {
    var len = arr.length;
    var minIndex, temp;
    for (var i = 0; i < len - 1; i++) {
        minIndex = i;
        for (var j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {     //寻找最小的数
                minIndex = j;                 //将最小数的索引保存
            }
        }
        temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
    return arr;
}

插入排序:时间复杂度O(n^2)

  • 从第二个数开始往前比
  • 比它大就往后排
  • 以此类推直到最后一个数

ds_194

function insertionSort(arr) {
    var len = arr.length;
    var preIndex, current;
    for (var i = 1; i < len; i++) {
        preIndex = i - 1;
        current = arr[i];
        while(preIndex >= 0 && arr[preIndex] > current) {
            arr[preIndex+1] = arr[preIndex];
            preIndex--;
        }
        arr[preIndex+1] = current;
    }
    return arr;
}

归并排序:时间复杂度O(nlogn),分的时间复杂度O(logn),合并的过程的复杂度是O(n)

  • 分:把数组分成两半,递归子数组,进行分割操作,直到分成一个数

  • 合:把两个字数组合并成一个有序数组,直到全部子数组合并完毕,合并前先准备一个空数组,存放合并之后的结果,然后不断取出两个子数组的第一个元素,比较他们的大小,小的先进入之前准备的空数组中,然后继续遍历其他元素,直到子数组中的元素都完成遍历

ds_195

function mergeSort(arr) {  //采用自上而下的递归方法
    var len = arr.length;
    if(len < 2) {
        return arr;
    }
    var middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    var result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

快速排序:时间复杂度O(nlogn),递归复杂度是O(logn),分区复杂度O(n)

  • 分区:从数组中选一个基准值,比基准值小的放在它的前面,比基准值大的放在它的后面
  • 递归:递归对基准值前后的子数组进行第一步的操作

ds_196

function quickSort(arr, left, right) {
    var len = arr.length,
        partitionIndex,
        left = typeof left != 'number' ? 0 : left,
        right = typeof right != 'number' ? len - 1 : right;

    if (left < right) {
        partitionIndex = partition(arr, left, right);
        quickSort(arr, left, partitionIndex-1);
        quickSort(arr, partitionIndex+1, right);
    }
    return arr;
}

function partition(arr, left ,right) {     //分区操作
      //设定基准值位置(pivot)当然也可以选择最右边的元素为基准 也可以随机选择然后和最左或最右元素交换
    var pivot = left,                      
        index = pivot + 1;
    for (var 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) {
    var temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

148. 排序链表(medium)

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:

输入:head = []
输出:[]

提示:

链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105

动画过大,点击查看

方法1:自顶向下
  • 思路:用归并排序的思路,先不断分割,知道每个子区间只有一个节点位置,然后开始合并。
  • 复杂度:时间复杂度O(nlogn),和归并排序的复杂度一样。空间复杂度O(logn),递归的栈空间

js:

const merge = (head1, head2) => {
    const dummyHead = new ListNode(0);
    let temp = dummyHead, temp1 = head1, temp2 = head2;
    while (temp1 !== null && temp2 !== null) {//合并子区间 小的节点先连
        if (temp1.val <= temp2.val) {
            temp.next = temp1;
            temp1 = temp1.next;
        } else {
            temp.next = temp2;
            temp2 = temp2.next;
        }
        temp = temp.next;
    }
    if (temp1 !== null) {//两条链表还有节点没合并完,直接合并过来
        temp.next = temp1;
    } else if (temp2 !== null) {
        temp.next = temp2;
    }
    return dummyHead.next;
}

const toSortList = (head, tail) => {
    if (head === null) {//极端情况
        return head;
    }
    if (head.next === tail) {//分割到只剩一个节点
        head.next = null;
        return head;
    }
    let slow = head, fast = head;
    while (fast !== tail) {//的到中间节点
        slow = slow.next;
        fast = fast.next;
        if (fast !== tail) {
            fast = fast.next;
        }
    }
    const mid = slow;
    return merge(toSortList(head, mid), toSortList(mid, tail));//分割区间 递归合并
}

var sortList = function(head) {
    return toSortList(head, null);
};

方法2:自底向上

  • 思路:直接进行循环合并操作。
  • 复杂度:时间复杂度O(nlogn),空间复杂度O(1)

js:

const merge = (head1, head2) => {
    const dummyHead = new ListNode(0);
    let temp = dummyHead, temp1 = head1, temp2 = head2;
    while (temp1 !== null && temp2 !== null) {
        if (temp1.val <= temp2.val) {
            temp.next = temp1;
            temp1 = temp1.next;
        } else {
            temp.next = temp2;
            temp2 = temp2.next;
        }
        temp = temp.next;
    }
    if (temp1 !== null) {
        temp.next = temp1;
    } else if (temp2 !== null) {
        temp.next = temp2;
    }
    return dummyHead.next;
}

var sortList = function(head) {
    if (head === null) {
        return head;
    }
    let length = 0;
    let node = head;
    while (node !== null) {
        length++;
        node = node.next;
    }
    const dummyHead = new ListNode(0, head);
    for (let subLength = 1; subLength < length; subLength <<= 1) {
        let prev = dummyHead, curr = dummyHead.next;
        while (curr !== null) {
            let head1 = curr;
            for (let i = 1; i < subLength && curr.next !== null; i++) {
                curr = curr.next;
            }
            let head2 = curr.next;
            curr.next = null;
            curr = head2;
            for (let i = 1; i < subLength && curr != null && curr.next !== null; i++) {
                curr = curr.next;
            }
            let next = null;
            if (curr !== null) {
                next = curr.next;
                curr.next = null;
            }
            const merged = merge(head1, head2);
            prev.next = merged;
            while (prev.next !== null) {
                prev = prev.next;
            }
            curr = next;
        }
    }
    return dummyHead.next;
};

215. 数组中的第K个最大元素 (medium)

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104

方法1.维护大小为k的小顶堆,当堆的元素个数小于等于k时,遍历数组,让数组的元素不断加入堆,当堆的大小大于k时,让堆顶元素出列,遍历完数组之后,小顶堆堆顶的元素就是第k大元素。

复杂度:时间复杂度O(nlogk),循环n次,每次堆的操作是O(logk)。空间复杂度O(k)

ds_209

js:

class Heap {
    constructor(comparator = (a, b) => a - b, data = []) {
        this.data = data;
        this.comparator = comparator;//比较器
        this.heapify();//堆化
    }

    heapify() {
        if (this.size() < 2) return;
        for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i--) {
            this.bubbleDown(i);//bubbleDown操作
        }
    }

    peek() {
        if (this.size() === 0) return null;
        return this.data[0];//查看堆顶
    }

    offer(value) {
        this.data.push(value);//加入数组
        this.bubbleUp(this.size() - 1);//调整加入的元素在小顶堆中的位置
    }

    poll() {
        if (this.size() === 0) {
            return null;
        }
        const result = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            this.data[0] = last;//交换第一个元素和最后一个元素
            this.bubbleDown(0);//bubbleDown操作
        }
        return result;
    }

    bubbleUp(index) {
        while (index > 0) {
            const parentIndex = (index - 1) >> 1;//父节点的位置
            //如果当前元素比父节点的元素小,就交换当前节点和父节点的位置
            if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
                this.swap(index, parentIndex);//交换自己和父节点的位置
                index = parentIndex;//不断向上取父节点进行比较
            } else {
                break;//如果当前元素比父节点的元素大,不需要处理
            }
        }
    }

    bubbleDown(index) {
        const lastIndex = this.size() - 1;//最后一个节点的位置
        while (true) {
            const leftIndex = index * 2 + 1;//左节点的位置
            const rightIndex = index * 2 + 2;//右节点的位置
            let findIndex = index;//bubbleDown节点的位置
            //找出左右节点中value小的节点
            if (
                leftIndex <= lastIndex &&
                this.comparator(this.data[leftIndex], this.data[findIndex]) < 0
            ) {
                findIndex = leftIndex;
            }
            if (
                rightIndex <= lastIndex &&
                this.comparator(this.data[rightIndex], this.data[findIndex]) < 0
            ) {
                findIndex = rightIndex;
            }
            if (index !== findIndex) {
                this.swap(index, findIndex);//交换当前元素和左右节点中value小的
                index = findIndex;
            } else {
                break;
            }
        }
    }

    swap(index1, index2) {//交换堆中两个元素的位置
        [this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
    }

    size() {
        return this.data.length;
    }
}



var findKthLargest = function (nums, k) {
    const h = new Heap((a, b) => a - b);

    for (const num of nums) {
        h.offer(num);//加入堆
        if (h.size() > k) {//堆的size超过k时,出堆
            h.poll();
        }
    }

    return h.peek();
};

方法2:堆排序

ds_156

  • 思路:利用原地堆排序的思想,将前k-1大的元素加入队尾,最后队顶的元素就是第k大的元素
  • 复杂度:时间复杂度O(nlogn),堆的创建复杂度是O(n),移动前k-1大的元素然后堆化复杂度是O(klogn),k<=n,最差的情况下是O(nlogn),空间复杂度O(logn),递归的栈空间

js:

var findKthLargest = function (nums, k) {
    let heapSize = nums.length;
    buildMaxHeap(nums, heapSize); //构建大顶堆 大小为heapSize
    //大顶堆 前k-1个堆顶元素不断和数组的末尾元素交换 然后重新heapify堆顶元素
    //这个操作就是之前小顶堆出堆顶的操作,只不过现在是原地排序
    for (let i = nums.length - 1; i >= nums.length - k + 1; i--) {
        swap(nums, 0, i);//交换堆顶和数组末尾元素
        --heapSize; //堆大小减1
        maxHeapify(nums, 0, heapSize);//重新heapify
    }
    return nums[0];//返回堆顶元素,就是第k大的元素

    function buildMaxHeap(nums, heapSize) {
        for (let i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {//从第一个非叶子节点开始构建
            maxHeapify(nums, i, heapSize);
        }
    }
    // 从左向右,自上而下的调整节点
    function maxHeapify(nums, i, heapSize) {
        let l = i * 2 + 1;//左节点
        let r = i * 2 + 2;//右节点
        let largest = i;
        if (l < heapSize && nums[l] > nums[largest]) {
            largest = l;
        }
        if (r < heapSize && nums[r] > nums[largest]) {
            largest = r;
        }
        if (largest !== i) {
            swap(nums, i, largest); //找到左右节点中大的元素交换
            //递归交换后面的节点
            maxHeapify(nums, largest, heapSize);
        }
    }

    function swap(a, i, j) {//交换函数
        let temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
};

方法3:快速排序的分区方法

ds_157

  • 思路:借鉴快排的思路,不断随机选择基准元素,看进行partition之后,该元素是不是在n-k的位置。

  • 复杂度:

    1. 时间复杂度O(nlogn)
    2. 空间复杂度O(logn),递归的深度

js:

const findKthLargest = (nums, k) => {
    const n = nums.length;

    const quick = (l, r) => {
        if (l > r) return;//递归终止条件
        let random = Math.floor(Math.random() * (r - l + 1)) + l; //随机选取一个索引
        swap(nums, random, r); //将它和位置r的元素交换,让nums[r]作为基准元素

        //对基准元素进行partition
        let pivotIndex = partition(nums, l, r);
        //进行partition之后,基准元素左边的元素都小于它 右边的元素都大于它
        //如果partition之后,这个基准元素的位置pivotIndex正好是n-k 则找大了第k大的数
        //如果n-k<pivotIndex,则在pivotIndex的左边递归查找
        //如果n-k>pivotIndex,则在pivotIndex的右边递归查找
        if (n - k < pivotIndex) {
            quick(l, pivotIndex - 1);
        } else {
            quick(pivotIndex + 1, r);
        }
    };

    quick(0, n - 1);//函数开始传入的left=0,right= n - 1
    return nums[n - k]; //最后找到了正确的位置 也就是n-k等于pivotIndex 这个位置的元素就是第k大的数
};

function partition(nums, left, right) {
    let pivot = nums[right];                 //最右边的元素为基准
    let pivotIndex = left;                   //pivotIndex初始化为left
    for (let i = left; i < right; i++) {     //遍历left到right-1的元素
        if (nums[i] < pivot) {                 //如果当前元素比基准元素小
            swap(nums, i, pivotIndex);           //把它交换到pivotIndex的位置
            pivotIndex++;                        //pivotIndex往前移动一步
        }
    }
    swap(nums, right, pivotIndex);           //最后交换pivotIndex和right
    return pivotIndex;                       //返回pivotIndex
}

function swap(nums, p, q) {//交换数组中的两个元素
    const temp = nums[p];
    nums[p] = nums[q];
    nums[q] = temp;
}

视频讲解:传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/134045.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android入门第56天-在Android里使用OKHttp多线程下载文件并展示其进度

简介 OkHttp是一个神器。OkHttp分为异步、同步两种调用。今天我们就会基于OkHttp的异步调用实现一个多线程并行下载文件并以进度条展示总进度的实用例子。当然这不是我们的Android里使用OkHttp的最终目标&#xff0c;我们最终在下一篇中会在今天这一课的基础上加入“断点续传”…

【我在异世界学Linux】认识冯诺依曼体系结构

文章目录一、冯诺依曼体系结构是什么二、冯诺依曼为什么要这么设计&#xff1f;三、内存是怎么提高效率的呢&#xff1f;解释&#xff1a;程序要运行&#xff0c;必须加载到内存四、和QQ好友聊天的时候&#xff0c;数据是怎么流向的&#xff1f;一、冯诺依曼体系结构是什么 冯诺…

教你使用Java开发一款简单的扫雷小游戏 附实例代码

相信很多小伙伴都知道也玩过扫雷游戏,本篇文章将和大家分享一篇关于如何使用Java来实现一款简单的扫雷小游戏,这有助于大家对于Java相关知识的学习有一定的参考价值,下面是详情内容。 简介 学了几周的Java,闲来无事,写个乞丐版的扫雷,加强一下Java基础知识。 编写过程…

树莓派4b串口配置

从树莓派的相关资料我们可以看到&#xff0c;树莓派有两个串口可以使用&#xff0c;一个是硬件串口&#xff08;/dev/ttyAMA0&#xff09;,另一个是mini串口&#xff08;/dev/ttyS0&#xff09;。硬件串口有单独的波特率时钟源&#xff0c;性能好&#xff0c;稳定性强&#xff…

【Java寒假打卡】Java基础-接口

【Java寒假打卡】Java基础-接口接口的介绍接口的定义和特点接口中的成员特点JDK8 接口中的成员特点JDK9 接口中的成员特点类和接口的关系接口的介绍 &emsp&#xff1b;当一个类中的所有方法都是抽象方法的时候&#xff0c;我们就可以将其定义为接口&#xff0c;接口也是一…

Redis 缓存数据库

目录Redis 高可用方案高可用概念Redis 高可用的实现方案1、主从模式2、哨兵模式3、集群模式Redis 高可用方案 高可用概念 高可用&#xff08;High Availability&#xff0c;既HA&#xff09;&#xff0c;指的是通过尽量缩短日常维护操作和减少突发系统奔溃锁导致的停机时间来提…

Vagrant 安装 Centos7

首先准备VirtualBox, 当前实验版本为&#xff1a;7.0.4-154605-Win&#xff1b; 再次下载Vagrant windows版本&#xff1a;当前实验版本&#xff1a;vagrant_2.3.4_windows_i686 如果安装VirtualBox过程中提示缺少&#xff1a;Microsoft Visual C 2019 Redistributable &…

MySQL索引概述

索引的英文名叫 index 在数据库是一种帮助MySQL高效获取数据的数据结构 而且是一种有序的数据结构 在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用(指向)数据&#xff0c; 这样就可以在这些数据结构上实现高级找…

公钥基础设施 时间戳规范测评

声明 本文是学习信息安全技术 公钥基础设施 标准符合性测评. 下载地址而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 公钥基础设施 时间戳规范测评 时间戳的产生和颁发 申请和颁发方式 测评依据见GB/T 20520—2006中6.1的内容。 开发者应提供文档…

2.0、Linux-基础了解

2.0、开机关机和基本目录介绍 开机登录&#xff1a; 开会机会启动许多程序&#xff1b;他们在Windows叫做 "服务" &#xff0c;在 Linux 中叫做 "守护进程"&#xff08;daemon&#xff09;&#xff1b; 开机成功后&#xff0c;他会显示一个文本登录…

Tailoring Self-Supervision for Supervised Learning-读后总结

Tailoring Self-Supervision for Supervised Learning摘要文章思路一些值得读的参考文献和技术&#xff1a;值得一读的高引文献可视化技术摘要 近期&#xff0c;在监督学习中部署一个合适的自监督学习来提高监督学习的性能是一个具有前景的方式。然而&#xff0c;因为之前的前…

MP中定义全局常量用于xml的判断,List<String> list = new ArrayList<>(Arrays.asList(“test“));

,1.普通方式 mybatis-plus.configuration.variables.secretFilterSwitch0 yml的方式 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.demo configuration: variables: userId: 456132465 userName: 李四 配置完成后在代…

人文社科类文献去哪些数据库检索下载

查找下载人文社科类文献的数据库大盘点&#xff1a; 1、文献党下载器&#xff08;wxdown.org&#xff09; 大型文献馆&#xff0c;几乎整合汇集了所有中外文献数据库资源&#xff0c;可附带权限进入文献数据库查找下载文献&#xff0c;覆盖全科包括查找下载人文社科类文献的众…

【C和数据结构-5+1】习题第一天

文章目录一.选择题1.整型在内存中的存储2.大小端字节序3.指针的大小4.形参一级指针或二级指针的区别5.二维数组传参降维成数组指针二.编程题1.自守数2.质数判断一.选择题 1.整型在内存中的存储 猜一猜打印的结果? int main() {char a 101;int sum 200;a 27; sum a;printf…

2023年,无所谓,我会出手整合SSM

目录 一、简介&#xff1a; 二、Maven构建框架&#xff1a; 三、依赖配置 四、web.xml配置文件 五、创建SpringMVC的配置文件 六、Spring.xml配置环境 七、其他配置 八、Mapper.xml配置文件&#xff1a; 九、mybatis-config.xml配置文件&#xff1a; 十、log4j.xml日志…

Dropout详解:Dropout解决过拟合问题

Dropout 是一种能够有效缓解过拟合的正则化技术&#xff0c;被广泛应用于深度神经网络当中。但是被 dropout 所丢掉的位置都有助于缓解过拟合的吗&#xff1f; 中山大学 和 Meta AI 在 NeurIPS 2022 接收的论文在研究了注意力中的 dropout 后发现&#xff1a;不同注意力位置对过…

C++string类介绍

目录 一、介绍 二、string类对象的构造 string类有如下构造方法&#xff1a; 类对象的容量操作 类对象访问及遍历 string对象的修改操作&#xff1a; std::string::insert std::string::erase std::string::c_str std::string::find std::string::substr 一、介绍…

Linux学习笔记 超详细 0基础学习(上)

定义 linux是一个操作系统&#xff0c;与Windows、macos一样&#xff0c;是常见的操作系统之一&#xff0c;一般服务器会部署在Linux操作系统中&#xff0c;稳定应用广泛&#xff0c;Android系统是基于Linux内核的&#xff0c;所以了解Linux相关知识对于程序员来说是很重要的。…

人脸AI识别实战:用AI生成了这些人一生的样貌变化 | 附源码

这是来自斯坦福和华盛顿大学研究员发表的论文&#xff0c;提出了基于GAN的新方法&#xff0c;仅需要一张照片即可生成一个人从小时候到老了的样子。 论文&#xff1a;https://arxiv.org/abs/2003.09764 项目地址&#xff1a; https://github.com/royorel/Lifespan_Age_Trans…

windows上安装并使用exiftool修改图像exif信息

使用exiftool可以对图像的exif信息进行读取、修改、写入等操作。在linux系统上可以直接通过命令行安装&#xff0c;非常方便。但是在windows上&#xff0c;一开始我看了这篇博客&#xff0c;感觉里面写的还挺详细的&#xff0c;就跟着做了一下。里面是说要先安装perl&#xff0…