C++基本数据类型备忘

今天阅读《C++ Primer, 5e》的第二章,介绍C++的基本内置类型,觉得有一些平时工作容易出错的知识点,现摘录如下: 1 unsigned char c = -1; // 假设char占8比特,c的值为255 当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8比特大小的unsigned char可以表示0至255区间内的值,如果我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。因此,把-1赋给8比特大小的unsigned char所得的结果是255。 1 signed char c2 = 256; // 假设char占8比特,c2的值是未定义的 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。 1 2 3 4 unsigned u = 10; int i = -42; std::cout << i + i << std::endl; // 输出-84 std::cout << u + i << std::endl; // 如果int占32位,输出4294967264 在第一个输出表达式里,两个(负)整数相加并得到了期望的结果。在第二个输出表达式里,相加前首先把整数-42转换成无符号数。把负数转换成无符号数类似于直接给无符号数赋一个负数,结果等于这个负数加上无符号数的模。unsigned (int)的取值范围是0~\(2^{32}-1\),所以总数有\(2^{32}\)个数,-42%\(2^{32}\)=-42+\(2^{32}\),u+i=10+(-42+\(2^{32}\))=4294967264。 1 2 3 unsigned u1 = 42, u2 = 10; std::cout << u1 – u2 << std::endl; // 正确:输出32 std::cout << u2 – u1 << std::endl; // 正确:不过,结果是取模后的值 当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不能是一个负值。 ...

September 24, 2016 · 1 min

随机矩阵及其特征值

随机矩阵是这样一类方阵,其元素为非负实数,且行和或列和为1。如果行和为1,则称为行随机矩阵;如果列和为1,则称为列随机矩阵;如果行和和列和都为1,则称为双随机矩阵。 前面我们介绍的谷歌矩阵和HMM中的转移矩阵都属于随机矩阵,所以随机矩阵也称为概率矩阵、转移矩阵、或马尔可夫矩阵。 随机矩阵有一个性质,就是其所有特征值的绝对值小于等于1,且其最大特征值为1。下面通过两种方法证明这个结论。 首先,随机矩阵A肯定有特征值1,即 $$\begin{equation}A\vec 1=1\times\vec 1\end{equation}$$其中的单位向量\(\vec 1=(\frac{1}{n},…,\frac{1}{n})^T\),因为A的行和为1,所以上述等式成立。即1是A的特征值。 反证法 假设存在大于1的特征值\(\lambda\),则有\(A\vec x=\lambda\vec x\)。令\(x_k\)是\(\vec x\)中最大的元素。又因为A的元素非负,且行和为1,所以\(\lambda\vec x\)中的每个元素都是\(\vec x\)中元素的凸组合,所以\(\lambda\vec x\)中的每个元素都小于等于\(x_k\)。 $$\begin{equation}a_{i1}x_1+a_{i2}x_2+…+a_{in}x_n=\lambda x_i\leq x_k\end{equation}$$但是如果\(\lambda>1\),则\(\lambda x_k>x_k\),和(2)式矛盾,所以\(\lambda\leq 1\)。又因为(1)式,所以A的最大特征值为1。 常规证法 设对称随机矩阵A的特征值\(\lambda\)对应的特征向量为\(x\)(为了简便,以下省略向量符号),则有\(Ax=\lambda x\),即\(x^TAx=\lambda x^Tx\),欲证明\(|\lambda|\leq 1\),只需证明 $$\begin{equation}\lambda=\frac{< x, Ax >}{< x, x >}\leq 1\end{equation}$$根据定义有: $$\begin{equation}< x, Ax >=\sum_{i=1}^na_{ii}x_i^2+2\sum_{i < j, i\sim j}a_{ij}x_ix_j\end{equation}$$对于\(i < j, i\sim j\),有: $$\begin{equation}a_{ij}(x_i-x_j)^2=a_{ij}x_i^2-2a_{ij}x_ix_j+a_{ij}x_j^2\end{equation}$$两边求和并移项得到: $$ \begin{equation} \begin{array} \displaystyle{2\sum_{i < j}}a_{ij}x_ix_j & = & \displaystyle{\sum_{i < j}a_{ij}x_i^2+\sum_{i < j}a_{ij}x_j^2-\sum_{i < j}a_{ij}(x_i-x_j)^2}\\ & = & \displaystyle{\sum_{i < j}a_{ij}x_i^2+\sum_{i < j}a_{ji}x_j^2-\sum_{i < j}a_{ij}(x_i-x_j)^2}\\ & = & \displaystyle{\sum_{i < j}a_{ij}x_i^2+\sum_{i > j}a_{ij}x_i^2-\sum_{i < j}a_{ij}(x_i-x_j)^2}\\ & = & \displaystyle{\sum_i(\sum_{j\neq i}a_{ij}x_i^2)-\sum_{i < j}a_{ij}(x_i-x_j)^2}\\ & = & \displaystyle{\sum_i(x_i^2(1-a_{ii}))-\sum_{i < j}a_{ij}(x_i-x_j)^2} \end{array} \end{equation} $$第2、3个等号都是因为A是对称矩阵,所以可以把\(a_{ij}\)替换为\(a_{ji}\),然后互换\(i,j\)下标。最后一个等号是因为A的行和为1。 ...

August 23, 2016 · 1 min

马尔可夫聚类算法

马尔可夫聚类算法(The Markov Cluster Algorithm, MCL)是一种快速可扩展的基于图的聚类算法。它的基本思想为:在一个稀疏图G中,如果某个区域A是稠密的(是一个聚类),则在A中随机游走k步,还在A内的概率很大,也就是说,A内的k步路径(k-length path)很多。所以我们可以在图中随机游走k步,如果某个区域连通的概率很大,则该区域是一个聚类。随机游走的下一步只和当前所处节点有关,也就是说这是一个马尔可夫的随机游走过程。 我们用一个例子来演示马尔可夫聚类算法的过程。 上图是一个很小的网络,我们用肉眼大概能看出有三个聚类,分别是左边的{1,6,7,10},中间的{2,3,5}和右边的{4,8,9,11,12}。我们用MCL看看结果如何。 为了随机游走,我们常用邻接矩阵来表示图,如果i,j有边,则N[i][j]=1,否则N[i][j]=0。又随机游走可能有自回路,所以加上单位矩阵I,得到矩阵N+I。 MCL有两个关键的步骤,分别是Expansion和Inflation。 Expansion就是不断对矩阵进行幂次运算,相当于随机游走。假设随机游走了2步,则得到如下图的关联矩阵\((N+I)^2\),第1行第10列为4,说明1到10的2-length path有4条:1→6→10,1→7→10,1→1→10,1→10→10。随机游走k步之后,\((N+I)^k[i][j]\)越大,说明\(i\)和\(j\)之间的连通性越强。 $$\begin{equation}Expand(M)=M^k\end{equation}$$ Inflation是为了增强更强的连接,减弱更弱的连接,只有这样才能得到边界比较明确的聚类。MCL的做法是对元素做幂次运算,然后按列归一化,公式为: $$\begin{equation}(\Gamma_rM)_{pq}=\frac{(M_{pq})^r}{\sum_{i=1}^k(M_{iq})^r}\end{equation}$$参数经验值是\(k=r=2\)。不断做Expansion和Inflation操作,直到算法收敛,得到若干个聚类。中间过程请点此查看,下图为最终结果。 从图中可以看出,和1有边的只剩下6,7,10了,所以得到聚类{1,6,7,10},同理能得到聚类{2,3,5}和{4,8,9,11,12} ,和我们肉眼得到的结果是一致的。 MCL算法的原理很简单,得到的聚类效果也不错。下面总结一下MCL的算法过程: 给定无向图G,Expansion和Inflation的参数\(k\)和\(r\) 生成G的邻接矩阵\(N\) 添加自回路,得到矩阵\(N+I\) 循环对\(N+I\)做Expansion和Inflation操作,即计算公式(1)和(2),直到收敛 根据最终得到的矩阵,进行划分聚类 此算法是我在上《生物信息学中的算法设计》课上是学到的,当时觉得这个算法真是神奇,如此简单,但又如此有效,实在高明。查阅文献得知,此为Stijn van Dongen的博士论文,本博客的图片均来自其博士论文,想深入了解图聚类算法,请下载他的论文。

August 22, 2016 · 1 min

隐马尔可夫模型及其应用(2)学习问题&识别问题

上一回介绍了HMM的解码问题,今天我们介绍HMM的学习问题和识别问题,先来看学习问题。 正如上一回结束时所说,HMM的学习问题是:仅已知观测序列\(\vec y\),要估计出模型参数组\(\vec\lambda=(\mu,A,B)\),其中\(\mu\)为初始概率分布向量,\(A\)为转移概率矩阵,\(B\)为发射概率矩阵。 算法设计 求解HMM的参数学习问题,就是求解如下的最优化问题: $$\begin{equation} P(\vec Y = \vec y|\hat \lambda)=\max\limits_{\vec \lambda} P(\vec Y = \vec y|\vec \lambda)\end{equation}$$也就是找一个参数\(\vec \lambda\),使得模型在该参数下最有可能产生当前的观测\(\vec y\)。如果使用极大似然法求解,对于似然函数\(P(\vec Y=\vec y|\vec \lambda)=\sum\limits_{i_1,…,i_T}\mu_{i_1}b_{i_1y_1}a_{i_1i_2}…a_{i_{T-1}i_T}b_{i_Ty_T}\)而言,这个最大值问题的计算量过大,在实际中是不可能被采用的。为此,人们构造了一个递推算法,使其能相当合理地给出模型参数\(\vec \lambda\)的粗略估计。其核心思想是:并不要求备选\(\vec\lambda\)使得\(P(\vec Y=\vec y|\vec \lambda)\)达到最大或局部极大,而只要求使\(P(\vec Y=\vec y|\vec \lambda)\)相当大,从而使计算变为实际可能。 EM算法 为此,我们定义一个描述模型“趋势”的量\(Q(\vec\lambda^*|\vec\lambda)\)代替似然函数\(P(\vec Y=\vec y|\vec\lambda)\),其定义为: $$\begin{equation} Q(\vec\lambda^*|\vec\lambda)=\sum\limits_{\vec x}P(\vec x,\vec y|\vec\lambda)\ln P(\vec x,\vec y|\vec\lambda^*)\end{equation}$$利用在\(0 < x < 1\)时,不等式\(\ln x\leq x-1\)成立,可以证明: $$\begin{equation} Q(\vec\lambda^*|\vec\lambda)-Q(\vec\lambda|\vec\lambda)\leq P(\vec Y=\vec y|\vec\lambda^*)-P(\vec Y=\vec y|\vec\lambda)\end{equation}$$由此可见,对于固定的\(\vec\lambda\),只要\(Q(\vec\lambda^*|\vec\lambda)>Q(\vec\lambda|\vec\lambda)\),就有\(P(\vec Y=\vec y|\vec\lambda^*)>P(\vec Y=\vec y|\vec\lambda)\)。于是想把模型\(\vec\lambda_m\)修改为更好的模型\(\vec\lambda_{m+1}\),只需找\(\vec\lambda_{m+1}\)使得: $$\begin{equation}Q(\vec\lambda_{m+1}|\vec\lambda_m)=\sup_{\vec\lambda}Q(\vec\lambda|\vec\lambda_m)\end{equation}$$即只要把\(Q(\vec\lambda|\vec\lambda_m)\)关于\(\vec\lambda\)的最大值处取成\(\vec\lambda_{m+1}\),就有\(P(\vec Y=\vec y|\vec\lambda_{m+1})>P(\vec Y=\vec y|\vec\lambda_m)\)。 这样得到的模型序列\(\{\vec\lambda_m\}\)能保证\(P(\vec Y=\vec y|\vec\lambda_m)\)关于\(m\)是严格递增的,虽然在这里还不能在理论上证明\(P(\vec Y=\vec y|\vec\lambda_m)\)收敛到\(\max_{\vec\lambda}P(\vec Y=\vec y|\vec\lambda)\),但是当\(m\)充分大时,\(\vec\lambda_m\)也还能提供在实际中较为满意的粗略近似。 ...

August 21, 2016 · 1 min

隐马尔可夫模型及其应用(1)简介&解码问题

隐马尔可夫模型(Hidden Markov Model, HMM)是统计模型,它用来描述一个含有隐含未知参数的马尔可夫过程。其难点是从可观察的参数中确定该过程的隐含参数。然后利用这些参数来作进一步的分析,例如模式识别。 先举一个简单的例子以直观地理解HMM的实质——韦小宝的骰子。 假设韦小宝有两个骰子,一个正常的骰子A,A以1/6的概率均等的出现每个点;一个不正常的骰子B,B出现5,6点数的概率为0.3,出现其他点数的概率为0.1。显然投掷B更容易出现大的点数。每次试验第一次投掷时,韦小宝会以0.4的概率出千(即投掷B)。但是在一次试验中,韦小宝不太可能一直出千,所以骰子会在A、B之间转换,比如这次投了B,下次可能会以0.1的概率投A。A、B之间的转移概率如下图。 某一次试验,我们观察到韦小宝掷出的骰子序列为\(O=(1,3,4,5,5,6,6,3,2,6)\),请问韦小宝什么时候出千了。这个问题就可以通过HMM求解。 HMM有2个状态: 观测状态。我们观察到的骰子序列称为观测状态\(\mathbf{Y}=\{y_1,y_2,…,y_T\}\) 隐状态。隐含在每个观测状态里面的是隐状态\(\mathbf{X}=\{x_1,x_2,…,x_T\}\) T是时间,也可以认为是观测的次数。HMM有3个参数: 初始分布\(\mathbf{\mu}=(\mu_i)\),\(\mu_i=Pr(x_1=i)\),即第一次观测时,每个隐状态出现的概率 转移概率矩阵\(A=(a_{ij})\),\(a_{ij}=Pr(x_{t+1}=j|x_t=i)\),即t时刻的隐状态为i,t+1时刻转移到隐状态j的概率 发射概率矩阵\(B=(b_{il})\),\(b_{il}=Pr(y_t=l|x_t=i)\),即t时候隐状态为i的情况下,观测到状态为l的概率 参数\(\mathbf{\lambda=\{\mu,A,B\}}\)称为HMM的模型参数。具体到上面的例子,我们有初始分布和转移概率为: 发射概率为: 观测状态为\(\mathbf{Y}=(1,3,4,5,5,6,6,3,2,6)\),问题就是求解出隐状态\(\mathbf{X}\),此问题被称为HMM的解码问题,可以由著名的维特比算法(Viterbi algorithm)解决。 解码问题是要求出使得观测状态\(Y\)出现概率最大的隐状态\(X\),假设有N个隐状态(本例为2),共有T个时刻(本例为10),则每个时刻有N个取值可能,则共有\(N^T\)条可能的隐状态链(本例为\(2^{10}\))。我们需要求出每一条隐状态链下T个发射概率的乘积,然后取最大值,这是指数时间复杂度的(\(O(N^T)\))。 但是Viterbi算法是一个动态规划算法,只需多项式时间即可解决该问题。该算法的原理很好理解,假设我们求得到\(s_{i2}\)的最大概率路径为下图中的红线\(s_{11}\rightarrow s_{22}\rightarrow … s_{i2}\),则在求经过\(s_{i2}\)到\(s_{(i+1)1}\)的最大概率路径时,不需要再测试\(s_{13}\rightarrow s_{21}\rightarrow s_{i2}\rightarrow s_{(i+1)1}\)这条路径(下图蓝线),因为显然已经知道红线概率大于蓝线概率了。图中还有很多类似蓝线的路径都可以不用计算了,大大提高了求解速度。 因为计算第\(i+1\)时刻的累积概率只和第\(i\)时刻的概率有关,每次至多计算\(N*N\)个概率乘积(可以从\(i\)时刻的\(N\)个状态到达\(i+1\)时刻的某个状态,\(i+1\)时刻共有\(N\)个状态),最多计算T次(共T个时刻),所以时间复杂度降到了\(O(N^2T)\)。 下面我们形式化的描述Viterbi算法。 假设\(\delta_t(i)\)为\(t\)时刻取到隐状态\(i\),且1~t的观测状态都符合观测值\(Y\)的各个路径的最大概率,即 $$ \begin{equation}\delta_t(i)=\underset{i_1,…,i_{t-1}}{\max}Pr(X_t=i,X_{t-1}=i_{t-1},…,X_1=i_1,Y_t=y_t,…,Y_1=y_1|\mathbf{\lambda})\end{equation} $$联系上图,可认为\(\delta_t(i)\)为红线。则递推公式为: $$ \begin{equation}\delta_{t+1}(i)=b_{iy_{t+1}}\underset{j}{\max}(\delta_t(j)a_{ji})\end{equation} $$由\(j\)到\(i\)的转移概率,再乘上\(i\)发射\(y_{t+1}\)的概率。 在初始时刻\(t=1\),有: $$ \begin{equation}\delta_1(i)=\mu_ib_{iy_1}\end{equation} $$最后的全局最大概率为\(\underset{j}{\max}\delta_T(j)\)。为了得到完整路径,我们保留每一隐状态取得最大概率时的上一隐状态,即: $$ \begin{equation}\psi_{t+1}(i)=j^*\end{equation} $$其中\(j^*\)要满足 $$ \begin{equation}\delta_{t+1}(i)=b_{iy_{t+1}}\delta_t(j^*)a_{j^*i}\end{equation} $$最后使用如下回溯法得到所有最佳隐状态: $$\begin{equation}X_T=i^*\in\{i:\delta_T(i)=\underset{j}{\max}\delta_T(j)\}\end{equation}$$$$\begin{equation}X_t=\psi_{t+1}(X_{t+1})\end{equation}$$下面我们利用Viterbi算法来求解韦小宝的骰子这个例子。 \(t=1\)时,\(y_1=1\),有\(\delta_1(A)=0.6*1/6=0.1\),\(\delta_1(B)=0.4*0.1=0.04\)。 \(t=2\)时,\(y_2=3\),有: 隐状态为A:a)A->A有\(\delta_2(A)=(1/6)*0.1*0.8=1.33*10^{-2}\);b)B->A有\(\delta_2(A)=(1/6)*0.04*0.1=6.6*10^{-4}\)。所以A->A,\(\psi_2(A)=A\)。 隐状态为B:a)A->B有\(\delta_2(B)=0.1*0.1*0.2=2*10^{-3}\);b)B->B有\(\delta_2(B)=0.1*0.04*0.9=3.6*10^{-3}\)。所以B->B,\(\psi_2(B)=B\)。 如此计算下去,可以得到如下表: \(t=10\)时最大概率为\(\delta_{10}(B)\),经过回溯得到最佳隐状态为: 所以HMM很神奇吧,可以抓住韦小宝从第5次开始就一直在出千,而且出千之后,掷出的点数大部分为5和6。 Viterbi算法还可用于解决语音识别或者拼音输入法。我们知道中文的一个拼音可以对应多个汉字,连续的一段拼音就能组成成千上万种可能的句子,哪一个句子才是最佳候选呢?我们可以把每个拼音当成观测状态,同音的汉字当成可能的隐状态。通过背景语料库统计得到每个汉字出现在词首的概率、汉字之间的转移概率和汉字与拼音之间的发射概率,这样我们就能得到模型参数,然后利用Viterbi算法求解出一个最佳的隐状态序列,这样就能完成一个简易的拼音输入法。 HMM在实际中主要有3个方面的应用,分别是: 从一段观测序列\(\mathbf{Y}\)及已知模型\(\mathbf{\lambda=(\mu,A,B)}\)出发,估计出隐状态\(\mathbf{X}\)的最佳值,称为解码问题,这是状态估计问题。这篇博客讨论的就是这个问题。 从一段观测序列\(\mathbf{Y}\)出发,估计模型参数组\(\mathbf{\lambda=(\mu,A,B)}\),称为学习问题,就是参数估计问题。 对于一个特定的观测链\(\mathbf{Y}\),已知它可能是由已经学习好的若干模型之一所得的观测,要决定此观测究竟是得自其中哪一个模型,这称为识别问题,就是分类问题。 关于HMM的学习问题和识别问题,请听下回分解。

August 20, 2016 · 1 min

2016年中总结

半年的时光又过去了,圆满结束了一年的集中教学任务,离开了美丽的雁栖湖,回到闹市中关村。 这半年基本上延续了研一上学期的高强度学习,四门硬课。《高级算法》这门课由四位大师级的老师授课,内容囊括了近似算法、计算复杂性、随机算法、局部搜索、全息规约等,完全是神一样的课。最后复习的时候,大家都生不如死啊,不过经过一个月的挑灯夜战,我还是取得了97分的好成绩,值了。 《大数据系统与大规模数据分析》这门课的老师是一个年轻的海归,要求很严格,有专门的算法检查平时作业是否抄袭,真的有好几个同学因为抄袭而得0分。这门课的大作业是在GraphLite上实现SVD,我带领队员经过一个月的努力比较圆满的完成了大作业,感谢组里的编程大神。 《机器学习方法与应用》是面向电子学院的课程,讲得太简单,考试基本是概念题,不建议选修。 《生物信息学中的算法设计》这门课其实应该叫统计机器学习在生物信息领域的应用,讲的内容比《机器学习方法与应用》的内容更深更广。不过内容太多也难以消化,好好做大作业应该会有不少收获。 集中教学一年,研一上的GPA是87分,研一下的GPA是89.3分,平均是88.1分。 除了完成若干个课程大作业,这学期还完成了两个组内大作业,分别是倒排索引和蛋白质搜索引擎,也多谢XN和我一起查Bug、对答案。(天啊,我半年是做了多少个大作业啊…) 这半年每周二回所和师姐交接任务,真是要感谢天真呆萌的JL师姐,当初保研的时候就被师姐的热情所感染,现在又有幸接替师姐的接力棒,好幸运。 要说上半年最大的收获,应该是收获了一枚女朋友吧~没错,就是我这篇博客里提到的欣欣~真的没想到这么聊得来,一起吃饭、看电影、聊代码、骑行、游山玩水。这半年拍的照片,比我前22年拍的照片还多。和她在一起很开心,不过有时候也会很累,身体累(羸弱),有时候也心累,毕竟课程压力和组内压力摆在那里,白天去玩了,晚上还是要加班补回来的。有时候冷落了她,也会感到愧疚不安,特别是我在复习《高级算法》期间,两人都很少见面,那一次是真的惹欣欣生气了:-( 总结一下在雁栖湖一年的收获,大致有如下图的四个方面: 看看年初计划的完成情况: 完成国科大下学期的课程任务:完成 接手pLink软件:完成 刷完LeetCode所有题目:上半年基本没刷题,下半年一定完成 读10本书:目前读了《数学之美》、《大话设计模式》、《我不知道该说什么,关于死亡还是爱情》、《男人来自火星、女人来自金星,卷I》,还差6本,下半年加油! 去电影院看10场电影:目前看了《美人鱼》、《北京遇上西雅图之不二情书》、《忍者神龟2:破影而出》,还差好多… 改正坐姿:有一段时间刻意改正了,但是这东西貌似改不过来? 下半年就进入实验室,开始科研实战了,做交联的师兄师姐都毕业了,留下我一个人,感觉好艰难,希望我能顺利进入角色,协助师兄把文章发了,维护好pLink2的软件,并且开发集群版。

August 20, 2016 · 1 min

稳定版快速排序算法

我们知道常规的快速排序算法是一个不稳定的算法,也就是两个相等的数排序之后的顺序可能和在原序列中的顺序不同。这是因为当选定一个枢轴(pivot),要把其他数分到小于pivot和大于pivot的两边的时候,不同实现的分法不一样。 下面我实现了一种稳定版快速排序算法,在Partition函数中保持了原序列中所有元素的相对顺序,只把pivot放到了它的正确位置。具体方法是三遍扫描原序列:1)第一遍先把小于pivot的元素按先后顺序放到tmp里,然后把pivot放到它的正确位置tmp[k];2)第二遍把大于pivot的元素按先后顺序追加在tmp里,这样除了pivot以前的其他元素,都保持了和原序列中一样的顺序;3)第三遍把tmp赋值回原数组A。 当排序算法稳定之后,就可以借此统计逆序数了,文件Q5.txt中共包含100000个不同的整数,每行一个数。我们可以使用稳定版快速排序算法对其排序,并统计出其中的逆序数个数。 具体的Python 3实现如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 # -*- coding: utf-8 -*- """ Created on Tue Oct 6 00:21:37 2015 @author: bitjoy """ import time inversions = 0 def Partition(A, p, r): global inversions tmp = [0] * (r-p+1) pivot = A[p] k = 0 for i in range(p+1, r+1): # first if A[i] < pivot: tmp[k] = A[i] inversions = inversions + i – k – p k = k + 1 tmp[k] = pivot ans = k + p k = k + 1 for i in range(p+1, r+1): # second if A[i] > pivot: tmp[k] = A[i] k = k + 1 k = 0 for i in range(p, r+1): # third A[i] = tmp[k] k = k + 1 return ans def QuickSortAndCount(A, p, r): if p < r: q = Partition(A, p, r) QuickSortAndCount(A, p, q-1) QuickSortAndCount(A, q + 1, r) if __name__ == "__main__": Q5 = open('Q5.txt', encoding = 'utf-8') data = [ int(x) for x in Q5 ] Q5.close() start = time.clock() QuickSortAndCount(data, 0, len(data) -1 ) end = time.clock() print("number of inversions:%d\ntime:%f s"%(inversions,end-start)) 虽然这种快排的时间复杂度还是O(nlgn),但是在Partition函数中扫描了3次数组,并且借用了辅助数组tmp,不再是in-place排序算法,所以排序用时会比常规快排或者归并排序要慢。 ...

August 18, 2016 · 2 min

Huffman编码压缩算法及其实现

哈弗曼编码是一个很经典的压缩算法,压缩率能达到50%,甚至更低。它的基本原理包括四个步骤: 统计文件中每个字符出现的频率。 构建一个哈弗曼树。建树的过程是不断的合并频率最小的两个节点,父亲节点的频率为两个孩子节点的频率之和。如此循环直到合并成一个根节点。叶子节点为不同的字符及其频率。 生成哈弗曼编码。从树根开始对树进行编码,比如进入左孩子的边标记为0,进入右孩子的边标记为1,这里的0和1都是二进制位。这样之后,每个叶子节点都有一个唯一的二进制编码,这就是哈弗曼编码。频率越低的字符哈弗曼编码越长,频率越高的字符哈弗曼编码越短,这样就能起到压缩的效果。 第二遍扫描文件,把字符转换为对应的哈弗曼编码,保存成压缩文件。 解压缩的过程就是解析二进制位,然后查找哈弗曼树,每找到一个叶子节点,就解析出一个字符,直到解析完所有二进制位。下面详细解释我的C++实现。 首先定义一个哈弗曼编码类,对外只提供压缩Compress和解压缩Decompress两个接口。值得注意的是有一个Node结构体,用于构成哈弗曼树的节点。此外count_node的key是字符频率,value是所在节点,且是multimap类型的,所以count_node会自动按字符频率有小到大排序,在构建哈弗曼树时,每次只需要取count_node的前两个节点进行合并即可。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class HuffmanCode { public: HuffmanCode(); void Compress(string src, string dest); void Decompress(string src, string dest); virtual ~HuffmanCode(); private: void CountLetter(string src); void ConstructHuffmanTree(); void GenerateHuffmanCode(); void WriteHuffmanCode(ofstream &os); void Compressing(string src, string dest); void InsertIntoHuffmanTree(char letter, string &code, int &k); void ConstructHuffmanTreeFromFile(ifstream &is); void Decompressing(ifstream &is, ofstream &os); map<char, int> letter_count; typedef struct Node { int id; bool is_leaf; char letter; int parent, lchild, rchild; Node() { } Node(int i, bool il, char lt, int p, int lc, int rc) : id(i), is_leaf(il), letter(lt), parent(p), lchild(lc), rchild(rc) { } }; multimap<int, Node> count_node; vector<Node> huffman_tree; map<char, vector<char>> letter_hcode; // hufman code for each letter }; 压缩函数Compress串起压缩的整个流程,包括统计字符频率、构建哈弗曼树、生成哈弗曼编码以及最后将原始文件转换成哈弗曼编码的二进制文件。 ...

August 18, 2016 · 3 min

还原谷歌PageRank算法真相

之前写了七篇博客详细介绍了搜索引擎的工作原理。彼时的搜索引擎主要讲查询和网页的相关性匹配,是动态的、在线的、实时的。相关性匹配有一个问题,网页很容易作弊,比如可以在一个网页中写满诸如“免费”、“美容”之类的垃圾关键词,进而提升查询相关性。但是用户在查询时,一定希望返回的网页比较权威可信,比如同样搜索“苹果电脑”,排名第一的应该是Apple的官网,而不应该是中关村在线之类的第三方网站。 权威性是一个静态的(或者说变化较慢的)衡量网页重要性的指标。但是应该怎样度量权威性呢,HITS算法使用authority来度量,即指向自身的网页数量越多,则自身的authority值越大。谷歌的PageRank算法是用PageRank值来衡量权威性的。HITS和PageRank一个比较大的区别是HITS和查询有关,而PageRank和查询无关,所以PageRank可以离线计算。下面主要介绍PageRank算法。 PageRank’s thesis is that a webpage is important if it is pointed to by other important pages. 我先不加解释的给出PageRank的公式,然后带领大家一步步推导出这个公式。 $$\pi^T=\pi^T(\alpha S+(1-\alpha)E)$$我们首先明确目标:PageRank计算的是网页的静态权威度(PR值),也就是如果给定了一个网络结构,则每个网页的PR值就可以通过PageRank算法计算出。假设网页\(P_i\)的PR值为\(r(P_i)\),则\(r(P_i)\)等于所有指向\(P_i\)的网页的PR值之和,即 $$\begin{equation}r(P_i)=\sum\limits_{P_j\in B_{P_i}}\frac{r(P_j)}{|P_j|}\end{equation}$$其中\(B_{P_i}\)为指向\(P_i\)的网页集合,\(|P_j|\)为\(P_j\)的出边的数量。这个式子很好理解,包括两方面内容:1)\(\sum\limits_{P_j\in B_{P_i}}\)表示如果指向\(P_i\)的网页数量越多,说明网页\(P_i\)越重要;2)\(\frac{r(P_j)}{|P_j|}\)表示如果\(P_j\)指向的页面数量越少,但有一个指向了\(P_i\),说明网页\(P_i\)越重要(如果一个大牛写了很多推荐信(\(|P_j|\)大),则这些推荐信的效力就下降了,如果大牛只给你写了推荐信(\(|P_j|=1\)),则这封推荐信的效力一定很高)。 (1)式有一个问题,初始给定一个网络结构时,并不知道\(r(P_i), r(P_j)\),如何计算呢?Brin和Page利用递归的思想求解,初始假设所有网页的PR值相等,都为\(\frac{1}{n}\),其中\(n\)为网络中网页的数量。则第\(k+1\)轮的PR计算公式为: $$\begin{equation}r_{k+1}(P_i)=\sum\limits_{P_j\in B_{P_i}}\frac{r_k(P_j)}{|P_j|}\end{equation}$$初始对所有网页\(P_i\)有\(r_0(P_i)=\frac{1}{n}\),迭代\(k\)步之后,可以计算出所有网页的PR值,然后按PR值从大到小排序,就可以知道每个网页的重要性了。 对于上图的小网络,我们可以计算出其每一步的PR值: 可以看到经过2次迭代之后,节点4的PR值最大,从图中也可以看出,节点4的出入边较多,它可能比较重要。 注意到对于(2)式,当\(i,j\)之间有边时,\(\frac{1}{|P_j|}\)相当于对\(P_j\)出度的归一化,设矩阵\(H\)为图的邻接矩阵的行归一化矩阵,对于上图,为 设行向量\(\pi^{(k)T}\)为第\(k\)轮迭代时所有网页的PR值,则式(2)可以转换为如下的矩阵形式: $$\begin{equation}\pi^{(k+1)T}=\pi^{(k)T}H\end{equation}$$初始有\(\pi^{(0)T}=\frac{1}{n}e^T\),\(e^T\)为全1的行向量。我们可以从(3)式观测出几点信息: (3)式的每一轮计算涉及到向量和矩阵的乘法,复杂度为\(O(n^2)\),\(n\)为矩阵\(H\)的大小 \(H\)是一个稀疏矩阵,因为大部分网页只和很少的网页有链接关系,所以上述向量和矩阵的乘法复杂度还可以降低 \(H\)有点像马尔科夫链中的随机转移矩阵,但又不完全是,因为如果有dangling nodes,则这一行就是全0,所以\(H\)被称为substochastic matrix 上图中的节点3就是一个dangling node,它只有入边,没有出边,也就是说,每一轮迭代,PR值只会流入3号节点,不会从3号节点流出,久而久之,3就像一个水槽(sink)一样,吸走了大部分的PR,导致PR值虚高。 所以问题随之而来,怎样保证(3)式一定能够收敛到一个平稳概率分布\(\pi^T\),\(\pi^T\)和\(\pi^{(0)T}\)有关吗,怎样解决dangling nodes问题,等等。此时需要引入一点马尔科夫链理论的知识。 在马尔科夫理论呢中,如果一个矩阵\(P\)是随机的(stochastic)、不可约的(irreducible)和非周期的(aperiodic),则对于任意的起始向量,都能收敛到一个唯一的平稳正向量。所以如果PageRank矩阵\(H\)满足上述三个条件,则可以用幂法(Power Method)找到一个平稳概率分布\(\pi^T\)。幂法是用来计算最大特征值的特征向量。因为\(H\)的最大特征值为1,所以可以用幂法找到稳态时(\(\pi^T=\pi^TH\))的概率分布\(\pi^T\)。 下面我们就将矩阵\(H\)调整为随机的(stochastic)、不可约的(irreducible)和非周期的(aperiodic)。 行随机矩阵是指行和为1的非负矩阵。如果图中含有dangling nodes,则\(H\)不是随机的,比如上面的例子,第二行为全0。所以第一个调整是对于所有dangling nodes,都加上一个随机跳转向量\(e^T/n\),含义就是如果进入死胡同(dangling nodes),则随机跳转到网络中的任意一个网页。定义向量\(a\): $$\begin{equation}a_i=\begin{cases}1\quad\text{if page}~i\text{ is a dangling node}\\0\quad\text{otherwise}\end{cases}\end{equation}$$则新的Google矩阵为: $$\begin{equation}S=H+a\frac{1}{n}e^T\end{equation}$$新矩阵\(S\)就是一个行随机矩阵了。对于上图的例子,有 为了保证矩阵\(S\)满足不可约性(irreducible)和非周期性(aperiodic),必须使\(S\)对应的图是强连通的且每个节点有自回路。所以再次调整为: $$\begin{equation}G=\alpha S+(1-\alpha)\frac{1}{n}ee^T\end{equation}$$令 $$\begin{equation}E=\frac{1}{n}ee^T\end{equation}$$则得到本博客开头的Google矩阵公式: $$\begin{equation}G=\alpha S+(1-\alpha)E\end{equation}$$\(E\)即为随机平均游走矩阵。矩阵\(G\)也很好解释,大家上网的时候以\(\alpha\)的概率沿着某个网页里面的链接一步步深入进去(\(S\)),当沿着链接走累的时候,以\(1-\alpha\)的概率在地址栏输入一个新地址,随机跳走了(\(E\))。 此时的矩阵\(G\)满足随机性(stochastic)、不可约性(irreducible)和非周期性(aperiodic),所以可以根据幂法(Power Method)找到一个平稳概率分布\(\pi^T\),\(\pi^T_i\)就衡量了网页\(P_i\)的重要性或者权威性。 ...

August 4, 2016 · 1 min

调查问卷的有效性(2)相对误差

$$\begin{equation}Pr(|\hat{p}-p|\geq 5\%)\leq 5\%\end{equation}$$上一回我们讲到当\(p\)本身很小的时候,容易被5%(绝对误差)给淹没掉,导致结果的不可信。我们可以引入相对误差,把(1)式转换为如下的不等式 $$\begin{equation}Pr(|\hat{p}-p|\geq\delta p)\leq\epsilon\end{equation}$$同理,我们可以用 $$\begin{equation}\hat{p}=\frac{x_1+x_2+…+x_n}{n}\end{equation}$$代替\(\hat{p}\)(建议先看上一篇博客),转换为 $$\begin{equation}Pr(|X-np|\geq\delta np)\end{equation}$$类似的,\(X=x_1+x_2+…+x_n\),\(E(X)=\mu=np\),所以(4)式等价为 $$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\end{equation}$$这个时候,因为不等号右边和均值\(\mu\)有关,不能再用切比雪夫不等式了,我们需要另外一个武器:Chernoff bound。它有两种形式: $$\begin{equation}Pr(X\geq (1+\delta)\mu)\leq[\frac{e^\delta}{(1+\delta)^{1+\delta}}]^\mu\leq e^{-\frac{\mu}{3}\delta^2}\quad\forall\delta>0\end{equation}$$$$\begin{equation}Pr(X\leq (1-\delta)\mu)\leq[\frac{e^{-\delta}}{(1-\delta)^{1-\delta}}]^\mu\leq e^{-\frac{\mu}{2}\delta^2}\quad\forall 0<\delta<1\end{equation}$$Chernoff bound的证明需要用到马尔可夫不等式,有一点技巧。以上两种形式可以统一成 $$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\leq 2e^{-\frac{\mu}{3}\delta^2}\end{equation}$$也是一个很漂亮的不等式。 利用Chernoff bound求解(5)式: $$\begin{equation}Pr(|X-\mu|\geq\delta\mu)\leq 2e^{-\frac{\mu}{3}\delta^2}\\=2e^{-\frac{np}{3}\delta^2}\leq\epsilon\end{equation}$$解得 $$\begin{equation}n\geq\left\lceil\frac{3ln\frac{2}{\epsilon}}{p\delta^2}\right\rceil\end{equation}$$这个结果看起来就很复杂了。也就是说,如果要设计调查问卷使满足(2)式的精度,抽样的样本数必须满足(10)式。从(10)式可知,当要求的精度越高(即\(\delta\)和\(\epsilon\)越小),所需的样本数越大。并且结果还和真实值\(p\)有关。

July 23, 2016 · 1 min