std::random_shuffle算法是错的

fq1 2013-09-09 09:33:03
参考vs2008中<algorithm>文件中_Random_shuffle代码:

template<class _RanIt,
class _Diff> inline
void _Random_shuffle(_RanIt _First, _RanIt _Last, _Diff *)
{ // shuffle [_First, _Last)
_DEBUG_RANGE(_First, _Last);
const int _RANDOM_BITS = 15; // minimum random bits from rand()
const int _RANDOM_MAX = (1U << _RANDOM_BITS) - 1;

_RanIt _Next = _First;
for (unsigned long _Index = 2; ++_Next != _Last; ++_Index)
{ // assume unsigned long big enough for _Diff count
unsigned long _Rm = _RANDOM_MAX;
unsigned long _Rn = ::rand() & _RANDOM_MAX;
for (; _Rm < _Index && _Rm != ~0UL;
_Rm = _Rm << _RANDOM_BITS | _RANDOM_MAX)
_Rn = _Rn << _RANDOM_BITS
| (::rand() & _RANDOM_MAX); // build random value

std::iter_swap(_Next, _First + _Diff(_Rn % _Index)); // swap a pair
}
}


std::iter_swap(_Next, _First + _Diff(_Rn % _Index));
注意标红的部分,本质上是产生0~i中的一个随机数,然后和i交换
伪代码表示为:for i:=1 to n do swap(a[i], a[random(0,i)]);
正确的方法应该是产生i~n中的随机数和i交换
for i:=1 to n do swap(a[i], a[random(i,n)]);

参考matrix67的这篇博文: http://www.matrix67.com/blog/archives/879

如果matrix67的算法是正确的,那么stl中的Random_shuffle就是错的
...全文
502 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
ccsu_001 2015-06-02
  • 打赏
  • 举报
回复
引用 2 楼 fq1 的回复:
[quote=引用 1 楼 taodm 的回复:] for i:=1 to n do swap(a[i], a[random(0,i)]); for i:=1 to n do swap(a[i], a[random(i,n)]); for i:=1 to n do swap(a[i], a[random(0,n)]); 哪个对哪个? 你真确定matrix67讲了这个?
你看不懂那篇文章? for i:=1 to n do swap(a[i], a[random(i,n)]);是对的 for i:=1 to n do swap(a[i], a[random(0,i)]);是错的 std::random_shuffle用的就是这个错误的算法[/quote] 是你自己没看清楚吧, matrix那篇博文根本没有说 “ for i:=1 to n do swap(a[i], a[random(0,i)]);是错的 ” , 是说 “for i:=1 to n do swap(a[i], a[random(0,n)]);是错的”, 显然for i:=1 to n do swap(a[i], a[random(0,i)]); 可以生成n!中结果,每种结果具有相同的可能性。
fq1 2013-09-09
  • 打赏
  • 举报
回复
引用 9 楼 ri_aje 的回复:
[quote=引用 6 楼 fq1 的回复:] [quote=引用 3 楼 ri_aje 的回复:] g++-4.8.0 也用的类似的算法。

  template<typename _RandomAccessIterator>
   inline void
   random_shuffle(_RandomAccessIterator __first, _RandomAccessIterator __last)
   {
    if (__first != __last)
     for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
      std::iter_swap(__i, __first + (std::rand() % ((__i - __first) + 1)));
   }
我觉得 vs2008 和 g++-4.8.0 都是正确的,和主楼贴出的网页中给的第二种方法本质一样。 随机洗牌算法的重点是随机区间要随着当前元素移动,至于是左区间还是右区间到不是太重要,因为在区间顺序倒换的情况下,两者是等价的。
不认同,参考ls我的回复 如果去随机交换过的区间,会产生多次交换一个值的情况 能给出数学证明两个区间等价吗?[/quote] 应该可以。 主楼给出的那个网页,其中有一段证明网页中所说的第二种方法的正确性,同样的证明也适用于 random(0,i) 的算法。 根据你那个网页,对于 random (i,n) 的算法,能够选择的是 n * (n-1) * (n-2) ... * 1,产生 n! 种不同的排列。 对于 random (0,i) 的算法,能够选择的是 1 * 2 * 3 ... * (n-1) * n,同样产生 n! 种不同的排列。[/quote] 有道理,那看来这种算法对,给你分
ri_aje 2013-09-09
  • 打赏
  • 举报
回复
引用 6 楼 fq1 的回复:
[quote=引用 3 楼 ri_aje 的回复:] g++-4.8.0 也用的类似的算法。

  template<typename _RandomAccessIterator>
   inline void
   random_shuffle(_RandomAccessIterator __first, _RandomAccessIterator __last)
   {
    if (__first != __last)
     for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
      std::iter_swap(__i, __first + (std::rand() % ((__i - __first) + 1)));
   }
我觉得 vs2008 和 g++-4.8.0 都是正确的,和主楼贴出的网页中给的第二种方法本质一样。 随机洗牌算法的重点是随机区间要随着当前元素移动,至于是左区间还是右区间到不是太重要,因为在区间顺序倒换的情况下,两者是等价的。
不认同,参考ls我的回复 如果去随机交换过的区间,会产生多次交换一个值的情况 能给出数学证明两个区间等价吗?[/quote] 应该可以。 主楼给出的那个网页,其中有一段证明网页中所说的第二种方法的正确性,同样的证明也适用于 random(0,i) 的算法。 根据你那个网页,对于 random (i,n) 的算法,能够选择的是 n * (n-1) * (n-2) ... * 1,产生 n! 种不同的排列。 对于 random (0,i) 的算法,能够选择的是 1 * 2 * 3 ... * (n-1) * n,同样产生 n! 种不同的排列。
taodm 2013-09-09
  • 打赏
  • 举报
回复

你上当了吧,被人家微博的评论方式骗了吧。
你真的真的不够认真啊。
fq1 2013-09-09
  • 打赏
  • 举报
回复
引用 5 楼 taodm 的回复:
显然,楼主没有点进评论去看
评论根本不用点击好不好?
fq1 2013-09-09
  • 打赏
  • 举报
回复
引用 3 楼 ri_aje 的回复:
g++-4.8.0 也用的类似的算法。

  template<typename _RandomAccessIterator>
   inline void
   random_shuffle(_RandomAccessIterator __first, _RandomAccessIterator __last)
   {
    if (__first != __last)
     for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
      std::iter_swap(__i, __first + (std::rand() % ((__i - __first) + 1)));
   }
我觉得 vs2008 和 g++-4.8.0 都是正确的,和主楼贴出的网页中给的第二种方法本质一样。 随机洗牌算法的重点是随机区间要随着当前元素移动,至于是左区间还是右区间到不是太重要,因为在区间顺序倒换的情况下,两者是等价的。
不认同,参考ls我的回复 如果去随机交换过的区间,会产生多次交换一个值的情况 能给出数学证明两个区间等价吗?
taodm 2013-09-09
  • 打赏
  • 举报
回复
显然,楼主没有点进评论去看
fq1 2013-09-09
  • 打赏
  • 举报
回复
引用 1 楼 taodm 的回复:
for i:=1 to n do swap(a[i], a[random(0,i)]); for i:=1 to n do swap(a[i], a[random(i,n)]); for i:=1 to n do swap(a[i], a[random(0,n)]); 哪个对哪个? 你真确定matrix67讲了这个?
虽然文章中没提到,但你看看底下的评论就会知道这个 for i:=1 to n do swap(a[i], a[random(0,i)]); 肯定是错的。 一个基本的原则就是 被换过的不能再动 这个算法不保证这一点
ri_aje 2013-09-09
  • 打赏
  • 举报
回复
g++-4.8.0 也用的类似的算法。

  template<typename _RandomAccessIterator>
   inline void
   random_shuffle(_RandomAccessIterator __first, _RandomAccessIterator __last)
   {
    if (__first != __last)
     for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i)
      std::iter_swap(__i, __first + (std::rand() % ((__i - __first) + 1)));
   }
我觉得 vs2008 和 g++-4.8.0 都是正确的,和主楼贴出的网页中给的第二种方法本质一样。 随机洗牌算法的重点是随机区间要随着当前元素移动,至于是左区间还是右区间到不是太重要,因为在区间顺序倒换的情况下,两者是等价的。
fq1 2013-09-09
  • 打赏
  • 举报
回复
引用 1 楼 taodm 的回复:
for i:=1 to n do swap(a[i], a[random(0,i)]); for i:=1 to n do swap(a[i], a[random(i,n)]); for i:=1 to n do swap(a[i], a[random(0,n)]); 哪个对哪个? 你真确定matrix67讲了这个?
你看不懂那篇文章? for i:=1 to n do swap(a[i], a[random(i,n)]);是对的 for i:=1 to n do swap(a[i], a[random(0,i)]);是错的 std::random_shuffle用的就是这个错误的算法
taodm 2013-09-09
  • 打赏
  • 举报
回复
for i:=1 to n do swap(a[i], a[random(0,i)]); for i:=1 to n do swap(a[i], a[random(i,n)]); for i:=1 to n do swap(a[i], a[random(0,n)]); 哪个对哪个? 你真确定matrix67讲了这个?
内容简介回到顶部↑这本书不适合C++ 初学者,不适合 Genericity(泛型技术)初学者,或 STL 初学者。这本书也不适合带领你学习面向对象(Object Oriented)技术 — 是的,STL 与面向对象没有太多关连。本书前言清楚说明了书籍的定位和合适的读者,以及各类基础读物。如果你的Generic Programming/STL实力足以阅读本书所呈现的源码,那么,恭喜,你踏上了基度山岛,这儿有一座大宝库等着你。源码之前了无秘密,你将看到vector的实现、list的实现、heap的实现、deque的实现、RB-tree的实现、hash-table的实现、set/map 的实现;你将看到各种算法(排序、搜寻、排列组合、数据移动与复制…)的实现;你甚至将看到底层的memory pool 和高阶抽象的traits 机制的实现。那些数据结构、那些算法、那些重要观念、那些编程实务中最重要最根本的珍宝,那些蜇伏已久彷佛已经还给老师的记忆,将重新在你的脑中闪闪发光。 目录回到顶部↑庖丁解牛(侯捷自序) i 目录 v 前言 xvii 本书定位 xvii 合适的读者 xviii 最佳阅读方式 xviii 我所选择的剖析对象 xix 各章主题 xx 编译工具 xx 中英术语的运用风格 xxi 英文术语采用原则 xxii 版面字形风格 xxiii 源码形式与下载 xxiv 在线服务 xxvi 推荐读物 xxvi 第1章 STL 概论与版本简介001 1.1 STL 概论 001 1.1.1 STL的历史 003 1.1.2 STL与C++ 标准程序库 003 . 1.2 STL 六大组件 - 功能与运用 004 1.3 GNU源码开放精神 007 1.4 HP STL实现版本 009 1.5 P.J. Plauger STL实现版本 010 1.6 Rouge Wave STL实现版本 011 1.7 STLport 实现版本 012 1.8 SGI STL实现版本 总览 013 1.8.1 GNU C++ header 文件分布 014 1.8.2 SGI STL 文件分布与简介 016 STL 标准头文件(无扩展名) 017 C++ 标准规格定案前,HP规范的STL头文件(扩展名 .h) 017 SGI STL 内部文件(SGI STL真正实现于此) 018 1.8.3 SGI STL 的组态设定(configuration) 019 1.9可能令你困惑的C++ 语法 026 1.9.1 stl_config.h 中的各种组态 027 组态3:static template member 027 组态5:class template partial specialization 028 组态6:function template partial order 028 组态7:explicit function template arguments 029 组态8:member templates 029 组态10:default template argument depend on previous template parameters 030 组态11:non-type template parameters 031 组态:bound friend template function 032 组态:class template explicit specialization 034 1.9.2 临时对象的产生与运用 036 1.9.3 静态常数整数成员在class 内部直接初始化 037 in-class static const integral data member initialization 1.9.4 increment/decrement/dereference 运算子 037 1.9.5 "前闭后开"区间表示法 [ ) 039 1.9.6 function call运算子(operator()) 040 第2章 空间配置器(allocator) 043 2.1 空间配置器的标准接口 043 2.1.1 设计一个简单的空间配置器,JJ::allocator 044 2.2 具备次配置力(sub-allocation)的SGI 空间配置器 047 2.2.1 SGI 标准的空间配置器,std::allocator 047 2.2.2 SGI 特殊的空间配置器,std::alloc 049 2.2.3 构造和析构基本工具:construct() 和 destroy() 051 2.2.4 空间的配置与释
学习并掌握C++2.0(11+14+17+20)的新特性,学习线程及线程池的应用 ---------------------------------------------------给小白学员的3年学习路径及计划技术方面分三块:1.纯开发技术方向2.音视频流媒体专业方向3.项目实战---------------------------------------------------1.纯开发技术方向(1) C++必须要过硬(至少学会10本经典好书)(2) 系统级编程(Windows、Linux),必须特别熟练系统API,灵活运用(3) 框架与工具(Qt、MFC):必须精通其中一种。(4) 架构与设计模式:需要提升一个高度,不再是简单的编码,而是思维模式。(5) 驱动级别(如果有兴趣,可以深入到驱动级:包括Windows、Linux)(6) 最好学习点Java+Html+javascript等WEB技术。2.音视频流媒体专业方向(1) 音视频流媒体基础理论:   必须认真学会,否则看代码就是看天书(2) 编解码方向:精通h.264,h.265(hevc), 包括理论和各个开源库(ffmpeg,libx264,libx265,...)。(3) 直播方向:  精通各种直播协议(rtsp,rtmp,hls,http-flv,...), 钻研各个开源库(live555,darwin,srs,zlmediakit,crtmpserver,...)(4) 视频监控:  理论+开源库(onvif+281818)(EasyMonitor、iSpy、ZoneMinder(web)、...) 3.项目实战(1) Qt项目:  至少要亲手练习10个实战项目(网络服务器、多线程、数据库、图像处理、多人聊天、等等)(2)音视频项目:包括编解码、视频监控、直播等各个方向,都需要亲手实战项目,包括视频服务器、后台管理系统、前端播放器(多端)---------------------------------------------------  第1章 C++11新特性 41). nullptr关键字与新语法 42). auto和decltype类型推导 6 auto讲解 6 auto示例 7 decltype 83). for区间迭代 94). 初始化列表 105). 模板增强 11外部模板 11类型别名模板 12默认模板参数 126). 构造函数 13委托构造 13继承构造 147). Lambda 表达式 158). 新增容器 20std::array 20std::forward_list 21无序容器 22元组 std::tuple 239). 正则表达式 2610). 语言级线程支持 28多线程库简介 2811). 右值引用和move语义 31右值引用和move语义 32转移左值 3412). constexpr 35第2章 C++14新特性 36Lambda 函数 36类型推导 37返回值类型推导(Return type deduction) 37泛型lambda 39[[弃用的]]  [[deprecated]]属性 40二进制数字和数字分隔符 41第3章 C++17新特性 42安装GCC10.2 42安装msys2-x86_64-20200720 42更新镜像 42更新软件库 43安装 MinGW64 等必要的软件 43环境变量Path 43编译命令 43constexpr 44typename 45折叠表达式 47结构化绑定 48条件分支语句初始化 49聚合初始化 50嵌套命名空间 52lambda表达式捕获*this的值 53改写/继承构造函数 54用auto作为非类型模板参数 55__has_include 56fallthrough 57nodiscard 57maybe_unused 58第4章 C++20新特性 59编译命令 59concept 59typename 60explicit 61constinit 62位域变量的默认成员初始化 62指定初始化 63基于范围的for循环初始化 64放宽基于范围的for循环,新增自定义范围方法 65嵌套内联命名空间 66允许用圆括弧的值进行聚合初始化 67unicode字符串字面量 68允许转换成未知边界的数组 68likely和unlikely 69第5章 C++2.0(11/14/17/20)总结与分析 705.1 C语言与C++ 715.2 语言可用性的强化 725.2.1 常量 725.2.2 变量及其初始化 735.2.3 类型推导 745.2.4 控制流 765.2.5 模板 775.2.6 面向对象 815.3 语言运行期的强化 835.3.1 Lambda 表达式 835.3.2 右值引用 865.4 容器 885.4.1 线性容器 885.4.2 无序容器 895.4.3 元组 895.5 智能指针与内存管理 905.5.1 RAII 与引用计数 905.5.2 std::shared_ptr 905.5.3 std::unique_ptr 915.5.4 std::weak_ptr 91第6章 C++2.0多线程原理与实战 93什么是并发 93并发的方式 93为什么使用并发 95线程简介 96创建线程的三种方式 971. 通过函数 972.通过类对象创建线程 993.通过lambda表达式创建线程 101thread线程的使用 101互斥量与临界区 105期物Future 111条件变量 112原子操作 114内存模型 118第7章 C++2.0线程池原理与实战 120线程与线程池的基本原理 1201)、线程 1202)、线程的生命周期 1213)、什么是单线程和多线程 1214)、线程池 1225)、四种常见的线程池 123线程池的架构与流程 123线程池代码实战 125    

64,644

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧