树(一)——线段树
问题
现在有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(),输出为题目所求,旨在求映射关系。
既然题目中的几何概型已被求出,那么产生相应随机数的基本步骤如下:
- set S=P1+P2+…P30,generate rand(0<=rand<S)
- for i=1 to 30 do:
- if(rand<P(i)) return i
- end for
三、寻求优化的方法
上面的方法很简单,但是存在效率问题。
普通的if-else嵌套分支都可以转化为二叉查找树(if=1=left,else=0=right)。上述方法也如此,将其转化为二叉树之后,发现是一棵斜树。
斜树有个缺点,就是作为查找树效率低下,因此存在优化的空间。
对斜树而言:
- 考虑最优情况,只判断一次,O(1)
- 考虑最坏情况,全部判断一次,O(n)
- 考虑平均情况,折半n/2,O(n)
而对经典的二叉平衡树而言(折半查找):
- 考虑最优情况,为深度,O(logn)
- 考虑最坏情况,为深度,O(logn)
- 考虑平均情况,为深度,O(logn)
由于是处理随机数,这里考虑平均情况,可知:二叉平衡树优于斜树。
但是,有一个问题:二叉平衡树是用来查找数的,不是用来查找区间的,所以这里用不了平衡树。
因此,作为查找区间的一种数据结构——区间树(也称线段树),就应运而生了。
我这里设计的区间树属于2-3树(键数<=2,值数<=3),结合了广义表的设计思想(结点和数据共用)。
由于数据源是排序过的,所以可以直接采用分治法构建树。(这是由于分组后的数据仍保持有序)
注:
- 打印的树结构设计参考自系统自带tree.exe的结果
- 键和值的关系为,val0<key0<=val1<key1<=val2
- 树的生成,以及分治产生的多余结点处理问题(分配不均等)的解决详见源码
源码
源码:itvtree.cpp
- #include "stdafx.h"
- #include <stdio.h>
- #include <math.h>
- #include <time.h>
- #include <vector>
- /************************************************************************/
- /* 线段树/区间树 */
- /* Interval Tree */
- /************************************************************************/
- typedef double TreeKeyType;
- //实质是2-3平衡树
- struct tree_node
- {
- unsigned char type[4];//只用到type[3],此是为了内存对齐
- TreeKeyType key[2];//2-键
- void* value[3];//3-值
- };
- #define TREE_NODE_UNUSED 0
- #define TREE_NODE_VALUE 1
- #define TREE_NODE_POINTER 2
- #define TREE_NOT_FOUND -1
- #define TREE_PRINT_BLANK 0
- #define TREE_PRINT_TRUNK 1
- #define TREE_PRINT_BRANCH 2
- #define TREE_PRINT_BRANCHD 3
- //************************************
- // Method: 递归构造线段树
- // FullName: tree_init_recursive
- // Access: public
- // Returns: void*
- // Qualifier:
- // Parameter: TreeKeyType * s 数组指针(数据来源)
- // Parameter: int count 当前处理的范围
- // Parameter: int start 当前处理的位置
- // Parameter: unsigned char * type 修改结点类型
- //************************************
- void* tree_init_recursive(TreeKeyType* s, int count, int start, unsigned char* type)
- {
- //此递归调用函数的返回值可以为结点或真值,由type确定
- //树生成采用的是分治方法,当s数组分成3份,各自递归生成子树,再作为某个结点的孩子结点
- //注意:实际长度应为count+1
- tree_node* node;
- if (count == 0)//只有一个数时,只返回值类型,类比快排找中间轴(向中间逼近)
- {
- if (type)
- *type = TREE_NODE_VALUE;
- return (void*)start;//值
- }
- node = new(tree_node);
- if (type)
- *type = TREE_NODE_POINTER;//当前处理长度大于1,需生成新结点,返回结点类型
- switch (count)
- {
- case 1://当前处理的长度为2,type长度为2
- node->type[0] = TREE_NODE_VALUE;
- node->type[1] = TREE_NODE_VALUE;
- node->type[2] = TREE_NODE_UNUSED;
- node->key[0] = s[0];//只需一个键,即大于和小于
- node->value[0] = (void*)(start);
- node->value[1] = (void*)(start + 1);
- break;
- case 2://当前处理的长度为3,type长度为3
- node->type[0] = TREE_NODE_VALUE;
- node->type[1] = TREE_NODE_VALUE;
- node->type[2] = TREE_NODE_VALUE;
- node->key[0] = s[0];
- node->key[1] = s[1];//需要两个键,即大于上界、区间内、小于下界
- node->value[0] = (void*)(start);
- node->value[1] = (void*)(start + 1);
- node->value[2] = (void*)(start + 2);
- break;
- default://处理的长度大于3,采用分治
- int a = count / 3;//三等分
- switch (count % 3)//处理三等分多余的数
- {
- case 0://多一个
- //分组:
- //1)start+[0]->start+[a-1]
- //2)start+[a]->start+[2a]
- //3)start+[2a+1]->start+[3a]
- //长度:
- //1)a-1
- //2)a
- //3)a-1
- //键:
- //1)a-1
- //2)2a
- node->key[0] = s[a - 1];
- node->key[1] = s[a * 2];
- node->value[0] = tree_init_recursive(&s[0], a - 1, start, &node->type[0]);
- node->value[1] = tree_init_recursive(&s[a], a, start + a, &node->type[1]);
- node->value[2] = tree_init_recursive(&s[a * 2 + 1], a - 1, start + a * 2 + 1, &node->type[2]);
- break;
- case 1://多两个
- //分组:
- //1)start+[0]->start+[a-1]
- //2)start+[a]->start+[2a+1]
- //3)start+[2a+2]->start+[3a+1]
- //长度:
- //1)a-1
- //2)a+1
- //3)a-1
- //键:
- //1)a-1
- //2)2a+1
- node->key[0] = s[a - 1];
- node->key[1] = s[a * 2 + 1];
- node->value[0] = tree_init_recursive(&s[0], a - 1, start, &node->type[0]);
- node->value[1] = tree_init_recursive(&s[a], a + 1, start + a, &node->type[1]);
- node->value[2] = tree_init_recursive(&s[a * 2 + 2], a - 1, start + a * 2 + 2, &node->type[2]);
- break;
- case 2://不多
- //分组:
- //1)start+[0]->start+[a]
- //2)start+[a+1]->start+[2a+1]
- //3)start+[2a+2]->start+[3a+2]
- //长度:
- //1)a
- //2)a
- //3)a
- //键:
- //1)a
- //2)2a+1
- node->key[0] = s[a];
- node->key[1] = s[a * 2 + 1];
- node->value[0] = tree_init_recursive(&s[0], a, start, &node->type[0]);
- node->value[1] = tree_init_recursive(&s[a + 1], a, start + a + 1, &node->type[1]);
- node->value[2] = tree_init_recursive(&s[a * 2 + 2], a, start + a * 2 + 2, &node->type[2]);
- break;
- }
- }
- return (void*)node;
- }
- tree_node* tree_init(TreeKeyType* s, int count, int start)
- {
- //初始化
- //给定线段各点坐标,构建树
- return (tree_node*)tree_init_recursive(s, count, start, NULL);
- }
- int tree_find_recursive(tree_node* node, TreeKeyType s)
- {
- //当前结点类型为值就直接返回,否则递归调用
- if (s < node->key[0])//小于左键,双键小于或单键小于,找第一值
- {
- if (node->type[0] == TREE_NODE_VALUE)
- return (int)node->value[0];
- else if (node->type[0] == TREE_NODE_POINTER)
- return (int)tree_find_recursive((tree_node*)node->value[0], s);
- else
- return TREE_NOT_FOUND;
- }
- if (node->type[2] == TREE_NODE_UNUSED || s < node->key[1])//双键区间内部或单键大于,找第二值
- {
- if (node->type[1] == TREE_NODE_VALUE)
- return (int)node->value[1];
- else if (node->type[1] == TREE_NODE_POINTER)
- return (int)tree_find_recursive((tree_node*)node->value[1], s);
- else
- return TREE_NOT_FOUND;
- }
- {//双键大于,找第三值
- if (node->type[2] == TREE_NODE_VALUE)
- return (int)node->value[2];
- else if (node->type[2] == TREE_NODE_POINTER)
- return (int)tree_find_recursive((tree_node*)node->value[2], s);
- else
- return TREE_NOT_FOUND;
- }
- }
- int tree_find(tree_node* node, TreeKeyType s)
- {
- if (!node) return TREE_NOT_FOUND;
- return tree_find_recursive(node, s);
- }
- int tree_size_recursive(tree_node* node)
- {
- int size = 1;
- if (node->type[0] == TREE_NODE_POINTER)
- size += tree_size_recursive((tree_node*)node->value[0]);
- if (node->type[1] == TREE_NODE_POINTER)
- size += tree_size_recursive((tree_node*)node->value[1]);
- if (node->type[2] == TREE_NODE_POINTER)
- size += tree_size_recursive((tree_node*)node->value[2]);
- return size;
- }
- int tree_size(tree_node* node)
- {
- if (!node) return 0;
- return tree_size_recursive(node);
- }
- void tree_destroy_recursive(tree_node* node)
- {
- if (node->type[0] == TREE_NODE_POINTER)
- tree_destroy_recursive((tree_node*)node->value[0]);
- if (node->type[1] == TREE_NODE_POINTER)
- tree_destroy_recursive((tree_node*)node->value[1]);
- if (node->type[2] == TREE_NODE_POINTER)
- tree_destroy_recursive((tree_node*)node->value[2]);
- delete (node);
- }
- void tree_destroy(tree_node* node)
- {
- if (!node) return;
- tree_destroy_recursive(node);
- }
- void tree_print_helper(const std::vector<int>& mark)
- {
- for (auto m : mark)
- {
- switch (m)
- {
- case TREE_PRINT_BLANK:
- printf(" ");
- break;
- case TREE_PRINT_TRUNK:
- printf("│ ");
- break;
- case TREE_PRINT_BRANCH:
- printf("├───");
- break;
- case TREE_PRINT_BRANCHD:
- printf("└───");
- break;
- default:
- break;
- }
- }
- }
- void tree_print_recursive(tree_node* node, std::vector<int>& mark)
- {
- if (node == NULL) return;
- tree_print_helper(mark);
- int last_branch = node->type[2] != TREE_NODE_UNUSED ? 2 :
- node->type[1] != TREE_NODE_UNUSED ? 1 : 0;
- if (last_branch == 2)
- printf("<%f, %f>\n", node->key[0], node->key[1]);
- else
- printf("<%f>\n", node->key[0]);
- int last_mark = *mark.rbegin();
- if (last_mark != TREE_PRINT_BLANK)
- {
- mark.pop_back();
- mark.push_back(last_mark == TREE_PRINT_BRANCHD ? TREE_PRINT_BLANK : TREE_PRINT_TRUNK);
- }
- for (int i = 0; i <= last_branch; i++)
- {
- if (last_branch == i)
- {
- mark.push_back(TREE_PRINT_BRANCHD);
- }
- else
- {
- mark.push_back(TREE_PRINT_BRANCH);
- }
- if (node->type[i] == TREE_NODE_POINTER)
- {
- tree_print_recursive((tree_node*)node->value[i], mark);
- }
- else if (node->type[i] == TREE_NODE_VALUE)
- {
- tree_print_helper(mark);
- printf("<%d>\n", (int)node->value[i]);
- }
- if (last_branch != i)
- {
- mark.pop_back();
- }
- }
- mark.pop_back();
- }
- void tree_print(tree_node* node)
- {
- if (!node) return;
- std::vector<int> mark;
- mark.push_back(TREE_PRINT_BLANK);
- tree_print_recursive(node, mark);
- }
- int main(int argc, char* argv[])
- {
- //要求:对第i的页面的访问概率正比于1/sqrt(i+1)
- const int count = 30;
- const int tests = 10;
- TreeKeyType* sum = new TreeKeyType[count];
- sum[0] = 1;
- //sum[0]=1
- //sum[1]=sum[0]+1/sqrt(2)
- //sum[2]=sum[1]+1/sqrt(3)
- //...
- //sum[n-1]=sum[n-2]+1/sqrt(n)
- for (int i = 1; i < count; i++)
- {
- sum[i] = sum[i - 1] + (double)(1 / sqrt(i + 1));
- }
- TreeKeyType MaxRandValue = sum[count - 1] - 0.00001;
- tree_node* search_node = tree_init(sum, count, 0);
- printf("Search node size: %d\n", tree_size(search_node) * sizeof(search_node));
- printf("========== tree ==========\n");
- tree_print(search_node);
- printf("========== find ==========\n");
- srand((unsigned int)time(NULL));
- for (int i = 0; i < tests; i++)
- {
- TreeKeyType rnd = rand() / double(RAND_MAX) * MaxRandValue;
- printf("key: %f, val: %d\n", rnd, tree_find(search_node, rnd));
- }
- delete[] (sum);
- return 0;
- }
树(一)——线段树的更多相关文章
- UVALive 7148 LRIP【树分治+线段树】
题意就是要求一棵树上的最长不下降序列,同时不下降序列的最小值与最大值不超过D. 做法是树分治+线段树,假设树根是x,y是其当前需要处理的子树,对于子树y,需要处理出两个数组MN,MX,MN[i]表示以 ...
- [BZOJ 1901] Dynamic Rankings 【树状数组套线段树 || 线段树套线段树】
题目链接:BZOJ - 1901 题目分析 树状数组套线段树或线段树套线段树都可以解决这道题. 第一层是区间,第二层是权值. 空间复杂度和时间复杂度均为 O(n log^2 n). 线段树比树状数组麻 ...
- BZOJ_2238_Mst_树剖+线段树
BZOJ_2238_Mst_树剖+线段树 Description 给出一个N个点M条边的无向带权图,以及Q个询问,每次询问在图中删掉一条边后图的最小生成树.(各询问间独立,每次询问不对之后的询问产生影 ...
- BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树
BZOJ_4551_[Tjoi2016&Heoi2016]树_树剖+线段树 Description 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为 ...
- BZOJ_2157_旅游_树剖+线段树
BZOJ_2157_旅游_树剖+线段树 Description Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但 ...
- ZJOI 2017 树状数组(线段树套线段树)
题意 http://uoj.ac/problem/291 思路 不难发现,九条カレン醬所写的树状数组,在查询区间 \([1,r]\) 的时候,其实在查询后缀 \([r,n]\) :在查询 \([l,r ...
- BZOJ4317Atm的树&BZOJ2051A Problem For Fun&BZOJ2117[2010国家集训队]Crash的旅游计划——二分答案+动态点分治(点分树套线段树/点分树+vector)
题目描述 Atm有一段时间在虐qtree的题目,于是,他满脑子都是tree,tree,tree…… 于是,一天晚上他梦到自己被关在了一个有根树中,每条路径都有边权,一个神秘的声音告诉他,每个点到其他的 ...
- 【BZOJ5210】最大连通子块和 树剖线段树+动态DP
[BZOJ5210]最大连通子块和 Description 给出一棵n个点.以1为根的有根树,点有点权.要求支持如下两种操作: M x y:将点x的点权改为y: Q x:求以x为根的子树的最大连通子块 ...
- hdu-4819-线段树套线段树
http://acm.hdu.edu.cn/showproblem.php?pid=4819 给出一个N*N的矩阵,每次询问一个m*m的子矩阵里的floor((maxv+minv)/2)并把中间的元素 ...
- [LNOI2014]LCA(树剖+线段树)
\(\%\%\% Fading\) 此题是他第一道黑题(我的第一道黑题是蒲公英) 一直不敢开,后来发现是差分一下,将询问离线,树剖+线段树维护即可 \(Code\ Below:\) #include ...
随机推荐
- require,include,require_once,include_once的区别
最近面试时被问到,一时间还真没想到太多,仅仅回答了大概的几个,于是回来再确认一下. 以下内容为网络摘抄: ①作用及用法 可以减少代码的重复 include(_once)("文件的路径&qu ...
- 曲线提取数据Engauge Digitizer
可导出CSV格式数据 其它参考: http://blog.sina.com.cn/s/blog_4ae65b4d0100z8cg.html 其它曲线提取数据的软件还有: GetData.Windig ...
- VS2013 密钥
MXS&Vincene ─╄OvЁ &0000017─╄OvЁ MXS&Vincene MXS&Vincene ─╄OvЁ:今天很残酷,明天更残酷,后天很美好, ...
- asp.net单例模式
目的:保证一个类只有一个单一的实例 好处:1.在资源共享的情况下,避免由多个操作而导致的资源消耗:2.提供可变数目的实例. 标准的单例代码如下: using System; using System. ...
- phpcms V9 数据模型基类
在学习<phpcms V9首页模板文件解析>的第七步,我们看到content_model类,文件路径:phpcms/model/content_model.class.php 从代码中,可 ...
- 今日随笔:scrollTop与overflow
今天想写一个页面一加载滚动条就自动滚到底部的效果,结果在IE上实现成功了,chrome上完全没反应,最后测试了一下,居然是因为css文件中,html,body都写了overflow:auto这一语句, ...
- android获取状态栏高度
获取android屏幕上状态栏的高度方法网上很多这里不再敖述,只举一个例子 Rect rect = new Rect();getWindow().getDecorView().getWindowVis ...
- Codeforces Round #374 (div.2)遗憾题合集
C.Journey 读错题目了...不是无向图,结果建错图了(喵第4样例是变成无向就会有环的那种图) 并且这题因为要求路径点尽可能多 其实可以规约为限定路径长的拓扑排序,不一定要用最短路做 #prag ...
- vs2015手动安装xamarin
1.安装jdk Download the Java JDK v1.7.0 installer to any directory on your disk, double-click the downl ...
- Flowplayer-JavaScript API
source url: https://flowplayer.org/docs/api.html Global API access Use the flowplayer function to ge ...