Skip to content

Commit c3bb392

Browse files
L-LYRNickLee2050colinaaa
authored
Ln9 分治算法之最近点对 & Ln11 分治算法之大数乘法 (#9)
* Added tex file to be written * Modified .gitignore * Pseudocode for the first algorithm finished. * First finished version. * First two subsections * temp * Group 5 finished * Update Makefile fix Makefile indent * Update book.tex merge algorithm2e package options Co-authored-by: NickLee <lishuhan_740@163.com> Co-authored-by: Colin Wang <colinwang.0616@gmail.com>
1 parent 72a5790 commit c3bb392

9 files changed

Lines changed: 328 additions & 2 deletions

Ln9.image/NearestPointsCpi.png

19.3 KB
Loading

Ln9.image/NearestPointsDef.png

23.1 KB
Loading

Ln9.image/NearestPointsDivide.png

24.4 KB
Loading

Ln9.image/NearestPointsMerge.png

23.5 KB
Loading

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ TEX=\
44
$(SRC)/example.tex\
55
$(SRC)/dynamic-programming-1.tex\
66
$(SRC)/Network-flows.tex\
7-
7+
$(SRC)/Ln9-NearestPoints.tex\
8+
$(SRC)/Ln11-LargeIntegerMultiplication.tex\
9+
810
all: book.pdf
911
.PHONY: all clean dev clean-all
1012

book.pdf

1.37 MB
Binary file not shown.

book.tex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
\usepackage{tikz}
44
\usepackage{graphicx}
55
\usepackage{subcaption}
6-
\usepackage[ruled,linesnumbered]{algorithm2e}
6+
\usepackage[ruled,linesnumbered,vlined]{algorithm2e}
77
\usepackage{hyperref}
88
\usepackage{listings}
99

@@ -31,6 +31,8 @@
3131
\input{src/example.tex}
3232
\input{src/dynamic-programming-1.tex}
3333
\input{src/Network-flows.tex}
34+
\input{src/Ln9-NearestPoints.tex}
35+
\input{src/Ln11-LargeIntegerMultiplication.tex}
3436

3537
\bibliography{ref.bib}
3638
\end{document}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
\chapter{分治法之大数乘法}
2+
\begin{introduction}
3+
\item 问题背景
4+
\item 直接分治法
5+
\item 改进分治法
6+
\end{introduction}
7+
8+
\section{问题描述}
9+
给定两个大数$A$$B$, 试计算
10+
\begin{math}
11+
A \times B
12+
\end{math}.
13+
其中$A$$B$分别表示为
14+
\begin{math}
15+
A = a_n a_{n-1} a_{n-2} \ldots a_2 a_1
16+
\end{math}
17+
,
18+
\begin{math}
19+
B = b_n b_{n-1} b_{n-2} \ldots b_2 b_1
20+
\end{math}.
21+
根据已学知识,给出如下引理。
22+
23+
\begin{lemma}{}{label_for_a+b}
24+
直接计算$A + B$,其复杂度为$O(n)$, 其中$n$$A$$B$的十进制位数。
25+
\end{lemma}
26+
27+
直接计算$A \times B$时,我们将$A$$B$的各位相乘,在将各中间结果相加,得到最终结果。
28+
不难得出,这一过程需要进行$n$次基本乘法与$n+1$次加法。
29+
根据引理\ref{lem:label_for_a+b},有:
30+
\begin{theorem}{}{label_for_a*b}
31+
直接计算$A \times B$的时间复杂度为$O(n^2)$.
32+
\end{theorem}
33+
34+
由定理\ref{thm:label_for_a*b}和引理\ref{lem:label_for_a+b}可知,如果我们直接相乘两个大数,其时间复杂度相比加法运算高出一个量级。
35+
由于乘法在计算机中大量存在,我们希望找到更好的算法来降低乘法计算的时间复杂度,以提升计算机的性能。
36+
分治法为我们提供了一条途径。
37+
\section{直接分治法}
38+
\subsection{算法描述}
39+
这是一种简单的分治方法,将两个大数分为前后两部分,进行相乘。不失一般性,这里假设$n$为偶数。
40+
$A$$B$分割为$A_2$, $A_1$, $B_2$, $B_1$,即:
41+
\begin{displaymath}
42+
\begin{split}
43+
A_2 &= a_{n} a_{n-1} \ldots a_{\frac{n}{2} + 2} a_{\frac{n}{2} + 1}\\
44+
A_1 &= a_{\frac{n}{2}} a_{\frac{n}{2} - 1} \ldots a_2 a_1\\
45+
B_2 &= b_{n} b_{n-1} \ldots b_{\frac{n}{2} + 2} b_{\frac{n}{2} + 1}\\
46+
B_1 &= b_{\frac{n}{2}} b_{\frac{n}{2} - 1} \ldots b_2 b_1
47+
\end{split}
48+
\end{displaymath}
49+
50+
$A$可以写为$A = A_2 \times 2^{\frac{n}{2}} + A_1$.
51+
$B$可以写为$B = B_2 \times 2^{\frac{n}{2}} + B_1$.
52+
计算$A \times B$的问题在进行上述转换后表示为:
53+
\begin{displaymath}
54+
\begin{split}
55+
A \times B
56+
& = (A_2 \times 2^{\frac{n}{2}} + A_1) \times (B_2 \times 2^{\frac{n}{2}} + B_1) \\
57+
& = A_2 B_2 \times 2^n + (A_2 B_1 + A_1 B_2) \times 2^{\frac{n}{2}} + A_1 B_1
58+
\end{split}
59+
\end{displaymath}
60+
61+
此时将两个大数相乘的问题转化为4个乘法子问题和3个加法子问题。显然,分治策略还可以对子问题使用,继续减小问题的规模。
62+
63+
\subsection{伪代码}
64+
\begin{algorithm}
65+
\DontPrintSemicolon{}
66+
\KwIn{Two large numbers $A$, $B$, which both have $n$ decimal digits}
67+
\KwResult{$A \times B$}
68+
\Begin{
69+
$n \leftarrow $ Number of Decimal Digits of $A$ and $B$\;
70+
\If{$n \neq 1$}{
71+
Divide $A$, $B$ into $A_2$, $A_1$, $B_2$ and $B_1$\;
72+
$C_3 \leftarrow DirectDAC(A_2, B_2)$\;
73+
$C_2 \leftarrow DirectDAC(A_2, B_1)$\;
74+
$C_1 \leftarrow DirectDAC(A_1, B_2)$\;
75+
$C_0 \leftarrow DirectDAC(A_1, B_1)$\;
76+
\KwRet{$C_3 \ll n + (C_2 + C_1) \ll (n - 1) + C_0$}\;
77+
}
78+
\Else{
79+
\KwRet{$A \times B$}
80+
}
81+
}
82+
\caption{DirectDAC\label{label_for_pseudo_DirectDAC}}
83+
\end{algorithm}
84+
85+
\subsection{复杂度分析}
86+
由上述的算法描述可知,算法的主要开销来自于每次分支带来的4个乘法子问题和3个加法子问题,由于移位可在机器中由一个简单的指令完成,我们忽略这个操作的时间。\\
87+
假设$T(n)$表示两个$n$位大数相乘所需的时间开销,则在直接分治法中:
88+
\begin{displaymath}
89+
\begin{split}
90+
T(n)
91+
&= 4T(\frac{n}{2}) + 3n \\
92+
&= 4T(\frac{n}{2}) + O(n)
93+
\end{split}
94+
\end{displaymath}
95+
96+
根据主方法,$\log_2 4 = 2> 1$, 推出如下定理:
97+
\begin{theorem}{}{label_for_DirectDAC_complexity}
98+
用直接分治法计算$A \times B$的时间复杂度为$O(n^2)$.
99+
\end{theorem}
100+
101+
根据定理\ref{thm:label_for_DirectDAC_complexity},直接分治法的性能是令人失望的,因为其并不能提供时间上优于直接相乘的性能。
102+
但分治策略提示我们,这个算法的性能与乘法子问题的数目强相关。我们如果能够用一些其他的开销换取更少的乘法子问题数目,也许能得到更好的算法。
103+
104+
105+
\newpage
106+
\section{改进分治法}
107+
\subsection{改进思路}
108+
在直接分治法中,通过对大数进行分割,我们有:
109+
\begin{displaymath}
110+
A \times B = A_2 B_2 \times 2^n + (A_2 B_1 + A_1 B_2) \times 2^{\frac{n}{2}} + A_1 B_1
111+
\end{displaymath}
112+
113+
这个过程中,引入了4次乘法运算;在上一节中提到,分治策略和主定理提示我们尽可能减少乘法的次数。
114+
但换取更低的乘法子问题数,需要其他的开销。
115+
一种想法是,由于加法的复杂度为$O(n)$,我们也许可以用略多的加法子问题,来减少乘法子问题数。
116+
基于此想法,我们对直接分治法作出一些改进。首先将直接分治法中的计算式修改为:
117+
\begin{displaymath}
118+
\begin{split}
119+
A \times B
120+
&= A_2 B_2 \times 2^n + (A_2 B_1 + A_1 B_2) \times 2^{\frac{n}{2}} + A_1 B_1\\
121+
&= A_2 B_2 \times 2^n + ((A_2 + A_1)\times(B_2 + B_1) - A_2 B_2 - (A_1 B_1)) \times 2^{\frac{n}{2}} + A_1 B_1
122+
\end{split}
123+
\end{displaymath}
124+
125+
观察上式,我们只需要做3次乘法,即计算$A_2 B_2$, $A_1 B_1$, $(A_2 + A_1)\times(B_2 + B_1)$, 以及4次加法,2次减法。
126+
考虑到加法和减法本质上等同,我们成功地将这一问题转化为了3个乘法子问题和6个加法子问题。相比于直接分治法,我们降低了乘法的数量。
127+
128+
下面给出该算法的伪代码及复杂度分析。
129+
\subsection{伪代码}
130+
\begin{algorithm}
131+
\DontPrintSemicolon{}
132+
\KwIn{Two large numbers $A$, $B$, which both have $n$ decimal digits}
133+
\KwResult{$A \times B$}
134+
\Begin{
135+
$n \leftarrow $ Number of Decimal Digits of $A$ and $B$\;
136+
\If{$n \neq 1$}{
137+
Divide $A$, $B$ into $A_2$, $A_1$, $B_2$ and $B_1$\;
138+
$C_2 \leftarrow DirectDAC(A_2, B_2)$\;
139+
$C_1 \leftarrow DirectDAC(A_1, B_1)$\;
140+
$C_0 \leftarrow DirectDAC(A_2 + A_1, B_2 + B_1)$\;
141+
\KwRet{$C_2 \ll n + (C_0 - C_2 - C_1) \ll (n - 1) + C_1$}\;
142+
}
143+
\Else{
144+
\KwRet{$A \times B$}
145+
}
146+
}
147+
\caption{ModifiedDAC\label{label_for_pseudo_ModifiedDAC}}
148+
\end{algorithm}
149+
150+
\subsection{复杂度分析}
151+
同上节的复杂度分析,我们此处也忽略移位操作带来的开销。改进分治法中,我们将问题分解为3个乘法子问题与6个加法子问题。
152+
因此有:
153+
\begin{displaymath}
154+
\begin{split}
155+
T(n)
156+
&= 3T(\frac{n}{2}) + 6n\\
157+
&= 3T(\frac{n}{2}) + O(n)
158+
\end{split}
159+
\end{displaymath}
160+
161+
根据主方法,$\log_2 3 > 1$. 推出如下定理:
162+
\begin{theorem}{}{label_for_ModifiedDAC_complexity}
163+
用改进分治法计算$A \times B$的时间复杂度为$O(n^{\log_2 3}) \approx O(n^{1.585})$.
164+
\end{theorem}

src/Ln9-NearestPoints.tex

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
\chapter{分治算法之平面最近点对问题}
2+
3+
\begin{introduction}
4+
\item 平面最近点对问题定义
5+
\item 分治算法设计
6+
\item 分治算法时间复杂度分析
7+
\item 伪代码
8+
\end{introduction}
9+
10+
\section{平面最近点对问题定义}
11+
给定二维平面上的$n(n \ge 2)$个不同的点$p$组成点集$P = \{p_i \big| 1\le i \le n\}$
12+
设计算法寻找欧式距离最近的点对$(A,B)$
13+
\begin{figure}[htb]
14+
\centering
15+
\includegraphics[scale=0.5]{Ln9.image/NearestPointsDef.png}
16+
\caption{问题定义图例}\label{fig1}
17+
\end{figure}
18+
19+
如上图\autoref{fig1}中点对$(A,B)$即为问题的答案。
20+
21+
\section{分治算法设计}
22+
对于这样一个问题,我们很直接地可以使用BF (Brute Force)算法进行暴力求解,
23+
即二重循环计算所有点之间的距离,从而获得最小距离,显然该算法的时间复杂度为
24+
$O(n^2)$。那么有没有更快的算法呢?本章我们使用经典的算法思想——分治,
25+
设计一个$O(n\log n)$的算法。
26+
27+
\subsection{分治问题}
28+
遵循分治思想,我们首先要考虑如何分治问题使得问题规模约减。
29+
30+
我们使用X坐标作为第一关键字、Y坐标作为第二关键字,对点集$P$进行排序,
31+
并以点$p_{\lfloor\frac{n}{2}\rfloor}$作为分治点,获得如下两个点集:
32+
\begin{equation*}
33+
P_1 = \{p_i\ \big|\ 1 \le i \le \lfloor\frac{n}{2}\rfloor \}
34+
\end{equation*}
35+
\begin{equation*}
36+
P_2 = \{p_i\ \big|\ \lfloor\frac{n}{2}\rfloor < i \le n\}
37+
\end{equation*}
38+
这样就将当前问题约减为两个规模为$\frac{n}{2}$的子问题
39+
分治过程如\autoref{fig2}中所示。
40+
41+
\begin{figure}[htb]
42+
\centering
43+
\includegraphics[scale=0.5]{Ln9.image/NearestPointsDivide.png}
44+
\caption{分治过程图例}\label{fig2}
45+
\end{figure}
46+
47+
如此递归下去,我们可以求得两个点集相对应的最近点对距离$\delta_1, \delta_2$,取其中较小值
48+
记为$\delta = \min \{ \delta_1 , \delta_2 \}$
49+
50+
当分治到点集大小为2个或3个时,可以在常数时间内计算出子问题的解。
51+
52+
\subsection{合并结果}
53+
54+
接着,我们需要考虑如何合并子问题的解。
55+
56+
上述的$\delta$一定是正确的合并结果嘛?显然不是,我们并没有考虑,一端在$P_1$
57+
一端在$P_2$的线段。因此,在合并阶段,我们要将这种情况考虑在内。
58+
59+
这里,我们将所有横坐标与分治点$p_{\lfloor\frac{n}{2}\rfloor}$的横坐标
60+
$x_{\lfloor\frac{n}{2}\rfloor}$差值小于$\delta$的点组成集合$B$,即
61+
\begin{equation*}
62+
B = \{p_i\ \big|\
63+
\left|x_i - x_{\lfloor\frac{n}{2}\rfloor}\right| \le \delta ,\
64+
1 \le i \le n\}
65+
\end{equation*}
66+
因为只有$B$集合中的点之间的距离才有可能小于$\delta$
67+
$B$集合如下图\autoref{fig3}中阴影部分所示:
68+
\begin{figure}[htb]
69+
\centering
70+
\includegraphics[scale=0.5]{Ln9.image/NearestPointsMerge.png}
71+
\caption{合并过程图例}\label{fig3}
72+
\end{figure}
73+
74+
进一步,我们的目标是检验在$B$集合中是否存在距离比$\delta$更近的点对,以此更新当前问题的解
75+
。因此,对于每个$p_i = (x_i, y_i) \in B$遍历所有在其之下竖直距离不超过$\delta$的点,
76+
即遍历集合
77+
\begin{equation*}
78+
C(p_i) = \{ p_j\ \big|\ y_i - \delta \le y_j \le y_i, p_j \in B \}
79+
\end{equation*}
80+
为了方便遍历,我们可能会想到对$B$集合中的点,以Y坐标为第一关键字,X坐标为第二关键字,进行排序。
81+
但是如此一来,每一次合并的时间复杂度为$O(n \log n)$,徒增时间消耗,因此我们采取合并策略,即
82+
按照Y坐标为关键字,进行$P_1, P_2$的归并来直接获得排序后的集合$B$,这样只需要$O(n)$的时间。
83+
84+
考虑到$C(p_i)$会因为归并操作而维持在$O(n)$数量级,其实不然,该集合的大小不会超过7。下面给出
85+
证明。
86+
87+
根据定义,$C(p_i)$中的点的纵坐标均处于$(y_i - \delta, y_i]$范围内,且其中的所有点
88+
的横坐标均处于$\left( x_m - \delta, x_m + \delta \right)$范围内。
89+
这样便构成了一个$2\delta\times\delta$的矩形。如下图\autoref{fig4}所示
90+
\begin{figure}[htb]
91+
\centering
92+
\includegraphics[scale=0.5]{Ln9.image/NearestPointsCpi.png}
93+
\caption{$C(p_i)$}\label{fig4}
94+
\end{figure}。
95+
96+
接着,我们将这个矩形分拆成左右两个$\delta \times \delta$的正方形,左侧正方形的点集为
97+
$C(p_i)\cap P_1$,右侧正方形的点集为$C(p_i)\cap P_2$,从上述的分治过程可知,这两个点集
98+
内的点之间的距离一定不小于$\delta$
99+
100+
进一步,我们将$\delta \times \delta$正方形,分拆成四个$\frac{\delta}{2}\times\frac{\delta}{2}$
101+
小正方形,因为这个小正方形的对角线为$\frac{\delta}{\sqrt{2}} < \delta$,所以小正方形中最多
102+
只有一个点,而总共有8个小正方形,最多有8个点,除去$p_i$,则最多只有7个点。
103+
104+
至此,我们完成了父问题的分治与子问题的合并。
105+
106+
\section{分治算法的时间复杂度分析}
107+
首先,第一次排序可以使用时间复杂度为$O(n\log n)$的排序算法,如快速排序或者归并排序。
108+
109+
接着,我们考虑分治过程,即通过分治,我们将规模为$n$的父问题,分为两个规模为$\frac{n}{2}$的子问题。
110+
111+
最后,归并过程中,根据采用的合并策略以及上述对更新操作的证明,我们需要$O(n)$级别的时间完成。
112+
113+
综上,给出递推式如下:
114+
115+
\[
116+
T(n) = \begin{cases}
117+
O(1) & 2 \le n \le 3 \\
118+
2T(\frac{n}{2}) + O(n) & n > 3
119+
\end{cases}
120+
\]
121+
122+
推导如下:
123+
\begin{align*}
124+
T(n) &= 2T(\frac{n}{2}) + O(n)\\
125+
&= 2^2T(\frac{n}{2^2}) + 2O(\frac{n}{2}) + O(n)\\
126+
&= 2^2T(\frac{n}{2^2}) + 2O(n)\\
127+
&\vdots \\
128+
&= 2^k T(\frac{n}{2^k}) + kO(n)\ \ (n = 2 ^ k)\\
129+
&= O(n) + O(n\log n) \\
130+
&= O(n\log n)
131+
\end{align*}
132+
133+
\section{伪代码}
134+
\begin{algorithm}
135+
\DontPrintSemicolon{}
136+
\KwData{
137+
Point List $P = \{p_i\ \big|\ 1 \le i\le n, p_i = (x_i, y_i)\}$\;
138+
$P$ should be sorted by x-coordinate in descending order.
139+
}
140+
\KwResult{the minimum distance $\delta$}
141+
\Begin{
142+
\If{$\left| P \right| <= 3$}{
143+
Return the minimum Euclidean-Distance between each pair of points.
144+
}
145+
$m \leftarrow \lfloor \frac{n}{2} \rfloor$\;
146+
$\delta_1 \leftarrow \text{Nearest-Pair}(P[1,\ \ldots,\ m])$\;
147+
$\delta_2 \leftarrow \text{Nearest-Pair}(P[m + 1,\ \ldots ,\ n])$\;
148+
$\delta \leftarrow \min \{ \delta_1,\ \delta_2 \}$\;
149+
$B \leftarrow \text{MergeByY}(P_1,\ P_2)$\;
150+
\ForEach{$p_i \in B$}{
151+
\ForEach{$p_j \in C(p_i)$} {
152+
$\delta \leftarrow \min \{\delta,\ \text{Euclidean-Distance}(p_i, p_j)\}$
153+
}
154+
}
155+
Return $\delta$
156+
}
157+
\caption{Nearest-Pair\label{NPP}}
158+
\end{algorithm}

0 commit comments

Comments
 (0)