STL算法之其它算法_下

news2025/2/22 2:30:51

random_shuffle

        这个算法将[first,last)的元素次序随机排列。也就说,在N!中可能的元素排列中随机选出一种,此处N为last-first。

        N个元素的序列,其排列方式为N!中,random_shuffle会产生一个均匀分布,因此任何一个排列被选中的几率为1/N!。这很重要(稍后看源码,事实上依赖于随机数是否真随机),因为有不少算法可能会依赖排列的随机性。

        其算法实现过程,基本也是依次对每个元素进行随机的替换,以实现随机排列的功能。代码如下:

// SGI 版本一
template <class RandomAccessItertor>
inline void random_shuffle(RandomAccessItertor first, RandomAccessItertor last) {
    __random_shuffle(first, last, distance_type(first));
}

template <class RandomAccessItertor, class Distance>
void __random_shuffle(RandomAccessItertor first, RandomAccessItertor last, Distance *) {
    if (first == last) return ;
    for (RandomAccessItertor I = first; I != last; ++I) 
    #ifdef __STL_NO_DRAND48
        iter_swap(i, first + Distance(rand() % ((I - first) + 1)));
    #else
        iter_swap(i, first + Distance(lrand48() % ((I - first) + 1)));
    #endif
} 

// SGI 版本二
template <class RandomAccessItertor, class RandomNumberGenerator>
void random_shuffle(RandomAccessItertor first, RandomAccessItertor last, RandomNumberGenerator& rand) {
    if (first == last) return ;
    for (RandomAccessItertor I = first; I != last; ++I) 
        iter_swap(i, first + rand((I - first) + 1);
} 

从以上代码不难看出random_shuffle第一个版本会使用到内置的rand()函数,我们不难验证如果我们不调用srand函数指定随机数种子,会导致每次调用的结果是相同的序列依次出现。

测试代码如下:

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

template<class T>
struct display
{
    void operator() (const T &x) {
        cout << x << " ";
    }
};

int main() {
    int ia[] = {0, 1, 2,3, 4, 5, 6,7};
    vector<int> iv(ia , ia+8);
    srand(time(NULL));
    for (int i = 0; i < 10; ++i) {
        random_shuffle(iv.begin(), iv.end());
        for_each(iv.begin(), iv.end(), display<int>());
        cout << endl;
    }
    return 0;
}

如果将上述代码中的srand(time(NULL))删除,会发现每次运行的结果都是一样的。

partial_sort/partial_sort_copy

        本算法接受一个middle迭代器(位于序列[first,last)之内),然后重新安排[first, last),使序列中的middle-first个最小元素以递增顺序排序,置于[first,middle)内。其余last-middle个元素安置于[middle,last)中,不保证有任何特定顺序。

        使用sort算法,同样能够保证较小的N个元素以递增顺序置于[first, first+N)之内。选择partial_sort而非sort的唯一理由是效率。是的,如果只是跳出前N个最小元素排序,当然比对整个序列排序快上很多。

        partial_sort有两个版本;区别在于第二个版本会提供反函数comp替代默认的less-than操作符。算法内部采用heap sort来完成任务,简述如下。

        partial_sort的任务是找出middle-first个最小元素,因此,首先界定出区间[first, middle),并利用STL序列式容器之heap(堆)_heap stl-CSDN博客中的make_heap()将它组织为max-heap,然后将[middle,last)中的元素拿来与max-heap中的最大值比较(max-heap的最大值就在第一个元素上,轻松可获得);如果小于该最大值,就互换位置并重新保持max-heap状态。如此一来,当我们走遍整个[middle,last)时,较大的元素都已经被剥离出[first,middle),这时候再以sort_heap()将[first,middle)做一次排序,即可完成算法的述求。下图描述详情:

        下面是partial_sort的实现细节。

// 版本一
template <class RandoemAccessIterator>
inline void partial_sort(RandoemAccessIterator first, RandoemAccessIterator middle, RandoemAccessIterator last) {
    __partial_sort(first, middle, last, value_type(first));
}

template <class RandoemAccessIterator, class T>
void __partial_sort(RandoemAccessIterator first, RandoemAccessIterator middle, RandoemAccessIterator last, T*) {
    make_heap(first, middle);
    for (RandoemAccessIterator I = middle; I < last; ++I) 
        if (*I < *first) 
            __pop_heap(first, middle, I, T(*I), distance_type(first));
    sort_heap(first, middle);
}

        partial_sort有一个姊妹,就是partial_sort_copy,partial_sort和partial_sort_copy两者行为逻辑完全相同,只不过后者将(last-first)个最小元素排序后的所得结果至于[result_first, result_last)。

sort

        sort介绍篇幅会比较长,将单独罗列一篇进行描述。

equal_range

        算法equal_range是二分查找法的一个版本,试图在已排序的[first,last)中寻找value。它返回一对迭代器i和j,其中i是在不破坏次序的前提下,value可插入的第一个位置(亦即lower_bound),j则是不破坏次序的前提下,value可插入的最后一个位置(亦即upper_bound)。因此,[i,j)内的每个元素都等同于value,而且[i,j)是[first, last)之中符合此性质的最大子区间。

        如果以稍许不同的角度思考equal_range,我们可以把它想成是[first, last)内“与value等同”之所有元素所形成的区间A。由于[first, last)有序,所以我们知道“与value等同”之所有元素一定相邻。于是,算法lower_bound返回区间A的第一个迭代器,算法upper_bound返回区间A的最后一个元素的下一个位置,算法equal_range则是以pair的形式将两者返回。

        即使[first, last)并未含有“与value等同”之任何元素,以上叙述仍然合理。这种情况下“与value等同”之所有元素所形成的,其实是个空区间。在不破坏次序的前提下,只有一个位置可以插入value,而equal_range所返回的pair,其第一和第二元素皆指向该位置。

        本算法有两个版本一个是使用operator<进行比较,第二版本采用仿函数comp进行比较。代码如下:

// 版本一
template<class ForwardIterator, class T>
inline pair< ForwardIterator, ForwardIterator>
equal_range(ForwardIterator first, ForwardIterator last, const T& value) {
    return __equal_range(first, last, value, distance_type(first), iterator_category(first));
}

// 版本一的random_access_iterator_tag版本
template<class RandomAccessIterator, class T, class Distance>
pair< RandomAccessIterator, RandomAccessIterator >
__equal_range(RandomAccessIterator first, RandomAccessIterator last, const T& value, Distance*, random_access_iterator_tag) {
    Distance len = last - first;
    Distance half;
    RandomAccessIterator middle, left, right;
    
    while (len > 0) {
        half = len >> 1;
        middle = first + half;
        if (*middle < value) {
            first = middle + 1;
            len = len - half - 1;
        } else if (value < *middle) {
            len = half;
        } else {
            left = lower_bound(first, middle, value);
            right = upper_bound(++middle, first + len, value);
            return pair< RandomAccessIterator, RandomAccessIterator>(left, right);
        }
    }
    // 未找到相等的元素,返回一对迭代器,指向第一个大于value的元素
    return pair< RandomAccessIterator, RandomAccessIterator>(first, first);
}

// 版本一的forward_iterator_tag版本
template<class ForwardIterator, class T, class Distance>
pair< ForwardIterator, ForwardIterator >
__equal_range(ForwardIterator first, ForwardIterator last, const T& value, Distance*, random_access_iterator_tag) {
    Distance len = last - first;
    Distance half;
    ForwardIterator middle, left, right;
    
    while (len > 0) {
        half = len >> 1;
        middle = first;
        advance(middle, half);
        if (*middle < value) {
            first = middle;
            ++first;
            len = len - half - 1;
        } else if (value < *middle) {
            len = half;
        } else {
            left = lower_bound(first, middle, value);
            advance(first, len);
            right = upper_bound(++middle, first , value);
            return pair< ForwardIterator, ForwardIterator>(left, right);
        }
    }
    return pair< ForwardIterator, ForwardIterator>(first, first);
}

inplace_merge(应用于有序区间)

        如果两个连接在一起的序列[first, middle)和[middle, last)都以排序,那么inplace_merge可将他们结合成单一一个序列,并仍保有序性。如果原先两个序列式递增排序,执行结果也会是递增排序,如果原先两个时递减排序,执行结果也是递减排序。

        和merge一样,inplace_merge也是一种稳定操作。每个作为数据来源的子序列中的元素先对次序都不会变动;如果两个子序列有等同的元素,第一个序列的元素会排在第二个序列元素之前。

        implace_merge有两个版本,其差别在于使用operator<还是仿函数comp。以下是代码:

template<class BidrectionalIterator>
inline void inplace_merge(BidrectionalIterator first, BidrectionalIterator middle, BidrectionalIterator last) {
    if (first == middle || middle == last) return ;
    __inplace_merge_aux(first, middle, last, value_type(first), distance_type(first));
}

// 辅助函数
template<class BidrectionalIterator, class T, class Distance>
inline void __inplace_merge_aux(BidrectionalIterator first, BidrectionalIterator middle, BidrectionalIterator last, T*, Distance*) {
    Distance len1 = 0;
    distance(first, middle, len1);
    Distance len2 = 0;
    distance(middle, last, len2);
    
    // 注意,本算法会使用额外的内存空间(暂时缓冲区)
    temporary_buffer<BidrectionalIterator, T> buf(first, last);
    if (buf.begin() == 0) 
        __merge_without_buffer(first, middle, last, len1, lend2);
    else 
        __merge_adaptive(first, middle, last, len1, len2, buf.begin(), Distance(buf.size()));

}

        这个算法如果有额外的内存辅助,效率会好许多。鉴于篇幅,我们主要关注有缓冲区的情况。

// 辅助函数
template <class BidirectionalIterator, class Distance, class Pointer>
void __merge_adaptive(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last, Distance len1, Distance len2, Pointer buffer, Distance buffer_size) {
    if (len1 <= len2 && len1 <= buffer_size) {
        // case 1.缓冲区足够安置序列一
        Pointer end_buffer = copy(first, middle, buffer);
        merge(buffer, end_buffer, middle, last, first);
    } else (len2 <= buffer_size) {
        // case 2.缓冲区足够安装序列二
        Pointer end_buffer = copy(middle, last buffer);
        __merge_backward(first, middle, buffer, end_buffer, last);
    } else { // case 3. 缓冲区空间不足以安置任何一个序列
        BidirectionalIterator first_cut = first;
        BidirectionalIterator second_cut = middle;
        Distance len11 = 0;
        Distance len22 = 0;
        if (len1 > len2) {
            len11 = len1 / 2;
            advance(first_cut, len11);
            second_cut = lower_bound(middle, last, *first_cut);
            distance(middle, second_cut, len22);
        } else {
            len22 = len2 / 2;
            advance(second_cut, len22);
            first_cut = upper_bound(first, middle, *second_cut);
            distance(first, first_cut, len11);
        }
        BidirectionalIterator new_middle = __rotate_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size);
        //针对左段,递归调用
        __merge_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size);
        //针对右段,递归调用
        __merge_adaptive(new_middle, second_cut, last, second_cut, len1 - len11, len2 - len22, buffer, buffer_size);
    }
}

        上述辅助函数首先判断缓冲区是否足以容纳inplace_merge所接受的两个序列的任何一个。如果空间充裕,工作逻辑很简单:把两个序列中的某个copy到缓冲区,在使用merge完成余下工作。是的,merge足堪胜任,它的功能就是将两个有序但分离的区间合并,形成一个有序区间,依次只需将merge的结果置放处result指定为inplace_merge所接受序列起始点(迭代器first)即可。其余情况使用递归逐渐减少对缓存的使用以达到合并的目的。(未完待续)

nth_element

        这个算法会重新排列[first, last),是迭代器nth所指的元素,与“整个[first, last)完整排列后,同一位置的元素”同值。此外并保证[nth, last)内没有任何一个元素小于(更精确地说是不大于)[first, nth)内的元素,但对于[first, nth)和[nth, last)两个子区间内的元素次序则无任何保证--这一点也是它与partial_sort很大的不同之处。以此观之,neth_element比较近似partition而非sort或partial_sort.

        例如,假设序列{22,30,30,17,33,40,17,23,22,12,20},以下操作

neth(iv.begin(), iv.begin() + 5, iv.end());便是将小于*(iv.being()+5)(本例40)的元素置于该元素之左,其余置于该元素之右,并且不保证维持原有的相对位置。获得的结果为{20,12,22,17,17,22,23,30,30,33,40}。执行完毕后的5^{th}位置上的元素值22,与整个序列完整排序后{12,17,17,20,22,22,23,30,30,33,40}的5^{th}个位置上的元素值相同。

        如果以上述结果{20,12,22,17,17,22,23,30,30,33,40}为根据,在执行以下操作:

        nth_element(iv.begin(), iv.begin() + 5, iv.end(), greater<int>());那便是将大于*(iv.being()+5)(本例22)的元素置于该元素值左,其余置于该元素之右,并且不保证位置原有的相对位置,获得的结果为{40,33,3030,23,22,17,17,22,12,20}.

        由于nth_element比partial_sort的保证更少,所以它当然比partial_sort较快。

        nth_element只接受RandomAccessIterator。

        nth_element的做法是,不断地以median-of-3 partition(以首、尾、中央三点中值为枢轴之分割法)将整个序列分割为更小的L,R子序列。如果nth迭代器落于左子序列,就再对左子序列进行分割,否则就再对右子序列进行分割。依次类推,直到分割后的子序列不大于3,便对最后这个待分割的子序列左Insertion Sort,大功告成。

代码如下:

template <class RandomAccessIterator>
inline void nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last) {
    __nth_element(first, nth, last, value_type(first));
}

template <class RandomAccessIterator, class T>
void __nth_element(RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last, T*) {
    while (last - first > 3) {
        RandomAccessIterator cut = __unguarded_partition(first, last, T(__median(*first, *(first + (last - first)/2), *(last -1));
        if (cut < nth) first = cut;
        else last = cut;
    }
    __insertion_sort(first, last);
}

操作示例如下:

由于当中缺失了_unguarded_partition算法的实现,代码逻辑不是很清晰;此文中缺失的部分,如sort,inplace_merge中涉及的内容,将在后续的博文中再进行呈现。敬请期待。

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

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

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

相关文章

模拟简单的iOT工作流

没有实际接触过iOT的流程&#xff0c;应该实际使用比这个接口返回要复杂&#xff0c;只是演示~希望能参与实际的接口接入&#xff0c;而不是只展示个假数据。 启动RabbitQ 使用的是3.8.5 启动命令 RabbitMQ Service - start RabbitMQ Command Prompt rabbitmqctl start_app …

【快速入门 LVGL】-- 1、STM32 工程移植 LVGL

目录 一、LVGL 简述 二、复制一个STM32工程 三、下载 LVGL 四、裁剪 源文件 五、工程添加 LVGL 文件 六、注册 显示 七、注册 触摸屏 八、LVGL 心跳、任务刷新 九、开跑 LVGL 十、控件的事件添加、响应处理 十 一、几个好玩小事情 十 二、显示中文 ~~ 约定 ~~ 在…

关于线扫相机的使用和注意事项

引言 线扫相机作为工业视觉系统中的核心设备之一&#xff0c;以其高分辨率和高速成像的特点被广泛应用于印刷质量检测、电子元件检测、纺织品缺陷检测等领域。本文从线扫相机的基本原理出发&#xff0c;探讨其使用方法&#xff0c;并总结在实际应用中的注意事项&#xff0c;为…

MybatisPlus字段类型处理器TypeHandler

个人博客&#xff1a;无奈何杨&#xff08;wnhyang&#xff09; 个人语雀&#xff1a;wnhyang 共享语雀&#xff1a;在线知识共享 Github&#xff1a;wnhyang - Overview 简介 官网&#xff1a;字段类型处理器 在 MyBatis 中&#xff0c;类型处理器&#xff08;TypeHandle…

c++编译版本问题#error C++17 or later compatible compiler is required to use xx

问题解决方向 网上多数给出的解决方法是找到setup.py&#xff0c;然后修改extra_compile_args参数中的cxx&#xff0c;由-stdc14改为-stdc17&#xff0c;但是这个方法在我这里没用。 所以我重新理解了下这个error&#xff0c;应该是说为了编译安装当前的库&#xff0c;需要的…

【AI大模型】大型语言模型LLM基础概览:技术原理、发展历程与未来展望

目录 &#x1f354; 大语言模型 (LLM) 背景 &#x1f354; 语言模型 (Language Model, LM) 2.1 基于规则和统计的语言模型&#xff08;N-gram&#xff09; 2.2 神经网络语言模型 2.3 基于Transformer的预训练语言模型 2.4 大语言模型 &#x1f354; 语言模型的评估指标 …

一文理解多模态大语言模型——下

作者&#xff1a;Sebastian Raschka 博士&#xff0c; 翻译&#xff1a;张晶&#xff0c;Linux Fundation APAC Open Source Evangelist 编者按&#xff1a;本文并不是逐字逐句翻译&#xff0c;而是以更有利于中文读者理解的目标&#xff0c;做了删减、重构和意译&#xff0c…

uC/OSII学习笔记(二)任务的堆栈检验

加入OSTaskCreateExt()创建拓展任务函数的使用。 加入OSTaskStkChk()堆栈检验函数的使用。 堆栈检验函数可检查任务堆栈的使用字节数量和空闲字节数量。 具体使用方法如下&#xff1a; 1.创建拓展任务OSTaskCreateExt()用于堆栈检验&#xff0c;堆栈检验必须用拓展任务OSTaskCr…

WPF+LibVLC开发播放器-进度条显示和拖动控制

进度条显示和拖动控制 视频教程界面上代码实现进度条显示进度进度条拖动视频进度 效果 视频教程 WPFLibVLC开发播放器-进度条控制 界面上 界面上线增加一个Slider控件&#xff0c;当做播放进度条 <SliderName"PlaySlider"Grid.Row"1"Width"800&qu…

【Rust WebAssembly 入门实操遇到的问题】

Rust WebAssembly 入门实操遇到的问题 什么是WebAssembly跟着教程走wasm-pack build error总结 什么是WebAssembly WebAssembly&#xff08;简称Wasm&#xff09;是一种基于堆栈的虚拟机的二进制指令 格式。Wasm 被设计为编程语言的可移植编译目标&#xff0c;支持在 Web 上部…

同为科技(TOWE)柔性定制化PDU插座

随着科技的进步&#xff0c;越来越多的精密电子设备&#xff0c;成为工作生活密不可分的工具。 电子电气设备的用电环境也变得更为复杂&#xff0c;所以安全稳定的供电是电子电气设备的生命线。 插座插排作为电子电气设备最后十米范围内供配电最终核心部分&#xff0c;便捷、安…

AI RPA 影刀基础教程:开启自动化之旅

RPA 是什么 RPA 就是机器人流程自动化&#xff0c;就是将重复的工作交给机器人来执行。只要是标准化的、重复的、有逻辑行的操作&#xff0c;都可以用 RPA 提效 准备 安装并注册影刀 影刀RPA - 影刀官网 安装 Chrome 浏览器 下载链接&#xff1a;Google Chrome 网络浏览器 …

HTTP 长连接(HTTP Persistent Connection)简介

HTTP长连接怎么看&#xff1f; HTTP 长连接&#xff08;HTTP Persistent Connection&#xff09;简介 HTTP 长连接&#xff08;Persistent Connection&#xff09;是 HTTP/1.1 的一个重要特性&#xff0c;它允许在一个 TCP 连接上发送多个 HTTP 请求和响应&#xff0c;而无需为…

VS与SQL Sever(C语言操作数据库)

作者这里使用的是程序是&#xff1a; Visual Studio SQL Sever (1 对VS的操作 1.首先我们打开Visual Studio Installer&#xff0c;并以管理员身份运行 2.点击修改 3.先选择数据存储和处理&#xff0c;再在右方添加处理工具&#…

基于“开源 2+1 链动 O2O 商城小程序”的门店拉新策略与流程设计

摘要&#xff1a;在数字化商业浪潮席卷之下&#xff0c;实体门店面临着激烈的市场竞争&#xff0c;如何高效拉新成为关乎门店生存与发展的关键问题。本文聚焦于“开源 21 链动 O2O 商城小程序”&#xff0c;深入探讨结合多种手段的门店拉新策略及详细流程设计。通过剖析到店扫码…

微服务即时通讯系统(5)用户管理子服务,网关子服务

用户管理子服务&#xff08;user文件&#xff09; 用户管理子服务也是这个项目中的一个业务最多的子服务&#xff0c;接口多&#xff0c;但是主要涉及的数据表只有user表&#xff0c;Redis的键值对和ES的一个搜索引擎&#xff0c;主要功能是对用户的个人信息进行修改管理&#…

ceph的存储池管理

1 查看存储池信息 查看存储池的名称 [rootceph141ceph]# ceph osd pool ls .mgr查看存储池机器编号 [rootceph141ceph]# ceph osd pool ls 1 .mgr查看存储池的详细信息 [rootceph141ceph]# ceph osd pool ls detail pool 1 .mgr replicated size 3 min_size 2 crush_rule 0 ob…

Spring和SpringBoot的关系和区别?

大家好&#xff0c;我是锋哥。今天分享关于【Spring和SpringBoot的关系和区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; Spring和SpringBoot的关系和区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring和Spring Boot是两种相关但有所…

21个Python脚本自动执行日常任务(1)

引言 作为编程领域摸爬滚打超过十年的老手&#xff0c;我深刻体会到&#xff0c;自动化那些重复性工作能大大节省我们的时间和精力。 Python以其简洁的语法和功能强大的库支持&#xff0c;成为了编写自动化脚本的首选语言。无论你是专业的程序员&#xff0c;还是希望简化日常工…

蘑菇书(EasyRL)学习笔记(3)

q1、学习与规划 学习&#xff08;learning&#xff09;和规划&#xff08;planning&#xff09;是序列决策的两个基本问题。如下图所示&#xff0c;在强化学习中&#xff0c;环境初始时是未知的&#xff0c;智能体不知道环境如何工作&#xff0c;它通过不断地与环境交互&#x…