C++面试基础知识:排序算法 C++实现

news2024/11/12 22:45:05

在这里插入图片描述

上周实习面试,手撕代码快排没写出来,非常丢人,把面试官都给逗笑了。
基础不牢,地动山摇,基础的算法还是要牢记于心的。

插入排序

分为有序区和无序区,每次从无序区中选出一个,放到有序区域中。
实现:

void InsertionSort(vector<int> &nums, int left, int right){
    for(int i=left+1; i<=right; i++){
        int key = nums[i];
        int j = i-1;
        while(j >= left && nums[j] > key){
            nums[j+1] = nums[j];
            j--;
        }
        nums[j+1] = key;
    }
}

快速排序

选择一个基准元素,小于基准的放前面,大于基准的放后面,一边下来基准的位置就已经确定了,然后再对递归对两边区域进行快排。

#include <iostream>
#include <vector>
#include <chrono>

void QuickSort(vector<int> &nums,int left, int right){
    if(left >= right) return;
    int pivot = nums[left];
    int i = left, j = right;

    while( i < j){
        while(i < j && nums[j] >= pivot) j--;
        while(i < j && nums[i] < pivot) i++;
        if(i < j){
            swap(nums[i], nums[j]);
        }
    }
    QuickSort(nums, left, i);
    QuickSort(nums, i+1, right);
}

快速排序的思想是分治法,每次将待排序区域划分为两部分,平均划分 O ( l o g n ) O(logn) O(logn)次,平均时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn),且前面的常数比较小,大部分情况相比于时间复杂度恒等于 O ( n l o g n ) O(nlogn) O(nlogn)的归并排序更快。
最坏情况时间复杂度是 O ( n 2 ) O(n^2) O(n2),当每次选取的pivot恰好是最大值或最小值时,分完后两侧一边是0个,一边是n-1个,这种情况快排退化成冒泡排序。

优化

为了避免出现最差情况,可以从待排序区域中取多个数,从这其中取中间数。

void QuickSortV1(vector<int> &nums, int left, int right){
    if(left >= right) return;
    if(nums[right] < nums[left]){
        swap(nums[left], nums[right]);
    }
    if(nums[(left+right)/2] < nums[right]){
        swap(nums[right], nums[(left+right)/2]);
    }
    if(nums[left] < nums[(left+right)/2]){
        swap(nums[left], nums[(left+right)/2]);
    }
    int pivot = nums[left];
    int i = left, j = right;
    while(i < j){
        while(i < j && nums[j] >= pivot) j--;
        while(i < j && nums[i] < pivot) i++;
        if(i < j){
            swap(nums[i], nums[j]);
        }
    }
    QuickSortV1(nums, left, i);
    QuickSortV1(nums, i+1, right);
}
  • 对于待排序区间小于10的时候,选择使用插入排序,这样可以避免由于快速排序的递归成本,插入排序反而比快速排序快。
void InsertionSort(vector<int> &nums, int left, int right){
    for(int i=left+1; i<=right; i++){
        int key = nums[i];
        int j = i-1;
        while(j >= left && nums[j] > key){
            nums[j+1] = nums[j];
            j--;
        }
        nums[j+1] = key;
    }
}

void QuickSortV2(vector<int> &nums, int left, int right){
    if(right - left < 10){
        InsertionSort(nums, left, right);
        return;
    }
    if(nums[right] < nums[left]){
        swap(nums[left], nums[right]);
    }
    if(nums[(left+right)/2] < nums[right]){
        swap(nums[right], nums[(left+right)/2]);
    }
    if(nums[left] < nums[(left+right)/2]){
        swap(nums[left], nums[(left+right)/2]);
    }
    int pivot = nums[left];
    int i = left, j = right;
    while(i < j){
        while(i < j && nums[j] >= pivot) j--;
        while(i < j && nums[i] < pivot) i++;
        if(i < j){
            swap(nums[i], nums[j]);
        }
    }
    QuickSortV2(nums, left, i);
    QuickSortV2(nums, i+1, right);
}
  • 如果待排序的数组中存在较多的重复数字,还可以确定好pivot的位置后,遍历一遍将所有与pivot相同的数字放到一起,然后再进入递归,这样在具有较多重复时可以很好的提速。
void QuickSortV3(vector<int> &nums, int left, int right){
    if(right - left < 10){
        InsertionSort(nums, left, right);
        return;
    }
    if(nums[right] < nums[left]){
        swap(nums[left], nums[right]);
    }
    if(nums[(left+right)/2] < nums[right]){
        swap(nums[right], nums[(left+right)/2]);
    }
    if(nums[left] < nums[(left+right)/2]){
        swap(nums[left], nums[(left+right)/2]);
    }
    int pivot = nums[left];
    int i = left, j = right;
    int equal_right = 0;
    while(i < j){
        while(i < j && nums[i] < pivot) i++;
        while(i < j && nums[j] >= pivot){
            if(nums[j] == pivot){
                swap(nums[j], nums[right-equal_right]);
                equal_right++;
            }
            j--;
        }
        if(i < j){
            swap(nums[i], nums[j]);
        }
    }
    for(int k=0; k<equal_right; k++){
        swap(nums[i+k], nums[right-k]);
    }
    QuickSortV3(nums, left, i-1);
    QuickSortV3(nums, i+equal_right, right);
}

对比:

void randInit(vector<int> &nums){
    for(int i=0; i<nums.size(); i++){
        nums[i] = rand()%10000;
    }
}

int main(){
    vector<int> nums(10000);
    randInit(nums);
    auto start = chrono::steady_clock::now();
    for(int i=0;i<20;i++){
        QuickSort(nums, 0, nums.size()-1);
    }
    auto end = chrono::steady_clock::now();
    auto diff = end - start;
    cout << chrono::duration <double, milli> (diff).count()/20.0 << " ms" << endl;

    start = chrono::steady_clock::now();
    for(int i=0;i<20;i++){
        QuickSortV1(nums, 0, nums.size()-1);
    }
    end = chrono::steady_clock::now();
    diff = end - start;
    cout << chrono::duration <double, milli> (diff).count()/20.0 << " ms" << endl;

    start = chrono::steady_clock::now();
    for(int i=0;i<20;i++){
        QuickSortV2(nums, 0, nums.size()-1);
    }
    end = chrono::steady_clock::now();
    diff = end - start;
    cout << chrono::duration <double, milli> (diff).count()/20.0 << " ms" << endl;

    start = chrono::steady_clock::now();
    for(int i=0;i<20;i++){
        QuickSortV3(nums, 0, nums.size()-1);
    }
    end = chrono::steady_clock::now();
    diff = end - start;
    cout << chrono::duration <double, milli> (diff).count()/20.0 << " ms" << endl;
    return 0;
}

在最差情况,每次都是选取到的最小值作为pivot,二者速度差了上百倍。

归并排序

也是分治思想,将待排序区域分为多个区域单独排序,再进行合并。不止可以分为两个区域,还可以分为多个区域,称为多路归并。
归并排序实现:

#include <iostream>
#include <vector>

using namespace std;

void merge_sort(vector<int> &num1, int left, int right){
    if(left >= right) return;
    int mid = left + (right - left) / 2;
    merge_sort(num1, left, mid);
    merge_sort(num1, mid + 1, right);

    int i = left, j = mid + 1, k = 0;
    vector<int> temp(right - left+1);
    while(i <= mid && j <= right){
        if(num1[i] <num1[j]){
            temp[k++] = num1[i++];
        }else{
            temp[k++] = num1[j++];
        }
    }
    while(i <= mid){
        temp[k++] = num1[i++];
    }
    while(j <= right){
        temp[k++] = num1[j++];
    }
    for(int i = 0; i < right - left+1; i++){
        num1[left + i] = temp[i];
    }
}

int main(){
    vector<int> num1 = {3, 2, 1, 5, 4};
    vector<int> result;
    merge_sort(num1, 0, num1.size() - 1);
    for(auto i : num1){
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

优化

  • 可以使用多线程进行优化,每个线程负责一个待排序区域
  • 通过改为非递归等方法,可以只需要一个temp就可以了,减小内存占用

堆排序

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( 1 ) O(1) O(1),不稳定算法。
维持一个大顶堆/小顶堆。一开始先进行建堆,建好堆后将堆的根和最后一个叶子节点进行交换,这时一个最大值的位置就已经固定了,接下来接着调整堆,调整好后再重复以上操作。

堆就是一棵完全二叉树,每一次都是满的,最后一层可以不是满的但是必须是从左往右紧密排列的:
在这里插入图片描述
堆分为大顶堆和小顶堆,大顶堆即父节点大于左右子节点,小顶堆即父节点小于左右子节点。
堆的存储方式最适合用数组存储,从左向右层序遍历的结果存在数组中。第i个数的父节点为 ⌊ i 2 ⌋ \left \lfloor \frac{i}{2} \right \rfloor 2i
不过需要注意从0开始存储的和从1开始存储的情况,计算父节点方式不一样,从0开始存储的情况,计算方式为 ⌊ i 2 ⌋ − 1 \left \lfloor \frac{i}{2} \right \rfloor -1 2i1
建堆的方式为从最后一个叶子节点开始,依次检查是不是大顶堆/小顶堆,如果是,接着向右排查,如果不是将该节点与子节点中较大的进行交换并判断交换后该节点是否为堆,如果不是,接着交换,直到该节点符合堆的要求为止。

实现:

#include <iostream>
#include <vector>


using namespace std;

void CreateHeap(vector<int> &nums, int i, int n){
    int left = 2*i+1;
    int right = 2*i+2;
    int max = i;
    if(left < n && nums[left] > nums[max]){
        max = left;
    }
    if(right < n && nums[right] > nums[max]){
        max = right;
    }
    if(max != i){
        swap(nums[i], nums[max]);
        CreateHeap(nums, max, n);
    }
}

void HeapSort(vector<int> &nums){
    int n = nums.size();
    for(int i=n/2-1; i>=0; i--){
        CreateHeap(nums, i, n);
    }
    for(int i=n-1; i>=0; i--){
        swap(nums[0], nums[i]);
        CreateHeap(nums, 0, i);
    }
}

int main()
{
    vector<int> nums = {3, 2, 1, 5, 4};
    HeapSort(nums);
    for(auto i : nums){
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

扩展问题

  • 复杂度越小,算法越好吗?
    并不一定,第一点,算法复杂度衡量的是操作次数的数量级,如果要完全评价一个算法复杂度,还应该考虑每次操作的时间,所以同等复杂度等级的代码,速度可能不一样快。第二点,对于问题规模较小时,复杂度大的算法不一定比复杂度小的算法慢,例如在问题规模小于10时,插入排序是比快速排序要快的。

  • C++ 的STL库中的sort是怎么实现的?
    STL中的sort实现是内省排序,内省排序是快速排序和堆排序的结合,当递归深度大于 l o g n logn logn时,会切换为堆排序。

  • 什么是完全二叉树,什么是有序二叉树?
    完全二叉树就是类似于堆这种,简而言之就是从左向右层序遍历不存在缺口的二叉树。如果每一层都是满的,那就是满二叉树,所以完全二叉树也可以定义为顺序存储时,每个节点的位置都和满二叉树中节点的存储位置相同的树。
    有序二叉树指的是父子节点关系存在顺序管理,例如如果二叉树中父子节点满足:右子节点> 父节点> 左子节点,那这个树就是二叉查找树,可以用于快速查找数组中是否存在某个元素,查找时间复杂度为 O ( l o g n ) O(logn) O(logn),如果这个树是用链表存储的,那么插入复杂度也是 O ( l o g n ) O(logn) O(logn)

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

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

相关文章

yarn报错`warning ..\..\package.json: No license field`:已解决

出现这个报错有两个原因 1、项目中没有配置许可证 在项目根目录package.json添加 {"name": "next-starter","version": "1.0.0",# 添加这一行"license": "MIT", }或者配置私有防止发布到外部仓库 {"priv…

批量缓存模版

批量缓存模版 缓存通常有两种使用方式&#xff0c;一种是Cache-Aside&#xff0c;一种是cache-through。也就是旁路缓存和缓存即数据源。 一般一种用于读&#xff0c;另一种用于读写。参考后台服务架构高性能设计之道。 最典型的Cache-Aside的样例&#xff1a; //读操作 da…

亚信安全并购亚信科技交易正式完成

亚信安全与亚信科技联合宣布&#xff0c;亚信安全正式完成对亚信科技的控股权收购&#xff0c;由此&#xff0c;规模近百亿的中国最大的软件企业之一诞生&#xff01;双方将全面实现公司发展战略&#xff0c;以及优势能力与资源的深度融合&#xff0c;形成业界独有的“懂网、懂…

MybatisPlus入门(十)MybatisPlus-逻辑删除和多记录操作

一、Mybatis-Plus 多记录操作 按照主键删除多条记录 List<Long> ids Arrays.asList(new Long[]{2,3}) userDao.deleteBatchIds(ids); 示例代码如下: Testvoid testDelete(){//删除指定多条数据List<Long> list new ArrayList<>();list.add(14025513424818…

【css】overflow: hidden效果

1. 不添加overflow: hidden 1.1 效果 上面无圆角 1.2 代码 <template><view class"parent"><view class"child1">child1</view><view class"child2">child2</view></view></template><…

「QT」几何数据类 之 QPolygonF 浮点型多边形类

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「QT」QT5程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…

架构篇(04理解架构的演进)

目录 学习前言 一、架构演进 1. 初始阶段的网站架构 2. 应用服务和数据服务分离 3. 使用缓存改善网站性能 4. 使用应用服务器集群改善网站的并发处理能力 5. 数据库读写分离 6. 使用反向代理和CDN加上网站相应 7. 使用分布式文件系统和分布式数据库系统 8. 使用NoSQL和…

OpenCV基础05_GUI和PyMsql

目录 一、PySimpleGUI 1、布局和窗口 2、文本框组件 3、视频处理 4、图片处理 二、pymsql 1、数据库操作 2、数据采集 3、人脸识别 一、PySimpleGUI PySimpleGUI 是一个用于简化 GUI 编程的 Python 包&#xff0c;它封装了多种底层 GUI 框架&#xff08;如 tkinter、…

ModuleNotFoundError: No module named ‘_ssl‘ centos7中的Python报错

报错 ModuleNotFoundError: No module named ‘_ssl’ 解决步骤&#xff1a; 1.下载openssl wget https://www.openssl.org/source/openssl-3.0.7.tar.gz tar -zxvf openssl-3.0.7.tar.gz cd openssl-3.0.72.编译安装 ./config --prefix/usr/local/openssl make make install3…

外呼系统只需这 3 种功能,电销效率快速提升

在当今竞争激烈的商业环境中&#xff0c;电销团队面临着诸多挑战。如何提高电销效率&#xff0c;成为了企业关注的焦点。今天&#xff0c;小编就给大家介绍&#xff0c;沃创云三种外呼系统功能&#xff0c;让你的电销效率快速提升。 一、智能拨号功能 传统的电销方式中&#x…

18. Mouse 鼠标、KeyBoard 键盘和 Action 消息事件处理

在本节的例子中&#xff0c;会自定义很多UI控件实现不同的事件响应&#xff0c;如下图所示&#xff1a; IOKit 事件框架 事件流程 OS X的事件依赖 IOKit 框架&#xff0c;事件发生后首先会传递到IOKit框架中处理&#xff0c;然后通知Window Server服务层处理&#xff0c;由…

C# 实现对指定句柄的窗口进行键盘输入的实现

在C#中实现对指定句柄的窗口进行键盘操作&#xff0c;可以通过多种方式来实现。以下是一篇详细的指南&#xff0c;介绍如何在C#中实现这一功能。 1. 使用Windows API函数 在C#中&#xff0c;我们可以通过P/Invoke调用Windows API来实现对指定窗口的键盘操作。以下是一些关键的…

Spring Plugin与策略模式:打造动态可扩展的应用

目录 一、策略模式 二、Spring Plugin 2.1 Spring Plugin 实现策略模式开发 2.2 策略模式优缺点 三、Spring Plugin 原理 一、策略模式 策略模式是一种设计模式&#xff0c;它允许程序在运行中动态的选择不同的行为方式进行动态执行。策略模式的核心思想是将行为封装在一个个…

Word大珩助手:超大数字怎么读?35位数字?69位数字?

俄罗斯日前对谷歌开出了20000000000000000000000000000000000&#xff08;35位数字&#xff09;美元的罚款 这一数字远超全球GDP总和&#xff0c;消息一出很快就登上热搜。 面对这样一个庞大的数字&#xff0c;人们不禁好奇&#xff0c;这样的数字该如何读出来&#xff1f; …

asp.net文件防盗链

URLRewriter实现 可以参考下面的文章 代码 .net framework 新建asp.net framework的web项目&#xff0c;新建AntiTheftChainHandler using System.Web;namespace AntiTheftChainStu01.Handler {public class AntiTheftChainHandler : IHttpHandler{public bool IsReusable…

【含开题报告+文档+PPT+源码】基于SSM的蛋糕店销售管理系统的设计与实现

开题报告 在现代社会&#xff0c;蛋糕作为一种受欢迎的甜点&#xff0c;广泛应用于各种庆祝活动和节日。传统的蛋糕预订方式往往需要用户亲自到店面进行预订&#xff0c;预订流程繁琐&#xff0c;时间和地点限制也给用户带来了不便。随着智能手机和移动互联网的普及&#xff0…

政治经济学笔记

【拯救者】政治经济学速成&#xff08;基础习题&#xff09; 研究生产关系必须联系生产力和上层建筑 1.生产力与生产关系 生产力代表生产的物质内容&#xff0c;生产关系是生产的社会形式。生产力决定生产关系&#xff0c;生产关系对生产力具有 反作用 *其中的”反作用”指的是…

005.精读《B-Tree vs LSM-Tree》

文章目录 1. 引言&#xff1a;2. 精读2.1 性能指标2.2 B-tree2.3 LSM-tree2.4 性能对比 3. 写在最后 1. 引言&#xff1a; 在本期的技术深度解析中&#xff0c;我们将聚焦于数据领域的两个重要成员——B-Tree和LSM-Tree。这两种数据结构在数据管理系统中最为普遍且广泛采用的数…

关于 el-table 的合计行问题

目录 一.自定义合计行 二.合计行不展示&#xff0c;只有缩放/变大窗口或者F12弹出后台时才展示 三.合计行出现了表格滚动条下方 四.合计行整体样式的修改 五.合计行单元格样式修改 1.css 2.jsx方式 六.合计行单元格合并 一.自定义合计行 通过 show-summary 属性开启合计…

C++ | Leetcode C++题解之第554题砖墙

题目&#xff1a; 题解&#xff1a; class Solution { public:int leastBricks(vector<vector<int>>& wall) {unordered_map<int, int> cnt;for (auto& widths : wall) {int n widths.size();int sum 0;for (int i 0; i < n - 1; i) {sum wi…