面试之快速学习STL-常用算法

news2024/12/23 4:09:36

1. 排序算法

在这里插入图片描述

  1. sort() 函数基于快速排序实现的,故不保证相对位置,但是stable_sort (first, last)保证,它基于归并排序
  2. sort()只适用于支持随机迭代器的容器(array, vector, deque),好理解,毕竟用的快排
  3. 如果用默认的comp func排序,那么要支持 < (>) 重载
  4. 时间复杂度N*log2N
#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector
//以普通函数的方式实现自定义排序规则
bool mycomp(int i, int j) {
    return (i < j);
}
//以函数对象的方式实现自定义排序规则
class mycomp2 {
public:
    bool operator() (int i, int j) {
        return (i < j);
    }
};

int main() {
    std::vector<int> myvector{ 32, 71, 12, 45, 26, 80, 53, 33 };
    //调用第一种语法格式,对 32、71、12、45 进行排序
    std::sort(myvector.begin(), myvector.begin() + 4); //(12 32 45 71) 26 80 53 33
    //调用第二种语法格式,利用STL标准库提供的其它比较规则(比如 greater<T>)进行排序
    std::sort(myvector.begin(), myvector.begin() + 4, std::greater<int>()); //(71 45 32 12) 26 80 53 33
   
    //调用第二种语法格式,通过自定义比较规则进行排序
    std::sort(myvector.begin(), myvector.end(), mycomp2());//12 26 32 33 45 53 71 80
    //输出 myvector 容器中的元素
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    return 0;
}

2. 找第N小的数nth_element()

//排序规则采用默认的升序排序
void nth_element (RandomAccessIterator first,
                 RandomAccessIterator nth,
                 RandomAccessIterator last);
//排序规则为自定义的 comp 排序规则
void nth_element (RandomAccessIterator first,
                 RandomAccessIterator nth,
                 RandomAccessIterator last,
                 Compare comp);
  1. 运行完之后会出现一个现象是:该函数会找到第 n 大的元素 K 并将其移动到第 n 的位置处,同时所有位于 K 之前的元素都比 K 大,所有位于 K 之后的元素都比 K 小。
  2. 同样和大小有关,基本上需要随机访问迭代器啦,那么也需要重载运算符咯。
  3. 原理:其实就是快排一样的方法,只是找到第n个数就停止了

3. 选出前n个大的元素

有一个存有 100 万个元素的容器,但我们只想从中提取出值最小的 10 个元素,该如何实现呢?

//按照默认的升序排序规则,对 [first, last) 范围的数据进行筛选并排序
void partial_sort (RandomAccessIterator first,
                   RandomAccessIterator middle,
                   RandomAccessIterator last);
//按照 comp 排序规则,对 [first, last) 范围的数据进行筛选并排序
void partial_sort (RandomAccessIterator first,
                   RandomAccessIterator middle,
                   RandomAccessIterator last,
                   Compare comp);

1 . partial_sort() 函数会以交换元素存储位置的方式实现部分排序的。具体来说,partial_sort() 会将 [first, last) 范围内最小(或最大)的 middle-first 个元素移动到 [first, middle) 区域中,并对这部分元素做升序(或降序)排序
2. partial_sort采用的堆排序(heapsort),它在任何情况下的复杂度都是n*log(n). 如果你希望用partial_sort来实现全排序,你只要让middle=last就可以了。

4. 合适的排序算法

在这里插入图片描述

5. std::list排序,采用归并

参考下面的方法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    
    ListNode* merge(ListNode *head1,ListNode *head2) {
        ListNode *n_head=new ListNode(0);
        ListNode *h_head=n_head;
        while(head1!=nullptr && head2!=nullptr) {
            if(head1->val<head2->val) {
                ListNode *temp=head1->next;
                h_head->next=head1;
                head1=temp;
            } else if(head1->val>head2->val) {
                ListNode *temp=head2->next;
                h_head->next=head2;
                head2=temp;
            } else {
                ListNode *temp=head1->next;
                ListNode *temp1=head2->next;
                h_head->next=head1;
                h_head=h_head->next;
                h_head->next=head2;
                head1=temp;
                head2=temp1;
            }
            h_head=h_head->next;
        }
 
        if(head1!=nullptr) {
            h_head->next=head1;
        } else if(head2!=nullptr) {
            h_head->next=head2;
        } else h_head->next=nullptr;
        return n_head->next;
    }
    
    ListNode *helper(ListNode* head,ListNode *tail) {
        if(head==nullptr) return head;
        if(head->next==tail) {
            head->next=nullptr;
            return head;
        }
        ListNode *slow=head,*fast=head;
        while(fast!=tail) {
            slow=slow->next;
            fast=fast->next;
            if(fast!=tail) {
                fast=fast->next;
            }
        }
        ListNode *mid=slow;
        return merge(helper(head,mid),helper(mid,tail));
    }
    
    ListNode* sortList(ListNode* head) {
        return helper(head,nullptr);
    }
};


说明:
1.归并,找到中间值用的快慢指针
2. 但实际STL里面的排序用的是非递归的归并,代码可参考:
https://blog.csdn.net/qq_31720329/article/details/85535787
3. 如果对std::list做排序用std::sort,那么实际上随机访问数值时需要逐个获取,时间复杂度也相当于n^2了

6. 排序算法优先使用函数对象

性能优势
对于排序算法,使用函数对象编译器可以直接进行内联,减少函数调用次数。而使用普通函数时,传入算法内部的实际是函数指针,编译器无法对其进行优化。

7.合并两个有序的容器

借助 merge() 或者 inplace_merge() 函数实现。

//以默认的升序排序作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result);
//以自定义的 comp 规则作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result, Compare comp);
  1. 当 2 个有序序列存储在同一个数组或容器中时,如果想将它们合并为 1 个有序序列,除了使用 merge() 函数,更推荐使用 inplace_merge() 函数。

8. 查找

  1. find()函数就不说了
  2. 和 find() 函数相同,find_if() 函数也用于在指定区域内执行查找操作。不同的是,前者需要明确指定要查找的元素的值,而后者则允许自定义查找规则。
    std::vector<int> find_if_vec{4,2,3,1,5};
    auto find_if_it = std::find_if(find_if_vec.begin(), find_if_vec.end(), [](int a){return a%2 == 0;});
    std::cout << "find_if_it : " << *find_if_it << std::endl;
    /* find_if_it : 4 */
  1. find_if_not()相反
  2. find_end()和 search() :find_end() 函数用于在序列 A 中查找序列 B 最后一次出现的位置, 序列 B 在序列 A 中第一次出现的位置,该如何实现呢?可以借助 search() 函数。
  3. search() 和 search_n() :用于在指定区域内查找第一个符合要求的子序列。不同之处在于,前者查找的子序列中可包含多个不同的元素,而后者查找的只能是包含多个相同元素的子序列。

关于 search() 函数和 search_n() 函数的区别,给大家举个例子,下面有 3 个序列:

序列 A:1,2,3,4,4,4,1,2,3,4,4,4
序列 B:1,2,3
序列 C:4,4,4

如果想查找序列 B 在序列 A 中第一次出现的位置,就只能使用 search() 函数;而如果想查找序列 C 在序列 A 中第一次出现的位置,既可以使用 search() 函数,也可以使用 search_n() 函数。

9. 数据分类partition()和stable_partition()

  1. 我理解类似快排只是比较的不是大小,是一种条件
  2. partition 可直译为“分组”,partition() 函数可根据用户自定义的筛选规则,重新排列指定区域内存储的数据,使其分为 2 组,第一组为符合筛选条件的数据,另一组为不符合筛选条件的数据。
ForwardIterator partition (ForwardIterator first,
                           ForwardIterator last,
                           UnaryPredicate pred);
  1. partition() 函数还会返回一个正向迭代器,其指向的是两部分数据的分界位置,更确切地说,指向的是第二组数据中的第 1 个元素
#include <iostream>     // std::cout
#include <algorithm>    // std::partition
#include <vector>       // std::vector
using namespace std;
//以普通函数的方式定义partition()函数的筛选规则
bool mycomp(int i) { return (i % 2) == 0; }

//以函数对象的形式定义筛选规则
class mycomp2 {
public:
    bool operator()(const int& i) {
        return (i%2 == 0);
    }
};

int main() {
    std::vector<int> myvector{1,2,3,4,5,6,7,8,9};
    std::vector<int>::iterator bound;
    //以 mycomp2 规则,对 myvector 容器中的数据进行分组
    bound = std::partition(myvector.begin(), myvector.end(), mycomp2());
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        cout << *it << " ";
    }
    cout << "\nbound = " << *bound;
    return 0;
}

10. 二分查找lower_bound()函数

  1. find()、find_if()、search() 等。值得一提的是,这些函数的底层实现都采用的是顺序查找(逐个遍历)的方式,在某些场景中的执行效率并不高.
  2. C++ STL标准库中还提供有 lower_bound()、upper_bound()、equal_range() 以及 binary_search() 这 4 个查找函数,它们的底层实现采用的都是二分查找的方式。
  3. lower_bound() 函数用于在指定区域内查找不小于目标值的第一个元素。也就是说,使用该函数在指定范围内查找某个目标值时,最终查找到的不一定是和目标值相等的元素,还可能是比目标值大的元素。
  4. 注意这个不小于可以自己定义。
  5. 再次强调,该函数仅适用于已排好序的序列。所谓“已排好序”,指的是 [first, last) 区域内所有令 element<val(或者 comp(element,val),其中 element 为指定范围内的元素)成立的元素都位于不成立元素的前面。
std::vector<int> lower_bound_vec{4,5,3,1,2};
    auto lower_bound_vec_it = std::lower_bound(lower_bound_vec.begin(), lower_bound_vec.end(), 3, [](int i, int j) {return i >j; });
    std::cout<< "lower_bound_vec_it = " << *lower_bound_vec_it<<std::endl;
    /* 3 */
  1. 注意,myvector 容器中存储的元素看似是乱序的,但对于元素 3 来说,大于 3 的所有元素都位于其左侧,小于 3 的所有元素都位于其右侧,且查找规则选用的是 mycomp2(),其查找的就是第一个不大于 3 的元素,因此 lower_bound() 函数是可以成功运行的。

11. 二分查找upper_bound()函数

用于在指定范围内查找大于目标值的第一个元素。

举个例子,假设 有一个数组num:

1 2 2 2 3 4 5
value = 2
lower_bound 得到的是num[1]
uppper_bound得到的是nun[4]

12. 查找所有相等的元素

//找到 [first, last) 范围中所有等于 val 的元素
pair<ForwardIterator,ForwardIterator> equal_range (ForwardIterator first, ForwardIterator last, const T& val);

  1. 也要排好序
  2. 该函数会返回一个 pair 类型值,其包含 2 个正向迭代器。当查找成功时:
    第 1 个迭代器指向的是 [first, last) 区域中第一个等于 val 的元素;
    第 2 个迭代器指向的是 [first, last) 区域中第一个大于 val 的元素。
    反之如果查找失败,则这 2 个迭代器要么都指向大于 val 的第一个元素(如果有),要么都和 last 迭代器指向相同。
  3. 我可以理解为第一个返回的是lower_bound获取的值,第二个是upper_bound获取的值

13. 检查算法

  1. 用来检查在算法应用到序列中的元素上时,什么时候使谓词返回 true
  2. all_of() 算法会返回 true,前提是序列中的所有元素都可以使谓词返回 true。
  3. any_of() 算法会返回 true,前提是序列中的任意一个元素都可以使谓词返回 true。
  4. none_of() 算法会返回 true,前提是序列中没有元素可以使谓词返回 true。
    std::vector<int> none_of_vec{22, 19, 46, 75, 54, 19, 27, 66, 61, 33, 22, 19};
    std::cout << "There are "
    << (std::none_of(none_of_vec.begin(), none_of_vec.end(), [](const int &i){return i == 18;}) ? "no" : "some")
        << " student age = 18 ."
        << std::endl;
    /* There are no student age = 18 .*/
  1. 注意返回的是true or false
  2. count_if() 会返回可以使作为第三个参数的谓词返回 true 的元素个数。

14. copy_n(STL copy_n)算法详解

  1. copy_n() 算法可以从源容器复制指定个数的元素到目的容器中。第一个参数是指向第一个源元素的输入迭代器,第二个参数是需要复制的元素的个数,第三个参数是指向目的容器的第一个位置的迭代器
  2. 这个算法会返回一个指向最后一个被复制元素的后一个位置的迭代器
    std::vector<int> none_of_vec{22, 19, 46, 75, 54, 19, 27, 66, 61, 33, 22, 19};
    std::cout << "There are "
    << (std::none_of(none_of_vec.begin(), none_of_vec.end(), [](const int &i){return i == 18;}) ? "no" : "some")
        << " student age = 18 ."
        << std::endl;
    /* There are no student age = 18 .*/
    std::vector<std::string> names {"A1","George" ,"Harry", "Iain", "Beth","Carol",  "Eve","Dan", "Joe","Fred"};
    std::unordered_set<std::string> more_names {"Janet", "John"};
    std::copy_n(std::rbegin(names)+1, 3, std::inserter(more_names, std::begin(more_names)));
    
    /*
     [0] = "Eve"
     [1] = "Dan"
     [2] = "Joe"
     [3] = "John"
     [4] = "Janet"
     */
  1. 还有copy_if

后面还有很多算法不想看了,其实都是基本的算法
在这里插入图片描述

  1. unique() 算法可以在序列中原地移除重复的元素,这就要求被处理的序列必须是正向迭代器所指定的。在移除重复元素后,它会返回一个正向迭代器作为新序列的结束迭代器。可以提供一个函数对象作为可选的第三个参数,这个参数会定义一个用来代替 == 比较元素的方法。例如:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    remove是覆盖的方法和uinque一样

条件相等 replace
在这里插入图片描述

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

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

相关文章

Mysql 开窗函数(窗口函数)

文章目录 全部数据示例1&#xff08;说明&#xff09;开窗函数可以比groupby多查出条件列外的字段&#xff0c;开窗函数主要是为了跟聚合函数一起使用&#xff0c;达到分组统计效果&#xff0c;并且开窗函数的结果集基本都是跟总行数一样示例2示例3示例4错误示例1错误示例2错误…

基于vue的小说阅读网/基于springboot的小说网站/阅读网站的设计与实现

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&a…

学习设计模式之享元模式,但是宝可梦

前言 作者在准备秋招中&#xff0c;学习设计模式&#xff0c;做点小笔记&#xff0c;用宝可梦为场景举例&#xff0c;有错误欢迎指出。 享元模式 1 介绍 享元模式很好理解&#xff0c;它主要是为了减少创建对象的数量&#xff0c;属于结构型设计模式 目的&#xff1a;减少…

【第三阶段】kotlin语言的内置函数run

1.run函数返回类型是根据匿名函数最后一行的类型变化而变化 run 函数里面持有的是this fun main() {val info"kotlin is ok"var r info.run {true}println(r) }执行结果 2.run搭配具名函数 package Stage3fun main() {val info"kotlin is ok"var r info…

IAR编译报错 ErrorLi005]: no definition for

IAR编译报错 ErrorLi005]: no definition for… 具体报错如下图所示&#xff1a; 错误原因&#xff1a;出现此问题是由于没有写 VehicleDriveStatusCheck 此功能函数&#xff0c;而程序中其他地方调用此函数而导致的错误 解决措施&#xff1a;编写 VehicleDriveStatusCheck …

docker的安装以及基本操作

一.认识docker Docker是一种用于构建、打包和运行应用程序的开源平台。它基于操作系统级虚拟化技术&#xff0c;可以将应用程序和其依赖的库、环境等资源打包到一个可移植的容器中&#xff0c;形成一个轻量级、独立的可执行单元。 开发者在本地编译测试通过的容器可以批量地在…

Win 7 修改开机密码,使用F8修复提前终止导致蓝屏

前言 一台win7一体机密码突然不对&#xff0c;也不知道怎么回事就登录不了 使用PE中的NTPWEdit查看修改密码&#xff0c;没有用户列表 步骤 正常启动或者按F8&#xff0c;选择修复 正常电脑如下显示 这种可以直接使用PE改密码&#xff0c;就不放图了 问题电脑如下显示…

kafka--技术文档--基本docker中安装<单机>-linux

安装zookeeper 阿丹小科普&#xff1a; Kafka在0.11.0.0版本之后不再依赖Zookeeper&#xff0c;而是使用基于Raft协议的Kafka自身的仲裁机制来替代Zookeeper。具体来说&#xff0c;Kafka 2.8.0版本是第一个不需要Zookeeper就可以运行Kafka的版本&#xff0c;这被称为Kafka Raf…

C#详解-Contains、StartsWith、EndsWith、Indexof、lastdexof

目录 简介: 过程: 举例1.1 举例1.2 ​ 总结: 简介: 在C#中Contains、StarsWith和EndWith、IndexOf都是字符串函数。 1.Contains函数用于判断一个字符串是否包含指定的子字符串&#xff0c;返回一个布尔值&#xff08;True或False&#xff09;。 2.StartsWith函数用于判断一…

Vue Element upload组件和Iview upload 组件上传文件

今天要分享的是使用这俩个UI组件库的upload组件分别实现调用组件本身的上传方法实现和后台交互。接下来就是开车的时间&#xff0c;请坐稳扶好~ 一、element upload组件传送门 1、html文件 <el-upload ref"uploadRef" :action"uploadUrl" :data"…

第五讲:常见的BeanPostProcessor

常见的BeanPostProcessor 一、入门Demo二、添加BeanPostProcessor1. AutowiredAnnotationBeanPostProcessor2. CommonAnnotationBeanPostProcessor3. ConfigurationPropertiesBindingPostProcessor 前文我们简单讲了Bean的生命周期&#xff0c;以及生命周期的前后&#xff0c;本…

科大讯飞AI大模型,太猛了!

最近几个月&#xff0c;以ChatGPT为代表的AIGC迅速崛起&#xff0c;国内头部科技企业之间也掀起了百模大战&#xff0c;在众多的大模型当中我比较看好的一家就是&#xff1a;科大讯飞。 我很早就通过科大讯飞老朋友&#xff0c;拿到了体验账号&#xff0c;简单体验了一下&…

C++新经典06-- 语言特性

1.继承性、多态性、封装性。 2.C本身是属于编译型语言。 什么叫编译型语言呢&#xff1f;程序在执行之前需要一个专门的编译过程&#xff0c;把程序编译成二进制文件&#xff08;可执行文件&#xff09;&#xff0c;执行的时候&#xff0c;不需要重新翻译&#xff0c;直接使用…

shell脚本中linux命令的特殊用法记录

shell脚本中linux命令的特殊用法记录 1、linux命令特殊参数选项1.1、sed -e1.2、echo -e 2、 shell 扩展2.1、[[ ]]支持用~进行正则匹配 3、特殊命令用法3.1、{} 变量替换 1、linux命令特殊参数选项 1.1、sed -e sed -e以严格模式执行脚本&#xff0c;在sed -e 后面的所有命令…

反转链表II

江湖一笑浪滔滔&#xff0c;红尘尽忘了 题目 示例 思路 链表这部分的题&#xff0c;不少都离不开单链表的反转&#xff0c;参考&#xff1a;反转一个单链表 这道题加上哨兵位的话会简单很多&#xff0c;如果不加的话&#xff0c;还需要分情况一下&#xff0c;像是从头节点开始…

【Apollo学习笔记】——规划模块TASK之PATH_BORROW_DECIDER

文章目录 前言PATH_BORROW_DECIDER功能简介PATH_BORROW_DECIDER相关配置PATH_BORROW_DECIDER总体流程PATH_BORROW_DECIDER相关子函数IsNecessaryToBorrowLaneIsBlockingObstacleFarFromIntersectionIsNonmovableObstacleCheckLaneBorrow 参考 前言 在Apollo星火计划学习笔记—…

MySql014——分组的GROUP BY子句和排序ORDER BYSELECT子句顺序

前提&#xff1a;使用《MySql006——检索数据&#xff1a;基础select语句》中创建的products表 一、GROUP BY子句基础用法 SELECT vend_id, COUNT(*) AS num_prods FROMstudy.products GROUP BY vend_id;上面的SELECT语句指定了两个列&#xff0c;vend_id包含产品供应商的ID&…

Protobuf 语法详解

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

mybatisplus批量写入

1.新建MybatisPlusConfig /*** MybatisPlusConfig.*/ Configuration MapperScan("com.test.mapper") public class MybatisPlusConfig {/*** 自定义批量插入 SQL 注入器.*/Beanpublic InsertBatchSqlInjector insertBatchSqlInjector() {return new InsertBatchSqlI…

微服务(多级缓存)

多级缓存 1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后&#xff0c;先查询Redis&#xff0c;如果未命中则查询数据库&#xff0c;如图&#xff1a; 存在下面的问题&#xff1a; 请求要经过Tomcat处理&#xff0c;Tomcat的性能成为整个系统的瓶颈Redis缓存失效时&…