STL之算法概览

目录

算法概览

算法分析与复杂度标识O()

STL算法总览

质变算法mutating algorithms----会改变操作对象之值

非质变算法nonmutating algorithms----不改变操作对象之值

STL算法的一般形式

算法的泛化过程


算法概览

        算法,问题之解法也。

        以有限的步骤,解决逻辑或数学上的问题,这一专门科目我们称为算法(Algorithms)。大学信息相关教育里面,与编程最有关直接关系的科目,首推算法与数据结构(Data Structures,亦即STL中的容器)。STL算法即是将最被运用的算法规范出来,其涵盖区间有可能在每五年一次的c++标准委员会中不断增订。

        广义而言,我们所写的每个程序都是一个算法,其中的每个函数也都是一个算法,毕竟它们都用来解决或大或小的逻辑问题或数学问题。唯有用来解决特定问题(例如排序,查找,最短路径,三点共线。。。),并且获得数学上的效能分析与证明,这样的算法才具有可复用性。本章讨论的便是被收录于STL之中,极具复用价值的70余个STL算法,包括赫赫有名的排序(sorting),查找(searching),排列组合(permutation)算法,以及用于数据移动,复制,删除,比较,组合,运算等算法。

        特定的算法往往搭配特定的数据结构。例如binary search tree(二叉查找树)和RB-tree(红黑树)便是为了解决查找问题而发展出来的特殊数据结构,hashtable拥有快速查找的能力。又例如max-heap(或min-heap)可以协助完成所谓的heap sort(堆排序)。几乎可以说,特定的数据结构是为了实现某种特定的算法。这一类"与特定数据结构相关"的算法,被本书(及STL)归类为“关联式容器”(associated containers)之列。本章所讨论的,是可施行于“无太多特殊条件限制”之空间中的某一段元素区间的算法。

算法分析与复杂度标识O()

        当我们发现(发明)一个可以解决问题的算法时,下一个重要步骤就是决定该算法所耗用的资源,包括空间和时间。这个操作称为算法分析(algorithm analysis)。可以这么说,如果一个算法得耗用数GB的内存空间才能获得令人满意的效率,这种算法没有用--至少在目前的计算机架构下没有使用价值。

        一般而言,算法的执行时间和其所要处理的数据量有关,两者之间存在某种函数关系,可能是一次(线性linear),二次(quadratic),三次(cubic)或对数(logarithm)关系。当数据量小时,多项式函数中的每一项都可能对结果带来相当程度的影响,但是当数据量够大(这是我们应该关注的情况)时,只有最高次方的项目才具主导地位。

        下面是三个复杂度各异的问题

  • 1.最小元素问题:求取array中的最小元素
  • 2.最短距离问题:求取X-Y平面上N个点中,距离最近的两个点
  • 3.三点共线问题:决定X-Y平面上的N个点,是否有任何三点共线。

        最小元素问题的解法一定必须两两元素比对,逐一进行。N个元素需要N次比对,所以数据量和执行时间呈线性关系。“最短距离”问题所需计算的元素对(pairs)共有N(N-1)/2!,所以大数量和执行时间呈二次关系。“三点共线”问题要计算的元素对共有N(N-1)(N-2)/3!,所以大数据量和执行时间呈三次关系。

        上述三种复杂度,以所谓BIG-Oh标记法表示为O(N),O(N^2),O(N^3).这种标记法的定义如下:

        如果有任何正值常数c和N_0,使得N\geq N_0时,T(N)\leq cF(N),那么我们便可将T(N)的复杂度标识为O(F(N))

        以下三个问题出现一种新的复杂度形式:

  • 4.需要多少bits才能表现出N个连续整数?
  • 5.从X=1开始,每次将X扩充两倍,需要多少次扩充才能使X\geq N?
  • 6.从X=N开始,每次将X缩减一半,需要多少次缩减才能使X\leq 1?

        就问题4而言,B个bits可表现出2^B个不同的整数,因此欲表现N个连续整数,需满足方程式2^B\geq N,亦即B\geq logN

        问题5称为"持续加倍问题",必须满足方程式2^K\geq N,此式同问题4,因此解答相同。问题6称为“程序减半问题”,与问题5意义相同,只不过方向相反,因此解答相同。

        如果有一个算法,花费固定时间(常数时间,O(1))将问题的规模降低某个固定比例(通常是1/2),基于上述问题6的解答,我们便说此算法的复杂度是O(logN)。注意问题规模的降低比例如何,并不会带来影响,因此它会反应在对数的底上,而底对于Big-Oh标记法是没有影响。

        算法复杂度,可以作为我们衡量算法效率的标准。

STL算法总览

        STL算法,主要包括查找,填充,排序,排序判断;算法名称包括accumulate,binary_search, fill,find_if,max, min, sort, remove,reverse, rotate等算法。

质变算法mutating algorithms----会改变操作对象之值

        所有的STL算法都作用在有迭代器[first, last)所标示出来的区间上,所谓"质变算法",是指运算过程中会更改区间内(迭代器所指)的元素内容。诸如拷贝(copy),互换(swap),替换(replace),填写(fill), 删除(remove),排列组合(permutation),分割(partition),随机重排(random shuffling)、排序(sort)等算法,都属此类。如果将此类算法运用于常数区间上,例如:

int ia[] = {22, 30, 30, 17, 33, 40, 17, 23, 22, 12, 20};
vector<int> iv(ia, ia + sizeof(ia) / sizeof(int));vector<int>::const_iterator cite1 = iv.begin();
vector<int>::const_iterator cite2 = iv.end();sort(cit1, cit2);

非质变算法nonmutating algorithms----不改变操作对象之值

        所有的STL算法都作用在由迭代器[first, last)所标示出来的区间上。所以非质变算法,是指运算过程中不会更改区间内(迭代器所指)的元素内容。诸如查找(find),匹配(search),计数(count),巡访(for_each),比较(match,mismatch),寻找极值(max,min)等算法,都属此类。但是如果你在for_each(巡访每个元素)算法上应用了一个会改变元素的仿函数(functor),例如

template<class T>
struct plus2 {void operator()(T&x) const {x += 2;}
};int ia[] = {22, 30, 30, 17, 33, 40, 17, 23, 22, 12, 20};
vector<int> iv(ia, ia + sizeof(ia) / sizeof(int));for_each(iv.begin(), iv.end(), plus2<int>());

那么当然元素会被改变。

STL算法的一般形式

        所有泛型算法的前两个参数都是一对迭代器(iterators),通常称为first和last,用以标示算法的操作区间。STL习惯采用前闭后开标识法,写成[first,last),表示区间涵盖first至last(不含last)之间的所有元素。当first==last时,上述所表现的便是一个空区间。

        这个[first, last)区间的必要条件是,必须能够经由increment(累加)操作符的反复运用,从first到last。编译器本身无法强求这一点。如果这个条件不成立,会导致未可预期的结果。

        根据行进特性,迭代器可分为5类。每个STL算法的声明,都表现出它所需要的最低程度的迭代器类型。例如find()需要一个InputIterator,这是它的最低要求,但它也可以接受更高类型的迭代器,如ForwardIterator,BidirectionalIterator或RandomAccessIterator,因为无论是ForwardIterator,BidirectionalIterator或RandomAccessIterator也都是一种InputIterator。但如果你交给find()一个OutputIterator,会导致错误。

        将无效的迭代器传给某个算法,虽然是一个错误,却不保证能够在编译器就被捕捉出来,因为所谓迭代器类型并不是真实的型别,它们只是function template的一种型别参数。

        许多STL算法不知支持一个版本。这一类算法的某个版本采用缺省运算行为,另一个版本提供额外参数,接受外界传入一个仿函数(functor),以便采用其他策略。例如unique()缺省情况下使用equality操作符来比较两个相邻元素,但如果这些元素的型别并未供应equality操作符,或用户希望定义自己的equality操作符,便可以传一个仿函数给另一个版本的unique()。有些算法干脆将两个版本命名为两个不同名称的实体,附从的那个总是以_if作为尾词,例如find_if(),另一个例子是replace(),使用内建的equality操作符进行比对操作,replace_if则以接收到的仿函数进行比对行为。

        质变算法通常提供两个版本:一个是in-place(就地进行)版本,就地改变其操作对象;另一个是copy(另地进行)版,将操作对象的内容复制一份副本,然后再副本上进行修改并返回该副本。copy版本总是以_copy作为函数名称尾词,例如replace和replace_copy()。并不是所有的质变算法都有copy版,例如sort()就没有。如果我们希望以这类"无copy版本"之质变算法施行于某段区间的元素的副本上,我们必须自行制作并传递那一份副本。

        所有的数值算法,包括adjacent_difference(), accumulate(), inner_product, partial_sum等等,都实现与SGI<stl_numeric.h>之中,这是个内部文件,STL规定用户必须包含的是上层的<numeric>.其中STL算法都实现于SGI的<stl_algo.h>和<stl_algobase.h>文件中,夜都市内部文件,欲使用这些算法,必须包含上层相关头文件<algorithm>

算法的泛化过程

         将一个叙述完整的算法转化为程序代码,是任何训练有素的程序员胜任愉快的工作。这些工作由的极其简单(例如循序查找),有的稍微复杂(例如快速排序法),有的十分繁复 (例如红黑树之建立与元素存取),但基本上都不应该形成任何难以跨越的障碍。

         然而,如何将算法独立于其所处理的数据结构之外,不受数据结构的羁绊,思想层面就不是那么简答了。如何设计一个算法,是他适用于任何(或大多数)数据结构呢?换个说法,我们如何在即将处理的未知的数据结构(也许是array, 也许是vector,也许是list,也是是deque...)上,正确实现所有操作呢?

        关键在于,只要把操作对象的型别加以抽象化,把操作对象的标示法和区间目标的移动行为抽象化,整个算法也就在一个抽象层面上工作了。整个过程称为算法的泛型化(generalized),简称泛化。

        让我们看看算法泛化的一个实例。以简单的循序查找为例,假设我们要写一个find函数,在array中寻找特定值。面对整数array,我们的直觉反应是:

int* find(int *arrayHead, int arraySize, int value) {int I = 0;for (I =0; I<arraySize; ++I) {if (arrayHead[I] == value) break;}return &(arrayHead[I])
}

        该函数在某个区间内查找value。返回一个指针,指向它所找到的第一个符合条件的元素;如果没有找到,就返回最后一个元素的下一个位置(地址)。

        “最后元素的下一个未知”成为end.返回end以表示"查找无结果"似乎是个可笑的做法,为什么不返回null?因为,一如稍后即将看到的,end指针可以对其他种类的容器带来泛型效果,这是null所无法达到的。是的,从小我们被教导,使用array时千万不要超越其区间,但事实上一个指向array元素的指针,不但可以合法指向array内的任何未知,也可以指向array尾端以外的任何位置。只不过当指针指向array尾端以外的位置时,它只能用来与其他array指针相比较,不能提领(dereference)其值。现在,我们可以这样使用find函数。

const int arraySize = 7;
int ia[arraySize] = {0, 1, 2, 3, 4, 5, 6};
int *end = ia + arraySize;int *ip = find(ia, arraySize, 4);
if (ip == end) {cout << "4 not found" << endl;
} else {cout << "4 found. " << *ip << endl;
}

   上述find的做法暴露了容器太多的细节(例如arraySize),也因此太过依附特定容器。为了让find适用于所有类型的容器,其操作应该更抽象化些。让find接受连个指针作为参数,标识出一个操作区间,就是很好的做法:

int* find(int* begin, int* end, int value) {while(begin != end && *begin != value) {++begin;}return begin;
}

        这个函数在"前闭后开"区间[begin, end)内查找value,并返回一个指针,指向它所找到的第一个符合条件的元素;如果没找到就返回end。现在,你可以这样使用find函数:

const int arraySize = 7;
int ia[arraySize] = {0, 1, 2, 3, 4, 5, 6};
int *end = ia + arraySize;int *ip = find(ia, end, 4);
if (ip == end) {cout << "4 not found" << endl;
} else {cout << "4 found. " << *ip << endl;
}

find函数也可以很方便地用来查找array的子区间

int*p = find(ia +2, ia+5, 3);
if (ip == end)cout << "3 not found" << endl;
else cout << "3 found " << *p << endl;

       由于find()函数之内并无任何操作是针对特定的整数array而发的,所以我们可以将它改为template

template <class T>
T* find(T* begin, T* end, const T& value) {while(begin != end && *begin != value) {++begin;}return begin;
}

请注意数值的传递由pass-by-value改为了pass-by-reference-const,因为如今所传递的value,其型别可为任意;于是对象一大,传递成本便会提升,这是我们不愿见到的。pass-by-reference可完全避免这些成本。

        这样find很好,几乎使用与任何容器---只要该容器允许指针指入,而指针们又都支持一下四种find()函数中出现的操作行为:

  • inequality 判断不相等操作符
  • dereferencelm 提领、取值操作符
  • prefix increment 前置式递增操作符
  • copy 复制行为

        C++有一个极大的优点便是,几乎所有东西都可以改写为程序员自定义的形式或行为。是的,上述这些操作符或操作行为都可以被重载(overloaded),既事如此,何必将find限制为只能使用指针呢?何不让支持以上四种行为的、行为很像指针的某种对象都可以被find()使用呢?如此一来,find()函数便可以从原生(native)指针的思想框框中跳脱出来。如果我们以一个原生指针指向某个list,则该指针进行"++"操作并不能使指针指向下一个串行节点。但如果我们设计一个class,拥有原生指针,并使其"++"操作指向list的下一个节点,那么find就可以施行于list容器身上了。

        这便是迭代器(iterator)的观念。迭代器是一种行为类似指针的对象,换句话说,是一种smart pointers.现在我们将find()函数内的指针以迭代器取代,重新写过

template <class Iterator, class T>
Iterator find(Iterator begin, Iterator end, const T& value) {while(begin != end && *begin != value) {++begin;}return begin;
}

        这便是一个完全泛化话的find()函数。我们可以再任何c++标准库的头文件里看到它,长相几乎一模一样。SGISTL把它放在<stl_algo.h>之中。

        有了这样的观念准备,后续看到的STL的各种泛型算法,将随处可见迭代器及数据作为泛型参数作为算法的输入参数。

参考文档《STL源码剖析》--侯捷

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

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

相关文章

华为IPD流程管理体系L1至L5最佳实践-解读

该文档主要介绍了华为IPD流程管理体系&#xff0c;包括流程体系架构、流程框架实施方法、各业务流程框架示例以及相关案例等内容&#xff0c;旨在帮助企业建立高效、规范的流程管理体系&#xff0c;实现业务的持续优化和发展。具体内容如下&#xff1a; 1. 华为流程体系概述 -…

【青牛科技】 D2822M 双通道音频功率放大电路芯片介绍,用于便携式录音机和收音机作音频功率放大器

概述&#xff1a; D2822M 用于便携式录音机和收音机作音频功率放大器。D2822M 采用 DIP8 和 SOP8 封装形式。 特点&#xff1a;  电源电压降到 1.8V 时仍能正常工作  交越失真小  静态电流小  可作桥式或立体声式功放应用  外围元件少  通道分离度高  开机和关机…

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…

抖音短视频矩阵源代码部署搭建流程

抖音短视频矩阵源代码部署搭建流程 1. 硬件准备 需确保具备一台性能足够的服务器或云主机。这些硬件设施应当拥有充足的计算和存储能力&#xff0c;以便支持抖音短视频矩阵系统的稳定运行。 2. 操作系统安装 在选定的服务器或云主机上安装适合的操作系统是关键步骤之一。推…

kmeans 最佳聚类个数 | 轮廓系数(越大越好)

轮廓系数越大&#xff0c;表示簇内实例之间紧凑&#xff0c;簇间距离大&#xff0c;这正是聚类的标准概念。 簇内的样本应该尽可能相似。不同簇之间应该尽可能不相似。 目的&#xff1a;鸢尾花数据进行kmeans聚类&#xff0c;最佳聚类个数是多少&#xff1f; plot(iris[,1:4…

day04 企业级Linux安装及远程连接知识实践

1. 使用传统的网卡命名方式 在启动虚拟机时&#xff0c;按tab键进入编辑模式 添加命令&#xff1a; net.ifnames0 biosdevname0 这样linux系统会使用传统的网卡命名&#xff0c;例如eth0、eth1…… 2. 快照 做系统关键操作时&#xff0c;一定要使用快照(先将系统关机) 3.…

STM32C011开发(2)----nBOOT_SEL设置

STM32C011开发----2.nBOOT_SEL设置 概述硬件准备视频教学样品申请源码下载参考程序自举模式BOOT0设置配置 nBOOT_SEL生成STM32CUBEMX串口配置LED配置堆栈设置串口重定向主循环演示 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工…

onvif协议相关:3.1.5 Digest方式获取预置位

背景 关于onvif的其实很早之前我已经在专栏中写了不少了, 使用onvif协议操作设备 但最近有陆陆续续的粉丝问我, 希望我在写一些关于 onvif的设备自动发现、预置位跳转、云台操作的博客。 满足粉丝的需求,安排。 今天我们来实现 获取预置位 准备工作 我们这里的话选择Diges…

docker 通过Dockerfile自定义的镜像部署Springboot项目

一、镜像结构介绍&#xff1a; 镜像&#xff1a;层&#xff08;Layer&#xff09;添加安装包、依赖、配置等&#xff0c;每一次操作都形成新的一层&#xff1b;基础镜像&#xff08;BaseImage&#xff09;应用依赖的系统函数库、环境、配置、文件等&#xff1b;入口&#xff0…

【Canvas与图标】GUI图标

【成图】 120*120的png图标 各种大小图&#xff1a; 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>GUI图标 Draft1</titl…

CCF GESP C++ 一级上机题(十六道题及其思路详解合集)

#include <iostream> using namespace std;int main() {// 定义起始年份、结束年份、循环变量以及用于累加的变量&#xff0c;并初始化累加变量为0int start, end, i, sum 0;// 从标准输入读取起始年份和结束年份cin >> start >> end;// 循环遍历从起始年份…

Opencv+ROS实现颜色识别应用

目录 一、工具 二、原理 概念 本质 三、实践 添加发布话题 主要代码 四、成果 五、总结 一、工具 opencvros ubuntu18.04 摄像头 二、原理 概念 彩色图像&#xff1a;RGB&#xff08;红&#xff0c;绿&#xff0c;蓝&#xff09; HSV图像&#xff1a;H&#xff0…

scala模式匹配

object test47 {def main(args: Array[String]): Unit {val id"445646546548858548648"//取出id前两位val provinceid.substring(0,2) // println(province) // if (province"42"){ // println("湖北") // }else if(province&quo…

AI加持,华为全屋智能品牌升级为“鸿蒙智家”

1.传统智能家居的困境&#xff1a;从便利到繁琐 近年来&#xff0c;智能家居因其便捷性和科技感受到消费者的青睐。然而&#xff0c;随着用户需求的多样化&#xff0c;传统智能家居的弊端逐渐显现&#xff1a; 设备连接复杂&#xff0c;品牌间兼容性不足&#xff0c;用户不得不…

string类部分(C++)

目录 1. string类 1.1 auto和范围for auto关键词&#xff1a; 范围for&#xff1a; 1.2 string类的常用接口说明 a&#xff09;string类对象的常见构造 b&#xff09; string类对象的容量操作 size与length&#xff1a; capacity: empty: clear: reserve: 1.reserve&am…

大厂也在用的分布式链路追踪:TraceIdFilter + MDC + Skywalking

痛点 查线上日志时&#xff0c;同一个 Pod 内多线程日志交错&#xff0c;很难追踪每个请求对应的日志信息。 日志收集工具将多个 Pod 的日志收集到同一个数据库中后&#xff0c;情况就更加混乱不堪了。 解决 TraceId MDC 前端每次请求时&#xff0c;添加 X-App-Trace-Id 请…

Dashboard Tactics

1&#xff1a;相关链接Dashboard Tactics :: OpenCPN Dashboard Tactics Plugin rgleason/dashboard_tactics_pi: OpenCPN dashboard built-in plugin merger with external tactics_pi plugin NMEAconverter :: OpenCPN 2&#xff1a;显示样式 3&#xff1a;代码 这个插件…

【leetcode】动态规划

31. 873. 最长的斐波那契子序列的长度 题目&#xff1a; 如果序列 X_1, X_2, ..., X_n 满足下列条件&#xff0c;就说它是 斐波那契式 的&#xff1a; n > 3对于所有 i 2 < n&#xff0c;都有 X_i X_{i1} X_{i2} 给定一个严格递增的正整数数组形成序列 arr &#xff0…

24.11.26 Mybatis2

resultMap 中的标签和属性 如果是主键列 一般用id标签对应 propertyjava对象的属性 column 数据库中的列( javaType实体类数据类型 jdbcType数据库列的数据类型 ) 不需要配置 <id property"empno" column"empno" />如果是普通列 一般用result对…

第六届国际科技创新学术交流大会暨新能源科学与电力工程国际(NESEE 2024)

重要信息 会议官网&#xff1a;nesee.iaecst.org 会议时间&#xff1a;2024年12月6-8日 会议地点&#xff1a; 中国-广州&#xff08;越秀国际会议中心) 大会简介 新能源科学与电力工程国际学术会议&#xff08;NESEE 2024&#xff09;作为第六届国际科技创新学术交流大会分…