问题

现在有1~30这30个数,数N被抽上的概率正比于1/sqrt(N+1),求满足这个概率分布的随机数发生器。

思路

第一,如何解决这个“概率正比”问题。

第二,如何产生满足条件的随机数。

第三,有更好的方法吗?

一、解决“概率正比”问题

在概率论中有一个概念叫作“几何概型”,举个例子,如何求圆的面积?

先画一个正方形记作A,再在A中画内切圆B。现在随机在A上面撒豆子,落在A上的豆子总数为AN,落在B上的为BN。

那么,当豆子总数趋向无穷大时,正方形与圆的面积比率趋向于AN/BN。

也就是说,样本数量足够大时,面积比(几何比)近似于概率

回到刚刚的问题,设数N被抽中的概率为PN,做一条直线,从原点出发,依次放上长为PN的线段LN(N从1到30),现在在L1~L30上随机撒豆子。假设LN上的豆子数为TN,那么TN之近似于PN之比,从而解决第一个问题。

二、产生满足要求的随机数

几何概型其实就是均匀分布要面积(长度、体积)分布的映射,而映射就是函数。

所以,这个随机数发生器的输入是随机数rand(),输出为题目所求,旨在求映射关系。

既然题目中的几何概型已被求出,那么产生相应随机数的基本步骤如下:

  1. set S=P1+P2+…P30,generate rand(0<=rand<S)
  2. for i=1 to 30 do:
  3. if(rand<P(i)) return i
  4. end for

三、寻求优化的方法

上面的方法很简单,但是存在效率问题。

普通的if-else嵌套分支都可以转化为二叉查找树(if=1=left,else=0=right)。上述方法也如此,将其转化为二叉树之后,发现是一棵斜树

斜树有个缺点,就是作为查找树效率低下,因此存在优化的空间。

对斜树而言:

  1. 考虑最优情况,只判断一次,O(1)
  2. 考虑最坏情况,全部判断一次,O(n)
  3. 考虑平均情况,折半n/2,O(n)

而对经典的二叉平衡树而言(折半查找):

  1. 考虑最优情况,为深度,O(logn)
  2. 考虑最坏情况,为深度,O(logn)
  3. 考虑平均情况,为深度,O(logn)

由于是处理随机数,这里考虑平均情况,可知:二叉平衡树优于斜树

但是,有一个问题:二叉平衡树是用来查找数的,不是用来查找区间的,所以这里用不了平衡树。

因此,作为查找区间的一种数据结构——区间树(也称线段树),就应运而生了。

我这里设计的区间树属于2-3树(键数<=2,值数<=3),结合了广义表的设计思想(结点和数据共用)。

由于数据源是排序过的,所以可以直接采用分治法构建树。(这是由于分组后的数据仍保持有序)

注:

  • 打印的树结构设计参考自系统自带tree.exe的结果
  • 键和值的关系为,val0<key0<=val1<key1<=val2
  • 树的生成,以及分治产生的多余结点处理问题(分配不均等)的解决详见源码

源码

源码:itvtree.cpp

  1. #include "stdafx.h"
  2. #include <stdio.h>
  3. #include <math.h>
  4. #include <time.h>
  5. #include <vector>
  6.  
  7. /************************************************************************/
  8. /* 线段树/区间树 */
  9. /* Interval Tree */
  10. /************************************************************************/
  11.  
  12. typedef double TreeKeyType;
  13.  
  14. //实质是2-3平衡树
  15. struct tree_node
  16. {
  17. unsigned char type[4];//只用到type[3],此是为了内存对齐
  18. TreeKeyType key[2];//2-键
  19. void* value[3];//3-值
  20. };
  21.  
  22. #define TREE_NODE_UNUSED 0
  23. #define TREE_NODE_VALUE 1
  24. #define TREE_NODE_POINTER 2
  25.  
  26. #define TREE_NOT_FOUND -1
  27.  
  28. #define TREE_PRINT_BLANK 0
  29. #define TREE_PRINT_TRUNK 1
  30. #define TREE_PRINT_BRANCH 2
  31. #define TREE_PRINT_BRANCHD 3
  32.  
  33. //************************************
  34. // Method: 递归构造线段树
  35. // FullName: tree_init_recursive
  36. // Access: public
  37. // Returns: void*
  38. // Qualifier:
  39. // Parameter: TreeKeyType * s 数组指针(数据来源)
  40. // Parameter: int count 当前处理的范围
  41. // Parameter: int start 当前处理的位置
  42. // Parameter: unsigned char * type 修改结点类型
  43. //************************************
  44. void* tree_init_recursive(TreeKeyType* s, int count, int start, unsigned char* type)
  45. {
  46. //此递归调用函数的返回值可以为结点或真值,由type确定
  47. //树生成采用的是分治方法,当s数组分成3份,各自递归生成子树,再作为某个结点的孩子结点
  48.  
  49. //注意:实际长度应为count+1
  50.  
  51. tree_node* node;
  52.  
  53. if (count == 0)//只有一个数时,只返回值类型,类比快排找中间轴(向中间逼近)
  54. {
  55. if (type)
  56. *type = TREE_NODE_VALUE;
  57. return (void*)start;//值
  58. }
  59.  
  60. node = new(tree_node);
  61. if (type)
  62. *type = TREE_NODE_POINTER;//当前处理长度大于1,需生成新结点,返回结点类型
  63.  
  64. switch (count)
  65. {
  66. case 1://当前处理的长度为2,type长度为2
  67. node->type[0] = TREE_NODE_VALUE;
  68. node->type[1] = TREE_NODE_VALUE;
  69. node->type[2] = TREE_NODE_UNUSED;
  70. node->key[0] = s[0];//只需一个键,即大于和小于
  71. node->value[0] = (void*)(start);
  72. node->value[1] = (void*)(start + 1);
  73. break;
  74. case 2://当前处理的长度为3,type长度为3
  75. node->type[0] = TREE_NODE_VALUE;
  76. node->type[1] = TREE_NODE_VALUE;
  77. node->type[2] = TREE_NODE_VALUE;
  78. node->key[0] = s[0];
  79. node->key[1] = s[1];//需要两个键,即大于上界、区间内、小于下界
  80. node->value[0] = (void*)(start);
  81. node->value[1] = (void*)(start + 1);
  82. node->value[2] = (void*)(start + 2);
  83. break;
  84. default://处理的长度大于3,采用分治
  85. int a = count / 3;//三等分
  86. switch (count % 3)//处理三等分多余的数
  87. {
  88. case 0://多一个
  89. //分组:
  90. //1)start+[0]->start+[a-1]
  91. //2)start+[a]->start+[2a]
  92. //3)start+[2a+1]->start+[3a]
  93. //长度:
  94. //1)a-1
  95. //2)a
  96. //3)a-1
  97. //键:
  98. //1)a-1
  99. //2)2a
  100. node->key[0] = s[a - 1];
  101. node->key[1] = s[a * 2];
  102. node->value[0] = tree_init_recursive(&s[0], a - 1, start, &node->type[0]);
  103. node->value[1] = tree_init_recursive(&s[a], a, start + a, &node->type[1]);
  104. node->value[2] = tree_init_recursive(&s[a * 2 + 1], a - 1, start + a * 2 + 1, &node->type[2]);
  105. break;
  106. case 1://多两个
  107. //分组:
  108. //1)start+[0]->start+[a-1]
  109. //2)start+[a]->start+[2a+1]
  110. //3)start+[2a+2]->start+[3a+1]
  111. //长度:
  112. //1)a-1
  113. //2)a+1
  114. //3)a-1
  115. //键:
  116. //1)a-1
  117. //2)2a+1
  118. node->key[0] = s[a - 1];
  119. node->key[1] = s[a * 2 + 1];
  120. node->value[0] = tree_init_recursive(&s[0], a - 1, start, &node->type[0]);
  121. node->value[1] = tree_init_recursive(&s[a], a + 1, start + a, &node->type[1]);
  122. node->value[2] = tree_init_recursive(&s[a * 2 + 2], a - 1, start + a * 2 + 2, &node->type[2]);
  123. break;
  124. case 2://不多
  125. //分组:
  126. //1)start+[0]->start+[a]
  127. //2)start+[a+1]->start+[2a+1]
  128. //3)start+[2a+2]->start+[3a+2]
  129. //长度:
  130. //1)a
  131. //2)a
  132. //3)a
  133. //键:
  134. //1)a
  135. //2)2a+1
  136. node->key[0] = s[a];
  137. node->key[1] = s[a * 2 + 1];
  138. node->value[0] = tree_init_recursive(&s[0], a, start, &node->type[0]);
  139. node->value[1] = tree_init_recursive(&s[a + 1], a, start + a + 1, &node->type[1]);
  140. node->value[2] = tree_init_recursive(&s[a * 2 + 2], a, start + a * 2 + 2, &node->type[2]);
  141. break;
  142. }
  143. }
  144.  
  145. return (void*)node;
  146. }
  147.  
  148. tree_node* tree_init(TreeKeyType* s, int count, int start)
  149. {
  150. //初始化
  151. //给定线段各点坐标,构建树
  152. return (tree_node*)tree_init_recursive(s, count, start, NULL);
  153. }
  154.  
  155. int tree_find_recursive(tree_node* node, TreeKeyType s)
  156. {
  157. //当前结点类型为值就直接返回,否则递归调用
  158. if (s < node->key[0])//小于左键,双键小于或单键小于,找第一值
  159. {
  160. if (node->type[0] == TREE_NODE_VALUE)
  161. return (int)node->value[0];
  162. else if (node->type[0] == TREE_NODE_POINTER)
  163. return (int)tree_find_recursive((tree_node*)node->value[0], s);
  164. else
  165. return TREE_NOT_FOUND;
  166. }
  167. if (node->type[2] == TREE_NODE_UNUSED || s < node->key[1])//双键区间内部或单键大于,找第二值
  168. {
  169. if (node->type[1] == TREE_NODE_VALUE)
  170. return (int)node->value[1];
  171. else if (node->type[1] == TREE_NODE_POINTER)
  172. return (int)tree_find_recursive((tree_node*)node->value[1], s);
  173. else
  174. return TREE_NOT_FOUND;
  175. }
  176. {//双键大于,找第三值
  177. if (node->type[2] == TREE_NODE_VALUE)
  178. return (int)node->value[2];
  179. else if (node->type[2] == TREE_NODE_POINTER)
  180. return (int)tree_find_recursive((tree_node*)node->value[2], s);
  181. else
  182. return TREE_NOT_FOUND;
  183. }
  184. }
  185.  
  186. int tree_find(tree_node* node, TreeKeyType s)
  187. {
  188. if (!node) return TREE_NOT_FOUND;
  189. return tree_find_recursive(node, s);
  190. }
  191.  
  192. int tree_size_recursive(tree_node* node)
  193. {
  194. int size = 1;
  195. if (node->type[0] == TREE_NODE_POINTER)
  196. size += tree_size_recursive((tree_node*)node->value[0]);
  197. if (node->type[1] == TREE_NODE_POINTER)
  198. size += tree_size_recursive((tree_node*)node->value[1]);
  199. if (node->type[2] == TREE_NODE_POINTER)
  200. size += tree_size_recursive((tree_node*)node->value[2]);
  201. return size;
  202. }
  203.  
  204. int tree_size(tree_node* node)
  205. {
  206. if (!node) return 0;
  207. return tree_size_recursive(node);
  208. }
  209.  
  210. void tree_destroy_recursive(tree_node* node)
  211. {
  212. if (node->type[0] == TREE_NODE_POINTER)
  213. tree_destroy_recursive((tree_node*)node->value[0]);
  214. if (node->type[1] == TREE_NODE_POINTER)
  215. tree_destroy_recursive((tree_node*)node->value[1]);
  216. if (node->type[2] == TREE_NODE_POINTER)
  217. tree_destroy_recursive((tree_node*)node->value[2]);
  218. delete (node);
  219. }
  220.  
  221. void tree_destroy(tree_node* node)
  222. {
  223. if (!node) return;
  224. tree_destroy_recursive(node);
  225. }
  226.  
  227. void tree_print_helper(const std::vector<int>& mark)
  228. {
  229. for (auto m : mark)
  230. {
  231. switch (m)
  232. {
  233. case TREE_PRINT_BLANK:
  234. printf(" ");
  235. break;
  236. case TREE_PRINT_TRUNK:
  237. printf("│ ");
  238. break;
  239. case TREE_PRINT_BRANCH:
  240. printf("├───");
  241. break;
  242. case TREE_PRINT_BRANCHD:
  243. printf("└───");
  244. break;
  245. default:
  246. break;
  247. }
  248. }
  249. }
  250.  
  251. void tree_print_recursive(tree_node* node, std::vector<int>& mark)
  252. {
  253. if (node == NULL) return;
  254. tree_print_helper(mark);
  255. int last_branch = node->type[2] != TREE_NODE_UNUSED ? 2 :
  256. node->type[1] != TREE_NODE_UNUSED ? 1 : 0;
  257. if (last_branch == 2)
  258. printf("<%f, %f>\n", node->key[0], node->key[1]);
  259. else
  260. printf("<%f>\n", node->key[0]);
  261. int last_mark = *mark.rbegin();
  262. if (last_mark != TREE_PRINT_BLANK)
  263. {
  264. mark.pop_back();
  265. mark.push_back(last_mark == TREE_PRINT_BRANCHD ? TREE_PRINT_BLANK : TREE_PRINT_TRUNK);
  266. }
  267. for (int i = 0; i <= last_branch; i++)
  268. {
  269. if (last_branch == i)
  270. {
  271. mark.push_back(TREE_PRINT_BRANCHD);
  272. }
  273. else
  274. {
  275. mark.push_back(TREE_PRINT_BRANCH);
  276. }
  277. if (node->type[i] == TREE_NODE_POINTER)
  278. {
  279. tree_print_recursive((tree_node*)node->value[i], mark);
  280. }
  281. else if (node->type[i] == TREE_NODE_VALUE)
  282. {
  283. tree_print_helper(mark);
  284. printf("<%d>\n", (int)node->value[i]);
  285. }
  286. if (last_branch != i)
  287. {
  288. mark.pop_back();
  289. }
  290. }
  291. mark.pop_back();
  292. }
  293.  
  294. void tree_print(tree_node* node)
  295. {
  296. if (!node) return;
  297. std::vector<int> mark;
  298. mark.push_back(TREE_PRINT_BLANK);
  299. tree_print_recursive(node, mark);
  300. }
  301.  
  302. int main(int argc, char* argv[])
  303. {
  304. //要求:对第i的页面的访问概率正比于1/sqrt(i+1)
  305. const int count = 30;
  306. const int tests = 10;
  307. TreeKeyType* sum = new TreeKeyType[count];
  308. sum[0] = 1;
  309. //sum[0]=1
  310. //sum[1]=sum[0]+1/sqrt(2)
  311. //sum[2]=sum[1]+1/sqrt(3)
  312. //...
  313. //sum[n-1]=sum[n-2]+1/sqrt(n)
  314. for (int i = 1; i < count; i++)
  315. {
  316. sum[i] = sum[i - 1] + (double)(1 / sqrt(i + 1));
  317. }
  318. TreeKeyType MaxRandValue = sum[count - 1] - 0.00001;
  319. tree_node* search_node = tree_init(sum, count, 0);
  320. printf("Search node size: %d\n", tree_size(search_node) * sizeof(search_node));
  321. printf("========== tree ==========\n");
  322. tree_print(search_node);
  323. printf("========== find ==========\n");
  324. srand((unsigned int)time(NULL));
  325. for (int i = 0; i < tests; i++)
  326. {
  327. TreeKeyType rnd = rand() / double(RAND_MAX) * MaxRandValue;
  328. printf("key: %f, val: %d\n", rnd, tree_find(search_node, rnd));
  329. }
  330. delete[] (sum);
  331. return 0;
  332. }

树(一)——线段树的更多相关文章

  1. UVALive 7148 LRIP【树分治+线段树】

    题意就是要求一棵树上的最长不下降序列,同时不下降序列的最小值与最大值不超过D. 做法是树分治+线段树,假设树根是x,y是其当前需要处理的子树,对于子树y,需要处理出两个数组MN,MX,MN[i]表示以 ...

  2. [BZOJ 1901] Dynamic Rankings 【树状数组套线段树 || 线段树套线段树】

    题目链接:BZOJ - 1901 题目分析 树状数组套线段树或线段树套线段树都可以解决这道题. 第一层是区间,第二层是权值. 空间复杂度和时间复杂度均为 O(n log^2 n). 线段树比树状数组麻 ...

  3. BZOJ_2238_Mst_树剖+线段树

    BZOJ_2238_Mst_树剖+线段树 Description 给出一个N个点M条边的无向带权图,以及Q个询问,每次询问在图中删掉一条边后图的最小生成树.(各询问间独立,每次询问不对之后的询问产生影 ...

  4. BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树

    BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树 Description 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为 ...

  5. BZOJ_2157_旅游_树剖+线段树

    BZOJ_2157_旅游_树剖+线段树 Description Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但 ...

  6. ZJOI 2017 树状数组(线段树套线段树)

    题意 http://uoj.ac/problem/291 思路 不难发现,九条カレン醬所写的树状数组,在查询区间 \([1,r]\) 的时候,其实在查询后缀 \([r,n]\) :在查询 \([l,r ...

  7. BZOJ4317Atm的树&BZOJ2051A Problem For Fun&BZOJ2117[2010国家集训队]Crash的旅游计划——二分答案+动态点分治(点分树套线段树/点分树+vector)

    题目描述 Atm有一段时间在虐qtree的题目,于是,他满脑子都是tree,tree,tree…… 于是,一天晚上他梦到自己被关在了一个有根树中,每条路径都有边权,一个神秘的声音告诉他,每个点到其他的 ...

  8. 【BZOJ5210】最大连通子块和 树剖线段树+动态DP

    [BZOJ5210]最大连通子块和 Description 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块 ...

  9. hdu-4819-线段树套线段树

    http://acm.hdu.edu.cn/showproblem.php?pid=4819 给出一个N*N的矩阵,每次询问一个m*m的子矩阵里的floor((maxv+minv)/2)并把中间的元素 ...

  10. [LNOI2014]LCA(树剖+线段树)

    \(\%\%\% Fading\) 此题是他第一道黑题(我的第一道黑题是蒲公英) 一直不敢开,后来发现是差分一下,将询问离线,树剖+线段树维护即可 \(Code\ Below:\) #include ...

随机推荐

  1. require,include,require_once,include_once的区别

    最近面试时被问到,一时间还真没想到太多,仅仅回答了大概的几个,于是回来再确认一下. 以下内容为网络摘抄: ①作用及用法  可以减少代码的重复 include(_once)("文件的路径&qu ...

  2. 曲线提取数据Engauge Digitizer

    可导出CSV格式数据 其它参考: http://blog.sina.com.cn/s/blog_4ae65b4d0100z8cg.html 其它曲线提取数据的软件还有: GetData.Windig ...

  3. VS2013 密钥

    MXS&Vincene  ─╄OvЁ  &0000017─╄OvЁ  MXS&Vincene MXS&Vincene  ─╄OvЁ:今天很残酷,明天更残酷,后天很美好, ...

  4. asp.net单例模式

    目的:保证一个类只有一个单一的实例 好处:1.在资源共享的情况下,避免由多个操作而导致的资源消耗:2.提供可变数目的实例. 标准的单例代码如下: using System; using System. ...

  5. phpcms V9 数据模型基类

    在学习<phpcms V9首页模板文件解析>的第七步,我们看到content_model类,文件路径:phpcms/model/content_model.class.php 从代码中,可 ...

  6. 今日随笔:scrollTop与overflow

    今天想写一个页面一加载滚动条就自动滚到底部的效果,结果在IE上实现成功了,chrome上完全没反应,最后测试了一下,居然是因为css文件中,html,body都写了overflow:auto这一语句, ...

  7. android获取状态栏高度

    获取android屏幕上状态栏的高度方法网上很多这里不再敖述,只举一个例子 Rect rect = new Rect();getWindow().getDecorView().getWindowVis ...

  8. Codeforces Round #374 (div.2)遗憾题合集

    C.Journey 读错题目了...不是无向图,结果建错图了(喵第4样例是变成无向就会有环的那种图) 并且这题因为要求路径点尽可能多 其实可以规约为限定路径长的拓扑排序,不一定要用最短路做 #prag ...

  9. vs2015手动安装xamarin

    1.安装jdk Download the Java JDK v1.7.0 installer to any directory on your disk, double-click the downl ...

  10. Flowplayer-JavaScript API

    source url: https://flowplayer.org/docs/api.html Global API access Use the flowplayer function to ge ...