刷题 排序算法

news2024/11/25 22:29:01

912. 排序数组

注意这道题目所有 O(n^2) 复杂度的算法都会超过时间限制,只有 O(nlogn) 的可以通过

在这里插入图片描述

  • 快速排序空间复杂度为 O(logn)是由于递归的栈的调用
  • 归并排序空间复杂度为 O(n) 是由于需要一个临时数组 (当然也需要栈的调用,但是 O(logn) < O(n) 的忽略了)

基于插入的排序算法

直接插入排序

类似于打扑克牌的操作 直接插入排序(算法过程, 效率分析, 稳定性分析)

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:是稳定的
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        
        // 插入排序
        for (int i = 1; i < nums.size(); ++i) {
            int cur_val = nums[i];
            int j = i - 1;
            while (j >= 0 && nums[j] > cur_val) {	// 寻找插入位置
                nums[j + 1] = nums[j];
                --j;
            }
            nums[j + 1] = cur_val;
        }

        return nums;
    }
};

折半插入排序

直接插入排序是使用 顺序查找的方法,从后往前寻找插入的位置
同理我们也可以使用二分查找的方式来寻找插入的位置
折半查找减少了比较的次数,将比较操作的时间复杂度降低为 O(logn),但没有减少移动的次数,整体时间复杂度还是 O(n^2)

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:是稳定的
class Solution {
public:
    int binarySearch(vector<int> nums, int right, int target) {
        // 找到第一个大于 target 的值
        int left = 0;
        // 使用左闭右闭区间
        while (left <= right) { // 区间不为空
            int mid = left + (right - left) / 2;
            // 循环不变量
            // nums[left - 1] <= target
            // nums[right + 1] > target
            if (nums[mid] <= target) {
                left = mid + 1;
            } else if (nums[mid] > target) {
                right = mid - 1;
            }
        }
        return left;
    }
    vector<int> sortArray(vector<int>& nums) {
        
        // 折半插入排序
        for (int i = 1; i < nums.size(); ++i) {
            int cur_val = nums[i];
            int insert_pos = binarySearch(nums, i - 1, cur_val);
            for (int j = i - 1; j >= insert_pos; --j) {
                nums[j + 1] = nums[j]; 
            }
            nums[insert_pos] = cur_val;
        }

        return nums;
    }
};

希尔排序 - 插入排序的改进 - 缩小增量排序

插入排序在序列基本有序时效率较高

基于这个特点,希尔排序就是对数组分组进行插入排序,分组的组数就是 d,也即增量,一种简单的增量序列就是从 num.size() / 2 开始,一直缩小到 1,当然也可以采用其他的增量序列

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2),平均复杂度 O(n^1.3)(了解即可)
  • 空间复杂度:O(1)
  • 稳定性:不稳定的
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        for (int d = nums.size() / 2; d >= 1; --d) {
            // 分组插入排序
            for (int k = 0; k < d; ++k) {
                // 组内进行插入排序
                for (int i = k + d; i < nums.size(); i += d) {
                    int cur_val = nums[i];
                    int j = i - d;
                    while (j >= 0 && nums[j] > cur_val) {
                        nums[j + d] = nums[j];
                        j -= d;
                    }
                    nums[j + d] = cur_val;
                }
            }
        }
        return nums;
    }
};

基于交换的排序算法

冒泡排序

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定
class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        // 冒泡排序

        for (int i = nums.size() - 1; i >= 1; --i) {
            bool swapped = false;
            for (int j = 0; j < i; ++j) {
                if (nums[j] > nums[j + 1]) {
                    swap(nums[j], nums[j + 1]);
                    swapped = true;
                }
            }
            if (!swapped) { // 没有发生交换,说明代码已经有序
                break;
            }
        }

        return nums;
    }
};

快速排序 图解 - 分治法

步骤:

  • 随机选取一个位置 nums[i] = x
  • 将大于 x 的值都移到 nums[i] 的左边,小于 x 的值都移动到 nums[i] 的右边
  • 对 nums[0 ~i -1] 和 nums[i + 1 ~ n -1] 分别进行快速排序

步骤中的核心问题:如何 将大于 x 的值都移到 nums[i] 的左边,小于 x 的值都移动到 nums[i] 的右边?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定
class Solution {
public:
    void quickSort(vector<int>& nums, int left, int right) {
        if (left >= right) return; // 递归终止条件
        int p = partition(nums, left, right);
        quickSort(nums, left, p - 1);
        quickSort(nums, p + 1, right);
    }

    int partition(vector<int>& nums, int left, int right) {
        int p = left + rand() % (right - left + 1); // 生成 [left ~ right] 区间内的随机数
        swap(nums[p], nums[right]); // 将 pivot 和末尾值交换
        int i = left;
        // 维护的区间: [left, i) 区间内的值小于等于 nums[right]
        // [j, right) 区间内的值大于 nums[right]
        for (int j = left; j < right; ++j) {
            if (nums[j] <= nums[right]) {
                // 此时不满足我们对区间的要求了
                // 调整区间使其满足要求
                // {nums[left] ... nums[i-1]} {[nums[i] ... nums[j]]}
                swap(nums[i], nums[j]);
                ++i;
                // --> {nums[left] ... nums[i-1] nums[j]} { ... nums[i]]}
            }
        }
        swap(nums[i], nums[right]);
        return i;
    }

    vector<int> sortArray(vector<int>& nums) {
        srand(time(0));     // 以当前时间为随机数种子
        quickSort(nums, 0, nums.size() - 1);
        return nums;
    }
};

但是上面这段代码提交还是会超过时间限制,由于当前的快速排序在处理包含大量相同元素的数组时,表现不佳。快速排序在最坏情况下的时间复杂度是 O(n^2)

使用三向切分的快速排序

三向切分是对标准快速排序的一种改进,特别适用于处理大量重复元素的情况。它将数组分为三个部分:

  • 小于基准的部分
  • 等于基准的部分
  • 大于基准的部分

通过三向切分,可以避免在处理大量重复元素时退化为 O(n²),使得时间复杂度保持在 O(n log n)。

class Solution {
public:
    void quickSort3Way(vector<int>& nums, int left, int right) {
        if (left >= right) return; // 递归终止条件
        int pivot = nums[left + rand() % (right - left + 1)]; // 选取随机基准
        int lt = left, i = left, gt = right;  // 初始化 lt、i、gt 指针
        // [left ~ lt) 小于 pivot
        // [lt, gt] 等于 pivot
        // [gt + 1, right] 大于 pivot 
        while (i <= gt) {
            if (nums[i] < pivot) {
                swap(nums[lt], nums[i]);
                ++lt;
                ++i;
            } else if (nums[i] > pivot) {
                swap(nums[i], nums[gt]);
                --gt;   // 不能++i,因为换下来的这个数的值还没有跟 pivot 比较过
            } else {
                ++i;
            }
        }
        // 递归处理小于和大于基准的部分
        quickSort3Way(nums, left, lt - 1);
        quickSort3Way(nums, gt + 1, right);
    }

    vector<int> sortArray(vector<int>& nums) {
        srand(time(0));  // 只需初始化一次随机数种子
        quickSort3Way(nums, 0, nums.size() - 1);
        return nums;
    }
};

选择排序

简单选择排序

  • 时间复杂度:最好情况 O(n), 最坏情况 O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

不稳定性分析:
假设有一个数组 [4a, 2, 4b, 3],其中 4a 和 4b 是两个相同值的元素,但具有不同的初始顺序。

  • 第一轮:选择 2 作为最小元素,然后与 4a 交换,数组变为 [2, 4a, 4b, 3]。

  • 第二轮:选择 3 作为最小元素,然后与 4a 交换,数组变为 [2, 3, 4b, 4a]。 注意此时 4a 和 4b 的相对顺序已经被改变:原本 4a 在 4b 之前,现在 4a被排在了 4b 之后。

因此,选择排序是不稳定的,因为它改变了相同值元素的初始顺序。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        
        // 选择排序
        for (int i = 0; i < nums.size() - 1; ++i) {
            int min_idx = i;
            for (int j = i + 1; j < nums.size(); ++j) {
                if (nums[j] < nums[i]) {
                    min_idx = j;            // 最小值的索引
                }
            }
            swap(nums[i], nums[min_idx]);   // 和最小值进行交换
        }

        return nums;
    }
};

堆排序 - 堆 - 完全二叉树 - 顺序存储

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    // 堆化函数:调整以 i 为根的子树,n 为堆的大小
    void heapify(vector<int>& nums, int n, int i) {
        int largest = i;      // 初始化为根节点
        int left = 2 * i + 1; // 左孩子
        int right = 2 * i + 2; // 右孩子

        // 如果左孩子比根节点大
        if (left < n && nums[left] > nums[largest]) {
            largest = left;
        }

        // 如果右孩子比当前最大值还大
        if (right < n && nums[right] > nums[largest]) {
            largest = right;
        }

        // 如果最大值不是根节点,交换并继续堆化
        if (largest != i) {
            swap(nums[i], nums[largest]);
            // 递归对受影响的子树进行堆化
            heapify(nums, n, largest);
        }
    }

    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();

        // 从最后一个非叶子节点开始建堆,调整整个堆
        for (int i = n / 2 - 1; i >= 0; --i) {
            heapify(nums, n, i);
        }

        // 逐一将堆顶元素与末尾元素交换,并重新调整堆
        for (int i = n - 1; i > 0; --i) {
            // 将当前堆顶(最大值)与末尾元素交换
            swap(nums[0], nums[i]);
            // 重新对剩下的部分进行堆化
            heapify(nums, i, 0);
        }

        return nums;
    }
};

归并排序

可以将排序问题分解成 将左半边排序 + 将右半边排序 + 合并左右两侧

  • 时间复杂度: O(n log n)
  • 空间复杂度:O(n) (源于临时数组)
  • 稳定性:稳定
class Solution {
public:
    
    void mergeArray(vector<int> &nums, vector<int>& tmp, int left, int right) {
        if (right == left) return;              // 递归终止条件
        int mid = left + (right - left) / 2;
        mergeArray(nums, tmp, left, mid);       // 对左半边进行排序
        mergeArray(nums, tmp, mid + 1, right);  // 对右半边进行排序

        // 重要优化:如果左右两部分已经有序,可以跳过合并
        if (nums[mid] <= nums[mid + 1]) return;

        // 左右两侧均已完成排序,对二者进行合并
        int i = left, j = mid + 1, k = left;
        while (i <= mid && j <= right) {
            if (nums[i] <= nums[j]) {
                tmp[k++] = nums[i++]; 
            } else {
                tmp[k++] = nums[j++];
            }
        }
        while (i <= mid) {
            tmp[k++] = nums[i++];
        }
        while (j <= right) {
            tmp[k++] = nums[j++];
        }
        copy(tmp.begin() + left, tmp.begin() + right + 1, nums.begin() + left);
    }
    vector<int> sortArray(vector<int>& nums) {
        vector<int> tmp(nums.size(), 0);
        mergeArray(nums, tmp, 0, nums.size() - 1);
        return nums;
    }
};

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

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

相关文章

【华为】配置RIP协议

RIP&#xff08;Routing Information Protocol&#xff09;是一种内部网关协议&#xff08;IGP&#xff09;&#xff0c;主要用于小型网络中的动态路由。RIP有两个主要版本&#xff1a;‌RIPv1和‌RIPv2&#xff0c;它们之间存在一些关键区别&#xff1a; ‌分类支持‌&#xf…

利用FnOS搭建虚拟云桌面,并搭建前端开发环境(一)

利用FnOS搭建虚拟云桌面&#xff0c;并搭建前端开发环境 一 飞牛FnOS官方文档一、安装FnOS【Win11系统】1.下载VirtualBox2.下载FnOS镜像3.创建虚拟机4.启动完成后&#xff0c;会进入这样一个界面&#xff0c;这个基本上后续就后台了 本人在网上冲浪了很久&#xff0c;一直也没…

python pyqt5 +vtk 显示obj模型文件

python pyqt5 vtk 显示obj模型文件 准备代码参考 准备 名称版本python3.8.19pyqt55.15.9pyqt5-tools5.15.9.3.3pyqt5-sip12.15.0vtk9.3.1 代码 使用wsl2 和 XLaunch 配合pyqt5进行可视化是可行的。使用pip在conda环境中安装pyqt5相关组件。以下代码在 WSL2 的 Ubuntu 20.04 …

Java运算符逻辑控制

目录 一、运算符 1.1基本四则运算符&#xff08;加减乘除模&#xff09; 1.2增量运算符 1.3关系运算符 1.4自增、自减运算符 1.5逻辑运算符 1.6位运算符 1.7移位运算符 1.8条件运算符&#xff08;三目运算符&#xff09; 二、逻辑控制 2.1if语句 2.2switch语句 2.…

Docker 搭建mysql 连接超时问题,xxl-job启动mysql连接报错

1.本地连接Navicat报错信息&#xff0c;猜测是navicat默认连接超时导致的&#xff0c;后面换成idea一个插件虽然慢但连接上了 2013 - Lost connection to MySQL server at reading initial communication packet 2.启动xxl-job会报错&#xff0c;网上有人mysql驱动与数据库不匹…

【高频SQL基础50题】31-35

又到SQL。 目录 1.查询结果的质量和占比 2.求关注者的数量 3.指定日期的产品价格 4.好友申请 II &#xff1a;谁有最多的好友 5.按日期分组销售产品 1.查询结果的质量和占比 聚合题。 # Write your MySQL query statement below SELECT t1.query_name,ROUND((SUM(t1.r…

问题记录(个人)

备注&#xff1a; 在7月10日记录之前遇到的问题及解决方法: 一&#xff1a;常见的访问问题&#xff1a; 403 Forbidden&#xff1a;&#xff08;未有请求权限&#xff09; 表示服务器理解请求但是拒绝执行它。这通常是由于服务器上的文件或资源没有正确的读、写或执行权限&…

【从零开始的LeetCode-算法】3164.优质数对的总数 II

给你两个整数数组 nums1 和 nums2&#xff0c;长度分别为 n 和 m。同时给你一个正整数 k。 如果 nums1[i] 可以被 nums2[j] * k 整除&#xff0c;则称数对 (i, j) 为 优质数对&#xff08;0 < i < n - 1, 0 < j < m - 1&#xff09;。 返回 优质数对 的总数。 示…

python画图|多个Y轴分列右侧

【1】引言 前述python画图学习中&#xff0c;已经探索过X轴和Y轴的共享&#xff0c;可通过下述链接直达&#xff1a; 西猫雷婶-CSDN博客 但现实的画图实践中总会有新的要求&#xff0c;之前将所有轴合到一起的形式可能不再适用&#xff0c;因此&#xff0c;很有必要探索多个…

下一代安全:融合网络和物理策略以实现最佳保护

在当今快速发展的技术环境中&#xff0c;网络和物理安全融合变得比以往任何时候都更加重要。随着物联网 (IoT) 和工业物联网 (IIoT) 的兴起&#xff0c;组织在保护数字和物理资产方面面临着独特的挑战。 本文探讨了安全融合的概念、说明其重要性的实际事件以及整合网络和物理安…

Pandas处理时间序列之光谱分析与聚类

import matplotlib.pylab as plt %matplotlib inline import numpy as np from numpy import fft import pandas as pd 一、光谱分析 • 将时间序列分解为许多正弦或余弦函数的总和 • 这些函数的系数应该具有不相关的值 • 对正弦函数进行回归 光谱分析应用场景 基于光谱的…

Windows 修改 Alt+Tab 键 切换 新版 Microsoft Edge 单个标签页/窗口

Windows 10 修改 AltTab 键 切换 新版 Microsoft Edge 单个标签页/窗口 解决方案&#xff1a;在 Windows 设置 的搜索框中 搜索 alt&#xff0c;选择 选择按下 AltTab 时显示的窗口和选项卡&#xff0c;将 按 Alt Tab 将显示 选为 仅打开的窗口 详细过程&#xff1a; 在 Win…

打造高效3D打印利器:STEP转STL格式转换器

随着3D打印技术的日益普及&#xff0c;越来越多的设计师和工程师开始使用三维建模软件来创建复杂的产品模型。然而&#xff0c;不同的软件往往采用不同的文件格式&#xff0c;这给模型的共享和打印带来了诸多不便。本文将重点介绍STEP格式转STL格式的转换器&#xff0c;帮助大家…

【图论】(三)图论的并查集问题

图论的并查集问题 并查集理论基础并查集理论路径压缩代码模板模拟过程 找亲戚寻找存在的路径冗余连接冗余连接II 并查集理论基础 参考学习视频&#xff1a; 图论——并查集(详细版) 并查集理论 并查集主要用于处理一些不相交集合的合并问题&#xff08;连通性问题&#xff0…

汽车网络安全 -- 后量子密码时代还远吗?

目录 1.量子计算对密码学的威胁 1.1 传统密码学速览 1.2 量子计算对密码学的威胁 2.后量子密码学 2.1 量子计算原理 2.2 后量子密码学 3.未来汽车网络安全面临的威胁 3.1 量子计算对汽车的威胁 3.2 TC4xx对于PQC算法的支持 国庆抽空回顾了下Hitex组织的《Designing wi…

西门子S7-1200选型指南之概述

S7-1200 控制器概述 S7-1200 虽然只是中小型控制器&#xff0c;但是功能很全面。它具有较高性能&#xff0c;并且具有很强的扩展能力&#xff0c;通信能力多种多样&#xff0c;本体即具有各种常见工艺功能&#xff0c;此外也具备西门子常见的诊断功能&#xff0c;下面将分几部…

vue3 + vite + cesium项目

GitHub - tingyuxuan2302/cesium-vue3-vite: 项目基于 vue3 vite cesium&#xff0c;已实现常见三维动画场&#xff0c;欢迎有兴趣的同学加入共建&#xff0c;官网服务器相对拉胯&#xff0c;请耐心等候...https://github.com/tingyuxuan2302/cesium-vue3-vite/tree/github

计算机网络:数据链路层 —— 数据链路层的三个重要问题

文章目录 数据链路层的重要问题封装成帧封装过程帧的数据载荷 透明传输实现透明传输 差错检测传输差错误码&#xff08;比特差错&#xff09;分组丢失分组失序分组重复 奇偶校验循环冗余校验生成多项式方法流程 纠错码 数据链路层的重要问题 封装成帧 在计算机网络中&#xf…

基于Java+SpringBoot+Uniapp的博客系统设计与实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

Jenkins整合Docker实现CICD自动化部署(若依项目)

前期准备 提前准备好jenkins环境 并且jenkins能使用docker命令&#xff0c;并且已经配置好了jdk、node、maven环境&#xff0c;我之前写了安装jenkins的博客&#xff0c;里面讲得比较详细&#xff0c;推荐用我这种方式安装 docker安装jenkins&#xff0c;并配置jdk、node和m…