First commit
This commit is contained in:
		
						commit
						c210d9497b
					
				| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
\chapter*{Preface}
 | 
			
		||||
\markboth{\MakeUppercase{Preface}}{}
 | 
			
		||||
\addcontentsline{toc}{chapter}{Preface}
 | 
			
		||||
 | 
			
		||||
The purpose of this book is to give you
 | 
			
		||||
a thorough introduction to competitive programming.
 | 
			
		||||
The book assumes that you already
 | 
			
		||||
know the basics of programming, but previous
 | 
			
		||||
background on competitive programming is not needed.
 | 
			
		||||
 | 
			
		||||
The book is especially intended for
 | 
			
		||||
high school students who want to learn
 | 
			
		||||
algorithms and possibly participate in
 | 
			
		||||
the International Olympiad in Informatics (IOI).
 | 
			
		||||
The book is also suitable for university students
 | 
			
		||||
and anybody else interested in competitive programming.
 | 
			
		||||
 | 
			
		||||
It takes a long time to become a good competitive
 | 
			
		||||
programmer, but it is also an opportunity to learn a lot.
 | 
			
		||||
You can be sure that you will learn a great deal
 | 
			
		||||
about algorithms if you spend time reading the book
 | 
			
		||||
and solving exercises.
 | 
			
		||||
 | 
			
		||||
The book is under continuous development.
 | 
			
		||||
You can always send feedback about the book to
 | 
			
		||||
\texttt{ahslaaks@cs.helsinki.fi}.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,113 @@
 | 
			
		|||
\documentclass[twoside,12pt,a4paper,english]{book}
 | 
			
		||||
 | 
			
		||||
\includeonly{luku01}
 | 
			
		||||
 | 
			
		||||
\usepackage[english]{babel}
 | 
			
		||||
\usepackage[utf8]{inputenc}
 | 
			
		||||
\usepackage{listings}
 | 
			
		||||
\usepackage[table]{xcolor}
 | 
			
		||||
\usepackage{tikz}
 | 
			
		||||
\usepackage{multicol}
 | 
			
		||||
\usepackage{hyperref}
 | 
			
		||||
\usepackage{array}
 | 
			
		||||
\usepackage{microtype}
 | 
			
		||||
 | 
			
		||||
\usepackage{fouriernc}
 | 
			
		||||
\usepackage[T1]{fontenc}
 | 
			
		||||
 | 
			
		||||
\usepackage{graphicx}
 | 
			
		||||
\usepackage{framed}
 | 
			
		||||
\usepackage{amssymb}
 | 
			
		||||
\usepackage{amsmath}
 | 
			
		||||
 | 
			
		||||
\usepackage{pifont}
 | 
			
		||||
\usepackage{ifthen}
 | 
			
		||||
\usepackage{makeidx}
 | 
			
		||||
\usepackage{enumitem}
 | 
			
		||||
 | 
			
		||||
\usepackage{titlesec}
 | 
			
		||||
 | 
			
		||||
\usetikzlibrary{patterns,snakes}
 | 
			
		||||
\pagestyle{plain}
 | 
			
		||||
 | 
			
		||||
\lstset{language=C++,frame=single,basicstyle=\ttfamily \small,showstringspaces=false,columns=flexible}
 | 
			
		||||
\lstset{
 | 
			
		||||
  literate={ö}{{\"o}}1
 | 
			
		||||
           {ä}{{\"a}}1
 | 
			
		||||
           {ü}{{\"u}}1
 | 
			
		||||
}
 | 
			
		||||
\lstset{xleftmargin=20pt,xrightmargin=5pt}
 | 
			
		||||
\lstset{aboveskip=12pt,belowskip=8pt}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\date{\Large \today}
 | 
			
		||||
 | 
			
		||||
\usepackage[a4paper,vmargin=30mm,hmargin=33mm,footskip=15mm]{geometry}
 | 
			
		||||
 | 
			
		||||
\title{\Huge Handbook of Competitive Programming}
 | 
			
		||||
\author{\Large Antti Laaksonen}
 | 
			
		||||
 | 
			
		||||
\makeindex
 | 
			
		||||
 | 
			
		||||
\titleformat{\subsubsection}
 | 
			
		||||
{\normalfont\large\bfseries\sffamily}{\thesubsection}{1em}{}
 | 
			
		||||
 | 
			
		||||
\begin{document}
 | 
			
		||||
 | 
			
		||||
%\selectlanguage{finnish}
 | 
			
		||||
 | 
			
		||||
%\setcounter{page}{1}
 | 
			
		||||
%\pagenumbering{roman}
 | 
			
		||||
 | 
			
		||||
\frontmatter
 | 
			
		||||
\maketitle
 | 
			
		||||
\setcounter{tocdepth}{1}
 | 
			
		||||
\tableofcontents
 | 
			
		||||
 | 
			
		||||
\include{johdanto}
 | 
			
		||||
 | 
			
		||||
\mainmatter
 | 
			
		||||
\pagenumbering{arabic}
 | 
			
		||||
\setcounter{page}{1}
 | 
			
		||||
 | 
			
		||||
\newcommand{\key}[1] {\textbf{#1}}
 | 
			
		||||
 | 
			
		||||
\part{Perusasiat}
 | 
			
		||||
\include{luku01}
 | 
			
		||||
\include{luku02}
 | 
			
		||||
\include{luku03}
 | 
			
		||||
\include{luku04}
 | 
			
		||||
\include{luku05}
 | 
			
		||||
\include{luku06}
 | 
			
		||||
\include{luku07}
 | 
			
		||||
\include{luku08}
 | 
			
		||||
\include{luku09}
 | 
			
		||||
\include{luku10}
 | 
			
		||||
\part{Graph algorithms}
 | 
			
		||||
\include{luku11}
 | 
			
		||||
\include{luku12}
 | 
			
		||||
\include{luku13}
 | 
			
		||||
\include{luku14}
 | 
			
		||||
\include{luku15}
 | 
			
		||||
\include{luku16}
 | 
			
		||||
\include{luku17}
 | 
			
		||||
\include{luku18}
 | 
			
		||||
\include{luku19}
 | 
			
		||||
\include{luku20}
 | 
			
		||||
\part{Advanced topics}
 | 
			
		||||
\include{luku21}
 | 
			
		||||
\include{luku22}
 | 
			
		||||
\include{luku23}
 | 
			
		||||
\include{luku24}
 | 
			
		||||
\include{luku25}
 | 
			
		||||
\include{luku26}
 | 
			
		||||
\include{luku27}
 | 
			
		||||
\include{luku28}
 | 
			
		||||
\include{luku29}
 | 
			
		||||
\include{luku30}
 | 
			
		||||
\include{kirj}
 | 
			
		||||
 | 
			
		||||
\cleardoublepage
 | 
			
		||||
\printindex
 | 
			
		||||
 | 
			
		||||
\end{document}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,835 @@
 | 
			
		|||
\chapter{Introduction}
 | 
			
		||||
 | 
			
		||||
Competitive programming combines two topics:
 | 
			
		||||
(1) design of algorithms and (2) implementation of algorithms.
 | 
			
		||||
 | 
			
		||||
The \key{design of algorithms} consists of problem solving
 | 
			
		||||
and mathematical thinking.
 | 
			
		||||
Skills for analyzing problems and solving them
 | 
			
		||||
using creativity is needed.
 | 
			
		||||
An algorithm for solving a problem
 | 
			
		||||
has to be both correct and efficient,
 | 
			
		||||
and the core of the problem is often
 | 
			
		||||
how to invent an efficient algorithm.
 | 
			
		||||
 | 
			
		||||
Theoretical knowledge of algorithms
 | 
			
		||||
is very important to competitive programmers.
 | 
			
		||||
Typically, a solution for a problem is
 | 
			
		||||
a combination of well-known techniques and
 | 
			
		||||
new insights.
 | 
			
		||||
The techniques that appear in competitive programming
 | 
			
		||||
also form the basis for the scientific research
 | 
			
		||||
of algorithms.
 | 
			
		||||
 | 
			
		||||
The \key{implementation of algorithms} requires good
 | 
			
		||||
programming skills.
 | 
			
		||||
In competitive programming, the solutions
 | 
			
		||||
are graded by testing an implemented algorithm
 | 
			
		||||
using a set of test cases.
 | 
			
		||||
Thus, it is not enough that the idea of the
 | 
			
		||||
algorithm is correct, but the implementation has
 | 
			
		||||
to be correct as well.
 | 
			
		||||
 | 
			
		||||
Good coding style in contests is
 | 
			
		||||
straightforward and concise.
 | 
			
		||||
The solutions should be written quickly,
 | 
			
		||||
because there is not much time available.
 | 
			
		||||
Unlike in traditional software engineering,
 | 
			
		||||
the solutions are short (usually at most some
 | 
			
		||||
hundreds of lines) and it is not needed to 
 | 
			
		||||
maintain them after the contest.
 | 
			
		||||
 | 
			
		||||
\section{Programming languages}
 | 
			
		||||
 | 
			
		||||
\index{programming language}
 | 
			
		||||
 | 
			
		||||
At the moment, the most popular programming
 | 
			
		||||
languages in contests are C++, Python and Java.
 | 
			
		||||
For example, in Google Code Jam 2016,
 | 
			
		||||
among the best 3,000 participants,
 | 
			
		||||
73 \% used C++,
 | 
			
		||||
15 \% used Python and
 | 
			
		||||
10 \% used Java\footnote{\url{https://www.go-hero.net/jam/16}}.
 | 
			
		||||
Some participants also used several languages.
 | 
			
		||||
 | 
			
		||||
Many people think that C++ is the best choice
 | 
			
		||||
for a competitive programmer,
 | 
			
		||||
and C++ is nearly always available in
 | 
			
		||||
contest systems.
 | 
			
		||||
The benefits in using C++ are that
 | 
			
		||||
it is a very efficient language and
 | 
			
		||||
its standard library contains a 
 | 
			
		||||
large collection
 | 
			
		||||
of data structures and algorithms.
 | 
			
		||||
 | 
			
		||||
On the other hand, it is good to
 | 
			
		||||
master several languages and know the
 | 
			
		||||
benefits of them.
 | 
			
		||||
For example, if big integers are needed
 | 
			
		||||
in the problem,
 | 
			
		||||
Python can be a good choice because it
 | 
			
		||||
contains a built-in library for handling
 | 
			
		||||
big integers.
 | 
			
		||||
Still, usually the goal is to write the problems so that
 | 
			
		||||
the use of a specific programming language
 | 
			
		||||
is not an unfair advantage in the contest.
 | 
			
		||||
 | 
			
		||||
All examples in this book are written in C++,
 | 
			
		||||
and the data structures and algorithms in
 | 
			
		||||
the standard library are often used.
 | 
			
		||||
The standard is C++11 that can be used in most
 | 
			
		||||
contests.
 | 
			
		||||
If you can't program in C++ yet,
 | 
			
		||||
now it is a good time to start learning.
 | 
			
		||||
 | 
			
		||||
\subsubsection{C++ template}
 | 
			
		||||
 | 
			
		||||
A typical C++ template for competitive programming
 | 
			
		||||
looks like this:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
#include <bits/stdc++.h>
 | 
			
		||||
 | 
			
		||||
using namespace std;
 | 
			
		||||
 | 
			
		||||
int main() {
 | 
			
		||||
    // koodi tulee tähän
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
The \texttt{\#include} line at the beginning
 | 
			
		||||
is a feature in the \texttt{g++} compiler
 | 
			
		||||
that allows to include the whole standard library.
 | 
			
		||||
Thus, it is not needed to separately include
 | 
			
		||||
libraries such as \texttt{iostream},
 | 
			
		||||
\texttt{vector} and \texttt{algorithm},
 | 
			
		||||
but they are available automatically.
 | 
			
		||||
 | 
			
		||||
The following \texttt{using} line determines
 | 
			
		||||
that the standard library can be used directly
 | 
			
		||||
in the code.
 | 
			
		||||
Without the \texttt{using} line we should write,
 | 
			
		||||
for example, \texttt{std::cout},
 | 
			
		||||
but now it is enough to write \texttt{cout}.
 | 
			
		||||
 | 
			
		||||
The code can be compiled using the following command:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
g++ -std=c++11 -O2 -Wall code.cpp -o code
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
This command produces a binary file \texttt{code}
 | 
			
		||||
from the source code \texttt{code.cpp}.
 | 
			
		||||
The compiler obeys the C++11 standard
 | 
			
		||||
(\texttt{-std=c++11}),
 | 
			
		||||
optimizes the code (\texttt{-O2})
 | 
			
		||||
and shows warnings about possible errors (\texttt{-Wall}).
 | 
			
		||||
 | 
			
		||||
\section{Input and output}
 | 
			
		||||
 | 
			
		||||
\index{input and output}
 | 
			
		||||
 | 
			
		||||
In most contests, standard streams are used for
 | 
			
		||||
reading input and writing output.
 | 
			
		||||
In C++, the standard streams are
 | 
			
		||||
\texttt{cin} for input and \texttt{cout} for output.
 | 
			
		||||
In addition, the C functions
 | 
			
		||||
\texttt{scanf} and \texttt{printf} can be used.
 | 
			
		||||
 | 
			
		||||
The input for the program usually consists of
 | 
			
		||||
numbers and strings that are separated with
 | 
			
		||||
spaces and newlines.
 | 
			
		||||
They can be read from the \texttt{cin} stream
 | 
			
		||||
as follows:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a, b;
 | 
			
		||||
string x;
 | 
			
		||||
cin >> a >> b >> x;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
This kind of code always works,
 | 
			
		||||
assuming that there is at least one space
 | 
			
		||||
or one newline between each element in the input.
 | 
			
		||||
For example, the above code accepts
 | 
			
		||||
both the following inputs:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
123 456 apina
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
123    456
 | 
			
		||||
apina
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
The \texttt{cout} stream is used for output
 | 
			
		||||
as follows:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a = 123, b = 456;
 | 
			
		||||
string x = "apina";
 | 
			
		||||
cout << a << " " << b << " " << x << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Handling input and output is sometimes
 | 
			
		||||
a bottleneck in the program.
 | 
			
		||||
The following lines at the beginning of the code
 | 
			
		||||
make input and output more efficient:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
ios_base::sync_with_stdio(0);
 | 
			
		||||
cin.tie(0);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Note that the newline \texttt{"\textbackslash n"}
 | 
			
		||||
works faster than \texttt{endl},
 | 
			
		||||
becauses \texttt{endl} always causes
 | 
			
		||||
a flush operation.
 | 
			
		||||
 | 
			
		||||
The C functions \texttt{scanf}
 | 
			
		||||
and \texttt{printf} are an alternative
 | 
			
		||||
to the C++ standard streams.
 | 
			
		||||
They are usually a bit faster,
 | 
			
		||||
but they are also more difficult to use.
 | 
			
		||||
The following code reads two integers from the input:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a, b;
 | 
			
		||||
scanf("%d %d", &a, &b);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
The following code prints two integers:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a = 123, b = 456;
 | 
			
		||||
printf("%d %d\n", a, b);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Sometimes the program should read a whole line
 | 
			
		||||
from the input, possibly with spaces.
 | 
			
		||||
This can be accomplished using the
 | 
			
		||||
\texttt{getline} function:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
string s;
 | 
			
		||||
getline(cin, s);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
If the amount of data is unknown, the following
 | 
			
		||||
loop can be handy:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
while (cin >> x) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
This loop reads elements from the input
 | 
			
		||||
one after another, until there is no
 | 
			
		||||
more data available in the input.
 | 
			
		||||
 | 
			
		||||
In some contest systems, files are used for
 | 
			
		||||
input and output.
 | 
			
		||||
An easy solution for this is to write
 | 
			
		||||
the code as usual using standard streams,
 | 
			
		||||
but add the following lines to the beginning of the code:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
freopen("input.txt", "r", stdin);
 | 
			
		||||
freopen("output.txt", "w", stdout);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
After this, the code reads the input from the file
 | 
			
		||||
''input.txt'' and writes the output to the file
 | 
			
		||||
''output.txt''.
 | 
			
		||||
 | 
			
		||||
\section{Handling numbers}
 | 
			
		||||
 | 
			
		||||
\index{integer}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Integers}
 | 
			
		||||
 | 
			
		||||
The most popular integer type in competitive programming
 | 
			
		||||
is \texttt{int}. This is a 32-bit type with
 | 
			
		||||
value range $-2^{31} \ldots 2^{31}-1$,
 | 
			
		||||
i.e., about $-2 \cdot 10^9 \ldots 2 \cdot 10^9$.
 | 
			
		||||
If the type \texttt{int} is not enough,
 | 
			
		||||
the 64-bit type \texttt{long long} can be used,
 | 
			
		||||
with value range $-2^{63} \ldots 2^{63}-1$,
 | 
			
		||||
i.e., about $-9 \cdot 10^{18} \ldots 9 \cdot 10^{18}$.
 | 
			
		||||
 | 
			
		||||
The following code defines a
 | 
			
		||||
\texttt{long long} variable:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
long long x = 123456789123456789LL;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
The suffix \texttt{LL} means that the
 | 
			
		||||
type of the number is \texttt{long long}.
 | 
			
		||||
 | 
			
		||||
A typical error when using the type \texttt{long long}
 | 
			
		||||
is that the type \texttt{int} is still used somewhere
 | 
			
		||||
in the code.
 | 
			
		||||
For example, the following code contains
 | 
			
		||||
a subtle error:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a = 123456789;
 | 
			
		||||
long long b = a*a;
 | 
			
		||||
cout << b << "\n"; // -1757895751
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Even though the variable \texttt{b} is of type \texttt{long long},
 | 
			
		||||
both numbers in the expression \texttt{a*a}
 | 
			
		||||
are of type \texttt{int} and the result is
 | 
			
		||||
also of type \texttt{int}.
 | 
			
		||||
Because of this, the variable \texttt{b} will
 | 
			
		||||
contain a wrong result.
 | 
			
		||||
The problem can be solved by changing the type
 | 
			
		||||
of \texttt{a} to \texttt{long long} or
 | 
			
		||||
by changing the expression to \texttt{(long long)a*a}.
 | 
			
		||||
 | 
			
		||||
Usually, the problems are written so that the
 | 
			
		||||
type \texttt{long long} is enough.
 | 
			
		||||
Still, it is good to know that
 | 
			
		||||
the \texttt{g++} compiler also features
 | 
			
		||||
an 128-bit type \texttt{\_\_int128\_t}
 | 
			
		||||
with value range
 | 
			
		||||
$-2^{127} \ldots 2^{127}-1$, i.e., $-10^{38} \ldots 10^{38}$.
 | 
			
		||||
However, this type is not available in all contest systems.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Modular arithmetic}
 | 
			
		||||
 | 
			
		||||
\index{remainder}
 | 
			
		||||
\index{modular arithmetic}
 | 
			
		||||
 | 
			
		||||
We denote by $x \bmod m$ the remainder
 | 
			
		||||
when $x$ is divided by $m$.
 | 
			
		||||
For example, $17 \bmod 5 = 2$,
 | 
			
		||||
because $17 = 3 \cdot 5 + 2$.
 | 
			
		||||
 | 
			
		||||
Sometimes, the answer for a problem is a
 | 
			
		||||
very big integer but it is enough to
 | 
			
		||||
print it ''modulo $m$'', i.e.,
 | 
			
		||||
the remainder when the answer is divided by $m$
 | 
			
		||||
(for example, ''modulo $10^9+7$'').
 | 
			
		||||
The idea is that even if the actual answer
 | 
			
		||||
may be very big,
 | 
			
		||||
it is enough to use the types
 | 
			
		||||
\texttt{int} and \texttt{long long}.
 | 
			
		||||
 | 
			
		||||
An important property of the remainder is that
 | 
			
		||||
in addition, subtraction and multiplication,
 | 
			
		||||
the remainder can be calculated before the operation:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{rcr}
 | 
			
		||||
(a+b) \bmod m & = & (a \bmod m + b \bmod m) \bmod m \\
 | 
			
		||||
(a-b) \bmod m & = & (a \bmod m - b \bmod m) \bmod m \\
 | 
			
		||||
(a \cdot b) \bmod m & = & (a \bmod m \cdot b \bmod m) \bmod m
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Thus, we can calculate the remainder after every operation
 | 
			
		||||
and the numbers will never become too large.
 | 
			
		||||
 | 
			
		||||
For example, the following code calculates $n!$,
 | 
			
		||||
the factorial of $n$, modulo $m$:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
long long x = 1;
 | 
			
		||||
for (int i = 2; i <= n i++) {
 | 
			
		||||
    x = (x*i)%m;
 | 
			
		||||
}
 | 
			
		||||
cout << x << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Usually, the answer should be always given so
 | 
			
		||||
that the remainder is between $0\ldots m-1$.
 | 
			
		||||
However, in C++ and other languages,
 | 
			
		||||
the remainder of a negative number can
 | 
			
		||||
be negative.
 | 
			
		||||
An easy way to make sure that this will
 | 
			
		||||
not happen is to first calculate
 | 
			
		||||
the remainder as usual and then add $m$
 | 
			
		||||
if the result is negative:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
x = x%m;
 | 
			
		||||
if (x < 0) x += m;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
However, this is only needed when there
 | 
			
		||||
are subtractions in the code and the
 | 
			
		||||
remainder may become negative.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Floating point numbers}
 | 
			
		||||
 | 
			
		||||
\index{floating point number}
 | 
			
		||||
 | 
			
		||||
The usual floating point types in
 | 
			
		||||
competitive programming are
 | 
			
		||||
the 64-bit \texttt{double}
 | 
			
		||||
and, as an extension in the \texttt{g++} compiler,
 | 
			
		||||
the 80-bit \texttt{long double}.
 | 
			
		||||
In most cases, \texttt{double} is enough,
 | 
			
		||||
but \texttt{long double} is more accurate.
 | 
			
		||||
 | 
			
		||||
The required precision of the answer
 | 
			
		||||
is usually given.
 | 
			
		||||
The easiest way is to use
 | 
			
		||||
the \texttt{printf} function
 | 
			
		||||
that can be given the number of decimal places.
 | 
			
		||||
For example, the following code prints
 | 
			
		||||
the value of $x$ with 9 decimal places:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
printf("%.9f\n", x);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
A difficulty when using floating point numbers
 | 
			
		||||
is that some numbers cannot be represented
 | 
			
		||||
accurately, but there will be rounding errors.
 | 
			
		||||
For example, the result of the following code
 | 
			
		||||
is surprising:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
double x = 0.3*3+0.1;
 | 
			
		||||
printf("%.20f\n", x); // 0.99999999999999988898
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Because of a rounding error,
 | 
			
		||||
the value of \texttt{x} is a bit less than 1,
 | 
			
		||||
while the correct value would be 1.
 | 
			
		||||
 | 
			
		||||
It is risky to compare floating point numbers
 | 
			
		||||
with the \texttt{==} operator,
 | 
			
		||||
because it is possible that the values should
 | 
			
		||||
be equal but they are not due to rounding errors.
 | 
			
		||||
A better way to compare floating point numbers
 | 
			
		||||
is to assume that two numbers are equal
 | 
			
		||||
if the difference between them is $\varepsilon$,
 | 
			
		||||
where $\varepsilon$ is a small number.
 | 
			
		||||
 | 
			
		||||
In practice, the numbers can be compared
 | 
			
		||||
as follows ($\varepsilon=10^{-9}$):
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
if (abs(a-b) < 1e-9) {
 | 
			
		||||
    // a ja b ovat yhtä suuret
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Note that while floating point numbers are inaccurate,
 | 
			
		||||
integers up to a certain limit can be still
 | 
			
		||||
represented accurately.
 | 
			
		||||
For example, using \texttt{double},
 | 
			
		||||
it is possible to accurately represent all
 | 
			
		||||
integers having absolute value at most $2^{53}$.
 | 
			
		||||
 | 
			
		||||
\section{Koodin lyhentäminen}
 | 
			
		||||
 | 
			
		||||
Kisakoodauksessa ihanteena on lyhyt koodi,
 | 
			
		||||
koska algoritmi täytyy pystyä toteuttamaan
 | 
			
		||||
mahdollisimman nopeasti.
 | 
			
		||||
Monet kisakoodarit käyttävätkin lyhennysmerkintöjä
 | 
			
		||||
tietotyypeille ja muille koodin osille.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tyyppinimet}
 | 
			
		||||
\index{tuppdef@\texttt{typedef}}
 | 
			
		||||
Komennolla \texttt{typedef} voi antaa lyhyemmän
 | 
			
		||||
nimen tietotyypille.
 | 
			
		||||
Esimerkiksi nimi \texttt{long long} on pitkä,
 | 
			
		||||
joten tyypille voi antaa lyhyemmän nimen \texttt{ll}:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
typedef long long ll;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän jälkeen koodin
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
long long a = 123456789;
 | 
			
		||||
long long b = 987654321;
 | 
			
		||||
cout << a*b << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
voi lyhentää seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
ll a = 123456789;
 | 
			
		||||
ll b = 987654321;
 | 
			
		||||
cout << a*b << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Komentoa \texttt{typedef} voi käyttää myös
 | 
			
		||||
monimutkaisempien tyyppien kanssa.
 | 
			
		||||
Esimerkiksi seuraava koodi antaa nimen \texttt{vi}
 | 
			
		||||
kokonaisluvuista muodostuvalle vektorille
 | 
			
		||||
sekä nimen \texttt{pi} kaksi
 | 
			
		||||
kokonaislukua sisältävälle parille.
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
typedef vector<int> vi;
 | 
			
		||||
typedef pair<int,int> pi;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Makrot}
 | 
			
		||||
\index{makro}
 | 
			
		||||
Toinen tapa lyhentää koodia on määritellä \key{makroja}.
 | 
			
		||||
Makro ilmaisee, että tietyt koodissa olevat
 | 
			
		||||
merkkijonot korvataan toisilla ennen koodin
 | 
			
		||||
kääntämistä.
 | 
			
		||||
C++:ssa makro määritellään
 | 
			
		||||
esikääntäjän komennolla \texttt{\#define}.
 | 
			
		||||
 | 
			
		||||
Määritellään esimerkiksi seuraavat makrot:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
#define F first
 | 
			
		||||
#define S second
 | 
			
		||||
#define PB push_back
 | 
			
		||||
#define MP make_pair
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän jälkeen koodin
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v.push_back(make_pair(y1,x1));
 | 
			
		||||
v.push_back(make_pair(y2,x2));
 | 
			
		||||
int d = v[i].first+v[i].second;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
voi kirjoittaa lyhyemmin seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v.PB(MP(y1,x1));
 | 
			
		||||
v.PB(MP(y2,x2));
 | 
			
		||||
int d = v[i].F+v[i].S;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Makro on mahdollista määritellä myös niin,
 | 
			
		||||
että sille voi antaa parametreja.
 | 
			
		||||
Tämän ansiosta makrolla voi lyhentää esimerkiksi
 | 
			
		||||
komentorakenteita.
 | 
			
		||||
Määritellään esimerkiksi seuraava makro:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
#define REP(i,a,b) for (int i = a; i <= b; i++)
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän jälkeen koodin
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    haku(i);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
voi lyhentää seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
REP(i,1,n) {
 | 
			
		||||
    haku(i);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Matematiikka}
 | 
			
		||||
 | 
			
		||||
Matematiikka on tärkeässä asemassa kisakoodauksessa,
 | 
			
		||||
ja menestyvän kisakoodarin täytyy osata myös
 | 
			
		||||
hyvin matematiikkaa.
 | 
			
		||||
Käymme seuraavaksi läpi joukon keskeisiä
 | 
			
		||||
matematiikan käsitteitä ja kaavoja.
 | 
			
		||||
Palaamme moniin aiheisiin myöhemmin tarkemmin kirjan aikana.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Summakaavat}
 | 
			
		||||
 | 
			
		||||
Jokaiselle summalle muotoa
 | 
			
		||||
\[\sum_{x=1}^n x^k = 1^k+2^k+3^k+\ldots+n^k\]
 | 
			
		||||
on olemassa laskukaava,
 | 
			
		||||
kun $k$ on jokin positiivinen kokonaisluku.
 | 
			
		||||
Tällainen laskukaava on aina astetta $k+1$
 | 
			
		||||
oleva polynomi. Esimerkiksi
 | 
			
		||||
\[\sum_{x=1}^n x = 1+2+3+\ldots+n = \frac{n(n+1)}{2}\]
 | 
			
		||||
ja
 | 
			
		||||
\[\sum_{x=1}^n x^2 = 1^2+2^2+3^2+\ldots+n^2 = \frac{n(n+1)(2n+1)}{6}.\]
 | 
			
		||||
 | 
			
		||||
\key{Aritmeettinen summa} on summa, \index{aritmeettinen summa@aritmeettinen summa}
 | 
			
		||||
jossa jokaisen vierekkäisen luvun erotus on vakio.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[3+7+11+15\]
 | 
			
		||||
on aritmeettinen summa,
 | 
			
		||||
jossa vakio on 4.
 | 
			
		||||
Aritmeettinen summa voidaan laskea kaavalla
 | 
			
		||||
\[\frac{n(a+b)}{2},\]
 | 
			
		||||
jossa summan ensimmäinen luku on $a$,
 | 
			
		||||
viimeinen luku on $b$ ja lukujen määrä on $n$.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[3+7+11+15=\frac{4 \cdot (3+15)}{2} = 36.\]
 | 
			
		||||
Kaava perustuu siihen, että summa muodostuu $n$ luvusta
 | 
			
		||||
ja luvun suuruus on keskimäärin $(a+b)/2$.
 | 
			
		||||
 | 
			
		||||
\index{geometrinen summa@geometrinen summa}
 | 
			
		||||
\key{Geometrinen summa} on summa,
 | 
			
		||||
jossa jokaisen vierekkäisen luvun suhde on vakio.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[3+6+12+24\]
 | 
			
		||||
on geometrinen summa,
 | 
			
		||||
jossa vakio on 2.
 | 
			
		||||
Geometrinen summa voidaan laskea kaavalla
 | 
			
		||||
\[\frac{bx-a}{x-1},\]
 | 
			
		||||
jossa summan ensimmäinen luku on $a$,
 | 
			
		||||
viimeinen luku on $b$ ja vierekkäisten lukujen suhde on $x$.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[3+6+12+24=\frac{24 \cdot 2 - 3}{2-1} = 45.\]
 | 
			
		||||
 | 
			
		||||
Geometrisen summan kaavan voi johtaa merkitsemällä
 | 
			
		||||
\[ S = a + ax + ax^2 + \cdots + b .\] 
 | 
			
		||||
Kertomalla molemmat puolet $x$:llä saadaan
 | 
			
		||||
\[ xS = ax + ax^2 + ax^3 + \cdots + bx,\]
 | 
			
		||||
josta kaava seuraa ratkaisemalla yhtälön
 | 
			
		||||
\[ xS-S = bx-a.\]
 | 
			
		||||
 | 
			
		||||
Geometrisen summan erikoistapaus on usein kätevä kaava
 | 
			
		||||
\[1+2+4+8+\ldots+2^{n-1}=2^n-1.\]
 | 
			
		||||
 | 
			
		||||
% Geometrisen summan sukulainen on
 | 
			
		||||
% \[x+2x^2+3x^3+\cdots+k x^k = \frac{kx^{k+2}-(k+1)x^{k+1}+x}{(x-1)^2}. \]
 | 
			
		||||
 | 
			
		||||
\index{harmoninen summa@harmoninen summa}
 | 
			
		||||
 | 
			
		||||
\key{Harmoninen summa} on summa muotoa
 | 
			
		||||
\[ \sum_{x=1}^n \frac{1}{x} = 1+\frac{1}{2}+\frac{1}{3}+\ldots+\frac{1}{n}.\]
 | 
			
		||||
 | 
			
		||||
Yläraja harmonisen summan suuruudelle on $\log_2(n)+1$.
 | 
			
		||||
Summaa voi näet arvioida ylöspäin
 | 
			
		||||
muuttamalla jokaista termiä $1/k$ niin,
 | 
			
		||||
että $k$:ksi tulee alempi 2:n potenssi.
 | 
			
		||||
Esimerkiksi tapauksessa $n=6$ arvioksi tulee
 | 
			
		||||
\[ 1+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\frac{1}{5}+\frac{1}{6} \le
 | 
			
		||||
1+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}.\]
 | 
			
		||||
Tämän seurauksena summa jakaantuu $\log_2(n)+1$ osaan
 | 
			
		||||
($1$, $2 \cdot 1/2$, $4 \cdot 1/4$, jne.),
 | 
			
		||||
joista jokaisen summa on enintään 1.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Joukko-oppi}
 | 
			
		||||
 | 
			
		||||
\index{joukko-oppi}
 | 
			
		||||
\index{joukko@joukko}
 | 
			
		||||
\index{leikkaus@leikkaus}
 | 
			
		||||
\index{yhdiste@yhdiste}
 | 
			
		||||
\index{erotus@erotus}
 | 
			
		||||
\index{osajoukko@osajoukko}
 | 
			
		||||
\index{perusjoukko}
 | 
			
		||||
 | 
			
		||||
\key{Joukko} on kokoelma alkioita.
 | 
			
		||||
Esimerkiksi joukko
 | 
			
		||||
\[X=\{2,4,7\}\]
 | 
			
		||||
sisältää alkiot 2, 4 ja 7.
 | 
			
		||||
Merkintä $\emptyset$ tarkoittaa tyhjää joukkoa.
 | 
			
		||||
Joukon $S$ koko eli alkoiden määrä on $|S|$.
 | 
			
		||||
Esimerkiksi äskeisessä joukossa $|X|=3$.
 | 
			
		||||
 | 
			
		||||
Merkintä $x \in S$ tarkoittaa,
 | 
			
		||||
että alkio $x$ on joukossa $S$,
 | 
			
		||||
ja merkintä $x \notin S$ tarkoittaa,
 | 
			
		||||
että alkio $x$ ei ole joukossa $S$.
 | 
			
		||||
Esimerkiksi äskeisessä joukossa
 | 
			
		||||
\[4 \in X \hspace{10px}\textrm{ja}\hspace{10px} 5 \notin X.\]
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Uusia joukkoja voidaan muodostaa joukko-operaatioilla
 | 
			
		||||
seuraavasti:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item \key{Leikkaus} $A \cap B$ sisältää alkiot,
 | 
			
		||||
jotka ovat molemmissa joukoista $A$ ja $B$.
 | 
			
		||||
Esimerkiksi jos $A=\{1,2,5\}$ ja $B=\{2,4\}$,
 | 
			
		||||
niin $A \cap B = \{2\}$.
 | 
			
		||||
\item \key{Yhdiste} $A \cup B$ sisältää alkiot,
 | 
			
		||||
jotka ovat ainakin toisessa joukoista $A$ ja $B$.
 | 
			
		||||
Esimerkiksi jos $A=\{3,7\}$ ja $B=\{2,3,8\}$,
 | 
			
		||||
niin $A \cup B = \{2,3,7,8\}$.
 | 
			
		||||
\item \key{Komplementti} $\bar A$ sisältää alkiot,
 | 
			
		||||
jotka eivät ole joukossa $A$.
 | 
			
		||||
Komplementin tulkinta riippuu siitä, mikä on
 | 
			
		||||
\key{perusjoukko} eli joukko, jossa on kaikki
 | 
			
		||||
mahdolliset alkiot. Esimerkiksi jos
 | 
			
		||||
$A=\{1,2,5,7\}$ ja perusjoukko on $P=\{1,2,\ldots,10\}$,
 | 
			
		||||
niin $\bar A = \{3,4,6,8,9,10\}$.
 | 
			
		||||
\item \key{Erotus} $A \setminus B = A \cap \bar B$ sisältää alkiot,
 | 
			
		||||
jotka ovat joukossa $A$ mutta eivät joukossa $B$.
 | 
			
		||||
Huomaa, että $B$:ssä voi olla alkioita,
 | 
			
		||||
joita ei ole $A$:ssa.
 | 
			
		||||
Esimerkiksi jos $A=\{2,3,7,8\}$ ja $B=\{3,5,8\}$,
 | 
			
		||||
niin $A \setminus B = \{2,7\}$.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Merkintä $A \subset S$ tarkoittaa,
 | 
			
		||||
että $A$ on $S$:n \key{osajoukko}
 | 
			
		||||
eli jokainen $A$:n alkio esiintyy $S$:ssä.
 | 
			
		||||
Joukon $S$ osajoukkojen yhteismäärä on $2^{|S|}$.
 | 
			
		||||
Esimerkiksi joukon $\{2,4,7\}$
 | 
			
		||||
osajoukot ovat
 | 
			
		||||
\begin{center}
 | 
			
		||||
$\emptyset$,
 | 
			
		||||
$\{2\}$, $\{4\}$, $\{7\}$, $\{2,4\}$, $\{2,7\}$, $\{4,7\}$ ja $\{2,4,7\}$.
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Usein esiintyviä joukkoja ovat
 | 
			
		||||
 | 
			
		||||
\begin{itemize}[noitemsep]
 | 
			
		||||
\item $\mathbb{N}$ (luonnolliset luvut),
 | 
			
		||||
\item $\mathbb{Z}$ (kokonaisluvut),
 | 
			
		||||
\item $\mathbb{Q}$ (rationaaliluvut) ja
 | 
			
		||||
\item $\mathbb{R}$ (reaaliluvut).
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Luonnollisten lukujen joukko $\mathbb{N}$ voidaan määritellä
 | 
			
		||||
tilanteesta riippuen kahdella tavalla:
 | 
			
		||||
joko $\mathbb{N}=\{0,1,2,\ldots\}$
 | 
			
		||||
tai $\mathbb{N}=\{1,2,3,...\}$.
 | 
			
		||||
 | 
			
		||||
Joukon voi muodostaa myös säännöllä muotoa
 | 
			
		||||
\[\{f(n) : n \in S\},\]
 | 
			
		||||
missä $f(n)$ on jokin funktio.
 | 
			
		||||
Tällainen joukko sisältää kaikki alkiot
 | 
			
		||||
$f(n)$, jossa $n$ on valittu joukosta $S$.
 | 
			
		||||
Esimerkiksi joukko
 | 
			
		||||
\[X=\{2n : n \in \mathbb{Z}\}\]
 | 
			
		||||
sisältää kaikki parilliset kokonaisluvut.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Logiikka}
 | 
			
		||||
 | 
			
		||||
\index{logiikka@logiikka}
 | 
			
		||||
\index{negaatio@negaatio}
 | 
			
		||||
\index{konjunktio@konjunktio}
 | 
			
		||||
\index{disjunktio@disjunktio}
 | 
			
		||||
\index{implikaatio@implikaatio}
 | 
			
		||||
\index{ekvivalenssi@ekvivalenssi}
 | 
			
		||||
 | 
			
		||||
Loogisen lausekkeen arvo on joko \key{tosi} (1) tai
 | 
			
		||||
\key{epätosi} (0).
 | 
			
		||||
Tärkeimmät loogiset operaatiot ovat
 | 
			
		||||
$\lnot$ (\key{negaatio}),
 | 
			
		||||
$\land$ (\key{konjunktio}),
 | 
			
		||||
$\lor$ (\key{disjunktio}),
 | 
			
		||||
$\Rightarrow$ (\key{implikaatio}) sekä
 | 
			
		||||
$\Leftrightarrow$ (\key{ekvivalenssi}).
 | 
			
		||||
Seuraava taulukko näyttää operaatioiden merkityksen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr|rrrrrrr}
 | 
			
		||||
$A$ & $B$ & $\lnot A$ & $\lnot B$ & $A \land B$ & $A \lor B$ & $A \Rightarrow B$ & $A \Leftrightarrow B$ \\
 | 
			
		||||
\hline
 | 
			
		||||
0 & 0 & 1 & 1 & 0 & 0 & 1 & 1 \\
 | 
			
		||||
0 & 1 & 1 & 0 & 0 & 1 & 1 & 0 \\
 | 
			
		||||
1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 \\
 | 
			
		||||
1 & 1 & 0 & 0 & 1 & 1 & 1 & 1 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Negaatio $\lnot A$ muuttaa lausekkeen käänteiseksi.
 | 
			
		||||
Lauseke $A \land B$ on tosi, jos molemmat $A$ ja $B$ ovat tosia,
 | 
			
		||||
ja lauseke $A \lor B$ on tosi, jos $A$ tai $B$ on tosi.
 | 
			
		||||
Lauseke $A \Rightarrow B$ on tosi,
 | 
			
		||||
jos $A$:n ollessa tosi myös $B$ on aina tosi.
 | 
			
		||||
Lauseke $A \Leftrightarrow B$ on tosi,
 | 
			
		||||
jos $A$:n ja $B$:n totuusarvo on sama.
 | 
			
		||||
 | 
			
		||||
\index{predikaatti@predikaatti}
 | 
			
		||||
 | 
			
		||||
\key{Predikaatti} on lauseke, jonka arvo on tosi tai epätosi
 | 
			
		||||
riippuen sen parametreista.
 | 
			
		||||
Yleensä predikaattia merkitään suurella kirjaimella.
 | 
			
		||||
Esimerkiksi voimme määritellä predikaatin $P(x)$,
 | 
			
		||||
joka on tosi tarkalleen silloin, kun $x$ on alkuluku.
 | 
			
		||||
Tällöin esimerkiksi $P(7)$ on tosi, kun taas $P(8)$ on epätosi.
 | 
			
		||||
 | 
			
		||||
\index{kvanttori@kvanttori}
 | 
			
		||||
 | 
			
		||||
\key{Kvanttori} ilmaisee, että looginen
 | 
			
		||||
lauseke liittyy jollakin tavalla joukon alkioihin.
 | 
			
		||||
Tavalliset kvanttorit
 | 
			
		||||
ovat $\forall$ (\key{kaikille}) ja $\exists$ (\key{on olemassa}).
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[\forall x (\exists y (y < x))\]
 | 
			
		||||
tarkoittaa, että jokaiselle joukon
 | 
			
		||||
alkiolle $x$ on olemassa
 | 
			
		||||
jokin joukon alkio $y$ niin, että $y$ on $x$:ää pienempi.
 | 
			
		||||
Tämä pätee kokonaislukujen joukossa,
 | 
			
		||||
mutta ei päde luonnollisten lukujen joukossa.
 | 
			
		||||
 | 
			
		||||
Yllä esitettyjen merkintöjä avulla on mahdollista esittää
 | 
			
		||||
monenlaisia loogisia väitteitä.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[\forall x ((x>2 \land \lnot P(x)) \Rightarrow (\exists a (\exists b (x = ab \land a > 1 \land b > 1))))\]
 | 
			
		||||
tarkoittaa, että jos luku $x$ on suurempi
 | 
			
		||||
kuin 2 eikä ole alkuluku,
 | 
			
		||||
niin on olemassa luvut $a$ ja $b$,
 | 
			
		||||
joiden tulo on $x$ ja jotka molemmat ovat suurempia kuin 1.
 | 
			
		||||
Tämä väite pitää paikkansa kokonaislukujen joukossa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Funktioita}
 | 
			
		||||
 | 
			
		||||
Funktio $\lfloor x \rfloor$ pyöristää luvun $x$
 | 
			
		||||
alaspäin kokonaisluvuksi ja
 | 
			
		||||
funktio $\lceil x \rceil$ pyöristää luvun $x$
 | 
			
		||||
ylöspäin kokonaisluvuksi. Esimerkiksi
 | 
			
		||||
\[ \lfloor 3/2 \rfloor = 1 \hspace{10px} \textrm{ja} \hspace{10px} \lceil 3/2 \rceil = 2.\]
 | 
			
		||||
 | 
			
		||||
Funktiot $\min(x_1,x_2,\ldots,x_n)$
 | 
			
		||||
ja $\max(x_1,x_2,\ldots,x_n)$
 | 
			
		||||
palauttavat pienimmän ja suurimman
 | 
			
		||||
arvoista $x_1,x_2,\ldots,x_n$.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[ \min(1,2,3)=1 \hspace{10px} \textrm{ja} \hspace{10px} \max(1,2,3)=3.\]
 | 
			
		||||
 | 
			
		||||
\index{kertoma@kertoma}
 | 
			
		||||
 | 
			
		||||
\key{Kertoma} $n!$ määritellään
 | 
			
		||||
\[\prod_{x=1}^n x = 1 \cdot 2 \cdot 3 \cdot \ldots \cdot n\]
 | 
			
		||||
tai vaihtoehtoisesti rekursiivisesti
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
0! & = & 1 \\
 | 
			
		||||
n! & = & n \cdot (n-1)! \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\index{Fibonaccin luku@Fibonaccin luku}
 | 
			
		||||
 | 
			
		||||
\key{Fibonaccin luvut} esiintyvät monissa erilaisissa yhteyksissä.
 | 
			
		||||
Ne määritellään seuraavasti rekursiivisesti:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(0) & = & 0 \\
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(n) & = & f(n-1)+f(n-2) \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Ensimmäiset Fibonaccin luvut ovat
 | 
			
		||||
\[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, \ldots\]
 | 
			
		||||
Fibonaccin lukujen laskemiseen on olemassa myös
 | 
			
		||||
suljetun muodon kaava
 | 
			
		||||
\[f(n)=\frac{(1 + \sqrt{5})^n - (1-\sqrt{5})^n}{2^n \sqrt{5}}.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Logaritmi}
 | 
			
		||||
 | 
			
		||||
\index{logaritmi@logaritmi}
 | 
			
		||||
 | 
			
		||||
Luvun $x$
 | 
			
		||||
\key{logaritmi} merkitään $\log_k(x)$, missä $k$ on logaritmin kantaluku.
 | 
			
		||||
Logaritmin määritelmän mukaan
 | 
			
		||||
$\log_k(x)=a$ tarkalleen silloin, kun $k^a=x$.
 | 
			
		||||
 | 
			
		||||
Algoritmiikassa hyödyllinen tulkinta on,
 | 
			
		||||
että logaritmi $\log_k(x)$ ilmaisee, montako kertaa lukua $x$
 | 
			
		||||
täytyy jakaa $k$:lla, ennen kuin tulos on 1.
 | 
			
		||||
Esimerkiksi $\log_2(32)=5$,
 | 
			
		||||
koska lukua 32 täytyy jakaa 2:lla 5 kertaa:
 | 
			
		||||
 | 
			
		||||
\[32 \rightarrow 16 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 1 \]
 | 
			
		||||
 | 
			
		||||
Logaritmi tulee usein vastaan algoritmien analyysissa,
 | 
			
		||||
koska monessa tehokkaassa algoritmissa jokin asia puolittuu
 | 
			
		||||
joka askeleella.
 | 
			
		||||
Niinpä logaritmin avulla voi arvioida algoritmin tehokkuutta.
 | 
			
		||||
 | 
			
		||||
Logaritmille pätee kaava
 | 
			
		||||
\[\log_k(ab) = \log_k(a)+\log_k(b),\]
 | 
			
		||||
josta seuraa edelleen
 | 
			
		||||
\[\log_k(x^n) = n \cdot \log_k(x).\]
 | 
			
		||||
Samoin logaritmille pätee
 | 
			
		||||
\[\log_k\Big(\frac{a}{b}\Big) = \log_k(a)-\log_k(b).\]
 | 
			
		||||
Lisäksi on voimassa kaava
 | 
			
		||||
\[\log_u(x) = \frac{\log_k(x)}{\log_k(u)},\]
 | 
			
		||||
minkä ansiosta logaritmeja voi laskea mille tahansa kantaluvulle,
 | 
			
		||||
jos on keino laskea logaritmeja jollekin kantaluvulle.
 | 
			
		||||
 | 
			
		||||
\index{luonnollinen logaritmi@luonnollinen logaritmi}
 | 
			
		||||
\index{Neperin luku@Neperin luku}
 | 
			
		||||
 | 
			
		||||
Luvun $x$ \key{luonnollinen logaritmi} $\ln(x)$ on logaritmi, jonka kantaluku on
 | 
			
		||||
\key{Neperin luku} $e \approx 2{,}71828$.
 | 
			
		||||
 | 
			
		||||
Vielä yksi logaritmin ominaisuus on, että
 | 
			
		||||
luvun $x$ numeroiden määrä $b$-kantaisessa
 | 
			
		||||
lukujärjestelmässä
 | 
			
		||||
on $\lfloor \log_b(x)+1 \rfloor$.
 | 
			
		||||
Esimerkiksi luvun $123$ esitys
 | 
			
		||||
2-järjestelmässä on 1111011 ja
 | 
			
		||||
$\lfloor \log_2(123)+1 \rfloor = 7$.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,551 @@
 | 
			
		|||
\chapter{Time complexity}
 | 
			
		||||
 | 
			
		||||
\index{aikavaativuus@aikavaativuus}
 | 
			
		||||
 | 
			
		||||
Kisakoodauksessa oleellinen asia on algoritmien tehokkuus.
 | 
			
		||||
Yleensä on helppoa suunnitella algoritmi,
 | 
			
		||||
joka ratkaisee tehtävän hitaasti,
 | 
			
		||||
mutta todellinen vaikeus piilee siinä,
 | 
			
		||||
kuinka keksiä nopeasti toimiva algoritmi.
 | 
			
		||||
Jos algoritmi on liian hidas, se tuottaa vain
 | 
			
		||||
osan pisteistä tai ei pisteitä lainkaan.
 | 
			
		||||
 | 
			
		||||
\key{Aikavaativuus} on kätevä tapa arvioida,
 | 
			
		||||
kuinka nopeasti algoritmi toimii.
 | 
			
		||||
Se esittää algoritmin tehokkuuden funktiona,
 | 
			
		||||
jonka parametrina on syötteen koko.
 | 
			
		||||
Aikavaativuuden avulla algoritmista voi päätellä ennen koodaamista,
 | 
			
		||||
onko se riittävän tehokas tehtävän ratkaisuun.
 | 
			
		||||
 | 
			
		||||
\section{Laskusäännöt}
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus merkitään $O(\cdots)$,
 | 
			
		||||
jossa kolmen pisteen tilalla
 | 
			
		||||
on kaava, joka kuvaa algoritmin ajankäyttöä.
 | 
			
		||||
Yleensä muuttuja $n$ esittää syötteen kokoa.
 | 
			
		||||
Esimerkiksi jos algoritmin syötteenä on taulukko lukuja,
 | 
			
		||||
$n$ on lukujen määrä,
 | 
			
		||||
ja jos syötteenä on merkkijono,
 | 
			
		||||
$n$ on merkkijonon pituus.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Silmukat}
 | 
			
		||||
 | 
			
		||||
Algoritmin ajankäyttö johtuu usein
 | 
			
		||||
pohjimmiltaan silmukoista,
 | 
			
		||||
jotka käyvät syötettä läpi.
 | 
			
		||||
Mitä enemmän sisäkkäisiä silmukoita
 | 
			
		||||
algoritmissa on, sitä hitaampi se on.
 | 
			
		||||
Jos sisäkkäisiä silmukoita on $k$,
 | 
			
		||||
aikavaativuus on $O(n^k)$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavan koodin aikavaativuus on $O(n)$:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Vastaavasti seuraavan koodin aikavaativuus on $O(n^2)$:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    for (int j = 1; j <= n; j++) {
 | 
			
		||||
        // koodia
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Suuruusluokka}
 | 
			
		||||
 | 
			
		||||
Aikavaativuus ei kerro tarkasti,
 | 
			
		||||
montako kertaa silmukan sisällä oleva koodi suoritetaan,
 | 
			
		||||
vaan se kertoo vain suuruusluokan.
 | 
			
		||||
Esimerkiksi seuraavissa esimerkeissä silmukat
 | 
			
		||||
suoritetaan $3n$, $n+5$ ja $\lceil n/2 \rceil$ kertaa,
 | 
			
		||||
mutta kunkin koodin aikavaativuus on sama $O(n)$.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= 3*n; i++) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n+5; i++) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i += 2) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Seuraavan koodin aikavaativuus on puolestaan $O(n^2)$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    for (int j = i+1; j <= n; j++) {
 | 
			
		||||
        // koodia
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Peräkkäisyys}
 | 
			
		||||
 | 
			
		||||
Jos koodissa on peräkkäisiä osia,
 | 
			
		||||
kokonaisaikavaativuus on suurin yksittäisen
 | 
			
		||||
osan aikavaativuus.
 | 
			
		||||
Tämä johtuu siitä, että koodin hitain
 | 
			
		||||
vaihe on yleensä koodin pullonkaula
 | 
			
		||||
ja muiden vaiheiden merkitys on pieni.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraava koodi muodostuu
 | 
			
		||||
kolmesta osasta,
 | 
			
		||||
joiden aikavaativuudet ovat $O(n)$, $O(n^2)$ ja $O(n)$.
 | 
			
		||||
Niinpä koodin aikavaativuus on $O(n^2)$.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    for (int j = 1; j <= n; j++) {
 | 
			
		||||
        // koodia
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    // koodia
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Monta muuttujaa}
 | 
			
		||||
 | 
			
		||||
Joskus syötteessä on monta muuttujaa,
 | 
			
		||||
jotka vaikuttavat aikavaativuuteen.
 | 
			
		||||
Tällöin myös aikavaativuuden kaavassa esiintyy
 | 
			
		||||
monta muuttujaa.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavan koodin
 | 
			
		||||
aikavaativuus on $O(nm)$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    for (int j = 1; j <= m; j++) {
 | 
			
		||||
        // koodia
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Rekursio}
 | 
			
		||||
 | 
			
		||||
Rekursiivisen funktion aikavaativuuden
 | 
			
		||||
määrittää, montako kertaa funktiota kutsutaan yhteensä
 | 
			
		||||
ja mikä on yksittäisen kutsun aikavaativuus.
 | 
			
		||||
Kokonais\-aikavaativuus saadaan kertomalla
 | 
			
		||||
nämä arvot toisillaan.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkiksi seuraavaa funktiota:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void f(int n) {
 | 
			
		||||
    if (n == 1) return;
 | 
			
		||||
    f(n-1);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Kutsu $\texttt{f}(n)$ aiheuttaa yhteensä $n$ funktiokutsua,
 | 
			
		||||
ja jokainen funktiokutsu vie aikaa $O(1)$,
 | 
			
		||||
joten aikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten seuraavaa funktiota:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void g(int n) {
 | 
			
		||||
    if (n == 1) return;
 | 
			
		||||
    g(n-1);
 | 
			
		||||
    g(n-1);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tässä tapauksessa funktio haarautuu kahteen osaan,
 | 
			
		||||
joten kutsu $\texttt{g}(n)$ aiheuttaa kaikkiaan seuraavat kutsut:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr}
 | 
			
		||||
kutsu & kerrat \\
 | 
			
		||||
\hline
 | 
			
		||||
$\texttt{g}(n)$ & 1 \\
 | 
			
		||||
$\texttt{g}(n-1)$ & 2 \\
 | 
			
		||||
$\cdots$ & $\cdots$ \\
 | 
			
		||||
$\texttt{g}(1)$ & $2^{n-1}$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän perusteella kutsun $\texttt{g}(n)$ aikavaativuus on
 | 
			
		||||
\[1+2+4+\cdots+2^{n-1} = 2^n-1 = O(2^n).\]
 | 
			
		||||
 | 
			
		||||
\section{Vaativuusluokkia}
 | 
			
		||||
 | 
			
		||||
\index{vaativuusluokka@vaativuusluokka}
 | 
			
		||||
 | 
			
		||||
Usein esiintyviä vaativuusluokkia ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\begin{description}
 | 
			
		||||
\item[$O(1)$]
 | 
			
		||||
\index{vakioaikainen algoritmi@vakioaikainen algoritmi}
 | 
			
		||||
\key{Vakioaikainen} algoritmi
 | 
			
		||||
käyttää saman verran aikaa minkä tahansa
 | 
			
		||||
syötteen käsittelyyn,
 | 
			
		||||
eli algoritmin nopeus ei riipu syötteen koosta.
 | 
			
		||||
Tyypillinen vakioaikainen algoritmi on suora kaava
 | 
			
		||||
vastauksen laskemiseen.
 | 
			
		||||
 | 
			
		||||
\item[$O(\log n)$]
 | 
			
		||||
\index{logaritminen algoritmi@logaritminen algoritmi}
 | 
			
		||||
\key{Logaritminen} aikavaativuus
 | 
			
		||||
syntyy usein siitä, että algoritmi
 | 
			
		||||
puolittaa syötteen koon joka askeleella.
 | 
			
		||||
Logaritmi $\log_2 n$ näet ilmaisee, montako
 | 
			
		||||
kertaa luku $n$ täytyy puolittaa,
 | 
			
		||||
ennen kuin tuloksena on 1.
 | 
			
		||||
 | 
			
		||||
\item[$O(\sqrt n)$]
 | 
			
		||||
 | 
			
		||||
Tällainen algoritmi sijoittuu
 | 
			
		||||
aikavaativuuksien $O(\log n)$ ja $O(n)$ välimaastoon.
 | 
			
		||||
Neliöjuuren erityinen ominaisuus on,
 | 
			
		||||
että $\sqrt n = n/\sqrt n$, joten neliöjuuri
 | 
			
		||||
osuu tietyllä tavalla syötteen puoliväliin.
 | 
			
		||||
 | 
			
		||||
\item[$O(n)$]
 | 
			
		||||
\index{lineaarinen algoritmi@lineaarinen algoritmi}
 | 
			
		||||
\key{Lineaarinen} algoritmi käy syötteen läpi
 | 
			
		||||
kiinteän määrän kertoja.
 | 
			
		||||
Tämä on usein paras mahdollinen aikavaativuus,
 | 
			
		||||
koska yleensä syöte täytyy käydä
 | 
			
		||||
läpi ainakin kerran,
 | 
			
		||||
ennen kuin algoritmi voi ilmoittaa vastauksen.
 | 
			
		||||
 | 
			
		||||
\item[$O(n \log n)$]
 | 
			
		||||
 | 
			
		||||
Tämä aikavaativuus viittaa usein
 | 
			
		||||
syötteen järjestämiseen,
 | 
			
		||||
koska tehokkaat järjestämisalgoritmit toimivat
 | 
			
		||||
ajassa $O(n \log n)$.
 | 
			
		||||
Toinen mahdollisuus on, että algoritmi
 | 
			
		||||
käyttää tietorakennetta,
 | 
			
		||||
jonka operaatiot ovat $O(\log n)$-aikaisia.
 | 
			
		||||
 | 
			
		||||
\item[$O(n^2)$]
 | 
			
		||||
\index{nelizllinen algoritmi@neliöllinen algoritmi}
 | 
			
		||||
\key{Neliöllinen} aikavaativuus voi syntyä
 | 
			
		||||
siitä, että algoritmissa on 
 | 
			
		||||
kaksi sisäkkäistä silmukkaa.
 | 
			
		||||
Neliöllinen algoritmi voi käydä läpi kaikki
 | 
			
		||||
tavat valita joukosta kaksi alkiota.
 | 
			
		||||
 | 
			
		||||
\item[$O(n^3)$]
 | 
			
		||||
\index{kuutiollinen algoritmi@kuutiollinen algoritmi}
 | 
			
		||||
\key{Kuutiollinen} aikavaativuus voi syntyä siitä,
 | 
			
		||||
että algoritmissa on 
 | 
			
		||||
kolme sisäkkäistä silmukkaa.
 | 
			
		||||
Kuutiollinen algoritmi voi käydä läpi kaikki
 | 
			
		||||
tavat valita joukosta kolme alkiota.
 | 
			
		||||
 | 
			
		||||
\item[$O(2^n)$]
 | 
			
		||||
 | 
			
		||||
Tämä aikavaativuus tarkoittaa usein,
 | 
			
		||||
että algoritmi käy läpi kaikki syötteen osajoukot.
 | 
			
		||||
Esimerkiksi joukon $\{1,2,3\}$ osajoukot ovat
 | 
			
		||||
$\emptyset$, $\{1\}$, $\{2\}$, $\{3\}$, $\{1,2\}$,
 | 
			
		||||
$\{1,3\}$, $\{2,3\}$ sekä $\{1,2,3\}$.
 | 
			
		||||
 | 
			
		||||
\item[$O(n!)$]
 | 
			
		||||
 | 
			
		||||
Tämä aikavaativuus voi syntyä siitä,
 | 
			
		||||
että algoritmi käy läpi kaikki syötteen permutaatiot.
 | 
			
		||||
Esimerkiksi joukon $\{1,2,3\}$ permutaatiot ovat
 | 
			
		||||
$(1,2,3)$, $(1,3,2)$, $(2,1,3)$, $(2,3,1)$,
 | 
			
		||||
$(3,1,2)$ sekä $(3,2,1)$.
 | 
			
		||||
 | 
			
		||||
\end{description}
 | 
			
		||||
 | 
			
		||||
\index{polynominen algoritmi@polynominen algoritmi}
 | 
			
		||||
Algoritmi on \key{polynominen},
 | 
			
		||||
jos sen aikavaativuus on korkeintaan $O(n^k)$,
 | 
			
		||||
kun $k$ on vakio.
 | 
			
		||||
Edellä mainituista aikavaativuuksista
 | 
			
		||||
kaikki paitsi $O(2^n)$ ja $O(n!)$
 | 
			
		||||
ovat polynomisia.
 | 
			
		||||
Käytännössä vakio $k$ on yleensä pieni,
 | 
			
		||||
minkä ansiosta
 | 
			
		||||
polynomisuus kuvastaa sitä,
 | 
			
		||||
että algoritmi on \emph{tehokas}.
 | 
			
		||||
\index{NP-vaikea ongelma}
 | 
			
		||||
 | 
			
		||||
Useimmat tässä kirjassa esitettävät algoritmit
 | 
			
		||||
ovat polynomisia.
 | 
			
		||||
Silti on paljon ongelmia, joihin ei tunneta
 | 
			
		||||
polynomista algoritmia eli ongelmaa ei osata
 | 
			
		||||
ratkaista tehokkaasti.
 | 
			
		||||
\key{NP-vaikeat} ongelmat ovat
 | 
			
		||||
tärkeä joukko ongelmia,
 | 
			
		||||
joihin ei tiedetä polynomista algoritmia.
 | 
			
		||||
 | 
			
		||||
\section{Tehokkuuden arviointi}
 | 
			
		||||
 | 
			
		||||
Aikavaativuuden hyötynä on,
 | 
			
		||||
että sen avulla voi arvioida ennen algoritmin
 | 
			
		||||
toteuttamista, onko algoritmi riittävän nopea
 | 
			
		||||
tehtävän ratkaisemiseen.
 | 
			
		||||
Lähtökohtana arviossa on, että nykyaikainen tietokone
 | 
			
		||||
pystyy suorittamaan sekunnissa joitakin
 | 
			
		||||
satoja miljoonia koodissa olevia komentoja.
 | 
			
		||||
 | 
			
		||||
Oletetaan esimerkiksi, että tehtävän aikaraja on 
 | 
			
		||||
yksi sekunti ja syötteen koko on $n=10^5$.
 | 
			
		||||
Jos algoritmin aikavaativuus on $O(n^2)$,
 | 
			
		||||
algoritmi suorittaa noin $(10^5)^2=10^{10}$ komentoa.
 | 
			
		||||
Tähän kuluu aikaa arviolta kymmeniä sekunteja,
 | 
			
		||||
joten algoritmi vaikuttaa liian hitaalta tehtävän ratkaisemiseen.
 | 
			
		||||
 | 
			
		||||
Käänteisesti syötteen koosta voi päätellä,
 | 
			
		||||
kuinka tehokasta algoritmia tehtävän laatija odottaa
 | 
			
		||||
ratkaisijalta.
 | 
			
		||||
Seuraavassa taulukossa on joitakin hyödyllisiä arvioita,
 | 
			
		||||
jotka olettavat, että tehtävän aikaraja on yksi sekunti.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{ll}
 | 
			
		||||
syötteen koko ($n$) & haluttu aikavaativuus \\
 | 
			
		||||
\hline
 | 
			
		||||
$n \le 10^{18}$ & $O(1)$ tai $O(\log n)$ \\
 | 
			
		||||
$n \le 10^{12}$ & $O(\sqrt n)$ \\
 | 
			
		||||
$n \le 10^6$ & $O(n)$ tai $O(n \log n)$ \\
 | 
			
		||||
$n \le 5000$ & $O(n^2)$ \\
 | 
			
		||||
$n \le 500$ & $O(n^3)$ \\
 | 
			
		||||
$n \le 25$ & $O(2^n)$ \\
 | 
			
		||||
$n \le 10$ & $O(n!)$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos syötteen koko on $n=10^5$,
 | 
			
		||||
tehtävän laatija odottaa luultavasti
 | 
			
		||||
algoritmia, jonka aikavaativuus on $O(n)$ tai $O(n \log n)$.
 | 
			
		||||
Tämä tieto helpottaa algoritmin suunnittelua,
 | 
			
		||||
koska se rajaa pois monia lähestymistapoja,
 | 
			
		||||
joiden tuloksena olisi hitaampi aikavaativuus.
 | 
			
		||||
 | 
			
		||||
\index{vakiokerroin}
 | 
			
		||||
 | 
			
		||||
Aikavaativuus ei kerro kuitenkaan kaikkea algoritmin
 | 
			
		||||
tehokkuudesta, koska se kätkee toteutuksessa olevat
 | 
			
		||||
\key{vakiokertoimet}. Esimerkiksi aikavaativuuden $O(n)$
 | 
			
		||||
algoritmi voi tehdä käytännössä $n/2$ tai $5n$ operaatiota.
 | 
			
		||||
Tällä on merkittävä vaikutus algoritmin
 | 
			
		||||
todelliseen ajankäyttöön.
 | 
			
		||||
 | 
			
		||||
\section{Suurin alitaulukon summa}
 | 
			
		||||
 | 
			
		||||
\index{suurin alitaulukon summa@suurin alitaulukon summa}
 | 
			
		||||
 | 
			
		||||
Usein ohjelmointitehtävän ratkaisuun on monta
 | 
			
		||||
luontevaa algoritmia, joiden aikavaativuudet eroavat.
 | 
			
		||||
Tutustumme seuraavaksi klassiseen ongelmaan,
 | 
			
		||||
jonka suoraviivaisen ratkaisun aikavaativuus on $O(n^3)$,
 | 
			
		||||
mutta algoritmia parantamalla aikavaativuudeksi
 | 
			
		||||
tulee ensin $O(n^2)$ ja lopulta $O(n)$.
 | 
			
		||||
 | 
			
		||||
Annettuna on taulukko, jossa on $n$ kokonaislukua
 | 
			
		||||
$x_1,x_2,\ldots,x_n$, ja tehtävänä on etsiä
 | 
			
		||||
taulukon \key{suurin alitaulukon summa}
 | 
			
		||||
eli mahdollisimman suuri summa
 | 
			
		||||
taulukon yhtenäisellä välillä.
 | 
			
		||||
Tehtävän kiinnostavuus on siinä, että taulukossa
 | 
			
		||||
saattaa olla negatiivisia lukuja.
 | 
			
		||||
Esimerkiksi taulukossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$-1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$-3$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$-5$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
suurimman summan $10$ tuottaa seuraava alitaulukko:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (6,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$-1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$-3$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$-5$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 1}
 | 
			
		||||
 | 
			
		||||
Suoraviivainen ratkaisu tehtävään on käydä
 | 
			
		||||
läpi kaikki tavat valita alitaulukko taulukosta,
 | 
			
		||||
laskea jokaisesta vaihtoehdosta lukujen summa
 | 
			
		||||
ja pitää muistissa suurinta summaa.
 | 
			
		||||
Seuraava koodi toteuttaa tämän algoritmin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int p = 0;
 | 
			
		||||
for (int a = 1; a <= n; a++) {
 | 
			
		||||
    for (int b = a; b <= n; b++) {
 | 
			
		||||
        int s = 0;
 | 
			
		||||
        for (int c = a; c <= b; c++) {
 | 
			
		||||
            s += x[c];
 | 
			
		||||
        }
 | 
			
		||||
        p = max(p,s);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
cout << p << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Koodi olettaa, että luvut on tallennettu taulukkoon \texttt{x},
 | 
			
		||||
jota indeksoidaan $1 \ldots n$.
 | 
			
		||||
Muuttujat $a$ ja $b$ valitsevat alitaulukon ensimmäisen
 | 
			
		||||
ja viimeisen luvun, ja alitaulukon summa lasketaan muuttujaan $s$.
 | 
			
		||||
Muuttujassa $p$ on puolestaan paras haun aikana löydetty summa.
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on $O(n^3)$, koska siinä on kolme
 | 
			
		||||
sisäkkäistä silmukkaa ja jokainen silmukka käy läpi $O(n)$ lukua.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 2}
 | 
			
		||||
 | 
			
		||||
Äskeistä ratkaisua on helppoa tehostaa hankkiutumalla
 | 
			
		||||
eroon sisimmästä silmukasta.
 | 
			
		||||
Tämä on mahdollista laskemalla summaa samalla,
 | 
			
		||||
kun alitaulukon oikea reuna liikkuu eteenpäin.
 | 
			
		||||
Tuloksena on seuraava koodi:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int p = 0;
 | 
			
		||||
for (int a = 1; a <= n; a++) {
 | 
			
		||||
    int s = 0;
 | 
			
		||||
    for (int b = a; b <= n; b++) {
 | 
			
		||||
        s += x[b];
 | 
			
		||||
        p = max(p,s);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
cout << p << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän muutoksen jälkeen koodin aikavaativuus on $O(n^2)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 3}
 | 
			
		||||
 | 
			
		||||
Yllättävää kyllä, tehtävään on olemassa myös
 | 
			
		||||
$O(n)$-aikainen ratkaisu eli koodista pystyy
 | 
			
		||||
karsimaan vielä yhden silmukan.
 | 
			
		||||
Ideana on laskea taulukon jokaiseen
 | 
			
		||||
kohtaan, mikä on suurin alitaulukon
 | 
			
		||||
summa, jos alitaulukko päättyy kyseiseen kohtaan.
 | 
			
		||||
Tämän jälkeen ratkaisu tehtävään on suurin
 | 
			
		||||
näistä summista.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan suurimman summan tuottavan
 | 
			
		||||
alitaulukon etsimistä,
 | 
			
		||||
kun valittuna on alitaulukon loppukohta $k$.
 | 
			
		||||
Vaihtoehtoja on kaksi:
 | 
			
		||||
\begin{enumerate}
 | 
			
		||||
\item Alitaulukossa on vain kohdassa $k$ oleva luku.
 | 
			
		||||
\item Alitaulukossa on ensin jokin kohtaan $k-1$ päättyvä alitaulukko
 | 
			
		||||
ja sen jälkeen kohdassa $k$ oleva luku.
 | 
			
		||||
\end{enumerate}
 | 
			
		||||
 | 
			
		||||
Koska tavoitteena on löytää alitaulukko,
 | 
			
		||||
jonka lukujen summa on suurin,
 | 
			
		||||
tapauksessa 2 myös kohtaan $k-1$ päättyvän
 | 
			
		||||
alitaulukon tulee olla sellainen,
 | 
			
		||||
että sen summa on suurin.
 | 
			
		||||
Niinpä tehokas ratkaisu syntyy käymällä läpi
 | 
			
		||||
kaikki alitaulukon loppukohdat järjestyksessä
 | 
			
		||||
ja laskemalla jokaiseen kohtaan suurin
 | 
			
		||||
mahdollinen kyseiseen kohtaan päättyvän alitaulukon summa.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi toteuttaa ratkaisun:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int p = 0, s = 0;
 | 
			
		||||
for (int k = 1; k <= n; k++) {
 | 
			
		||||
    s = max(x[k],s+x[k]);
 | 
			
		||||
    p = max(p,s);
 | 
			
		||||
}
 | 
			
		||||
cout << p << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmissa on vain yksi silmukka,
 | 
			
		||||
joka käy läpi taulukon luvut,
 | 
			
		||||
joten sen aikavaativuus on $O(n)$.
 | 
			
		||||
Tämä on myös paras mahdollinen aikavaativuus,
 | 
			
		||||
koska minkä tahansa algoritmin täytyy käydä
 | 
			
		||||
läpi ainakin kerran taulukon sisältö.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tehokkuusvertailu}
 | 
			
		||||
 | 
			
		||||
On kiinnostavaa tutkia, kuinka tehokkaita algoritmit
 | 
			
		||||
ovat käytännössä.
 | 
			
		||||
Seuraava taulukko näyttää, kuinka nopeasti äskeiset
 | 
			
		||||
ratkaisut toimivat eri $n$:n arvoilla
 | 
			
		||||
nykyaikaisella tietokoneella.
 | 
			
		||||
 | 
			
		||||
Jokaisessa testissä syöte on muodostettu satunnaisesti.
 | 
			
		||||
Ajankäyttöön ei ole laskettu syötteen lukemiseen
 | 
			
		||||
kuluvaa aikaa.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrrr}
 | 
			
		||||
taulukon koko $n$ & ratkaisu 1 & ratkaisu 2 & ratkaisu 3 \\
 | 
			
		||||
\hline
 | 
			
		||||
$10^2$ & $0{,}0$ s & $0{,}0$ s & $0{,}0$ s \\
 | 
			
		||||
$10^3$ & $0{,}1$ s & $0{,}0$ s & $0{,}0$ s \\
 | 
			
		||||
$10^4$ & > $10,0$ s & $0{,}1$ s & $0{,}0$ s \\
 | 
			
		||||
$10^5$ & > $10,0$ s & $5{,}3$ s & $0{,}0$ s \\
 | 
			
		||||
$10^6$ & > $10,0$ s & > $10,0$ s & $0{,}0$ s \\
 | 
			
		||||
$10^7$ & > $10,0$ s & > $10,0$ s & $0{,}0$ s \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Vertailu osoittaa,
 | 
			
		||||
että pienillä syötteillä kaikki algoritmit
 | 
			
		||||
ovat tehokkaita,
 | 
			
		||||
mutta suuremmat syötteet tuovat esille
 | 
			
		||||
merkittäviä eroja algoritmien suoritusajassa.
 | 
			
		||||
$O(n^3)$-aikainen ratkaisu 1 alkaa hidastua,
 | 
			
		||||
kun $n=10^3$, ja $O(n^2)$-aikainen ratkaisu 2
 | 
			
		||||
alkaa hidastua, kun $n=10^4$.
 | 
			
		||||
Vain $O(n)$-aikainen ratkaisu 3 selvittää
 | 
			
		||||
suurimmatkin syötteet salamannopeasti.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,972 @@
 | 
			
		|||
\chapter{Sorting}
 | 
			
		||||
 | 
			
		||||
\index{jxrjestxminen@järjestäminen}
 | 
			
		||||
 | 
			
		||||
\key{Järjestäminen}
 | 
			
		||||
on keskeinen algoritmiikan ongelma.
 | 
			
		||||
Moni tehokas algoritmi
 | 
			
		||||
perustuu järjestämiseen,
 | 
			
		||||
koska järjestetyn tiedon
 | 
			
		||||
käsittely on helpompaa
 | 
			
		||||
kuin sekalaisessa järjestyksessä olevan.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi kysymys ''onko taulukossa kahta samaa
 | 
			
		||||
alkiota?'' ratkeaa tehokkaasti järjestämisen avulla.
 | 
			
		||||
Jos taulukossa on kaksi samaa alkiota,
 | 
			
		||||
ne ovat järjestämisen jälkeen peräkkäin,
 | 
			
		||||
jolloin niiden löytäminen on helppoa.
 | 
			
		||||
Samaan tapaan ratkeaa myös kysymys
 | 
			
		||||
''mikä on yleisin alkio taulukossa?''.
 | 
			
		||||
 | 
			
		||||
Järjestämiseen on kehitetty monia
 | 
			
		||||
algoritmeja, jotka tarjoavat hyviä
 | 
			
		||||
esimerkkejä algoritmien suunnittelun tekniikoista.
 | 
			
		||||
Tehokkaat yleiset järjestämis\-algoritmit
 | 
			
		||||
toimivat ajassa $O(n \log n)$, ja tämä aikavaativuus
 | 
			
		||||
on myös monella järjestämistä käyttävällä algoritmilla.
 | 
			
		||||
 | 
			
		||||
\section{Järjestämisen teoriaa}
 | 
			
		||||
 | 
			
		||||
Järjestämisen perusongelma on seuraava:
 | 
			
		||||
\begin{framed}
 | 
			
		||||
\noindent
 | 
			
		||||
Annettuna on taulukko, jossa on $n$ alkiota.
 | 
			
		||||
Tehtäväsi on järjestää alkiot pienimmästä
 | 
			
		||||
suurimpaan.
 | 
			
		||||
\end{framed}
 | 
			
		||||
\noindent
 | 
			
		||||
Esimerkiksi taulukko
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$8$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$9$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on järjestettynä seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$6$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{$O(n^2)$-algoritmit}
 | 
			
		||||
 | 
			
		||||
\index{kuplajxrjestxminen@kuplajärjestäminen}
 | 
			
		||||
 | 
			
		||||
Yksinkertaiset algoritmit taulukon
 | 
			
		||||
järjestämiseen vievät aikaa $O(n^2)$.
 | 
			
		||||
Tällaiset algoritmit ovat lyhyitä ja
 | 
			
		||||
muodostuvat tyypillisesti
 | 
			
		||||
kahdesta sisäkkäisestä silmukasta.
 | 
			
		||||
Tunnettu $O(n^2)$-aikainen algoritmi on
 | 
			
		||||
\key{kuplajärjestäminen},
 | 
			
		||||
jossa alkiot ''kuplivat'' eteenpäin taulukossa
 | 
			
		||||
niiden suuruuden perusteella.
 | 
			
		||||
 | 
			
		||||
Kuplajärjestäminen muodostuu $n-1$ kierroksesta,
 | 
			
		||||
joista jokainen käy taulukon läpi vasemmalta oikealle.
 | 
			
		||||
Aina kun taulukosta löytyy kaksi vierekkäistä
 | 
			
		||||
alkiota, joiden järjestys on väärä, algoritmi
 | 
			
		||||
korjaa niiden järjestyksen.
 | 
			
		||||
Algoritmin voi toteuttaa seuraavasti
 | 
			
		||||
taulukolle
 | 
			
		||||
$\texttt{t}[1],\texttt{t}[2],\ldots,\texttt{t}[n]$:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n-1; i++) {
 | 
			
		||||
    for (int j = 1; j <= n-i; j++) {
 | 
			
		||||
        if (t[j] > t[j+1]) swap(t[j],t[j+1]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmin ensimmäisen kierroksen jälkeen suurin
 | 
			
		||||
alkio on paikallaan, toisen kierroksen jälkeen
 | 
			
		||||
kaksi suurinta alkiota on paikallaan, jne.
 | 
			
		||||
Niinpä $n-1$ kierroksen jälkeen koko taulukko
 | 
			
		||||
on järjestyksessä.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi taulukossa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$8$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$9$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
kuplajärjestämisen ensimmäinen
 | 
			
		||||
läpikäynti tekee seuraavat vaihdot:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$8$};
 | 
			
		||||
\node at (4.5,0.5) {$9$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,<->] (3.5,-0.25) .. controls (3.25,-1.00) and (2.75,-1.00) .. (2.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$8$};
 | 
			
		||||
\node at (4.5,0.5) {$2$};
 | 
			
		||||
\node at (5.5,0.5) {$9$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,<->] (5.5,-0.25) .. controls (5.25,-1.00) and (4.75,-1.00) .. (4.5,-0.25);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$8$};
 | 
			
		||||
\node at (4.5,0.5) {$2$};
 | 
			
		||||
\node at (5.5,0.5) {$5$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,<->] (6.5,-0.25) .. controls (6.25,-1.00) and (5.75,-1.00) .. (5.5,-0.25);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$8$};
 | 
			
		||||
\node at (4.5,0.5) {$2$};
 | 
			
		||||
\node at (5.5,0.5) {$5$};
 | 
			
		||||
\node at (6.5,0.5) {$6$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,<->] (7.5,-0.25) .. controls (7.25,-1.00) and (6.75,-1.00) .. (6.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Inversiot}
 | 
			
		||||
 | 
			
		||||
\index{inversio@inversio}
 | 
			
		||||
 | 
			
		||||
Kuplajärjestäminen on esimerkki algoritmista,
 | 
			
		||||
joka perustuu taulukon vierekkäisten alkioiden
 | 
			
		||||
vaihtamiseen keskenään.
 | 
			
		||||
Osoittautuu, että tällaisen algoritmin
 | 
			
		||||
aikavaativuus on \emph{aina} vähintään $O(n^2)$,
 | 
			
		||||
koska pahimmassa tapauksessa taulukon
 | 
			
		||||
järjestäminen vaatii $O(n^2)$ alkioparin vaihtamista.
 | 
			
		||||
 | 
			
		||||
Hyödyllinen käsite järjestämisalgoritmien
 | 
			
		||||
analyysissa on \key{inversio}.
 | 
			
		||||
Se on taulukossa oleva alkiopari
 | 
			
		||||
$(\texttt{t}[a],\texttt{t}[b])$,
 | 
			
		||||
missä $a<b$ ja $\texttt{t}[a]>\texttt{t}[b]$
 | 
			
		||||
eli alkiot ovat väärässä järjestyksessä taulukossa.
 | 
			
		||||
Esimerkiksi taulukon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$5$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$8$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
inversiot ovat $(6,3)$, $(6,5)$ ja $(9,8)$.
 | 
			
		||||
Inversioiden määrä kuvaa, miten lähellä
 | 
			
		||||
järjestystä taulukko on.
 | 
			
		||||
Taulukko on järjestyksessä tarkalleen
 | 
			
		||||
silloin, kun siinä ei ole yhtään inversiota.
 | 
			
		||||
Inversioiden määrä on puolestaan suurin,
 | 
			
		||||
kun taulukon järjestys on käänteinen,
 | 
			
		||||
jolloin inversioita on
 | 
			
		||||
\[1+2+\cdots+(n-1)=\frac{n(n-1)}{2} = O(n^2).\]
 | 
			
		||||
 | 
			
		||||
Jos vierekkäiset taulukon alkiot
 | 
			
		||||
ovat väärässä järjestyksessä,
 | 
			
		||||
niiden järjestyksen korjaaminen
 | 
			
		||||
poistaa taulukosta tarkalleen yhden inversion.
 | 
			
		||||
Niinpä jos järjestämisalgoritmi pystyy
 | 
			
		||||
vaihtamaan keskenään vain
 | 
			
		||||
taulukon vierekkäisiä alkioita,
 | 
			
		||||
jokainen vaihto voi poistaa enintään yhden inversion
 | 
			
		||||
ja algoritmin aikavaativuus on varmasti ainakin $O(n^2)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{$O(n \log n)$-algoritmit}
 | 
			
		||||
 | 
			
		||||
\index{lomitusjxrjestxminen@lomitusjärjestäminen}
 | 
			
		||||
 | 
			
		||||
Taulukon järjestäminen on mahdollista
 | 
			
		||||
tehokkaasti ajassa $O(n \log n)$
 | 
			
		||||
algoritmilla, joka ei rajoitu vierekkäisten
 | 
			
		||||
alkoiden vaihtamiseen.
 | 
			
		||||
Yksi tällainen algoritmi on
 | 
			
		||||
\key{lomitusjärjestäminen},
 | 
			
		||||
joka järjestää taulukon
 | 
			
		||||
rekursiivisesti jakamalla sen
 | 
			
		||||
pienemmiksi osataulukoiksi.
 | 
			
		||||
 | 
			
		||||
Lomitusjärjestäminen järjestää taulukon välin
 | 
			
		||||
$[a,b]$ seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{enumerate}
 | 
			
		||||
\item Jos $a=b$, älä tee mitään, koska väli on valmiiksi järjestyksessä.
 | 
			
		||||
\item Valitse välin jakokohdaksi $k=\lfloor (a+b)/2 \rfloor$.
 | 
			
		||||
\item Järjestä rekursiivisesti välin $[a,k]$ alkiot.
 | 
			
		||||
\item Järjestä rekursiivisesti välin $[k+1,b]$ alkiot.
 | 
			
		||||
\item \emph{Lomita} järjestetyt välit $[a,k]$ ja $[k+1,b]$
 | 
			
		||||
järjestetyksi väliksi $[a,b]$.
 | 
			
		||||
\end{enumerate}
 | 
			
		||||
 | 
			
		||||
Lomitusjärjestämisen tehokkuus perustuu siihen,
 | 
			
		||||
että se puolittaa joka askeleella välin kahteen osaan.
 | 
			
		||||
Rekursiosta muodostuu yhteensä $O(\log n)$ tasoa
 | 
			
		||||
ja jokaisen tason käsittely vie aikaa $O(n)$.
 | 
			
		||||
Kohdan 5 lomittaminen on mahdollista ajassa $O(n)$,
 | 
			
		||||
koska välit $[a,k]$ ja $[k+1,b]$ on jo järjestetty.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä seuraavan taulukon
 | 
			
		||||
järjestämistä:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$8$};
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Taulukko jakautuu ensin kahdeksi
 | 
			
		||||
osataulukoksi seuraavasti:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (4,1);
 | 
			
		||||
\draw (5,0) grid (9,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\node at (5.5,0.5) {$8$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$5$};
 | 
			
		||||
\node at (8.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (5.5,1.4) {$5$};
 | 
			
		||||
\node at (6.5,1.4) {$6$};
 | 
			
		||||
\node at (7.5,1.4) {$7$};
 | 
			
		||||
\node at (8.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmi järjestää osataulukot rekursiivisesti,
 | 
			
		||||
jolloin tuloksena on:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (4,1);
 | 
			
		||||
\draw (5,0) grid (9,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$3$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
\node at (5.5,0.5) {$2$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$8$};
 | 
			
		||||
\node at (8.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (5.5,1.4) {$5$};
 | 
			
		||||
\node at (6.5,1.4) {$6$};
 | 
			
		||||
\node at (7.5,1.4) {$7$};
 | 
			
		||||
\node at (8.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Lopuksi algoritmi lomittaa järjestetyt osataulukot,
 | 
			
		||||
jolloin syntyy lopullinen järjestetty taulukko:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$6$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Järjestämisen alaraja}
 | 
			
		||||
 | 
			
		||||
Onko sitten mahdollista järjestää taulukkoa
 | 
			
		||||
nopeammin kuin ajassa $O(n \log n)$?
 | 
			
		||||
Osoittautuu, että tämä \emph{ei} ole mahdollista,
 | 
			
		||||
kun rajoitumme
 | 
			
		||||
järjestämis\-algoritmeihin,
 | 
			
		||||
jotka perustuvat taulukon alkioiden
 | 
			
		||||
vertailemiseen.
 | 
			
		||||
 | 
			
		||||
Aikavaativuuden alaraja on mahdollista todistaa
 | 
			
		||||
tarkastelemalla järjestämistä
 | 
			
		||||
prosessina, jossa jokainen kahden alkion vertailu
 | 
			
		||||
antaa lisää tietoa taulukon sisällöstä.
 | 
			
		||||
Prosessista muodostuu seuraavanlainen puu:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) rectangle (3,1);
 | 
			
		||||
\node at (1.5,0.5) {$x < y?$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (1.5,0) -- (-2.5,-1.5);
 | 
			
		||||
\draw[thick,->] (1.5,0) -- (5.5,-1.5);
 | 
			
		||||
 | 
			
		||||
\draw (-4,-2.5) rectangle (-1,-1.5);
 | 
			
		||||
\draw (4,-2.5) rectangle (7,-1.5);
 | 
			
		||||
\node at (-2.5,-2) {$x < y?$};
 | 
			
		||||
\node at (5.5,-2) {$x < y?$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (-2.5,-2.5) -- (-4.5,-4);
 | 
			
		||||
\draw[thick,->] (-2.5,-2.5) -- (-0.5,-4);
 | 
			
		||||
\draw[thick,->] (5.5,-2.5) -- (3.5,-4);
 | 
			
		||||
\draw[thick,->] (5.5,-2.5) -- (7.5,-4);
 | 
			
		||||
 | 
			
		||||
\draw (-6,-5) rectangle (-3,-4);
 | 
			
		||||
\draw (-2,-5) rectangle (1,-4);
 | 
			
		||||
\draw (2,-5) rectangle (5,-4);
 | 
			
		||||
\draw (6,-5) rectangle (9,-4);
 | 
			
		||||
\node at (-4.5,-4.5) {$x < y?$};
 | 
			
		||||
\node at (-0.5,-4.5) {$x < y?$};
 | 
			
		||||
\node at (3.5,-4.5) {$x < y?$};
 | 
			
		||||
\node at (7.5,-4.5) {$x < y?$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (-4.5,-5) -- (-5.5,-6);
 | 
			
		||||
\draw[thick,->] (-4.5,-5) -- (-3.5,-6);
 | 
			
		||||
\draw[thick,->] (-0.5,-5) -- (0.5,-6);
 | 
			
		||||
\draw[thick,->] (-0.5,-5) -- (-1.5,-6);
 | 
			
		||||
\draw[thick,->] (3.5,-5) -- (2.5,-6);
 | 
			
		||||
\draw[thick,->] (3.5,-5) -- (4.5,-6);
 | 
			
		||||
\draw[thick,->] (7.5,-5) -- (6.5,-6);
 | 
			
		||||
\draw[thick,->] (7.5,-5) -- (8.5,-6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Merkintä ''$x<y?$'' tarkoittaa taulukon alkioiden
 | 
			
		||||
$x$ ja $y$ vertailua.
 | 
			
		||||
Jos $x<y$, prosessi jatkaa vasemmalle,
 | 
			
		||||
ja muuten oikealle.
 | 
			
		||||
Prosessin tulokset ovat taulukon mahdolliset
 | 
			
		||||
järjestykset, joita on kaikkiaan $n!$ erilaista.
 | 
			
		||||
Puun korkeuden tulee olla tämän vuoksi vähintään
 | 
			
		||||
\[ \log_2(n!) = \log_2(1)+\log_2(2)+\cdots+\log_2(n).\]
 | 
			
		||||
Voimme arvioida tätä summaa alaspäin
 | 
			
		||||
valitsemalla summasta $n/2$
 | 
			
		||||
viimeistä termiä ja muuttamalla kunkin
 | 
			
		||||
termin arvoksi $\log_2(n/2)$.
 | 
			
		||||
Tästä saadaan arvio
 | 
			
		||||
\[ \log_2(n!) \ge (n/2) \cdot \log_2(n/2),\]
 | 
			
		||||
eli puun korkeus ja sen myötä
 | 
			
		||||
pienin mahdollinen järjestämisalgoritmin askelten
 | 
			
		||||
määrä on pahimmassa tapauksessa ainakin luokkaa $n \log n$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laskemisjärjestäminen}
 | 
			
		||||
 | 
			
		||||
\index{laskemisjxrjestxminen@laskemisjärjestäminen}
 | 
			
		||||
 | 
			
		||||
Järjestämisen alaraja $n \log n$ ei koske algoritmeja,
 | 
			
		||||
jotka eivät perustu alkioiden vertailemiseen
 | 
			
		||||
vaan hyödyntävät jotain muuta tietoa alkioista.
 | 
			
		||||
Esimerkki tällaisesta algoritmista on
 | 
			
		||||
\key{laskemisjärjestäminen}, jonka avulla
 | 
			
		||||
on mahdollista järjestää
 | 
			
		||||
taulukko ajassa $O(n)$ olettaen,
 | 
			
		||||
että jokainen taulukon alkio on
 | 
			
		||||
kokonaisluku välillä $0 \ldots c$,
 | 
			
		||||
missä $c$ on pieni vakio.
 | 
			
		||||
 | 
			
		||||
Algoritmin ideana on luoda \emph{kirjanpito}, josta selviää,
 | 
			
		||||
montako kertaa mikäkin alkio esiintyy taulukossa.
 | 
			
		||||
Kirjanpito on taulukko, jonka indeksit ovat alkuperäisen
 | 
			
		||||
taulukon alkioita.
 | 
			
		||||
Jokaisen indeksin kohdalla lukee, montako kertaa
 | 
			
		||||
kyseinen alkio esiintyy alkuperäisessä taulukossa.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi taulukosta
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$9$};
 | 
			
		||||
\node at (4.5,0.5) {$9$};
 | 
			
		||||
\node at (5.5,0.5) {$3$};
 | 
			
		||||
\node at (6.5,0.5) {$5$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
syntyy seuraava kirjanpito:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (9,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$0$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$0$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$0$};
 | 
			
		||||
\node at (7.5,0.5) {$0$};
 | 
			
		||||
\node at (8.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
 | 
			
		||||
\node at (0.5,1.5) {$1$};
 | 
			
		||||
\node at (1.5,1.5) {$2$};
 | 
			
		||||
\node at (2.5,1.5) {$3$};
 | 
			
		||||
\node at (3.5,1.5) {$4$};
 | 
			
		||||
\node at (4.5,1.5) {$5$};
 | 
			
		||||
\node at (5.5,1.5) {$6$};
 | 
			
		||||
\node at (6.5,1.5) {$7$};
 | 
			
		||||
\node at (7.5,1.5) {$8$};
 | 
			
		||||
\node at (8.5,1.5) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi kirjanpidossa lukee indeksin 3 kohdalla 2,
 | 
			
		||||
koska luku 3 esiintyy kahdesti alkuperäisessä
 | 
			
		||||
taulukossa (indekseissä 2 ja 6).
 | 
			
		||||
 | 
			
		||||
Kirjanpidon muodostus vie aikaa $O(n)$,
 | 
			
		||||
koska riittää käydä taulukko läpi kerran.
 | 
			
		||||
Tämän jälkeen järjestetyn taulukon luominen
 | 
			
		||||
vie myös aikaa $O(n)$, koska kunkin alkion
 | 
			
		||||
määrän saa selville suoraan kirjanpidosta.
 | 
			
		||||
Niinpä laskemisjärjestämisen
 | 
			
		||||
kokonaisaikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
Laskemisjärjestäminen on hyvin tehokas algoritmi,
 | 
			
		||||
mutta sen käyttäminen vaatii,
 | 
			
		||||
että vakio $c$ on niin pieni,
 | 
			
		||||
että taulukon alkioita voi käyttää
 | 
			
		||||
kirjanpidon taulukon indeksöinnissä.
 | 
			
		||||
 | 
			
		||||
\section{Järjestäminen C++:ssa}
 | 
			
		||||
 | 
			
		||||
\index{sort@\texttt{sort}}
 | 
			
		||||
 | 
			
		||||
Järjestämisalgoritmia
 | 
			
		||||
ei yleensä koskaan kannata koodata itse,
 | 
			
		||||
vaan on parempi ratkaisu käyttää
 | 
			
		||||
ohjelmointikielen valmista toteutusta.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
C++:n standardikirjastossa on funktio \texttt{sort},
 | 
			
		||||
jonka avulla voi järjestää helposti taulukoita
 | 
			
		||||
ja muita tietorakenteita.
 | 
			
		||||
 | 
			
		||||
Valmiin toteutuksen käyttämisessä on monia etuja.
 | 
			
		||||
Ensinnäkin se säästää aikaa, koska järjestämisalgoritmia
 | 
			
		||||
ei tarvitse kirjoittaa itse.
 | 
			
		||||
Lisäksi valmis toteutus on varmasti tehokas ja toimiva:
 | 
			
		||||
vaikka järjestämisalgoritmin toteuttaisi itse,
 | 
			
		||||
siitä tulisi tuskin valmista toteutusta parempi.
 | 
			
		||||
 | 
			
		||||
Tutustumme seuraavaksi C++:n \texttt{sort}-funktion
 | 
			
		||||
käyttämiseen.
 | 
			
		||||
Seuraava koodi järjestää vektorin \texttt{v}
 | 
			
		||||
luvut pienimmästä suurimpaan:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v = {4,2,5,3,5,8,3};
 | 
			
		||||
sort(v.begin(),v.end());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Järjestämisen seurauksena
 | 
			
		||||
vektorin sisällöksi tulee
 | 
			
		||||
$\{2,3,3,4,5,5,8\}$.
 | 
			
		||||
Oletuksena \texttt{sort}-funktio järjestää
 | 
			
		||||
siis alkiot pienimmästä suurimpaan,
 | 
			
		||||
mutta järjestyksen saa käänteiseksi näin:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
sort(v.rbegin(),v.rend());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tavallisen taulukon voi järjestää seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int n = 7; // taulukon koko
 | 
			
		||||
int t[] = {4,2,5,3,5,8,3};
 | 
			
		||||
sort(t,t+n);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Seuraava koodi taas järjestää merkkijonon \texttt{s}:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
string s = "apina";
 | 
			
		||||
sort(s.begin(), s.end());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Merkkijonon järjestäminen tarkoittaa,
 | 
			
		||||
että sen merkit järjestetään aakkosjärjestykseen.
 | 
			
		||||
Esimerkiksi merkkijono ''apina''
 | 
			
		||||
on järjestettynä ''aainp''.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Vertailuoperaattori}
 | 
			
		||||
 | 
			
		||||
\index{vertailuoperaattori@vertailuoperaattori}
 | 
			
		||||
 | 
			
		||||
Funktion \texttt{sort} käyttäminen vaatii,
 | 
			
		||||
että järjestettävien alkioiden
 | 
			
		||||
tietotyypille on määritelty \key{vertailuoperaattori} \texttt{<},
 | 
			
		||||
jonka avulla voi selvittää, mikä on kahden alkion järjestys.
 | 
			
		||||
Järjestämisen aikana \texttt{sort}-funktio
 | 
			
		||||
käyttää operaattoria \texttt{<} aina, kun sen täytyy
 | 
			
		||||
vertailla järjestettäviä alkioita.
 | 
			
		||||
 | 
			
		||||
Vertailuoperaattori on määritelty valmiiksi
 | 
			
		||||
useimmille C++:n tietotyypeille,
 | 
			
		||||
minkä ansiosta niitä pystyy järjestämään automaattisesti.
 | 
			
		||||
Jos järjestettävänä on lukuja, ne järjestyvät
 | 
			
		||||
suuruusjärjestykseen,
 | 
			
		||||
ja jos järjestettävänä on merkkijonoja,
 | 
			
		||||
ne järjestyvät aakkosjärjestykseen.
 | 
			
		||||
 | 
			
		||||
\index{pair@\texttt{pair}}
 | 
			
		||||
 | 
			
		||||
Parit (\texttt{pair}) järjestyvät ensisijaisesti
 | 
			
		||||
ensimmäisen kentän (\texttt{first}) mukaan.
 | 
			
		||||
Jos kuitenkin parien ensimmäiset kentät ovat samat,
 | 
			
		||||
järjestys määräytyy toisen kentän (\texttt{second}) mukaan:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<pair<int,int>> v;
 | 
			
		||||
v.push_back({1,5});
 | 
			
		||||
v.push_back({2,3});
 | 
			
		||||
v.push_back({1,2});
 | 
			
		||||
sort(v.begin(), v.end());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän seurauksena parien järjestys on
 | 
			
		||||
$(1,2)$, $(1,5)$ ja $(2,3)$.
 | 
			
		||||
 | 
			
		||||
\index{tuple@\texttt{tuple}}
 | 
			
		||||
Vastaavasti \texttt{tuple}-rakenteet
 | 
			
		||||
järjestyvät ensisijaisesti ensimmäisen kentän,
 | 
			
		||||
toissijaisesti toisen kentän, jne., mukaan:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<tuple<int,int,int>> v;
 | 
			
		||||
v.push_back(make_tuple(2,1,4));
 | 
			
		||||
v.push_back(make_tuple(1,5,3));
 | 
			
		||||
v.push_back(make_tuple(2,1,3));
 | 
			
		||||
sort(v.begin(), v.end());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän seurauksena järjestys on
 | 
			
		||||
$(1,5,3)$, $(2,1,3)$ ja $(2,1,4)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Omat tietueet}
 | 
			
		||||
 | 
			
		||||
Jos järjestettävänä on omia tietueita,
 | 
			
		||||
niiden vertailuoperaattori täytyy toteuttaa itse.
 | 
			
		||||
Operaattori määritellään tietueen sisään
 | 
			
		||||
\texttt{operator<}-nimisenä funktiona,
 | 
			
		||||
jonka parametrina on toinen alkio.
 | 
			
		||||
Operaattorin tulee palauttaa \texttt{true},
 | 
			
		||||
jos oma alkio on pienempi kuin parametrialkio,
 | 
			
		||||
ja muuten \texttt{false}.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraava tietue \texttt{P}
 | 
			
		||||
sisältää pisteen x- ja y-koordinaatit.
 | 
			
		||||
Vertailuoperaattori on toteutettu niin,
 | 
			
		||||
että pisteet järjestyvät ensisijaisesti x-koor\-di\-naa\-tin
 | 
			
		||||
ja toissijaisesti y-koordinaatin mukaan.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
struct P {
 | 
			
		||||
    int x, y;
 | 
			
		||||
    bool operator<(const P &p) {
 | 
			
		||||
        if (x != p.x) return x < p.x;
 | 
			
		||||
        else return y < p.y;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Vertailufunktio}
 | 
			
		||||
 | 
			
		||||
\index{vertailufunktio@vertailufunktio}
 | 
			
		||||
 | 
			
		||||
On myös mahdollista antaa
 | 
			
		||||
\texttt{sort}-funktiolle ulkopuolinen \key{vertailufunktio}.
 | 
			
		||||
Esimerkiksi seuraava vertailufunktio
 | 
			
		||||
järjestää merkkijonot ensisijaisesti pituuden mukaan
 | 
			
		||||
ja toissijaisesti aakkosjärjestyksen mukaan:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bool cmp(string a, string b) {
 | 
			
		||||
    if (a.size() != b.size()) return a.size() < b.size();
 | 
			
		||||
    return a < b;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Tämän jälkeen merkkijonovektorin voi järjestää näin:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
sort(v.begin(), v.end(), cmp);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Binäärihaku}
 | 
			
		||||
 | 
			
		||||
\index{binxxrihaku@binäärihaku}
 | 
			
		||||
 | 
			
		||||
Tavallinen tapa etsiä alkiota taulukosta
 | 
			
		||||
on käyttää \texttt{for}-silmukkaa, joka käy läpi
 | 
			
		||||
taulukon sisällön alusta loppuun.
 | 
			
		||||
Esimerkiksi seuraava koodi etsii alkiota
 | 
			
		||||
$x$ taulukosta \texttt{t}.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    if (t[i] == x) // alkio x löytyi kohdasta i
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tämän menetelmän aikavaativuus on $O(n)$,
 | 
			
		||||
koska pahimmassa tapauksessa koko taulukko täytyy
 | 
			
		||||
käydä läpi.
 | 
			
		||||
Jos taulukon sisältö voi olla mikä tahansa,
 | 
			
		||||
tämä on kuitenkin tehokkain mahdollinen menetelmä,
 | 
			
		||||
koska saatavilla ei ole lisätietoa siitä,
 | 
			
		||||
mistä päin taulukkoa alkiota $x$ kannattaa etsiä.
 | 
			
		||||
 | 
			
		||||
Tilanne on toinen silloin, kun taulukko on
 | 
			
		||||
järjestyksessä.
 | 
			
		||||
Tässä tapauksessa haku on mahdollista toteuttaa
 | 
			
		||||
paljon nopeammin, koska taulukon järjestys
 | 
			
		||||
ohjaa etsimään alkiota oikeasta suunnasta.
 | 
			
		||||
Seuraavaksi käsiteltävä \key{binäärihaku}
 | 
			
		||||
löytää alkion järjestetystä taulukosta
 | 
			
		||||
tehokkaasti ajassa $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus 1}
 | 
			
		||||
 | 
			
		||||
Perinteinen tapa toteuttaa binäärihaku muistuttaa sanan etsimistä
 | 
			
		||||
sanakirjasta. Haku puolittaa joka askeleella hakualueen taulukossa,
 | 
			
		||||
kunnes lopulta etsittävä alkio löytyy tai osoittautuu,
 | 
			
		||||
että sitä ei ole taulukossa.
 | 
			
		||||
 | 
			
		||||
Haku tarkistaa ensin taulukon keskimmäisen alkion.
 | 
			
		||||
Jos keskimmäinen alkio on etsittävä alkio, haku päättyy.
 | 
			
		||||
Muuten haku jatkuu taulukon vasempaan tai oikeaan osaan sen mukaan,
 | 
			
		||||
onko keskimmäinen alkio suurempi vain pienempi kuin etsittävä alkio.
 | 
			
		||||
 | 
			
		||||
Yllä olevan idean voi toteuttaa seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int a = 1, b = n;
 | 
			
		||||
while (a <= b) {
 | 
			
		||||
    int k = (a+b)/2;
 | 
			
		||||
    if (t[k] == x) // alkio x löytyi kohdasta k
 | 
			
		||||
    if (t[k] > x) b = k-1;
 | 
			
		||||
    else a = k+1;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmi pitää yllä väliä $a \ldots b$, joka on
 | 
			
		||||
jäljellä oleva hakualue taulukossa.
 | 
			
		||||
Aluksi väli on $1 \ldots n$ eli koko taulukko.
 | 
			
		||||
Välin koko puolittuu algoritmin joka vaiheessa,
 | 
			
		||||
joten aikavaativuus on $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus 2}
 | 
			
		||||
 | 
			
		||||
Vaihtoehtoinen tapa toteuttaa binäärihaku
 | 
			
		||||
perustuu taulukon tehostettuun läpikäyntiin.
 | 
			
		||||
Ideana on käydä taulukkoa läpi hyppien
 | 
			
		||||
ja hidastaa vauhtia, kun etsittävä alkio lähestyy.
 | 
			
		||||
 | 
			
		||||
Haku käy taulukkoa läpi vasemmalta oikealle aloittaen
 | 
			
		||||
hypyn pituudesta $n/2$.
 | 
			
		||||
Joka vaiheessa hypyn pituus puolittuu:
 | 
			
		||||
ensin $n/4$, sitten $n/8$, sitten $n/16$ jne.,
 | 
			
		||||
kunnes lopulta hypyn pituus on 1.
 | 
			
		||||
Hyppyjen jälkeen joko haettava alkio on löytynyt
 | 
			
		||||
tai selviää, että sitä ei ole taulukossa.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi toteuttaa äskeisen idean:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int k = 1;
 | 
			
		||||
for (int b = n/2; b >= 1; b /= 2) {
 | 
			
		||||
    while (k+b <= n && t[k+b] <= x) k += b;
 | 
			
		||||
}
 | 
			
		||||
if (t[k] == x) // alkio x löytyi kohdasta k
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Muuttuja $k$ on läpikäynnin kohta taulukossa
 | 
			
		||||
ja muuttuja $b$ on hypyn pituus.
 | 
			
		||||
Jos alkio $x$ esiintyy taulukossa,
 | 
			
		||||
sen kohta on muuttujassa $k$ algoritmin päätteeksi.
 | 
			
		||||
Algoritmin aikavaativuus on $O(\log n)$,
 | 
			
		||||
koska \texttt{while}-silmukassa oleva koodi suoritetaan
 | 
			
		||||
aina enintään kahdesti.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Muutoskohdan etsiminen}
 | 
			
		||||
 | 
			
		||||
Käytännössä binäärihakua tarvitsee toteuttaa
 | 
			
		||||
harvoin alkion etsimiseen taulukosta,
 | 
			
		||||
koska sen sijasta voi käyttää standardikirjastoa.
 | 
			
		||||
Esimerkiksi C++:n funktiot \texttt{lower\_bound}
 | 
			
		||||
ja \texttt{upper\_bound} toteuttavat binäärihaun
 | 
			
		||||
ja tietorakenne \texttt{set} ylläpitää joukkoa,
 | 
			
		||||
jonka operaatiot ovat $O(\log n)$-aikaisia.
 | 
			
		||||
 | 
			
		||||
Sitäkin tärkeämpi binäärihaun käyttökohde on 
 | 
			
		||||
funktion \key{muutoskohdan} etsiminen.
 | 
			
		||||
Oletetaan, että haluamme löytää pienimmän arvon $k$,
 | 
			
		||||
joka on kelvollinen ratkaisu ongelmaan.
 | 
			
		||||
Käytössämme on funktio $\texttt{ok}(x)$,
 | 
			
		||||
joka palauttaa \texttt{true}, jos $x$ on kelvollinen
 | 
			
		||||
ratkaisu, ja muuten \texttt{false}.
 | 
			
		||||
Lisäksi tiedämme, että $\texttt{ok}(x)$ on \texttt{false}
 | 
			
		||||
aina kun $x<k$ ja \texttt{true} aina kun $x \geq k$.
 | 
			
		||||
% Toisin sanoen haluamme löytää funktion \texttt{ok} \emph{muutoskohdan},
 | 
			
		||||
% jossa arvosta \texttt{false} tulee arvo \texttt{true}.
 | 
			
		||||
Tilanne näyttää seuraavalta:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrrrrr}
 | 
			
		||||
$x$ & 0 & 1 & $\cdots$ & $k-1$ & $k$ & $k+1$ & $\cdots$ \\
 | 
			
		||||
\hline
 | 
			
		||||
$\texttt{ok}(x)$ & \texttt{false} & \texttt{false}
 | 
			
		||||
& $\cdots$ & \texttt{false} & \texttt{true} & \texttt{true} & $\cdots$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Nyt muutoskohta on mahdollista etsiä käyttämällä
 | 
			
		||||
binäärihakua:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int x = -1;
 | 
			
		||||
for (int b = z; b >= 1; b /= 2) {
 | 
			
		||||
    while (!ok(x+b)) x += b;
 | 
			
		||||
}
 | 
			
		||||
int k = x+1;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Haku etsii suurimman $x$:n arvon,
 | 
			
		||||
jolla $\texttt{ok}(x)$ on \texttt{false}.
 | 
			
		||||
Niinpä tästä seuraava arvo $k=x+1$
 | 
			
		||||
on pienin arvo, jolla $\texttt{ok}(k)$ on \texttt{true}.
 | 
			
		||||
Hypyn aloituspituus $z$ tulee olla 
 | 
			
		||||
sopiva suuri luku, esimerkiksi sellainen,
 | 
			
		||||
jolla $\texttt{ok}(z)$ on varmasti \texttt{true}.
 | 
			
		||||
 | 
			
		||||
Algoritmi kutsuu $O(\log z)$ kertaa funktiota
 | 
			
		||||
\texttt{ok}, joten kokonaisaikavaativuus
 | 
			
		||||
riippuu siitä, kauanko funktion \texttt{ok}
 | 
			
		||||
suoritus kestää.
 | 
			
		||||
Esimerkiksi jos ratkaisun tarkastus
 | 
			
		||||
vie aikaa $O(n)$, niin kokonaisaikavaativuus
 | 
			
		||||
on $O(n \log z)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Huippuarvon etsiminen}
 | 
			
		||||
 | 
			
		||||
Binäärihaulla voi myös etsiä
 | 
			
		||||
suurimman arvon funktiolle,
 | 
			
		||||
joka on ensin kasvava ja sitten laskeva.
 | 
			
		||||
Toisin sanoen tehtävänä on etsiä arvo
 | 
			
		||||
$k$ niin, että
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
$f(x)<f(x+1)$, kun $x<k$, ja
 | 
			
		||||
\item
 | 
			
		||||
$f(x)>f(x+1)$, kun $x >= k$.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Ideana on etsiä binäärihaulla
 | 
			
		||||
viimeinen kohta $x$,
 | 
			
		||||
jossa pätee $f(x)<f(x+1)$.
 | 
			
		||||
Tällöin $k=x+1$,
 | 
			
		||||
koska pätee $f(x+1)>f(x+2)$.
 | 
			
		||||
Seuraava koodi toteuttaa haun: 
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int x = -1;
 | 
			
		||||
for (int b = z; b >= 1; b /= 2) {
 | 
			
		||||
    while (f(x+b) < f(x+b+1)) x += b;
 | 
			
		||||
}
 | 
			
		||||
int k = x+1;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Huomaa, että toisin kuin tavallisessa binäärihaussa,
 | 
			
		||||
tässä ei ole sallittua,
 | 
			
		||||
että peräkkäiset arvot olisivat yhtä suuria.
 | 
			
		||||
Silloin ei olisi mahdollista tietää,
 | 
			
		||||
mihin suuntaan hakua tulee jatkaa.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,774 @@
 | 
			
		|||
\chapter{Data structures}
 | 
			
		||||
 | 
			
		||||
\index{tietorakenne@tietorakenne}
 | 
			
		||||
 | 
			
		||||
\key{Tietorakenne}
 | 
			
		||||
on tapa säilyttää tietoa tietokoneen muistissa.
 | 
			
		||||
Sopivan tietorakenteen valinta on tärkeää,
 | 
			
		||||
koska kullakin rakenteella on omat
 | 
			
		||||
vahvuutensa ja heikkoutensa.
 | 
			
		||||
Tietorakenteen valinnassa oleellinen kysymys on,
 | 
			
		||||
mitkä operaatiot rakenne toteuttaa tehokkaasti.
 | 
			
		||||
 | 
			
		||||
Tämä luku esittelee keskeisimmät
 | 
			
		||||
C++:n standardikirjaston tietorakenteet.
 | 
			
		||||
Valmiita tietorakenteita kannattaa käyttää
 | 
			
		||||
aina kun mahdollista, 
 | 
			
		||||
koska se säästää paljon aikaa toteutuksessa.
 | 
			
		||||
Myöhemmin kirjassa tutustumme erikoisempiin
 | 
			
		||||
rakenteisiin, joita ei ole valmiina C++:ssa.
 | 
			
		||||
 | 
			
		||||
\section{Dynaaminen taulukko}
 | 
			
		||||
 | 
			
		||||
\index{vektori@vektori}
 | 
			
		||||
\index{vector@\texttt{vector}}
 | 
			
		||||
 | 
			
		||||
\key{Dynaaminen taulukko} on taulukko,
 | 
			
		||||
jonka kokoa voi muuttaa
 | 
			
		||||
ohjelman suorituksen aikana.
 | 
			
		||||
C++:n tavallisin dynaaminen taulukko
 | 
			
		||||
on \key{vektori} (\texttt{vector}).
 | 
			
		||||
Sitä voi käyttää hyvin samalla tavalla
 | 
			
		||||
kuin tavallista taulukkoa.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi luo tyhjän vektorin
 | 
			
		||||
ja lisää siihen kolme lukua:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v;
 | 
			
		||||
v.push_back(3); // [3]
 | 
			
		||||
v.push_back(2); // [3,2]
 | 
			
		||||
v.push_back(5); // [3,2,5]
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen vektorin sisältöä voi käsitellä taulukon tavoin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
cout << v[0] << "\n"; // 3
 | 
			
		||||
cout << v[1] << "\n"; // 2
 | 
			
		||||
cout << v[2] << "\n"; // 5
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{size} kertoo, montako alkiota vektorissa on.
 | 
			
		||||
Seuraava koodi käy läpi ja tulostaa kaikki vektorin alkiot:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 0; i < v.size(); i++) {
 | 
			
		||||
    cout << v[i] << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Vektorin voi käydä myös läpi lyhyemmin näin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (auto x : v) {
 | 
			
		||||
    cout << x << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{back} hakee vektorin viimeisen alkion,
 | 
			
		||||
ja funktio \texttt{pop\_back} poistaa vektorin
 | 
			
		||||
viimeisen alkion:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v;
 | 
			
		||||
v.push_back(5);
 | 
			
		||||
v.push_back(2);
 | 
			
		||||
cout << v.back() << "\n"; // 2
 | 
			
		||||
v.pop_back();
 | 
			
		||||
cout << v.back() << "\n"; // 5
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Vektorin sisällön voi antaa myös sen luonnissa:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v = {2,4,2,5,1};
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Kolmas tapa luoda vektori on ilmoittaa
 | 
			
		||||
vektorin koko ja alkuarvo:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
// koko 10, alkuarvo 0
 | 
			
		||||
vector<int> v(10);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
// koko 10, alkuarvo 5
 | 
			
		||||
vector<int> v(10, 5);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Vektori on toteutettu sisäisesti tavallisena taulukkona.
 | 
			
		||||
Jos vektorin koko kasvaa ja taulukko jää liian pieneksi,
 | 
			
		||||
varataan uusi suurempi taulukko, johon kopioidaan
 | 
			
		||||
vektorin sisältö.
 | 
			
		||||
Näin tapahtuu kuitenkin niin harvoin, että vektorin
 | 
			
		||||
funktion \texttt{push\_back} aikavaativuus on
 | 
			
		||||
keskimäärin $O(1)$.
 | 
			
		||||
 | 
			
		||||
\index{merkkijono@merkkijono}
 | 
			
		||||
\index{string@\texttt{string}}
 | 
			
		||||
 | 
			
		||||
Myös \key{merkkijono} (\texttt{string}) on dynaaminen taulukko,
 | 
			
		||||
jota pystyy käsittelemään lähes samaan
 | 
			
		||||
tapaan kuin vektoria.
 | 
			
		||||
Merkkijonon käsittelyyn liittyy lisäksi erikoissyntaksia
 | 
			
		||||
ja funktioita, joita ei ole muissa tietorakenteissa.
 | 
			
		||||
Merkkijonoja voi yhdistää toisiinsa \texttt{+}-merkin avulla.
 | 
			
		||||
Funktio $\texttt{substr}(k,x)$ erottaa merkkijonosta
 | 
			
		||||
osajonon, joka alkaa kohdasta $k$ ja jonka pituus on $x$.
 | 
			
		||||
Funktio $\texttt{find}(\texttt{t})$ etsii kohdan,
 | 
			
		||||
jossa osajono \texttt{t} esiintyy merkkijonossa.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi esittelee merkkijonon käyttämistä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
string a = "hatti";
 | 
			
		||||
string b = a+a;
 | 
			
		||||
cout << b << "\n"; // hattihatti
 | 
			
		||||
b[5] = 'v';
 | 
			
		||||
cout << b << "\n"; // hattivatti
 | 
			
		||||
string c = b.substr(3,4);
 | 
			
		||||
cout << c << "\n"; // tiva
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Joukkorakenne}
 | 
			
		||||
 | 
			
		||||
\index{joukko@joukko}
 | 
			
		||||
\index{set@\texttt{set}}
 | 
			
		||||
\index{unordered\_set@\texttt{unordered\_set}}
 | 
			
		||||
 | 
			
		||||
\key{Joukko} on tietorakenne,
 | 
			
		||||
joka sisältää kokoelman alkioita.
 | 
			
		||||
Joukon perusoperaatiot ovat alkion lisäys,
 | 
			
		||||
haku ja poisto.
 | 
			
		||||
 | 
			
		||||
C++ sisältää kaksi
 | 
			
		||||
toteutusta joukolle: \texttt{set} ja \texttt{unordered\_set}.
 | 
			
		||||
Rakenne \texttt{set} perustuu tasapainoiseen
 | 
			
		||||
binääripuuhun, ja sen operaatioiden aikavaativuus
 | 
			
		||||
on $O(\log n)$.
 | 
			
		||||
Rakenne \texttt{unordered\_set} pohjautuu hajautustauluun,
 | 
			
		||||
ja sen operaatioiden aikavaativuus on keskimäärin $O(1)$.
 | 
			
		||||
 | 
			
		||||
Usein on makuasia, kumpaa joukon toteutusta käyttää.
 | 
			
		||||
Rakenteen \texttt{set} etuna on, että se säilyttää
 | 
			
		||||
joukon alkioita järjestyksessä ja tarjoaa
 | 
			
		||||
järjestykseen liittyviä funktioita,
 | 
			
		||||
joita \texttt{unordered\_set} ei sisällä.
 | 
			
		||||
Toisaalta \texttt{unordered\_set} on usein nopeampi rakenne.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi luo lukuja sisältävän joukon ja
 | 
			
		||||
esittelee sen käyttämistä.
 | 
			
		||||
Funktio \texttt{insert} lisää joukkoon alkion,
 | 
			
		||||
funktio \texttt{count} laskee alkion määrän joukossa
 | 
			
		||||
ja funktio \texttt{erase} poistaa alkion joukosta.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
set<int> s;
 | 
			
		||||
s.insert(3);
 | 
			
		||||
s.insert(2);
 | 
			
		||||
s.insert(5);
 | 
			
		||||
cout << s.count(3) << "\n"; // 1
 | 
			
		||||
cout << s.count(4) << "\n"; // 0
 | 
			
		||||
s.erase(3);
 | 
			
		||||
s.insert(4);
 | 
			
		||||
cout << s.count(3) << "\n"; // 0
 | 
			
		||||
cout << s.count(4) << "\n"; // 1
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Joukkoa voi käsitellä muuten suunnilleen samalla tavalla
 | 
			
		||||
kuin vektoria, mutta joukkoa ei voi indeksoida
 | 
			
		||||
\texttt{[]}-merkinnällä.
 | 
			
		||||
Seuraava koodi luo joukon, tulostaa sen 
 | 
			
		||||
alkioiden määrän ja käy sitten läpi kaikki alkiot.
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
set<int> s = {2,5,6,8};
 | 
			
		||||
cout << s.size() << "\n"; // 4
 | 
			
		||||
for (auto x : s) {
 | 
			
		||||
    cout << x << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tärkeä joukon ominaisuus on,
 | 
			
		||||
että tietty alkio voi esiintyä siinä
 | 
			
		||||
enintään kerran.
 | 
			
		||||
Niinpä funktio \texttt{count} palauttaa aina
 | 
			
		||||
arvon 0 (alkiota ei ole joukossa) tai 1 (alkio on joukossa)
 | 
			
		||||
ja funktio \texttt{insert} ei lisää alkiota
 | 
			
		||||
uudestaan joukkoon, jos se on siellä valmiina.
 | 
			
		||||
Seuraava koodi havainnollistaa asiaa:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
set<int> s;
 | 
			
		||||
s.insert(5);
 | 
			
		||||
s.insert(5);
 | 
			
		||||
s.insert(5);
 | 
			
		||||
cout << s.count(5) << "\n"; // 1
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\index{multiset@\texttt{multiset}}
 | 
			
		||||
\index{unordered\_multiset@\texttt{unordered\_multiset}}
 | 
			
		||||
 | 
			
		||||
C++ sisältää myös rakenteet
 | 
			
		||||
\texttt{multiset} ja \texttt{unordered\_multiset},
 | 
			
		||||
jotka toimivat muuten samalla tavalla kuin \texttt{set}
 | 
			
		||||
ja \texttt{unordered\_set},
 | 
			
		||||
mutta sama alkio voi esiintyä
 | 
			
		||||
monta kertaa joukossa.
 | 
			
		||||
Esimerkiksi seuraavassa koodissa
 | 
			
		||||
kaikki luvun 5 kopiot lisätään joukkoon:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
multiset<int> s;
 | 
			
		||||
s.insert(5);
 | 
			
		||||
s.insert(5);
 | 
			
		||||
s.insert(5);
 | 
			
		||||
cout << s.count(5) << "\n"; // 3
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Funktio \texttt{erase} poistaa
 | 
			
		||||
kaikki alkion esiintymät
 | 
			
		||||
\texttt{multiset}-rakenteessa:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
s.erase(5);
 | 
			
		||||
cout << s.count(5) << "\n"; // 0
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Usein kuitenkin tulisi poistaa
 | 
			
		||||
vain yksi esiintymä,
 | 
			
		||||
mikä onnistuu näin:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
s.erase(s.find(5));
 | 
			
		||||
cout << s.count(5) << "\n"; // 2
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Hakemisto}
 | 
			
		||||
 | 
			
		||||
\index{hakemisto@hakemisto}
 | 
			
		||||
\index{map@\texttt{map}}
 | 
			
		||||
\index{unordered\_map@\texttt{unordered\_map}}
 | 
			
		||||
 | 
			
		||||
\key{Hakemisto} on taulukon yleistys,
 | 
			
		||||
joka sisältää kokoelman avain-arvo-pareja.
 | 
			
		||||
Siinä missä taulukon avaimet ovat aina peräkkäiset
 | 
			
		||||
kokonaisluvut $0,1,\ldots,n-1$,
 | 
			
		||||
missä $n$ on taulukon koko,
 | 
			
		||||
hakemiston avaimet voivat
 | 
			
		||||
olla mitä tahansa tyyppiä
 | 
			
		||||
eikä niiden tarvitse olla peräkkäin.
 | 
			
		||||
 | 
			
		||||
C++ sisältää kaksi toteutusta hakemistolle
 | 
			
		||||
samaan tapaan kuin joukolle.
 | 
			
		||||
Rakenne
 | 
			
		||||
\texttt{map} perustuu
 | 
			
		||||
tasapainoiseen binääripuuhun ja sen
 | 
			
		||||
alkioiden käsittely vie aikaa $O(\log n)$,
 | 
			
		||||
kun taas rakenne
 | 
			
		||||
\texttt{unordered\_map} perustuu
 | 
			
		||||
hajautustauluun ja sen alkioiden
 | 
			
		||||
käsittely vie keskimäärin aikaa $O(1)$.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi toteuttaa hakemiston,
 | 
			
		||||
jossa avaimet ovat merkkijonoja ja
 | 
			
		||||
arvot ovat kokonaislukuja:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
map<string,int> m;
 | 
			
		||||
m["apina"] = 4;
 | 
			
		||||
m["banaani"] = 3;
 | 
			
		||||
m["cembalo"] = 9;
 | 
			
		||||
cout << m["banaani"] << "\n"; // 3
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Jos hakemistosta hakee avainta,
 | 
			
		||||
jota ei ole siinä,
 | 
			
		||||
avain lisätään hakemistoon
 | 
			
		||||
automaattisesti oletusarvolla.
 | 
			
		||||
Esimerkiksi seuraavassa koodissa
 | 
			
		||||
hakemistoon ilmestyy avain ''aybabtu'',
 | 
			
		||||
jonka arvona on 0:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
map<string,int> m;
 | 
			
		||||
cout << m["aybabtu"] << "\n"; // 0
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Funktiolla \texttt{count} voi
 | 
			
		||||
tutkia, esiintyykö avain hakemistossa:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
if (m.count("aybabtu")) {
 | 
			
		||||
    cout << "avain on hakemistossa";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Seuraava koodi listaa hakemiston
 | 
			
		||||
kaikki avaimet ja arvot:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (auto x : m) {
 | 
			
		||||
    cout << x.first << " " << x.second << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Iteraattorit ja välit}
 | 
			
		||||
 | 
			
		||||
\index{iteraattori@iteraattori}
 | 
			
		||||
 | 
			
		||||
Monet C++:n standardikirjaston funktiot
 | 
			
		||||
käsittelevät tietorakenteiden iteraattoreita
 | 
			
		||||
ja niiden määrittelemiä välejä.
 | 
			
		||||
\key{Iteraattori} on muuttuja,
 | 
			
		||||
joka osoittaa tiettyyn tietorakenteen alkioon.
 | 
			
		||||
 | 
			
		||||
Usein tarvittavat iteraattorit ovat \texttt{begin}
 | 
			
		||||
ja \texttt{end}, jotka rajaavat välin,
 | 
			
		||||
joka sisältää kaikki tietorakenteen alkiot.
 | 
			
		||||
Iteraattori \texttt{begin} osoittaa
 | 
			
		||||
tietorakenteen ensimmäiseen alkioon,
 | 
			
		||||
kun taas iteraattori \texttt{end} osoittaa
 | 
			
		||||
tietorakenteen viimeisen alkion jälkeiseen kohtaan.
 | 
			
		||||
Tilanne on siis tällainen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{llllllllll}
 | 
			
		||||
\{ & 3, & 4, & 6, & 8, & 12, & 13, & 14, & 17 & \} \\
 | 
			
		||||
& $\uparrow$ & & & & & & & & $\uparrow$ \\
 | 
			
		||||
& \multicolumn{3}{l}{\texttt{s.begin()}} & & & & & & \texttt{s.end()} \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Huomaa epäsymmetria iteraattoreissa:
 | 
			
		||||
\texttt{s.begin()} osoittaa tietorakenteen alkioon,
 | 
			
		||||
kun taas \texttt{s.end()} osoittaa tietorakenteen ulkopuolelle.
 | 
			
		||||
Iteraattoreiden rajaama joukon väli on siis \emph{puoliavoin}.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Välien käsittely}
 | 
			
		||||
 | 
			
		||||
Iteraattoreita tarvitsee
 | 
			
		||||
C++:n standardikirjaston funktioissa, jotka käsittelevät
 | 
			
		||||
tietorakenteen välejä.
 | 
			
		||||
Yleensä halutaan käsitellä tietorakenteiden kaikkia
 | 
			
		||||
alkioita, jolloin funktiolle annetaan
 | 
			
		||||
iteraattorit \texttt{begin} ja \texttt{end}.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraava koodi järjestää vektorin funktiolla \texttt{sort},
 | 
			
		||||
kääntää sitten alkioiden järjestyksen funktiolla \texttt{reverse}
 | 
			
		||||
ja sekoittaa lopuksi alkioiden järjestyksen funktiolla \texttt{random\_shuffle}.
 | 
			
		||||
 | 
			
		||||
\index{sort@\texttt{sort}}
 | 
			
		||||
\index{reverse@\texttt{reverse}}
 | 
			
		||||
\index{random\_shuffle@\texttt{random\_shuffle}}
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
sort(v.begin(), v.end());
 | 
			
		||||
reverse(v.begin(), v.end());
 | 
			
		||||
random_shuffle(v.begin(), v.end());
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Samoja funktioita voi myös käyttää tavallisen taulukon
 | 
			
		||||
yhteydessä, jolloin iteraattorin sijasta annetaan
 | 
			
		||||
osoitin taulukkoon:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
sort(t, t+n);
 | 
			
		||||
reverse(t, t+n);
 | 
			
		||||
random_shuffle(t, t+n);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Joukon iteraattorit}
 | 
			
		||||
 | 
			
		||||
Iteraattoreita tarvitsee usein joukon
 | 
			
		||||
alkioiden käsittelyssä.
 | 
			
		||||
Seuraava koodi määrittelee iteraattorin
 | 
			
		||||
\texttt{it}, joka osoittaa joukon \texttt{s} alkuun:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
set<int>::iterator it = s.begin();
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Koodin voi kirjoittaa myös lyhyemmin näin:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
auto it = s.begin();
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Iteraattoria vastaavaan joukon alkioon
 | 
			
		||||
pääsee käsiksi \texttt{*}-merkinnällä.
 | 
			
		||||
Esimerkiksi seuraava koodi tulostaa
 | 
			
		||||
joukon ensimmäisen alkion:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
auto it = s.begin();
 | 
			
		||||
cout << *it << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Iteraattoria pystyy liikuttamaan
 | 
			
		||||
operaatioilla \texttt{++} (eteenpäin)
 | 
			
		||||
ja \texttt{---} (taaksepäin).
 | 
			
		||||
Tällöin iteraattori siirtyy seuraavaan
 | 
			
		||||
tai edelliseen alkioon joukossa.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi tulostaa joukon kaikki alkiot:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (auto it = s.begin(); it != s.end(); it++) {
 | 
			
		||||
    cout << *it << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Seuraava koodi taas tulostaa joukon
 | 
			
		||||
viimeisen alkion:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
auto it = s.end();
 | 
			
		||||
it--;
 | 
			
		||||
cout << *it << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
% Iteraattoria täytyi liikuttaa askel taaksepäin,
 | 
			
		||||
% koska se osoitti aluksi joukon viimeisen
 | 
			
		||||
% alkion jälkeiseen kohtaan.
 | 
			
		||||
 | 
			
		||||
Funktio $\texttt{find}(x)$ palauttaa iteraattorin
 | 
			
		||||
joukon alkioon, jonka arvo on $x$.
 | 
			
		||||
Poikkeuksena jos alkiota $x$ ei esiinny joukossa,
 | 
			
		||||
iteraattoriksi tulee \texttt{end}.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
auto it = s.find(x);
 | 
			
		||||
if (it == s.end()) cout << "x puuttuu joukosta";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio $\texttt{lower\_bound}(x)$ palauttaa
 | 
			
		||||
iteraattorin joukon pienimpään alkioon,
 | 
			
		||||
joka on ainakin yhtä suuri kuin $x$.
 | 
			
		||||
Vastaavasti $\texttt{upper\_bound}(x)$ palauttaa
 | 
			
		||||
iteraattorin pienimpään alkioon,
 | 
			
		||||
joka on suurempi kuin $x$.
 | 
			
		||||
Jos tällaisia alkioita ei ole joukossa,
 | 
			
		||||
funktiot palauttavat arvon \texttt{end}.
 | 
			
		||||
Näitä funktioita ei voi käyttää
 | 
			
		||||
\texttt{unordered\_set}-rakenteessa,
 | 
			
		||||
joka ei pidä yllä alkioiden järjestystä.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi seuraava koodi etsii joukosta
 | 
			
		||||
alkion, joka on lähinnä lukua $x$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
auto a = s.lower_bound(x);
 | 
			
		||||
if (a == s.begin() && a == s.end()) {
 | 
			
		||||
    cout << "joukko on tyhjä\n";
 | 
			
		||||
} else if (a == s.begin()) {
 | 
			
		||||
    cout << *a << "\n";
 | 
			
		||||
} else if (a == s.end()) {
 | 
			
		||||
    a--;
 | 
			
		||||
    cout << *a << "\n";
 | 
			
		||||
} else {
 | 
			
		||||
    auto b = a; b--;
 | 
			
		||||
    if (x-*b < *a-x) cout << *b << "\n";
 | 
			
		||||
    else cout << *a << "\n";
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Koodi käy läpi mahdolliset tapaukset
 | 
			
		||||
iteraattorin \texttt{a} avulla.
 | 
			
		||||
Iteraattori
 | 
			
		||||
osoittaa aluksi pienimpään alkioon,
 | 
			
		||||
joka on ainakin yhtä suuri kuin $x$.
 | 
			
		||||
Jos \texttt{a} on samaan aikaan \texttt{begin}
 | 
			
		||||
ja \texttt{end}, joukko on tyhjä.
 | 
			
		||||
Muuten jos \texttt{a} on \texttt{begin},
 | 
			
		||||
sen osoittama alkio on $x$:ää lähin alkio.
 | 
			
		||||
Jos taas \texttt{a} on \texttt{end},
 | 
			
		||||
$x$:ää lähin alkio on joukon viimeinen alkio.
 | 
			
		||||
Jos mikään edellisistä tapauksista ei päde,
 | 
			
		||||
niin $x$:ää lähin alkio
 | 
			
		||||
on joko $a$:n osoittama alkio tai sitä edellinen alkio.
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\section{Muita tietorakenteita}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Bittijoukko}
 | 
			
		||||
 | 
			
		||||
\index{bittijoukko@bittijoukko}
 | 
			
		||||
\index{bitset@\texttt{bitset}}
 | 
			
		||||
 | 
			
		||||
\key{Bittijoukko} (\texttt{bitset}) on taulukko,
 | 
			
		||||
jonka jokaisen alkion arvo on 0 tai 1.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
seuraava koodi luo bittijoukon, jossa on 10 alkiota.
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bitset<10> s;
 | 
			
		||||
s[2] = 1;
 | 
			
		||||
s[5] = 1;
 | 
			
		||||
s[6] = 1;
 | 
			
		||||
s[8] = 1;
 | 
			
		||||
cout << s[4] << "\n"; // 0
 | 
			
		||||
cout << s[5] << "\n"; // 1
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Bittijoukon etuna on, että se vie tavallista
 | 
			
		||||
taulukkoa vähemmän muistia,
 | 
			
		||||
koska jokainen alkio vie
 | 
			
		||||
vain yhden bitin muistia.
 | 
			
		||||
Esimerkiksi $n$ bitin tallentaminen
 | 
			
		||||
\texttt{int}-taulukkona vie $32n$
 | 
			
		||||
bittiä tilaa, mutta bittijoukkona
 | 
			
		||||
vain $n$ bittiä tilaa.
 | 
			
		||||
Lisäksi bittijoukon sisältöä
 | 
			
		||||
voi käsitellä tehokkaasti bittioperaatioilla,
 | 
			
		||||
minkä ansiosta sillä voi tehostaa algoritmeja.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi näyttää toisen tavan
 | 
			
		||||
bittijoukon luomiseen:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bitset<10> s(string("0010011010"));
 | 
			
		||||
cout << s[4] << "\n"; // 0
 | 
			
		||||
cout << s[5] << "\n"; // 1
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{count} palauttaa
 | 
			
		||||
bittijoukon ykkösbittien määrän:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bitset<10> s(string("0010011010"));
 | 
			
		||||
cout << s.count() << "\n"; // 4
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi näyttää esimerkkejä
 | 
			
		||||
bittioperaatioiden käyttämisestä:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bitset<10> a(string("0010110110"));
 | 
			
		||||
bitset<10> b(string("1011011000"));
 | 
			
		||||
cout << (a&b) << "\n"; // 0010010000
 | 
			
		||||
cout << (a|b) << "\n"; // 1011111110
 | 
			
		||||
cout << (a^b) << "\n"; // 1001101110
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pakka}
 | 
			
		||||
 | 
			
		||||
\index{pakka@pakka}
 | 
			
		||||
\index{deque@\texttt{deque}}
 | 
			
		||||
 | 
			
		||||
\key{Pakka} (\texttt{deque}) on dynaaminen taulukko,
 | 
			
		||||
jonka kokoa pystyy muuttamaan tehokkaasti
 | 
			
		||||
sekä alku- että loppupäässä.
 | 
			
		||||
Pakka sisältää vektorin tavoin
 | 
			
		||||
funktiot \texttt{push\_back}
 | 
			
		||||
ja \texttt{pop\_back}, mutta siinä on lisäksi myös funktiot
 | 
			
		||||
\texttt{push\_front} ja \texttt{pop\_front},
 | 
			
		||||
jotka käsittelevät taulukon alkua.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi esittelee pakan käyttämistä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
deque<int> d;
 | 
			
		||||
d.push_back(5); // [5]
 | 
			
		||||
d.push_back(2); // [5,2]
 | 
			
		||||
d.push_front(3); // [3,5,2]
 | 
			
		||||
d.pop_back(); // [3,5]
 | 
			
		||||
d.pop_front(); // [5]
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Pakan sisäinen toteutus on monimutkaisempi kuin
 | 
			
		||||
vektorissa, minkä vuoksi se on
 | 
			
		||||
vektoria raskaampi rakenne.
 | 
			
		||||
Kuitenkin lisäyksen ja poiston
 | 
			
		||||
aikavaativuus on keskimäärin $O(1)$ molemmissa päissä.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pino}
 | 
			
		||||
 | 
			
		||||
\index{pino@pino}
 | 
			
		||||
\index{stack@\texttt{stack}}
 | 
			
		||||
 | 
			
		||||
\key{Pino} (\texttt{stack}) on tietorakenne,
 | 
			
		||||
joka tarjoaa kaksi $O(1)$-aikaista
 | 
			
		||||
operaatiota:
 | 
			
		||||
alkion lisäys pinon päälle ja alkion
 | 
			
		||||
poisto pinon päältä.
 | 
			
		||||
Pinossa ei ole mahdollista käsitellä muita
 | 
			
		||||
alkioita kuin pinon päällimmäistä alkiota.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi esittelee pinon käyttämistä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
stack<int> s;
 | 
			
		||||
s.push(3);
 | 
			
		||||
s.push(2);
 | 
			
		||||
s.push(5);
 | 
			
		||||
cout << s.top(); // 5
 | 
			
		||||
s.pop();
 | 
			
		||||
cout << s.top(); // 2
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\subsubsection{Jono}
 | 
			
		||||
 | 
			
		||||
\index{jono@jono}
 | 
			
		||||
\index{queue@\texttt{queue}}
 | 
			
		||||
 | 
			
		||||
\key{Jono} (\texttt{queue}) on kuin pino,
 | 
			
		||||
mutta alkion lisäys tapahtuu jonon loppuun
 | 
			
		||||
ja alkion poisto tapahtuu jonon alusta.
 | 
			
		||||
Jonossa on mahdollista käsitellä vain
 | 
			
		||||
alussa ja lopussa olevaa alkiota.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi esittelee jonon käyttämistä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
queue<int> s;
 | 
			
		||||
s.push(3);
 | 
			
		||||
s.push(2);
 | 
			
		||||
s.push(5);
 | 
			
		||||
cout << s.front(); // 3
 | 
			
		||||
s.pop();
 | 
			
		||||
cout << s.front(); // 2
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% Huomaa, että rakenteiden \texttt{stack} ja \texttt{queue}
 | 
			
		||||
% sijasta voi aina käyttää rakenteita
 | 
			
		||||
% \texttt{vector} ja \texttt{deque}, joilla voi
 | 
			
		||||
% tehdä kaiken saman ja enemmän.
 | 
			
		||||
% Kuitenkin \texttt{stack} ja \texttt{queue} ovat
 | 
			
		||||
% kevyempiä ja hieman tehokkaampia rakenteita,
 | 
			
		||||
% jos niiden operaatiot riittävät algoritmin toteuttamiseen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Prioriteettijono}
 | 
			
		||||
 | 
			
		||||
\index{prioriteettijono@prioriteettijono}
 | 
			
		||||
\index{keko@keko}
 | 
			
		||||
\index{priority\_queue@\texttt{priority\_queue}}
 | 
			
		||||
 | 
			
		||||
\key{Prioriteettijono} (\texttt{priority\_queue})
 | 
			
		||||
pitää yllä joukkoa alkioista.
 | 
			
		||||
Sen operaatiot ovat alkion lisäys ja
 | 
			
		||||
jonon tyypistä riippuen joko
 | 
			
		||||
pienimmän alkion haku ja poisto tai
 | 
			
		||||
suurimman alkion haku ja poisto.
 | 
			
		||||
Lisäyksen ja poiston aikavaativuus on $O(\log n)$
 | 
			
		||||
ja haun aikavaativuus on $O(1)$.
 | 
			
		||||
 | 
			
		||||
Vaikka prioriteettijonon operaatiot
 | 
			
		||||
pystyy toteuttamaan myös \texttt{set}-ra\-ken\-teel\-la,
 | 
			
		||||
prioriteettijonon etuna on,
 | 
			
		||||
että sen kekoon perustuva sisäinen
 | 
			
		||||
toteutus on yksinkertaisempi
 | 
			
		||||
kuin \texttt{set}-rakenteen tasapainoinen binääripuu,
 | 
			
		||||
minkä vuoksi rakenne on kevyempi ja
 | 
			
		||||
operaatiot ovat tehokkaampia.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
C++:n prioriteettijono toimii oletuksena niin,
 | 
			
		||||
että alkiot ovat järjestyksessä suurimmasta pienimpään
 | 
			
		||||
ja jonosta pystyy hakemaan ja poistamaan suurimman alkion.
 | 
			
		||||
Seuraava koodi esittelee prioriteettijonon käyttämistä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
priority_queue<int> q;
 | 
			
		||||
q.push(3);
 | 
			
		||||
q.push(5);
 | 
			
		||||
q.push(7);
 | 
			
		||||
q.push(2);
 | 
			
		||||
cout << q.top() << "\n"; // 7
 | 
			
		||||
q.pop();
 | 
			
		||||
cout << q.top() << "\n"; // 5
 | 
			
		||||
q.pop();
 | 
			
		||||
q.push(6);
 | 
			
		||||
cout << q.top() << "\n"; // 6
 | 
			
		||||
q.pop();
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Seuraava määrittely luo käänteisen prioriteettijonon,
 | 
			
		||||
jossa jonosta pystyy hakemaan ja poistamaan pienimmän alkion:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
priority_queue<int,vector<int>,greater<int>> q;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Vertailu järjestämiseen}
 | 
			
		||||
 | 
			
		||||
Monen tehtävän voi ratkaista tehokkaasti joko
 | 
			
		||||
käyttäen sopivia tietorakenteita
 | 
			
		||||
tai taulukon järjestämistä.
 | 
			
		||||
Vaikka erilaiset ratkaisutavat olisivat kaikki
 | 
			
		||||
periaatteessa tehokkaita, niissä voi olla
 | 
			
		||||
käytännössä merkittäviä eroja.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan ongelmaa, jossa
 | 
			
		||||
annettuna on kaksi listaa $A$ ja $B$,
 | 
			
		||||
joista kummassakin on $n$ kokonaislukua.
 | 
			
		||||
Tehtävänä on selvittää, moniko luku
 | 
			
		||||
esiintyy kummassakin listassa.
 | 
			
		||||
Esimerkiksi jos listat ovat
 | 
			
		||||
\[A = [5,2,8,9,4] \hspace{10px} \textrm{ja} \hspace{10px} B = [3,2,9,5],\]
 | 
			
		||||
niin vastaus on 3, koska luvut 2, 5
 | 
			
		||||
ja 9 esiintyvät kummassakin listassa.
 | 
			
		||||
Suoraviivainen ratkaisu tehtävään on käydä läpi
 | 
			
		||||
kaikki lukuparit ajassa $O(n^2)$, mutta seuraavaksi
 | 
			
		||||
keskitymme tehokkaampiin ratkaisuihin.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 1}
 | 
			
		||||
 | 
			
		||||
Tallennetaan listan $A$ luvut joukkoon
 | 
			
		||||
ja käydään sitten läpi listan $B$ luvut ja
 | 
			
		||||
tarkistetaan jokaisesta, esiintyykö se myös listassa $A$.
 | 
			
		||||
Joukon ansiosta on tehokasta tarkastaa,
 | 
			
		||||
esiintyykö listan $B$ luku listassa $A$.
 | 
			
		||||
Kun joukko toteutetaan \texttt{set}-rakenteella,
 | 
			
		||||
algoritmin aikavaativuus on $O(n \log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 2}
 | 
			
		||||
 | 
			
		||||
Joukon ei tarvitse säilyttää lukuja
 | 
			
		||||
järjestyksessä, joten
 | 
			
		||||
\texttt{set}-ra\-ken\-teen sijasta voi
 | 
			
		||||
käyttää myös \texttt{unordered\_set}-ra\-ken\-net\-ta.
 | 
			
		||||
Tämä on helppo tapa parantaa algoritmin
 | 
			
		||||
tehokkuutta, koska
 | 
			
		||||
algoritmin toteutus säilyy samana ja vain tietorakenne vaihtuu.
 | 
			
		||||
Uuden algoritmin aikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 3}
 | 
			
		||||
 | 
			
		||||
Tietorakenteiden sijasta voimme käyttää järjestämistä.
 | 
			
		||||
Järjestetään ensin listat $A$ ja $B$,
 | 
			
		||||
minkä jälkeen yhteiset luvut voi löytää
 | 
			
		||||
käymällä listat rinnakkain läpi.
 | 
			
		||||
Järjestämisen aikavaativuus on $O(n \log n)$ ja
 | 
			
		||||
läpikäynnin aikavaativuus on $O(n)$,
 | 
			
		||||
joten kokonaisaikavaativuus on $O(n \log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tehokkuusvertailu}
 | 
			
		||||
 | 
			
		||||
Seuraavassa taulukossa on mittaustuloksia
 | 
			
		||||
äskeisten algoritmien tehokkuudesta,
 | 
			
		||||
kun $n$ vaihtelee ja listojen luvut ovat
 | 
			
		||||
satunnaisia lukuja välillä $1 \ldots 10^9$:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrrr}
 | 
			
		||||
$n$ & ratkaisu 1 & ratkaisu 2 & ratkaisu 3 \\
 | 
			
		||||
\hline
 | 
			
		||||
$10^6$ & $1{,}5$ s & $0{,}3$ s & $0{,}2$ s \\
 | 
			
		||||
$2 \cdot 10^6$ & $3{,}7$ s & $0{,}8$ s & $0{,}3$ s \\
 | 
			
		||||
$3 \cdot 10^6$ & $5{,}7$ s & $1{,}3$ s & $0{,}5$ s \\
 | 
			
		||||
$4 \cdot 10^6$ & $7{,}7$ s & $1{,}7$ s & $0{,}7$ s \\
 | 
			
		||||
$5 \cdot 10^6$ & $10{,}0$ s & $2{,}3$ s & $0{,}9$ s \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ratkaisut 1 ja 2 ovat muuten samanlaisia,
 | 
			
		||||
mutta ratkaisu 1 käyttää \texttt{set}-rakennetta,
 | 
			
		||||
kun taas ratkaisu 2 käyttää
 | 
			
		||||
\texttt{unordered\_set}-rakennetta.
 | 
			
		||||
Tässä tapauksessa tällä valinnalla on
 | 
			
		||||
merkittävä vaikutus suoritusaikaan,
 | 
			
		||||
koska ratkaisu 2 on 4–5 kertaa
 | 
			
		||||
nopeampi kuin ratkaisu 1.
 | 
			
		||||
 | 
			
		||||
Tehokkain ratkaisu on kuitenkin järjestämistä
 | 
			
		||||
käyttävä ratkaisu 3, joka on vielä puolet
 | 
			
		||||
nopeampi kuin ratkaisu 2.
 | 
			
		||||
Kiinnostavaa on, että sekä ratkaisun 1 että
 | 
			
		||||
ratkaisun 3 aikavaativuus on $O(n \log n)$,
 | 
			
		||||
mutta siitä huolimatta
 | 
			
		||||
ratkaisu 3 vie aikaa vain kymmenesosan.
 | 
			
		||||
Tämän voi selittää sillä, että
 | 
			
		||||
järjestäminen on kevyt
 | 
			
		||||
operaatio ja se täytyy tehdä vain kerran
 | 
			
		||||
ratkaisussa 3 algoritmin alussa,
 | 
			
		||||
minkä jälkeen algoritmin loppuosa on lineaarinen.
 | 
			
		||||
Ratkaisu 1 taas pitää yllä monimutkaista
 | 
			
		||||
tasapainoista binääripuuta koko algoritmin ajan.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,756 @@
 | 
			
		|||
\chapter{Complete search}
 | 
			
		||||
 | 
			
		||||
\key{Täydellinen haku}
 | 
			
		||||
on yleispätevä tapa ratkaista
 | 
			
		||||
lähes mikä tahansa ohjelmointitehtävä.
 | 
			
		||||
Ideana on käydä läpi raa'alla voimalla kaikki
 | 
			
		||||
mahdolliset tehtävän ratkaisut ja tehtävästä riippuen
 | 
			
		||||
valita paras ratkaisu
 | 
			
		||||
tai laskea ratkaisuiden yhteismäärä.
 | 
			
		||||
          
 | 
			
		||||
Täydellinen haku on hyvä menetelmä, jos kaikki
 | 
			
		||||
ratkaisut ehtii käydä läpi,
 | 
			
		||||
koska haku on yleensä suoraviivainen toteuttaa
 | 
			
		||||
ja se antaa varmasti oikean vastauksen.
 | 
			
		||||
Jos täydellinen haku on liian hidas,
 | 
			
		||||
seuraavien lukujen ahneet algoritmit tai
 | 
			
		||||
dynaaminen ohjelmointi voivat soveltua
 | 
			
		||||
tehtävään.
 | 
			
		||||
 | 
			
		||||
\section{Osajoukkojen läpikäynti}
 | 
			
		||||
 | 
			
		||||
\index{osajoukko@osajoukko}
 | 
			
		||||
 | 
			
		||||
Aloitamme tapauksesta, jossa tehtävän
 | 
			
		||||
mahdollisia ratkaisuja ovat
 | 
			
		||||
$n$-alkioisen joukon osajoukot.
 | 
			
		||||
Tällöin täydellisen haun tulee
 | 
			
		||||
käydä läpi kaikki joukon osa\-joukot,
 | 
			
		||||
joita on yhteensä $2^n$ kappaletta.
 | 
			
		||||
Käymme läpi kaksi menetelmää
 | 
			
		||||
tällaisen haun toteuttamiseen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 1}
 | 
			
		||||
 | 
			
		||||
Kätevä tapa käydä läpi
 | 
			
		||||
kaikki joukon osajoukot on
 | 
			
		||||
käyttää rekursiota.
 | 
			
		||||
Seuraava funktio \texttt{haku} muodostaa
 | 
			
		||||
joukon $\{1,2,\ldots,n\}$ osajoukot.
 | 
			
		||||
Funktio pitää yllä vektoria \texttt{v},
 | 
			
		||||
johon se kokoaa osajoukossa olevat luvut.
 | 
			
		||||
Osajoukkojen muodostaminen alkaa
 | 
			
		||||
tekemällä funktiokutsu \texttt{haku(1)}.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku(int k) {
 | 
			
		||||
    if (k == n+1) {
 | 
			
		||||
        // käsittele osajoukko v
 | 
			
		||||
    } else {
 | 
			
		||||
        haku(k+1);
 | 
			
		||||
        v.push_back(k);
 | 
			
		||||
        haku(k+1);
 | 
			
		||||
        v.pop_back();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktion parametri $k$ on luku,
 | 
			
		||||
joka on ehdolla lisättäväksi osajoukkoon seuraavaksi.
 | 
			
		||||
Joka kutsulla funktio haarautuu kahteen tapaukseen:
 | 
			
		||||
joko luku $k$ lisätään tai ei lisätä osajoukkoon.
 | 
			
		||||
Aina kun $k=n+1$, kaikki luvut on käyty läpi
 | 
			
		||||
ja yksi osajoukko on muodostettu.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi kun $n=3$, funktiokutsut
 | 
			
		||||
muodostavat seuraavan kuvan mukaisen puun.
 | 
			
		||||
Joka kutsussa 
 | 
			
		||||
vasen haara jättää luvun pois osajoukosta
 | 
			
		||||
ja oikea haara lisää sen osajoukkoon.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.45]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \small
 | 
			
		||||
    \node at (0,0) {$\texttt{haku}(1)$};
 | 
			
		||||
 | 
			
		||||
    \node at (-8,-4) {$\texttt{haku}(2)$};
 | 
			
		||||
    \node at (8,-4) {$\texttt{haku}(2)$};
 | 
			
		||||
 | 
			
		||||
    \path[draw,thick,->] (0,0-0.5) -- (-8,-4+0.5);
 | 
			
		||||
    \path[draw,thick,->] (0,0-0.5) -- (8,-4+0.5);
 | 
			
		||||
 | 
			
		||||
    \node at (-12,-8) {$\texttt{haku}(3)$};
 | 
			
		||||
    \node at (-4,-8) {$\texttt{haku}(3)$};
 | 
			
		||||
    \node at (4,-8) {$\texttt{haku}(3)$};
 | 
			
		||||
    \node at (12,-8) {$\texttt{haku}(3)$};
 | 
			
		||||
 | 
			
		||||
    \path[draw,thick,->] (-8,-4-0.5) -- (-12,-8+0.5);
 | 
			
		||||
    \path[draw,thick,->] (-8,-4-0.5) -- (-4,-8+0.5);
 | 
			
		||||
    \path[draw,thick,->] (8,-4-0.5) -- (4,-8+0.5);
 | 
			
		||||
    \path[draw,thick,->] (8,-4-0.5) -- (12,-8+0.5);
 | 
			
		||||
 | 
			
		||||
    \node at (-14,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (-10,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (-6,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (-2,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (2,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (6,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (10,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
    \node at (14,-12) {$\texttt{haku}(4)$};
 | 
			
		||||
 | 
			
		||||
    \node at (-14,-13.5) {$\emptyset$};
 | 
			
		||||
    \node at (-10,-13.5) {$\{3\}$};
 | 
			
		||||
    \node at (-6,-13.5) {$\{2\}$};
 | 
			
		||||
    \node at (-2,-13.5) {$\{2,3\}$};
 | 
			
		||||
    \node at (2,-13.5) {$\{1\}$};
 | 
			
		||||
    \node at (6,-13.5) {$\{1,3\}$};
 | 
			
		||||
    \node at (10,-13.5) {$\{1,2\}$};
 | 
			
		||||
    \node at (14,-13.5) {$\{1,2,3\}$};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    \path[draw,thick,->] (-12,-8-0.5) -- (-14,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (-12,-8-0.5) -- (-10,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (-4,-8-0.5) -- (-6,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (-4,-8-0.5) -- (-2,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (4,-8-0.5) -- (2,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (4,-8-0.5) -- (6,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (12,-8-0.5) -- (10,-12+0.5);
 | 
			
		||||
    \path[draw,thick,->] (12,-8-0.5) -- (14,-12+0.5);
 | 
			
		||||
\end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 2}
 | 
			
		||||
 | 
			
		||||
Toinen tapa käydä osajoukot läpi on hyödyntää kokonaislukujen
 | 
			
		||||
bittiesitystä. Jokainen $n$ alkion osajoukko
 | 
			
		||||
voidaan esittää $n$ bitin jonona,
 | 
			
		||||
joka taas vastaa lukua väliltä $0 \ldots 2^n-1$.
 | 
			
		||||
Bittiesityksen ykkösbitit ilmaisevat,
 | 
			
		||||
mitkä joukon alkiot on valittu osajoukkoon.
 | 
			
		||||
 | 
			
		||||
Tavallinen käytäntö on tulkita kokonaisluvun
 | 
			
		||||
bittiesitys osajoukkona niin,
 | 
			
		||||
että alkio $k$ kuuluu osajoukkoon,
 | 
			
		||||
jos lopusta lukien $k$. bitti on 1.
 | 
			
		||||
Esimerkiksi luvun 25 bittiesitys on 11001,
 | 
			
		||||
mikä vastaa osajoukkoa $\{1,4,5\}$.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi käy läpi $n$ alkion joukon
 | 
			
		||||
osajoukkojen bittiesitykset:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int b = 0; b < (1<<n); b++) {
 | 
			
		||||
    // käsittele osajoukko b
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi muodostaa jokaisen osajoukon
 | 
			
		||||
kohdalla vektorin \texttt{v},
 | 
			
		||||
joka sisältää osajoukossa olevat luvut.
 | 
			
		||||
Ne saadaan selville tutkimalla, mitkä bitit ovat
 | 
			
		||||
ykkösiä osajoukon bittiesityksessä.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int b = 0; b < (1<<n); b++) {
 | 
			
		||||
    vector<int> v;
 | 
			
		||||
    for (int i = 0; i < n; i++) {
 | 
			
		||||
        if (b&(1<<i)) v.push_back(i+1);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Permutaatioiden läpikäynti}
 | 
			
		||||
 | 
			
		||||
\index{permutaatio@permutaatio}
 | 
			
		||||
 | 
			
		||||
Toinen usein esiintyvä tilanne on,
 | 
			
		||||
että tehtävän ratkaisut ovat $n$-alkioisen
 | 
			
		||||
joukon permutaatioita,
 | 
			
		||||
jolloin täydellisen haun tulee
 | 
			
		||||
käydä läpi $n!$ mahdollista permutaatiota.
 | 
			
		||||
Myös tässä tapauksessa on kaksi luontevaa
 | 
			
		||||
menetelmää täydellisen haun toteuttamiseen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 1}
 | 
			
		||||
 | 
			
		||||
Osajoukkojen tavoin permutaatioita voi muodostaa
 | 
			
		||||
rekursiivisesti.
 | 
			
		||||
Seuraava funktio \texttt{haku} käy läpi
 | 
			
		||||
joukon $\{1,2,\ldots,n\}$ permutaatiot.
 | 
			
		||||
Funktio muodostaa kunkin permutaation
 | 
			
		||||
vuorollaan vektoriin \texttt{v}.
 | 
			
		||||
Permutaatioiden muodostus alkaa kutsumalla
 | 
			
		||||
funktiota ilman parametreja.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku() {
 | 
			
		||||
    if (v.size() == n) {
 | 
			
		||||
        // käsittele permutaatio v
 | 
			
		||||
    } else {
 | 
			
		||||
        for (int i = 1; i <= n; i++) {
 | 
			
		||||
            if (p[i]) continue;
 | 
			
		||||
            p[i] = 1;
 | 
			
		||||
            v.push_back(i);
 | 
			
		||||
            haku();
 | 
			
		||||
            p[i] = 0;
 | 
			
		||||
            v.pop_back();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktion jokainen kutsu lisää uuden
 | 
			
		||||
luvun permutaatioon vektoriin \texttt{v}.
 | 
			
		||||
Taulukko \texttt{p} kertoo, mitkä luvut on jo
 | 
			
		||||
valittu permutaatioon.
 | 
			
		||||
Jos $\texttt{p}[k]=0$, luku $k$ ei ole mukana,
 | 
			
		||||
ja jos $\texttt{p}[k]=1$, luku $k$ on mukana.
 | 
			
		||||
Jos vektorin \texttt{v} koko on sama kuin
 | 
			
		||||
joukon koko $n$, permutaatio on tullut valmiiksi.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 2}
 | 
			
		||||
 | 
			
		||||
\index{next\_permutation@\texttt{next\_permutation}}
 | 
			
		||||
 | 
			
		||||
Toinen ratkaisu on aloittaa permutaatiosta
 | 
			
		||||
$\{1,2,\ldots,n\}$ ja muodostaa joka askeleella
 | 
			
		||||
järjestyksessä seuraava permutaatio.
 | 
			
		||||
C++:n standardikirjastossa on funktio
 | 
			
		||||
\texttt{next\_permutation}, joka tekee tämän muunnoksen.
 | 
			
		||||
Seuraava koodi käy läpi joukon $\{1,2,\ldots,n\}$
 | 
			
		||||
permutaatiot funktion avulla:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v;
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    v.push_back(i);
 | 
			
		||||
}
 | 
			
		||||
do {
 | 
			
		||||
    // käsittele permutaatio v
 | 
			
		||||
} while (next_permutation(v.begin(),v.end()));
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Peruuttava haku}
 | 
			
		||||
 | 
			
		||||
\index{peruuttava haku@peruuttava haku}
 | 
			
		||||
 | 
			
		||||
\key{Peruuttava haku}
 | 
			
		||||
aloittaa ratkaisun etsimisen tyhjästä
 | 
			
		||||
ja laajentaa ratkaisua askel kerrallaan.
 | 
			
		||||
Joka askeleella haku haarautuu kaikkiin
 | 
			
		||||
mahdollisiin suuntiin, joihin ratkaisua voi laajentaa.
 | 
			
		||||
Haaran tutkimisen jälkeen haku peruuttaa takaisin
 | 
			
		||||
ja jatkaa muihin mahdollisiin suuntiin.
 | 
			
		||||
 | 
			
		||||
\index{kuningatarongelma}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä \key{kuningatarongelmaa},
 | 
			
		||||
jossa laskettavana on,
 | 
			
		||||
monellako tavalla $n \times n$ -shakkilaudalle
 | 
			
		||||
voidaan asettaa $n$ kuningatarta niin,
 | 
			
		||||
että mitkään kaksi kuningatarta eivät uhkaa toisiaan.
 | 
			
		||||
Esimerkiksi kun $n=4$, mahdolliset ratkaisut ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (4, 4);
 | 
			
		||||
    \node at (1.5,3.5) {$K$};
 | 
			
		||||
    \node at (3.5,2.5) {$K$};
 | 
			
		||||
    \node at (0.5,1.5) {$K$};
 | 
			
		||||
    \node at (2.5,0.5) {$K$};
 | 
			
		||||
 | 
			
		||||
    \draw (6, 0) grid (10, 4);
 | 
			
		||||
    \node at (6+2.5,3.5) {$K$};
 | 
			
		||||
    \node at (6+0.5,2.5) {$K$};
 | 
			
		||||
    \node at (6+3.5,1.5) {$K$};
 | 
			
		||||
    \node at (6+1.5,0.5) {$K$};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tehtävän voi ratkaista peruuttavalla haulla
 | 
			
		||||
muodostamalla ratkaisua rivi kerrallaan.
 | 
			
		||||
Jokaisella rivillä täytyy valita yksi ruuduista,
 | 
			
		||||
johon sijoitetaan kuningatar niin,
 | 
			
		||||
ettei se uhkaa mitään aiemmin lisättyä kuningatarta.
 | 
			
		||||
Ratkaisu on valmis, kun viimeisellekin
 | 
			
		||||
riville on lisätty kuningatar.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi kun $n=4$, osa peruuttavan haun muodostamasta
 | 
			
		||||
puusta näyttää seuraavalta:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (4, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (-9, -6) grid (-5, -2);
 | 
			
		||||
    \draw (-3, -6) grid (1, -2);
 | 
			
		||||
    \draw (3, -6) grid (7, -2);
 | 
			
		||||
    \draw (9, -6) grid (13, -2);
 | 
			
		||||
 | 
			
		||||
    \node at (-9+0.5,-3+0.5) {$K$};
 | 
			
		||||
    \node at (-3+1+0.5,-3+0.5) {$K$};
 | 
			
		||||
    \node at (3+2+0.5,-3+0.5) {$K$};
 | 
			
		||||
    \node at (9+3+0.5,-3+0.5) {$K$};
 | 
			
		||||
 | 
			
		||||
    \draw (2,0) -- (-7,-2);
 | 
			
		||||
    \draw (2,0) -- (-1,-2);
 | 
			
		||||
    \draw (2,0) -- (5,-2);
 | 
			
		||||
    \draw (2,0) -- (11,-2);
 | 
			
		||||
 | 
			
		||||
    \draw (-11, -12) grid (-7, -8);
 | 
			
		||||
    \draw (-6, -12) grid (-2, -8);
 | 
			
		||||
    \draw (-1, -12) grid (3, -8);
 | 
			
		||||
    \draw (4, -12) grid (8, -8);
 | 
			
		||||
    \draw[white] (11, -12) grid (15, -8);
 | 
			
		||||
    \node at (-11+1+0.5,-9+0.5) {$K$};
 | 
			
		||||
    \node at (-6+1+0.5,-9+0.5) {$K$};
 | 
			
		||||
    \node at (-1+1+0.5,-9+0.5) {$K$};
 | 
			
		||||
    \node at (4+1+0.5,-9+0.5) {$K$};
 | 
			
		||||
    \node at (-11+0+0.5,-10+0.5) {$K$};
 | 
			
		||||
    \node at (-6+1+0.5,-10+0.5) {$K$};
 | 
			
		||||
    \node at (-1+2+0.5,-10+0.5) {$K$};
 | 
			
		||||
    \node at (4+3+0.5,-10+0.5) {$K$};
 | 
			
		||||
 | 
			
		||||
    \draw (-1,-6) -- (-9,-8);
 | 
			
		||||
    \draw (-1,-6) -- (-4,-8);
 | 
			
		||||
    \draw (-1,-6) -- (1,-8);
 | 
			
		||||
    \draw (-1,-6) -- (6,-8);
 | 
			
		||||
 | 
			
		||||
    \node at (-9,-13) {\ding{55}};
 | 
			
		||||
    \node at (-4,-13) {\ding{55}};
 | 
			
		||||
    \node at (1,-13) {\ding{55}};
 | 
			
		||||
    \node at (6,-13) {\ding{51}};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kuvan alimmalla tasolla kolme ensimmäistä osaratkaisua
 | 
			
		||||
eivät kelpaa, koska niissä kuningattaret uhkaavat
 | 
			
		||||
toisiaan.
 | 
			
		||||
Sen sijaan neljäs osaratkaisu kelpaa,
 | 
			
		||||
ja sitä on mahdollista laajentaa loppuun asti
 | 
			
		||||
kokonaiseksi ratkaisuksi
 | 
			
		||||
asettamalla vielä kaksi kuningatarta laudalle.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Seuraava koodi toteuttaa peruuttavan haun:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku(int y) {
 | 
			
		||||
    if (y == n) {
 | 
			
		||||
        c++;
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    for (int x = 0; x < n; x++) {
 | 
			
		||||
        if (r1[x] || r2[x+y] || r3[x-y+n-1]) continue;
 | 
			
		||||
        r1[x] = r2[x+y] = r3[x-y+n-1] = 1;
 | 
			
		||||
        haku(y+1);
 | 
			
		||||
        r1[x] = r2[x+y] = r3[x-y+n-1] = 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
Haku alkaa kutsumalla funktiota \texttt{haku(0)}.
 | 
			
		||||
Laudan koko on muuttujassa $n$,
 | 
			
		||||
ja koodi laskee ratkaisuiden määrän
 | 
			
		||||
muuttujaan $c$.
 | 
			
		||||
 | 
			
		||||
Koodi olettaa, että laudan vaaka- ja pystyrivit
 | 
			
		||||
on numeroitu 0:sta alkaen.
 | 
			
		||||
Funktio asettaa kuningattaren vaakariville $y$,
 | 
			
		||||
kun $0 \le y < n$.
 | 
			
		||||
Jos taas $y=n$, yksi ratkaisu on valmis
 | 
			
		||||
ja funktio kasvattaa muuttujaa $c$.
 | 
			
		||||
 | 
			
		||||
Taulukko \texttt{r1} pitää kirjaa,
 | 
			
		||||
millä laudan pystyriveillä on jo kuningatar.
 | 
			
		||||
Vastaavasti taulukot \texttt{r2} ja \texttt{r3}
 | 
			
		||||
pitävät kirjaa vinoriveistä.
 | 
			
		||||
Tällaisille riveille ei voi laittaa enää toista
 | 
			
		||||
kuningatarta.
 | 
			
		||||
Esimerkiksi $4 \times 4$ -laudan tapauksessa
 | 
			
		||||
rivit on numeroitu seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0-6, 0) grid (4-6, 4);
 | 
			
		||||
    \node at (-6+0.5,3.5) {$0$};
 | 
			
		||||
    \node at (-6+1.5,3.5) {$1$};
 | 
			
		||||
    \node at (-6+2.5,3.5) {$2$};
 | 
			
		||||
    \node at (-6+3.5,3.5) {$3$};
 | 
			
		||||
    \node at (-6+0.5,2.5) {$0$};
 | 
			
		||||
    \node at (-6+1.5,2.5) {$1$};
 | 
			
		||||
    \node at (-6+2.5,2.5) {$2$};
 | 
			
		||||
    \node at (-6+3.5,2.5) {$3$};
 | 
			
		||||
    \node at (-6+0.5,1.5) {$0$};
 | 
			
		||||
    \node at (-6+1.5,1.5) {$1$};
 | 
			
		||||
    \node at (-6+2.5,1.5) {$2$};
 | 
			
		||||
    \node at (-6+3.5,1.5) {$3$};
 | 
			
		||||
    \node at (-6+0.5,0.5) {$0$};
 | 
			
		||||
    \node at (-6+1.5,0.5) {$1$};
 | 
			
		||||
    \node at (-6+2.5,0.5) {$2$};
 | 
			
		||||
    \node at (-6+3.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (4, 4);
 | 
			
		||||
    \node at (0.5,3.5) {$0$};
 | 
			
		||||
    \node at (1.5,3.5) {$1$};
 | 
			
		||||
    \node at (2.5,3.5) {$2$};
 | 
			
		||||
    \node at (3.5,3.5) {$3$};
 | 
			
		||||
    \node at (0.5,2.5) {$1$};
 | 
			
		||||
    \node at (1.5,2.5) {$2$};
 | 
			
		||||
    \node at (2.5,2.5) {$3$};
 | 
			
		||||
    \node at (3.5,2.5) {$4$};
 | 
			
		||||
    \node at (0.5,1.5) {$2$};
 | 
			
		||||
    \node at (1.5,1.5) {$3$};
 | 
			
		||||
    \node at (2.5,1.5) {$4$};
 | 
			
		||||
    \node at (3.5,1.5) {$5$};
 | 
			
		||||
    \node at (0.5,0.5) {$3$};
 | 
			
		||||
    \node at (1.5,0.5) {$4$};
 | 
			
		||||
    \node at (2.5,0.5) {$5$};
 | 
			
		||||
    \node at (3.5,0.5) {$6$};
 | 
			
		||||
 | 
			
		||||
    \draw (6, 0) grid (10, 4);
 | 
			
		||||
    \node at (6.5,3.5) {$3$};
 | 
			
		||||
    \node at (7.5,3.5) {$4$};
 | 
			
		||||
    \node at (8.5,3.5) {$5$};
 | 
			
		||||
    \node at (9.5,3.5) {$6$};
 | 
			
		||||
    \node at (6.5,2.5) {$2$};
 | 
			
		||||
    \node at (7.5,2.5) {$3$};
 | 
			
		||||
    \node at (8.5,2.5) {$4$};
 | 
			
		||||
    \node at (9.5,2.5) {$5$};
 | 
			
		||||
    \node at (6.5,1.5) {$1$};
 | 
			
		||||
    \node at (7.5,1.5) {$2$};
 | 
			
		||||
    \node at (8.5,1.5) {$3$};
 | 
			
		||||
    \node at (9.5,1.5) {$4$};
 | 
			
		||||
    \node at (6.5,0.5) {$0$};
 | 
			
		||||
    \node at (7.5,0.5) {$1$};
 | 
			
		||||
    \node at (8.5,0.5) {$2$};
 | 
			
		||||
    \node at (9.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
    \node at (-4,-1) {\texttt{r1}};
 | 
			
		||||
    \node at (2,-1) {\texttt{r2}};
 | 
			
		||||
    \node at (8,-1) {\texttt{r3}};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Koodin avulla selviää esimerkiksi,
 | 
			
		||||
että tapauksessa $n=8$ on 92 tapaa sijoittaa 8
 | 
			
		||||
kuningatarta $8 \times 8$ -laudalle.
 | 
			
		||||
Kun $n$ kasvaa, koodi hidastuu nopeasti,
 | 
			
		||||
koska ratkaisujen määrä kasvaa räjähdysmäisesti.
 | 
			
		||||
Tapauksen $n=16$ laskeminen vie jo noin minuutin
 | 
			
		||||
nykyaikaisella tietokoneella (14772512 ratkaisua).
 | 
			
		||||
 | 
			
		||||
\section{Haun optimointi}
 | 
			
		||||
 | 
			
		||||
Peruuttavaa hakua on usein mahdollista tehostaa
 | 
			
		||||
erilaisten optimointien avulla.
 | 
			
		||||
Tavoitteena on lisätä hakuun ''älykkyyttä''
 | 
			
		||||
niin, että haku pystyy havaitsemaan
 | 
			
		||||
mahdollisimman aikaisin,
 | 
			
		||||
jos muodosteilla oleva ratkaisu ei voi
 | 
			
		||||
johtaa kokonaiseen ratkaisuun.
 | 
			
		||||
Tällaiset optimoinnit karsivat haaroja
 | 
			
		||||
hakupuusta, millä voi olla suuri vaikutus
 | 
			
		||||
peruuttavan haun tehokkuuteen.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää,
 | 
			
		||||
jossa laskettavana on reittien määrä
 | 
			
		||||
$n \times n$ -ruudukon
 | 
			
		||||
vasemmasta yläkulmasta oikeaan alakulmaan,
 | 
			
		||||
kun reitin aikana tulee käydä tarkalleen kerran 
 | 
			
		||||
jokaisessa ruudussa.
 | 
			
		||||
Esimerkiksi $7 \times 7$ -ruudukossa on
 | 
			
		||||
111712 mahdollista reittiä vasemmasta yläkulmasta
 | 
			
		||||
oikeaan alakulmaan, joista yksi on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (5.5,0.5) -- (5.5,3.5) -- (3.5,3.5) --
 | 
			
		||||
          (3.5,5.5) -- (1.5,5.5) -- (1.5,6.5) --
 | 
			
		||||
          (4.5,6.5) -- (4.5,4.5) -- (5.5,4.5) --
 | 
			
		||||
          (5.5,6.5) -- (6.5,6.5) -- (6.5,0.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Keskitymme seuraavaksi nimenomaan tapaukseen $7 \times 7$,
 | 
			
		||||
koska se on laskennallisesti sopivan haastava.
 | 
			
		||||
Lähdemme liikkeelle suoraviivaisesta peruuttavaa hakua
 | 
			
		||||
käyttävästä algoritmista
 | 
			
		||||
ja teemme siihen pikkuhiljaa optimointeja,
 | 
			
		||||
jotka nopeuttavat hakua eri tavoin.
 | 
			
		||||
Mittaamme jokaisen optimoinnin jälkeen
 | 
			
		||||
algoritmin suoritusajan sekä rekursiokutsujen yhteismäärän,
 | 
			
		||||
jotta näemme selvästi, mikä vaikutus kullakin
 | 
			
		||||
optimoinnilla on haun tehokkuuteen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Perusalgoritmi}
 | 
			
		||||
 | 
			
		||||
Algoritmin ensimmäisessä versiossa ei ole mitään optimointeja,
 | 
			
		||||
vaan peruuttava haku käy läpi kaikki mahdolliset tavat
 | 
			
		||||
muodostaa reitti ruudukon vasemmasta yläkulmasta
 | 
			
		||||
oikeaan alakulmaan.
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
suoritusaika: 483 sekuntia
 | 
			
		||||
\item
 | 
			
		||||
rekursiokutsuja: 76 miljardia
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Optimointi 1}
 | 
			
		||||
 | 
			
		||||
Reitin ensimmäinen askel on joko alaspäin
 | 
			
		||||
tai oikealle. Tästä valinnasta seuraavat tilanteet
 | 
			
		||||
ovat symmetrisiä ruudukon lävistäjän suhteen.
 | 
			
		||||
Esimerkiksi seuraavat ratkaisut ovat
 | 
			
		||||
symmetrisiä keskenään:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{ccc}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (5.5,0.5) -- (5.5,3.5) -- (3.5,3.5) --
 | 
			
		||||
          (3.5,5.5) -- (1.5,5.5) -- (1.5,6.5) --
 | 
			
		||||
          (4.5,6.5) -- (4.5,4.5) -- (5.5,4.5) --
 | 
			
		||||
          (5.5,6.5) -- (6.5,6.5) -- (6.5,0.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{20px}
 | 
			
		||||
& 
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}[yscale=1,xscale=-1,rotate=-90]
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (5.5,0.5) -- (5.5,3.5) -- (3.5,3.5) --
 | 
			
		||||
          (3.5,5.5) -- (1.5,5.5) -- (1.5,6.5) --
 | 
			
		||||
          (4.5,6.5) -- (4.5,4.5) -- (5.5,4.5) --
 | 
			
		||||
          (5.5,6.5) -- (6.5,6.5) -- (6.5,0.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän ansiosta voimme tehdä päätöksen,
 | 
			
		||||
että reitin ensimmäinen askel on alaspäin,
 | 
			
		||||
ja kertoa lopuksi reittien määrän 2:lla.
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
suoritusaika: 244 sekuntia
 | 
			
		||||
\item
 | 
			
		||||
rekursiokutsuja: 38 miljardia
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Optimointi 2}
 | 
			
		||||
 | 
			
		||||
Jos reitti menee oikean alakulman ruutuun ennen kuin
 | 
			
		||||
se on käynyt kaikissa muissa ruuduissa,
 | 
			
		||||
siitä ei voi mitenkään enää saada kelvollista ratkaisua.
 | 
			
		||||
Näin on esimerkiksi seuraavassa tilanteessa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (6.5,0.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Niinpä voimme keskeyttää hakuhaaran heti,
 | 
			
		||||
jos tulemme oikean alakulman ruutuun liian aikaisin.
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
suoritusaika: 119 sekuntia
 | 
			
		||||
\item
 | 
			
		||||
rekursiokutsuja: 20 miljardia
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Optimointi 3}
 | 
			
		||||
 | 
			
		||||
Jos reitti osuu seinään niin, että kummallakin puolella
 | 
			
		||||
on ruutu, jossa reitti ei ole vielä käynyt,
 | 
			
		||||
ruudukko jakautuu kahteen osaan.
 | 
			
		||||
Esimerkiksi seuraavassa tilanteessa
 | 
			
		||||
sekä vasemmalla että
 | 
			
		||||
oikealla puolella on tyhjä ruutu:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (5.5,0.5) -- (5.5,6.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Nyt ei ole enää mahdollista käydä kaikissa ruuduissa,
 | 
			
		||||
joten voimme keskeyttää hakuhaaran.
 | 
			
		||||
Tämä optimointi on hyvin hyödyllinen:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
suoritusaika: 1{,}8 sekuntia
 | 
			
		||||
\item
 | 
			
		||||
rekursiokutsuja: 221 miljoonaa
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Optimointi 4}
 | 
			
		||||
 | 
			
		||||
Äskeisen optimoinnin ideaa voi yleistää:
 | 
			
		||||
ruudukko jakaantuu kahteen osaan,
 | 
			
		||||
jos nykyisen ruudun ylä- ja alapuolella on
 | 
			
		||||
tyhjä ruutu sekä vasemmalla ja oikealla
 | 
			
		||||
puolella on seinä tai aiemmin käyty ruutu
 | 
			
		||||
(tai päinvastoin).
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa tilanteessa
 | 
			
		||||
nykyisen ruudun ylä- ja alapuolella on
 | 
			
		||||
tyhjä ruutu eikä reitti voi enää edetä
 | 
			
		||||
molempiin ruutuihin:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) grid (7, 7);
 | 
			
		||||
    \draw[thick,->] (0.5,6.5) -- (0.5,4.5) -- (2.5,4.5) --
 | 
			
		||||
          (2.5,3.5) -- (0.5,3.5) -- (0.5,0.5) --
 | 
			
		||||
          (3.5,0.5) -- (3.5,1.5) -- (1.5,1.5) --
 | 
			
		||||
          (1.5,2.5) -- (4.5,2.5) -- (4.5,0.5) --
 | 
			
		||||
          (5.5,0.5) -- (5.5,4.5) -- (3.5,4.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Haku tehostuu entisestään, kun keskeytämme
 | 
			
		||||
hakuhaaran kaikissa tällaisissa tapauksissa:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
suoritusaika: 0{,}6 sekuntia
 | 
			
		||||
\item
 | 
			
		||||
rekursiokutsuja: 69 miljoonaa
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
Nyt on hyvä hetki lopettaa optimointi ja muistella,
 | 
			
		||||
mistä lähdimme liikkeelle.
 | 
			
		||||
Alkuperäinen algoritmi vei aikaa 483 sekuntia,
 | 
			
		||||
ja nyt optimointien jälkeen algoritmi vie aikaa
 | 
			
		||||
vain 0{,}6 sekuntia.
 | 
			
		||||
Optimointien ansiosta algoritmi nopeutui
 | 
			
		||||
siis lähes 1000-kertaisesti.
 | 
			
		||||
 | 
			
		||||
Tämä on yleinen ilmiö peruuttavassa haussa,
 | 
			
		||||
koska hakupuu on yleensä valtava ja
 | 
			
		||||
yksinkertainenkin optimointi voi karsia suuren
 | 
			
		||||
määrän haaroja hakupuusta.
 | 
			
		||||
Erityisen hyödyllisiä ovat optimoinnit,
 | 
			
		||||
jotka kohdistuvat hakupuun yläosaan,
 | 
			
		||||
koska ne karsivat eniten haaroja.
 | 
			
		||||
 | 
			
		||||
\section{Puolivälihaku}
 | 
			
		||||
 | 
			
		||||
\index{puolivxlihaku@puolivälihaku}
 | 
			
		||||
 | 
			
		||||
\key{Puolivälihaku} (''meet in the middle'') on tekniikka,
 | 
			
		||||
jossa hakutehtävä jaetaan kahteen yhtä suureen osaan.
 | 
			
		||||
Kumpaankin osaan tehdään erillinen haku,
 | 
			
		||||
ja lopuksi hakujen tulokset yhdistetään.
 | 
			
		||||
 | 
			
		||||
Puolivälihaun käyttäminen edellyttää,
 | 
			
		||||
että erillisten hakujen tulokset pystyy
 | 
			
		||||
yhdistämään tehokkaasti.
 | 
			
		||||
Tällöin puolivälihaku on tehokkaampi
 | 
			
		||||
kuin yksi haku, joka käy läpi koko hakualueen.
 | 
			
		||||
Tyypillisesti puolivälihaku tehostaa algoritmia
 | 
			
		||||
niin, että aikavaativuuden kertoimesta $2^n$
 | 
			
		||||
tulee kerroin $2^{n/2}$.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan ongelmaa, jossa annettuna
 | 
			
		||||
on $n$ lukua sisältävä lista sekä kokonaisluku $x$.
 | 
			
		||||
Tehtävänä on selvittää, voiko listalta valita
 | 
			
		||||
joukon lukuja niin, että niiden summa on $x$.
 | 
			
		||||
Esimerkiksi jos lista on $[2,4,5,9]$ ja $x=15$,
 | 
			
		||||
voimme valita listalta luvut $[2,4,9]$,
 | 
			
		||||
jolloin $2+4+9=15$.
 | 
			
		||||
Jos taas lista säilyy ennallaan ja $x=10$,
 | 
			
		||||
mikään valinta ei täytä vaatimusta.
 | 
			
		||||
 | 
			
		||||
Tavanomainen ratkaisu tehtävään on käydä kaikki
 | 
			
		||||
listan alkioiden osajoukot läpi ja tarkastaa,
 | 
			
		||||
onko jonkin osajoukon summa $x$.
 | 
			
		||||
Tällainen ratkaisu kuluttaa aikaa $O(2^n)$,
 | 
			
		||||
koska erilaisia osajoukkoja on $2^n$.
 | 
			
		||||
Seuraavaksi näemme,
 | 
			
		||||
miten puolivälihaun avulla on mahdollista luoda
 | 
			
		||||
tehokkaampi $O(2^{n/2})$-aikainen ratkaisu.
 | 
			
		||||
Huomaa, että aikavaativuuksissa $O(2^n)$ ja
 | 
			
		||||
$O(2^{n/2})$ on merkittävä ero, koska
 | 
			
		||||
$2^{n/2}$ tarkoittaa samaa kuin $\sqrt{2^n}$.
 | 
			
		||||
 | 
			
		||||
Ideana on jakaa syötteenä oleva lista
 | 
			
		||||
kahteen listaan $A$ ja $B$,
 | 
			
		||||
joista kumpikin sisältää noin puolet luvuista.
 | 
			
		||||
Ensimmäinen haku muodostaa kaikki osajoukot
 | 
			
		||||
listan $A$ luvuista ja laittaa muistiin niiden summat
 | 
			
		||||
listaan $S_A$.
 | 
			
		||||
Toinen haku muodostaa vastaavasti listan
 | 
			
		||||
$B$ perusteella listan $S_B$.
 | 
			
		||||
Tämän jälkeen riittää tarkastaa,
 | 
			
		||||
onko mahdollista valita yksi luku listasta $S_A$
 | 
			
		||||
ja toinen luku listasta $S_B$ niin,
 | 
			
		||||
että lukujen summa on $x$.
 | 
			
		||||
Tämä on mahdollista tarkalleen silloin,
 | 
			
		||||
kun alkuperäisen listan luvuista saa summan $x$.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkiä,
 | 
			
		||||
jossa lista on $[2,4,5,9]$
 | 
			
		||||
ja $x=15$.
 | 
			
		||||
Puolivälihaku jakaa luvut kahteen
 | 
			
		||||
listaan niin, että $A=[2,4]$
 | 
			
		||||
ja $B=[5,9]$.
 | 
			
		||||
Näistä saadaan edelleen summalistat
 | 
			
		||||
$S_A=[0,2,4,6]$ ja $S_B=[0,5,9,14]$.
 | 
			
		||||
Summa $x=15$ on mahdollista muodostaa,
 | 
			
		||||
koska voidaan valita $S_A$:sta luku $6$
 | 
			
		||||
ja $S_B$:stä luku $9$.
 | 
			
		||||
Tämä valinta vastaa ratkaisua $[2,4,9]$.
 | 
			
		||||
 | 
			
		||||
Ratkaisun aikavaativuus on $O(2^{n/2})$,
 | 
			
		||||
koska kummassakin listassa $A$ ja $B$
 | 
			
		||||
on $n/2$ lukua ja niiden osajoukkojen
 | 
			
		||||
summien laskeminen listoihin $S_A$ ja $S_B$
 | 
			
		||||
vie aikaa $O(2^{n/2})$.
 | 
			
		||||
Tämän jälkeen on mahdollista tarkastaa
 | 
			
		||||
ajassa $O(2^{n/2})$, voiko summaa $x$ muodostaa
 | 
			
		||||
listojen $S_A$ ja $S_B$ luvuista.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,789 @@
 | 
			
		|||
\chapter{Greedy algorithms}
 | 
			
		||||
 | 
			
		||||
\index{ahne algoritmi@ahne algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Ahne algoritmi}
 | 
			
		||||
muodostaa tehtävän ratkaisun
 | 
			
		||||
tekemällä joka askeleella
 | 
			
		||||
sillä hetkellä parhaalta näyttävän valinnan.
 | 
			
		||||
Ahne algoritmi ei koskaan 
 | 
			
		||||
peruuta tekemiään valintoja vaan
 | 
			
		||||
muodostaa ratkaisun suoraan valmiiksi.
 | 
			
		||||
Tämän ansiosta ahneet algoritmit ovat
 | 
			
		||||
yleensä hyvin tehokkaita.
 | 
			
		||||
 | 
			
		||||
Vaikeutena ahneissa algoritmeissa on
 | 
			
		||||
keksiä toimiva ahne strategia,
 | 
			
		||||
joka tuottaa aina optimaalisen ratkaisun tehtävään.
 | 
			
		||||
Ahneen algoritmin tulee olla sellainen,
 | 
			
		||||
että kulloinkin parhaalta näyttävät valinnat
 | 
			
		||||
tuottavat myös parhaan kokonaisuuden.
 | 
			
		||||
Tämän perusteleminen on usein hankalaa.
 | 
			
		||||
 | 
			
		||||
\section{Kolikkotehtävä}
 | 
			
		||||
 | 
			
		||||
Aloitamme ahneisiin algoritmeihin tutustumisen
 | 
			
		||||
tehtävästä, jossa muodostettavana on
 | 
			
		||||
rahamäärä $x$ kolikoista.
 | 
			
		||||
Kolikoiden arvot ovat $\{c_1,c_2,\ldots,c_k\}$,
 | 
			
		||||
ja jokaista kolikkoa on saatavilla rajattomasti.
 | 
			
		||||
Tehtävänä on selvittää, mikä on pienin määrä
 | 
			
		||||
kolikoita, joilla rahamäärän voi muodostaa.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos muodostettava
 | 
			
		||||
rahamäärä on 520
 | 
			
		||||
ja kolikot ovat eurokolikot eli sentteinä
 | 
			
		||||
\[\{1,2,5,10,20,50,100,200\},\]
 | 
			
		||||
niin kolikoita tarvitaan vähintään 4.
 | 
			
		||||
Tämä on mahdollista valitsemalla kolikot
 | 
			
		||||
$200+200+100+20$, joiden summa on 520.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ahne algoritmi}
 | 
			
		||||
 | 
			
		||||
Luonteva ahne algoritmi tehtävään
 | 
			
		||||
on poistaa rahamäärästä aina mahdollisimman
 | 
			
		||||
suuri kolikko, kunnes rahamäärä on 0.
 | 
			
		||||
Tämä algoritmi toimii esimerkissä,
 | 
			
		||||
koska rahamäärästä 520 
 | 
			
		||||
poistetaan ensin kahdesti 200, sitten 100
 | 
			
		||||
ja lopuksi 20.
 | 
			
		||||
Mutta toimiiko ahne algoritmi aina oikein?
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että eurokolikoiden tapauksessa
 | 
			
		||||
ahne algoritmi \emph{toimii} aina oikein,
 | 
			
		||||
eli se tuottaa aina ratkaisun,
 | 
			
		||||
jossa on pienin määrä kolikoita.
 | 
			
		||||
Algoritmin toimivuuden voi perustella
 | 
			
		||||
seuraavasti:
 | 
			
		||||
 | 
			
		||||
Kutakin kolikkoa 1, 5, 10, 50 ja 100
 | 
			
		||||
on optimiratkaisussa enintään yksi.
 | 
			
		||||
Tämä johtuu siitä, että jos
 | 
			
		||||
ratkaisussa olisi kaksi tällaista kolikkoa,
 | 
			
		||||
saman ratkaisun voisi muodostaa
 | 
			
		||||
käyttäen vähemmän kolikoita.
 | 
			
		||||
Esimerkiksi jos ratkaisussa on
 | 
			
		||||
kolikot $5+5$, ne voi korvata kolikolla 10.
 | 
			
		||||
 | 
			
		||||
Vastaavasti kumpaakin kolikkoa 2 ja 20
 | 
			
		||||
on optimiratkaisussa enintään kaksi,
 | 
			
		||||
koska kolikot $2+2+2$ voi korvata kolikoilla $5+1$
 | 
			
		||||
ja kolikot $20+20+20$ voi korvata kolikoilla $50+10$.
 | 
			
		||||
Lisäksi ratkaisussa ei voi olla yhdistelmiä
 | 
			
		||||
$2+2+1$ ja $20+20+10$,
 | 
			
		||||
koska ne voi korvata kolikoilla 5 ja 50.
 | 
			
		||||
 | 
			
		||||
Näiden havaintojen perusteella
 | 
			
		||||
jokaiselle kolikolle $x$ pätee,
 | 
			
		||||
että $x$:ää pienemmistä kolikoista
 | 
			
		||||
ei ole mahdollista saada aikaan summaa
 | 
			
		||||
$x$ tai suurempaa summaa optimaalisesti.
 | 
			
		||||
Esimerkiksi jos $x=100$, pienemmistä kolikoista
 | 
			
		||||
saa korkeintaan summan $50+20+20+5+2+2=99$.
 | 
			
		||||
Niinpä ahne algoritmi,
 | 
			
		||||
joka valitsee aina suurimman kolikon,
 | 
			
		||||
tuottaa optimiratkaisun.
 | 
			
		||||
 | 
			
		||||
Kuten tästä esimerkistä huomaa,
 | 
			
		||||
ahneen algoritmin toimivuuden perusteleminen
 | 
			
		||||
voi olla työlästä,
 | 
			
		||||
vaikka kyseessä olisi yksinkertainen algoritmi.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yleinen tapaus}
 | 
			
		||||
 | 
			
		||||
Yleisessä tapauksessa kolikot voivat olla mitä tahansa.
 | 
			
		||||
Tällöin suurimman kolikon valitseva ahne algoritmi
 | 
			
		||||
\emph{ei} välttämättä tuota optimiratkaisua.
 | 
			
		||||
 | 
			
		||||
Jos ahne algoritmi ei toimi, tämän voi osoittaa
 | 
			
		||||
näyttämällä vastaesimerkin, jossa algoritmi
 | 
			
		||||
antaa väärän vastauksen.
 | 
			
		||||
Tässä tehtävässä vastaesimerkki on helppoa keksiä:
 | 
			
		||||
jos kolikot ovat $\{1,3,4\}$ ja muodostettava
 | 
			
		||||
rahamäärä on 6, ahne algoritmi tuottaa ratkaisun
 | 
			
		||||
$4+1+1$, kun taas optimiratkaisu on $3+3$.
 | 
			
		||||
 | 
			
		||||
Yleisessä tapauksessa kolikkotehtävän ratkaisuun
 | 
			
		||||
ei tunneta ahnetta algoritmia,
 | 
			
		||||
mutta palaamme tehtävään seuraavassa luvussa.
 | 
			
		||||
Tehtävään on nimittäin olemassa dynaamista
 | 
			
		||||
ohjelmointia käyttävä algoritmi,
 | 
			
		||||
joka tuottaa optimiratkaisun
 | 
			
		||||
millä tahansa kolikoilla ja rahamäärällä.
 | 
			
		||||
 | 
			
		||||
\section{Aikataulutus}
 | 
			
		||||
 | 
			
		||||
Monet aikataulutukseen liittyvät ongelmat
 | 
			
		||||
ratkeavat ahneesti.
 | 
			
		||||
Klassinen ongelma on seuraava:
 | 
			
		||||
Annettuna on $n$ tapahtumaa,
 | 
			
		||||
jotka alkavat ja päättyvät tiettyinä hetkinä.
 | 
			
		||||
Tehtäväsi on suunnitella aikataulu,
 | 
			
		||||
jota seuraamalla pystyt osallistumaan
 | 
			
		||||
mahdollisimman moneen tapahtumaan.
 | 
			
		||||
Et voi osallistua tapahtumaan vain osittain.
 | 
			
		||||
Esimerkiksi tilanteessa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{lll}
 | 
			
		||||
tapahtuma & alkuaika & loppuaika \\
 | 
			
		||||
\hline
 | 
			
		||||
$A$ & 1 & 3 \\
 | 
			
		||||
$B$ & 2 & 5 \\
 | 
			
		||||
$C$ & 3 & 9 \\
 | 
			
		||||
$D$ & 6 & 8 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
on mahdollista osallistua korkeintaan
 | 
			
		||||
kahteen tapahtumaan.
 | 
			
		||||
Yksi mahdollisuus on osallistua tapahtumiin
 | 
			
		||||
$B$ ja $D$ seuraavasti:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (2, 0) rectangle (6, -1);
 | 
			
		||||
    \draw[fill=lightgray] (4, -1.5) rectangle (10, -2.5);
 | 
			
		||||
    \draw (6, -3) rectangle (18, -4);
 | 
			
		||||
    \draw[fill=lightgray] (12, -4.5) rectangle (16, -5.5);
 | 
			
		||||
    \node at (2.5,-0.5) {$A$};
 | 
			
		||||
    \node at (4.5,-2) {$B$};
 | 
			
		||||
    \node at (6.5,-3.5) {$C$};
 | 
			
		||||
    \node at (12.5,-5) {$D$};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tehtävän ratkaisuun on mahdollista 
 | 
			
		||||
keksiä useita ahneita algoritmeja,
 | 
			
		||||
mutta mikä niistä toimii kaikissa tapauksissa?
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Algoritmi 1}
 | 
			
		||||
 | 
			
		||||
Ensimmäinen idea on valita ratkaisuun
 | 
			
		||||
mahdollisimman \emph{lyhyitä} tapahtumia.
 | 
			
		||||
Esimerkin tapauksessa tällainen
 | 
			
		||||
algoritmi valitsee seuraavat tapahtumat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw[fill=lightgray] (2, 0) rectangle (6, -1);
 | 
			
		||||
    \draw (4, -1.5) rectangle (10, -2.5);
 | 
			
		||||
    \draw (6, -3) rectangle (18, -4);
 | 
			
		||||
    \draw[fill=lightgray] (12, -4.5) rectangle (16, -5.5);
 | 
			
		||||
    \node at (2.5,-0.5) {$A$};
 | 
			
		||||
    \node at (4.5,-2) {$B$};
 | 
			
		||||
    \node at (6.5,-3.5) {$C$};
 | 
			
		||||
    \node at (12.5,-5) {$D$};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Lyhimpien tapahtumien valinta ei ole kuitenkaan
 | 
			
		||||
aina toimiva strategia,
 | 
			
		||||
vaan algoritmi epäonnistuu esimerkiksi seuraavassa tilanteessa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (1, 0) rectangle (7, -1);
 | 
			
		||||
    \draw[fill=lightgray] (6, -1.5) rectangle (9, -2.5);
 | 
			
		||||
    \draw (8, -3) rectangle (14, -4);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Kun lyhyt tapahtuma valitaan mukaan,
 | 
			
		||||
on mahdollista osallistua vain yhteen tapahtumaan.
 | 
			
		||||
Kuitenkin valitsemalla pitkät tapahtumat
 | 
			
		||||
olisi mahdollista osallistua kahteen tapahtumaan.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Algoritmi 2}
 | 
			
		||||
 | 
			
		||||
Toinen idea on valita aina seuraavaksi tapahtuma,
 | 
			
		||||
joka \emph{alkaa} mahdollisimman aikaisin.
 | 
			
		||||
Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw[fill=lightgray] (2, 0) rectangle (6, -1);
 | 
			
		||||
    \draw (4, -1.5) rectangle (10, -2.5);
 | 
			
		||||
    \draw[fill=lightgray] (6, -3) rectangle (18, -4);
 | 
			
		||||
    \draw (12, -4.5) rectangle (16, -5.5);
 | 
			
		||||
    \node at (2.5,-0.5) {$A$};
 | 
			
		||||
    \node at (4.5,-2) {$B$};
 | 
			
		||||
    \node at (6.5,-3.5) {$C$};
 | 
			
		||||
    \node at (12.5,-5) {$D$};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämäkään algoritmi ei kuitenkaan toimi aina.
 | 
			
		||||
Esimerkiksi seuraavassa tilanteessa
 | 
			
		||||
algoritmi valitsee vain yhden tapahtuman:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw[fill=lightgray] (1, 0) rectangle (14, -1);
 | 
			
		||||
    \draw (3, -1.5) rectangle (7, -2.5);
 | 
			
		||||
    \draw (8, -3) rectangle (12, -4);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Kun ensimmäisenä alkava tapahtuma
 | 
			
		||||
valitaan mukaan, mitään muuta tapahtumaa
 | 
			
		||||
ei ole mahdollista valita.
 | 
			
		||||
Kuitenkin olisi mahdollista osallistua
 | 
			
		||||
kahteen tapahtumaan valitsemalla
 | 
			
		||||
kaksi myöhempää tapahtumaa.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Algoritmi 3}
 | 
			
		||||
 | 
			
		||||
Kolmas idea on valita aina seuraavaksi tapahtuma,
 | 
			
		||||
joka \emph{päättyy} mahdollisimman aikaisin.
 | 
			
		||||
Tämä algoritmi valitsee esimerkissä seuraavat tapahtumat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw[fill=lightgray] (2, 0) rectangle (6, -1);
 | 
			
		||||
    \draw (4, -1.5) rectangle (10, -2.5);
 | 
			
		||||
    \draw (6, -3) rectangle (18, -4);
 | 
			
		||||
    \draw[fill=lightgray] (12, -4.5) rectangle (16, -5.5);
 | 
			
		||||
    \node at (2.5,-0.5) {$A$};
 | 
			
		||||
    \node at (4.5,-2) {$B$};
 | 
			
		||||
    \node at (6.5,-3.5) {$C$};
 | 
			
		||||
    \node at (12.5,-5) {$D$};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tämä ahne algoritmi
 | 
			
		||||
tuottaa \textit{aina} optimiratkaisun.
 | 
			
		||||
Algoritmi toimii, koska on aina kokonaisuuden
 | 
			
		||||
kannalta optimaalista valita
 | 
			
		||||
ensimmäiseksi tapahtumaksi
 | 
			
		||||
mahdollisimman aikaisin päättyvä tapahtuma.
 | 
			
		||||
Tämän jälkeen on taas optimaalista
 | 
			
		||||
valita seuraava aikatauluun sopiva
 | 
			
		||||
mahdollisimman aikaisin
 | 
			
		||||
päättyvä tapahtua, jne.
 | 
			
		||||
 | 
			
		||||
Yksi tapa perustella valintaa on miettiä,
 | 
			
		||||
mitä tapahtuu, jos ensimmäiseksi tapahtumaksi
 | 
			
		||||
valitaan jokin muu kuin mahdollisimman
 | 
			
		||||
aikaisin päättyvä tapahtuma.
 | 
			
		||||
Tällainen valinta ei ole koskaan parempi,
 | 
			
		||||
koska myöhemmin päättyvän tapahtuman
 | 
			
		||||
jälkeen on joko yhtä paljon tai vähemmän
 | 
			
		||||
mahdollisuuksia valita seuraavia tapahtumia
 | 
			
		||||
kuin aiemmin päättyvän tapahtuman jälkeen.
 | 
			
		||||
 | 
			
		||||
\section{Tehtävät ja deadlinet}
 | 
			
		||||
 | 
			
		||||
Annettuna on $n$ tehtävää,
 | 
			
		||||
joista jokaisella on kesto ja deadline.
 | 
			
		||||
Tehtäväsi on valita järjestys,
 | 
			
		||||
jossa suoritat tehtävät.
 | 
			
		||||
Saat kustakin tehtävästä $d-x$ pistettä,
 | 
			
		||||
missä $d$ on tehtävän deadline ja $x$
 | 
			
		||||
on tehtävän valmistumishetki.
 | 
			
		||||
Mikä on suurin mahdollinen
 | 
			
		||||
yhteispistemäärä, jonka voit saada tehtävistä?
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos tehtävät ovat
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{lll}
 | 
			
		||||
tehtävä & kesto & deadline \\
 | 
			
		||||
\hline
 | 
			
		||||
$A$ & 4 & 2 \\
 | 
			
		||||
$B$ & 3 & 5 \\
 | 
			
		||||
$C$ & 2 & 7 \\
 | 
			
		||||
$D$ & 4 & 5 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
niin optimaalinen ratkaisu on suorittaa
 | 
			
		||||
tehtävät seuraavasti:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) rectangle (4, -1);
 | 
			
		||||
    \draw (4, 0) rectangle (10, -1);
 | 
			
		||||
    \draw (10, 0) rectangle (18, -1);
 | 
			
		||||
    \draw (18, 0) rectangle (26, -1);
 | 
			
		||||
    \node at (0.5,-0.5) {$C$};
 | 
			
		||||
    \node at (4.5,-0.5) {$B$};
 | 
			
		||||
    \node at (10.5,-0.5) {$A$};
 | 
			
		||||
    \node at (18.5,-0.5) {$D$};
 | 
			
		||||
 | 
			
		||||
    \draw (0,1.5) -- (26,1.5);
 | 
			
		||||
    \foreach \i in {0,2,...,26}
 | 
			
		||||
    {
 | 
			
		||||
        \draw (\i,1.25) -- (\i,1.75);
 | 
			
		||||
    }
 | 
			
		||||
    \footnotesize
 | 
			
		||||
    \node at (0,2.5) {0};
 | 
			
		||||
    \node at (10,2.5) {5};
 | 
			
		||||
    \node at (20,2.5) {10};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tässä ratkaisussa $C$ tuottaa 5 pistettä,
 | 
			
		||||
$B$ tuottaa 0 pistettä, $A$ tuottaa $-7$ pistettä
 | 
			
		||||
ja $D$ tuottaa $-8$ pistettä,
 | 
			
		||||
joten yhteispistemäärä on $-10$.
 | 
			
		||||
 | 
			
		||||
Yllättävää kyllä, tehtävän optimaalinen ratkaisu
 | 
			
		||||
ei riipu lainkaan deadlineista,
 | 
			
		||||
vaan toimiva ahne strategia on
 | 
			
		||||
yksinkertaisesti
 | 
			
		||||
suorittaa tehtävät \emph{järjestyksessä keston mukaan}
 | 
			
		||||
lyhimmästä pisimpään.
 | 
			
		||||
Syynä tähän on, että jos missä tahansa vaiheessa
 | 
			
		||||
suoritetaan peräkkäin kaksi tehtävää,
 | 
			
		||||
joista ensimmäinen kestää toista kauemmin,
 | 
			
		||||
tehtävien järjestyksen vaihtaminen parantaa ratkaisua.
 | 
			
		||||
Esimerkiksi jos peräkkäin ovat tehtävät
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) rectangle (8, -1);
 | 
			
		||||
    \draw (8, 0) rectangle (12, -1);
 | 
			
		||||
    \node at (0.5,-0.5) {$X$};
 | 
			
		||||
    \node at (8.5,-0.5) {$Y$};
 | 
			
		||||
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (7.75,-1.5) -- (0.25,-1.5);
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (11.75,-1.5) -- (8.25,-1.5);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (4,-2.5) {$a$};
 | 
			
		||||
\node at (10,-2.5) {$b$};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja $a>b$, niin järjestyksen muuttaminen muotoon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.4]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (0, 0) rectangle (4, -1);
 | 
			
		||||
    \draw (4, 0) rectangle (12, -1);
 | 
			
		||||
    \node at (0.5,-0.5) {$Y$};
 | 
			
		||||
    \node at (4.5,-0.5) {$X$};
 | 
			
		||||
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (3.75,-1.5) -- (0.25,-1.5);
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (11.75,-1.5) -- (4.25,-1.5);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (2,-2.5) {$b$};
 | 
			
		||||
\node at (8,-2.5) {$a$};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
antaa $X$:lle $b$ pistettä vähemmän ja $Y$:lle $a$ pistettä enemmän,
 | 
			
		||||
joten kokonaismuutos pistemäärään on $a-b > 0$.
 | 
			
		||||
Optimiratkaisussa
 | 
			
		||||
kaikille peräkkäin suoritettaville tehtäville
 | 
			
		||||
tulee päteä, että lyhyempi tulee ennen pidempää,
 | 
			
		||||
mistä seuraa, että tehtävät tulee suorittaa
 | 
			
		||||
järjestyksessä keston mukaan.
 | 
			
		||||
 | 
			
		||||
\section{Keskiluvut}
 | 
			
		||||
 | 
			
		||||
Tarkastelemme seuraavaksi ongelmaa, jossa
 | 
			
		||||
annettuna on $n$ lukua $a_1,a_2,\ldots,a_n$
 | 
			
		||||
ja tehtävänä on etsiä luku $x$ niin, että summa
 | 
			
		||||
\[|a_1-x|^c+|a_2-x|^c+\cdots+|a_n-x|^c\]
 | 
			
		||||
on mahdollisimman pieni.
 | 
			
		||||
Keskitymme tapauksiin, joissa $c=1$ tai $c=2$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tapaus $c=1$}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa minimoitavana on summa
 | 
			
		||||
\[|a_1-x|+|a_2-x|+\cdots+|a_n-x|.\]
 | 
			
		||||
Esimerkiksi jos luvut ovat $[1,2,9,2,6]$,
 | 
			
		||||
niin paras ratkaisu on valita $x=2$,
 | 
			
		||||
jolloin summaksi tulee
 | 
			
		||||
\[
 | 
			
		||||
|1-2|+|2-2|+|9-2|+|2-2|+|6-2|=12.
 | 
			
		||||
\]
 | 
			
		||||
Yleisessä tapauksessa paras valinta $x$:n arvoksi
 | 
			
		||||
on lukujen \textit{mediaani}
 | 
			
		||||
eli keskimmäinen luku järjestyksessä.
 | 
			
		||||
Esimerkiksi luvut $[1,2,9,2,6]$
 | 
			
		||||
ovat järjestyksessä $[1,2,2,6,9]$,
 | 
			
		||||
joten mediaani on 2.
 | 
			
		||||
 | 
			
		||||
Mediaanin valinta on paras ratkaisu,
 | 
			
		||||
koska jos $x$ on mediaania pienempi,
 | 
			
		||||
$x$:n suurentaminen pienentää summaa,
 | 
			
		||||
ja vastaavasti jos $x$ on mediaania suurempi,
 | 
			
		||||
$x$:n pienentäminen pienentää summaa.
 | 
			
		||||
Niinpä $x$ kannattaa siirtää mahdollisimman
 | 
			
		||||
lähelle mediaania eli optimiratkaisu on
 | 
			
		||||
valita $x$ mediaaniksi.
 | 
			
		||||
Jos $n$ on parillinen ja mediaaneja on kaksi,
 | 
			
		||||
kumpikin mediaani sekä kaikki niiden välillä
 | 
			
		||||
olevat luvut tuottavat optimaalisen ratkaisun.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tapaus $c=2$}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa minimoitavana on summa
 | 
			
		||||
\[(a_1-x)^2+(a_2-x)^2+\cdots+(a_n-x)^2.\]
 | 
			
		||||
Esimerkiksi jos luvut ovat $[1,2,9,2,6]$,
 | 
			
		||||
niin paras ratkaisu on $x=4$,
 | 
			
		||||
jolloin summaksi tulee
 | 
			
		||||
\[
 | 
			
		||||
(1-4)^2+(2-4)^2+(9-4)^2+(2-4)^2+(6-4)^2=46.
 | 
			
		||||
\]
 | 
			
		||||
Yleisessä tapauksessa paras valinta $x$:n arvoksi on lukujen
 | 
			
		||||
\textit{keskiarvo}.
 | 
			
		||||
Esimerkissä lukujen keskiarvo on $(1+2+9+2+6)/5=4$.
 | 
			
		||||
Tämän tuloksen voi johtaa järjestämällä summan
 | 
			
		||||
uudestaan muotoon
 | 
			
		||||
\[
 | 
			
		||||
nx^2 - 2x(a_1+a_2+\cdots+a_n) + (a_1^2+a_2^2+\cdots+a_n^2).
 | 
			
		||||
\]
 | 
			
		||||
Viimeinen osa ei riipu $x$:stä, joten sen voi jättää huomiotta.
 | 
			
		||||
Jäljelle jäävistä osista muodostuu funktio
 | 
			
		||||
$nx^2-2xs$, kun $s=a_1+a_2+\cdots+a_n$.
 | 
			
		||||
Tämä on ylöspäin aukeava paraabeli,
 | 
			
		||||
jonka nollakohdat ovat $x=0$ ja $x=2s/n$
 | 
			
		||||
ja pienin arvo on näiden keskikohta
 | 
			
		||||
$x=s/n$ eli taulukon lukujen keskiarvo.
 | 
			
		||||
 | 
			
		||||
\section{Tiedonpakkaus}
 | 
			
		||||
 | 
			
		||||
\index{tiedonpakkaus}
 | 
			
		||||
\index{binxxrikoodi@binäärikoodi}
 | 
			
		||||
\index{koodisana@koodisana}
 | 
			
		||||
 | 
			
		||||
Annettuna on merkkijono ja tehtävänä on
 | 
			
		||||
\emph{pakata} se niin,
 | 
			
		||||
että tilaa kuluu vähemmän.
 | 
			
		||||
Käytämme tähän \key{binäärikoodia},
 | 
			
		||||
joka määrittää kullekin merkille
 | 
			
		||||
biteistä muodostuvan \key{koodisanan}.
 | 
			
		||||
Tällöin merkkijonon voi pakata
 | 
			
		||||
korvaamalla jokaisen merkin vastaavalla koodisanalla.
 | 
			
		||||
Esimerkiksi seuraava binäärikoodi määrittää
 | 
			
		||||
koodisanat merkeille \texttt{A}–\texttt{D}:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr}
 | 
			
		||||
merkki & koodisana \\
 | 
			
		||||
\hline
 | 
			
		||||
\texttt{A} & 00 \\
 | 
			
		||||
\texttt{B} & 01 \\
 | 
			
		||||
\texttt{C} & 10 \\
 | 
			
		||||
\texttt{D} & 11 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämä koodi on \key{vakiopituinen}
 | 
			
		||||
eli jokainen koodisana on yhtä pitkä.
 | 
			
		||||
Esimerkiksi merkkijono
 | 
			
		||||
\texttt{AABACDACA} on pakattuna
 | 
			
		||||
\[000001001011001000,\]
 | 
			
		||||
eli se vie tilaa 18 bittiä.
 | 
			
		||||
Pakkausta on kuitenkin mahdollista parantaa
 | 
			
		||||
ottamalla käyttöön \key{muuttuvan pituinen} koodi,
 | 
			
		||||
jossa koodisanojen pituus voi vaihdella.
 | 
			
		||||
Tällöin voimme antaa usein esiintyville merkeille
 | 
			
		||||
lyhyen koodisanan ja harvoin esiintyville
 | 
			
		||||
merkeille pitkän koodisanan.
 | 
			
		||||
Osoittautuu, että yllä olevalle merkkijonolle
 | 
			
		||||
\key{optimaalinen} koodi on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr}
 | 
			
		||||
merkki & koodisana \\
 | 
			
		||||
\hline
 | 
			
		||||
\texttt{A} & 0 \\
 | 
			
		||||
\texttt{B} & 110 \\
 | 
			
		||||
\texttt{C} & 10 \\
 | 
			
		||||
\texttt{D} & 111 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
Optimaalinen koodi tuottaa
 | 
			
		||||
mahdollisimman lyhyen pakatun merkkijonon.
 | 
			
		||||
Tässä tapauksessa optimaalinen koodi
 | 
			
		||||
pakkaa merkkijonon muotoon
 | 
			
		||||
\[001100101110100,\]
 | 
			
		||||
ja tilaa kuluu vain 15 bittiä.
 | 
			
		||||
Paremman koodin ansiosta onnistuimme siis säästämään
 | 
			
		||||
3 bittiä tilaa pakkauksessa.
 | 
			
		||||
 | 
			
		||||
Huomaa, että koodin tulee olla aina sellainen,
 | 
			
		||||
että mikään koodisana ei ole toisen koodisanan
 | 
			
		||||
alkuosa.
 | 
			
		||||
Esimerkiksi ei ole sallittua, että koodissa
 | 
			
		||||
olisi molemmat koodisanat 10 ja 1011.
 | 
			
		||||
Tämä rajoitus johtuu siitä,
 | 
			
		||||
että haluamme myös pystyä palauttamaan
 | 
			
		||||
alkuperäisen merkkijonon pakkauksen jälkeen.
 | 
			
		||||
Jos koodisana voisi olla toisen alkuosa,
 | 
			
		||||
tämä ei välttämättä olisi mahdollista.
 | 
			
		||||
Esimerkiksi seuraava koodi
 | 
			
		||||
\emph{ei} ole kelvollinen:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr}
 | 
			
		||||
merkki & koodisana \\
 | 
			
		||||
\hline
 | 
			
		||||
\texttt{A} & 10 \\
 | 
			
		||||
\texttt{B} & 11 \\
 | 
			
		||||
\texttt{C} & 1011 \\
 | 
			
		||||
\texttt{D} & 111 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tätä koodia käyttäen ei olisi mahdollista tietää,
 | 
			
		||||
tarkoittaako pakattu merkkijono 1011
 | 
			
		||||
merkkijonoa \texttt{AB} vai merkkijonoa \texttt{C}.
 | 
			
		||||
 | 
			
		||||
\index{Huffmanin koodaus}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Huffmanin koodaus}
 | 
			
		||||
 | 
			
		||||
\key{Huffmanin koodaus} on ahne algoritmi,
 | 
			
		||||
joka muodostaa optimaalisen koodin
 | 
			
		||||
merkkijonon pakkaamista varten.
 | 
			
		||||
Se muodostaa merkkien esiintymiskertojen
 | 
			
		||||
perustella binääripuun, josta voi lukea
 | 
			
		||||
kunkin merkin koodisanan
 | 
			
		||||
liikkumalla huipulta merkkiä vastaavaan solmuun.
 | 
			
		||||
Liikkuminen vasemmalle vastaa
 | 
			
		||||
bittiä 0 ja liikkuminen oikealle
 | 
			
		||||
vastaa bittiä 1.
 | 
			
		||||
 | 
			
		||||
Aluksi jokaista merkkijonon merkkiä vastaa solmu,
 | 
			
		||||
jonka painona on merkin esiintymiskertojen määrä merkkijonossa.
 | 
			
		||||
Sitten joka vaiheessa puusta valitaan
 | 
			
		||||
kaksi painoltaan pienintä solmua
 | 
			
		||||
ja ne yhdistetään luomalla niiden
 | 
			
		||||
yläpuolelle uusi solmu,
 | 
			
		||||
jonka paino on solmujen yhteispaino.
 | 
			
		||||
Näin jatketaan, kunnes kaikki solmut
 | 
			
		||||
on yhdistetty ja koodi on valmis.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan nyt, miten Huffmanin koodaus
 | 
			
		||||
muodostaa optimaalisen koodin merkkijonolle
 | 
			
		||||
\texttt{AABACDACA}.
 | 
			
		||||
Alkutilanteessa on neljä solmua,
 | 
			
		||||
jotka vastaavat merkkijonossa olevia merkkejä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$5$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (4,0) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$1$};
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (0,-0.75) {\texttt{A}};
 | 
			
		||||
\node[color=blue] at (2,-0.75) {\texttt{B}};
 | 
			
		||||
\node[color=blue] at (4,-0.75) {\texttt{C}};
 | 
			
		||||
\node[color=blue] at (6,-0.75) {\texttt{D}};
 | 
			
		||||
 | 
			
		||||
%\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Merkkiä \texttt{A} vastaavan solmun paino on
 | 
			
		||||
5, koska merkki \texttt{A} esiintyy 5 kertaa merkkijonossa.
 | 
			
		||||
Muiden solmujen painot on laskettu vastaavalla tavalla.
 | 
			
		||||
 | 
			
		||||
Ensimmäinen askel on yhdistää merkkejä \texttt{B} ja \texttt{D}
 | 
			
		||||
vastaavat solmut, joiden kummankin paino on 1.
 | 
			
		||||
Tuloksena on:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$5$};
 | 
			
		||||
\node[draw, circle] (3) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (2) at (4,0) {$1$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$1$};
 | 
			
		||||
\node[draw, circle] (5) at (5,1) {$2$};
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (0,-0.75) {\texttt{A}};
 | 
			
		||||
\node[color=blue] at (2,-0.75) {\texttt{C}};
 | 
			
		||||
\node[color=blue] at (4,-0.75) {\texttt{B}};
 | 
			
		||||
\node[color=blue] at (6,-0.75) {\texttt{D}};
 | 
			
		||||
 | 
			
		||||
\node at (4.3,0.7) {0};
 | 
			
		||||
\node at (5.7,0.7) {1};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen yhdistetään solmut, joiden paino on 2:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,0) {$5$};
 | 
			
		||||
\node[draw, circle] (3) at (3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (2) at (4,0) {$1$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$1$};
 | 
			
		||||
\node[draw, circle] (5) at (5,1) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (4,2) {$4$};
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (1,-0.75) {\texttt{A}};
 | 
			
		||||
\node[color=blue] at (3,1-0.75) {\texttt{C}};
 | 
			
		||||
\node[color=blue] at (4,-0.75) {\texttt{B}};
 | 
			
		||||
\node[color=blue] at (6,-0.75) {\texttt{D}};
 | 
			
		||||
 | 
			
		||||
\node at (4.3,0.7) {0};
 | 
			
		||||
\node at (5.7,0.7) {1};
 | 
			
		||||
\node at (3.3,1.7) {0};
 | 
			
		||||
\node at (4.7,1.7) {1};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Lopuksi yhdistetään kaksi viimeistä solmua:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (2,2) {$5$};
 | 
			
		||||
\node[draw, circle] (3) at (3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (2) at (4,0) {$1$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$1$};
 | 
			
		||||
\node[draw, circle] (5) at (5,1) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (4,2) {$4$};
 | 
			
		||||
\node[draw, circle] (7) at (3,3) {$9$};
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (2,2-0.75) {\texttt{A}};
 | 
			
		||||
\node[color=blue] at (3,1-0.75) {\texttt{C}};
 | 
			
		||||
\node[color=blue] at (4,-0.75) {\texttt{B}};
 | 
			
		||||
\node[color=blue] at (6,-0.75) {\texttt{D}};
 | 
			
		||||
 | 
			
		||||
\node at (4.3,0.7) {0};
 | 
			
		||||
\node at (5.7,0.7) {1};
 | 
			
		||||
\node at (3.3,1.7) {0};
 | 
			
		||||
\node at (4.7,1.7) {1};
 | 
			
		||||
\node at (2.3,2.7) {0};
 | 
			
		||||
\node at (3.7,2.7) {1};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (1) -- (7);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Nyt kaikki solmut ovat puussa, joten koodi on valmis.
 | 
			
		||||
Puusta voidaan lukea seuraavat koodisanat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rr}
 | 
			
		||||
merkki & koodisana \\
 | 
			
		||||
\hline
 | 
			
		||||
\texttt{A} & 0 \\
 | 
			
		||||
\texttt{B} & 110 \\
 | 
			
		||||
\texttt{C} & 10 \\
 | 
			
		||||
\texttt{D} & 111 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
% \subsubsection{Miksi algoritmi toimii?}
 | 
			
		||||
% 
 | 
			
		||||
% Huffmanin koodaus on ahne algoritmi, koska se
 | 
			
		||||
% yhdistää aina kaksi solmua, joiden painot ovat
 | 
			
		||||
% pienimmät.
 | 
			
		||||
% Miksi on varmaa, että tämä menetelmä tuottaa
 | 
			
		||||
% aina optimaalisen koodin?
 | 
			
		||||
% 
 | 
			
		||||
% Merkitään $c(x)$ merkin $x$ esiintymiskertojen
 | 
			
		||||
% määrää merkkijonossa sekä $s(x)$
 | 
			
		||||
% merkkiä $x$ vastaavan koodisanan pituutta.
 | 
			
		||||
% Näitä merkintöjä käyttäen merkkijonon
 | 
			
		||||
% bittiesityksen pituus on
 | 
			
		||||
% \[\sum_x c(x) \cdot s(x),\]
 | 
			
		||||
% missä summa käy läpi kaikki merkkijonon merkit.
 | 
			
		||||
% Esimerkiksi äskeisessä esimerkissä
 | 
			
		||||
% bittiesityksen pituus on
 | 
			
		||||
% \[5 \cdot 1 + 1 \cdot 3 + 2 \cdot 2 + 1 \cdot 3 = 15.\]
 | 
			
		||||
% Hyödyllinen havainto on, että $s(x)$ on yhtä suuri kuin
 | 
			
		||||
% merkkiä $x$ vastaavan solmun \emph{syvyys} puussa
 | 
			
		||||
% eli matka puun huipulta solmuun.
 | 
			
		||||
% 
 | 
			
		||||
% Perustellaan ensin, miksi optimaalista koodia vastaa
 | 
			
		||||
% aina binääripuu, jossa jokaisesta solmusta lähtee
 | 
			
		||||
% alaspäin joko kaksi haaraa tai ei yhtään haaraa.
 | 
			
		||||
% Tehdään vastaoletus, että jostain solmusta lähtisi
 | 
			
		||||
% alaspäin vain yksi haara.
 | 
			
		||||
% Esimerkiksi seuraavassa puussa tällainen tilanne on solmussa $a$:
 | 
			
		||||
% \begin{center}
 | 
			
		||||
% \begin{tikzpicture}[scale=0.9]
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (3) at (3,1) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (2) at (4,0) {$b$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (5) at (5,1) {$a$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (6) at (4,2) {\phantom{$a$}};
 | 
			
		||||
% 
 | 
			
		||||
% \path[draw,thick,-] (2) -- (5);
 | 
			
		||||
% \path[draw,thick,-] (3) -- (6);
 | 
			
		||||
% \path[draw,thick,-] (5) -- (6);
 | 
			
		||||
% \end{tikzpicture}
 | 
			
		||||
% \end{center}
 | 
			
		||||
% Tällainen solmu $a$ on kuitenkin aina turha, koska se
 | 
			
		||||
% tuo vain yhden bitin lisää polkuihin, jotka kulkevat
 | 
			
		||||
% solmun kautta, eikä sen avulla voi erottaa kahta
 | 
			
		||||
% koodisanaa toisistaan. Niinpä kyseisen solmun voi poistaa
 | 
			
		||||
% puusta, minkä seurauksena syntyy parempi koodi,
 | 
			
		||||
% eli optimaalista koodia vastaavassa puussa ei voi olla
 | 
			
		||||
% solmua, josta lähtee vain yksi haara.
 | 
			
		||||
% 
 | 
			
		||||
% Perustellaan sitten, miksi on joka vaiheessa optimaalista
 | 
			
		||||
% yhdistää kaksi solmua, joiden painot ovat pienimmät.
 | 
			
		||||
% Tehdään vastaoletus, että solmun $a$ paino on pienin,
 | 
			
		||||
% mutta sitä ei saisi yhdistää aluksi toiseen solmuun,
 | 
			
		||||
% vaan sen sijasta tulisi yhdistää solmu $b$
 | 
			
		||||
% ja jokin toinen solmu:
 | 
			
		||||
% \begin{center}
 | 
			
		||||
% \begin{tikzpicture}[scale=0.9]
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (1) at (0,0) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (2) at (-2,-1) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (3) at (2,-1) {$a$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (4) at (-3,-2) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (5) at (-1,-2) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (8) at (-2,-3) {$b$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (9) at (0,-3) {\phantom{$a$}};
 | 
			
		||||
% 
 | 
			
		||||
% \path[draw,thick,-] (1) -- (2);
 | 
			
		||||
% \path[draw,thick,-] (1) -- (3);
 | 
			
		||||
% \path[draw,thick,-] (2) -- (4);
 | 
			
		||||
% \path[draw,thick,-] (2) -- (5);
 | 
			
		||||
% \path[draw,thick,-] (5) -- (8);
 | 
			
		||||
% \path[draw,thick,-] (5) -- (9);
 | 
			
		||||
% \end{tikzpicture}
 | 
			
		||||
% \end{center}
 | 
			
		||||
% Solmuille $a$ ja $b$ pätee
 | 
			
		||||
% $c(a) \le c(b)$ ja $s(a) \le s(b)$.
 | 
			
		||||
% Solmut aiheuttavat bittiesityksen pituuteen lisäyksen
 | 
			
		||||
% \[c(a) \cdot s(a) + c(b) \cdot s(b).\]
 | 
			
		||||
% Tarkastellaan sitten toista tilannetta,
 | 
			
		||||
% joka on muuten samanlainen kuin ennen,
 | 
			
		||||
% mutta solmut $a$ ja $b$ on vaihdettu keskenään:
 | 
			
		||||
% \begin{center}
 | 
			
		||||
% \begin{tikzpicture}[scale=0.9]
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (1) at (0,0) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (2) at (-2,-1) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (3) at (2,-1) {$b$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (4) at (-3,-2) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (5) at (-1,-2) {\phantom{$a$}};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (8) at (-2,-3) {$a$};
 | 
			
		||||
% \node[draw, circle, minimum size=20pt] (9) at (0,-3) {\phantom{$a$}};
 | 
			
		||||
% 
 | 
			
		||||
% \path[draw,thick,-] (1) -- (2);
 | 
			
		||||
% \path[draw,thick,-] (1) -- (3);
 | 
			
		||||
% \path[draw,thick,-] (2) -- (4);
 | 
			
		||||
% \path[draw,thick,-] (2) -- (5);
 | 
			
		||||
% \path[draw,thick,-] (5) -- (8);
 | 
			
		||||
% \path[draw,thick,-] (5) -- (9);
 | 
			
		||||
% \end{tikzpicture}
 | 
			
		||||
% \end{center}
 | 
			
		||||
% Osoittautuu, että tätä puuta vastaava koodi on
 | 
			
		||||
% \emph{yhtä hyvä tai parempi} kuin alkuperäinen koodi, joten vastaoletus
 | 
			
		||||
% on väärin ja Huffmanin koodaus
 | 
			
		||||
% toimiikin oikein, jos se yhdistää aluksi solmun $a$
 | 
			
		||||
% jonkin solmun kanssa.
 | 
			
		||||
% Tämän perustelee seuraava epäyhtälöketju:
 | 
			
		||||
% \[\begin{array}{rcl}
 | 
			
		||||
% c(b) & \ge & c(a) \\
 | 
			
		||||
% c(b)\cdot(s(b)-s(a)) & \ge & c(a)\cdot (s(b)-s(a)) \\
 | 
			
		||||
% c(b)\cdot s(b)-c(b)\cdot s(a) & \ge & c(a)\cdot s(b)-c(a)\cdot s(a) \\
 | 
			
		||||
% c(a)\cdot s(a)+c(b)\cdot s(b) & \ge & c(a)\cdot s(b)+c(b)\cdot s(a) \\
 | 
			
		||||
% \end{array}\]
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,976 @@
 | 
			
		|||
\chapter{Dynamic programming}
 | 
			
		||||
 | 
			
		||||
\index{dynaaminen ohjelmointi@dynaaminen ohjelmointi}
 | 
			
		||||
 | 
			
		||||
\key{Dynaaminen ohjelmointi}
 | 
			
		||||
on tekniikka, joka yhdistää täydellisen haun
 | 
			
		||||
toimivuuden ja ahneiden algoritmien tehokkuuden.
 | 
			
		||||
Dynaamisen ohjelmoinnin käyttäminen edellyttää,
 | 
			
		||||
että tehtävä jakautuu osaongelmiin,
 | 
			
		||||
jotka voidaan käsitellä toisistaan riippumattomasti.
 | 
			
		||||
 | 
			
		||||
Dynaamisella ohjelmoinnilla on kaksi käyttötarkoitusta:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
\key{Optimiratkaisun etsiminen}:
 | 
			
		||||
Haluamme etsiä ratkaisun, joka on
 | 
			
		||||
jollakin tavalla suurin mahdollinen
 | 
			
		||||
tai pienin mahdollinen.
 | 
			
		||||
\item
 | 
			
		||||
\key{Ratkaisuiden määrän laskeminen}:
 | 
			
		||||
Haluamme laskea, kuinka monta mahdollista
 | 
			
		||||
ratkaisua on olemassa.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Tutustumme dynaamiseen ohjelmointiin ensin
 | 
			
		||||
optimiratkaisun etsimisen kautta ja käytämme sitten
 | 
			
		||||
samaa ideaa ratkaisujen määrän laskemiseen.
 | 
			
		||||
 | 
			
		||||
Dynaamisen ohjelmoinnin ymmärtäminen on yksi merkkipaalu
 | 
			
		||||
jokaisen kisakoodarin uralla.
 | 
			
		||||
Vaikka menetelmän perusidea on yksinkertainen,
 | 
			
		||||
haasteena on oppia soveltamaan sitä sujuvasti
 | 
			
		||||
erilaisissa tehtävissä.
 | 
			
		||||
Tämä luku esittelee joukon
 | 
			
		||||
perusesimerkkejä, joista on hyvä lähteä liikkeelle.
 | 
			
		||||
 | 
			
		||||
\section{Kolikkotehtävä}
 | 
			
		||||
 | 
			
		||||
Aloitamme dynaamisen ohjelmoinnin tutun tehtävän kautta:
 | 
			
		||||
Muodostettavana on rahamäärä $x$
 | 
			
		||||
käyttäen mahdollisimman vähän kolikoita.
 | 
			
		||||
Kolikoiden arvot ovat $\{c_1,c_2,\ldots,c_k\}$
 | 
			
		||||
ja jokaista kolikkoa on saatavilla rajattomasti.
 | 
			
		||||
 | 
			
		||||
Luvussa 6.1 ratkaisimme tehtävän ahneella algoritmilla,
 | 
			
		||||
joka muodostaa rahamäärän valiten mahdollisimman
 | 
			
		||||
suuria kolikoita.
 | 
			
		||||
Ahne algoritmi toimii esimerkiksi silloin,
 | 
			
		||||
kun kolikot ovat eurokolikot,
 | 
			
		||||
mutta yleisessä tapauksessa ahne algoritmi
 | 
			
		||||
ei välttämättä valitse pienintä määrää kolikoita.
 | 
			
		||||
 | 
			
		||||
Nyt on aika ratkaista tehtävä tehokkaasti
 | 
			
		||||
dynaamisella ohjelmoinnilla niin,
 | 
			
		||||
että algoritmi toimii millä tahansa kolikoilla.
 | 
			
		||||
Algoritmi perustuu rekursiiviseen funktioon,
 | 
			
		||||
joka käy läpi kaikki vaihtoehdot rahamäärän
 | 
			
		||||
muodostamiseen täydellisen haun kaltaisesti.
 | 
			
		||||
Algoritmi toimii kuitenkin tehokkaasti, koska
 | 
			
		||||
se tallentaa välituloksia muistitaulukkoon,
 | 
			
		||||
minkä ansiosta sen ei tarvitse laskea samoja
 | 
			
		||||
asioita moneen kertaan.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Rekursiivinen esitys}
 | 
			
		||||
 | 
			
		||||
\index{rekursioyhtxlz@rekursioyhtälö}
 | 
			
		||||
 | 
			
		||||
Dynaamisessa ohjelmoinnissa on ideana esittää
 | 
			
		||||
ongelma rekursiivisesti niin,
 | 
			
		||||
että ongelman ratkaisun voi laskea
 | 
			
		||||
saman ongelman pienempien tapausten ratkaisuista.
 | 
			
		||||
Tässä tehtävässä luonteva ongelma on seuraava:
 | 
			
		||||
mikä on pienin määrä kolikoita,
 | 
			
		||||
joilla voi muodostaa rahamäärän $x$?
 | 
			
		||||
 | 
			
		||||
Merkitään $f(x)$ funktiota,
 | 
			
		||||
joka antaa vastauksen ongelmaan,
 | 
			
		||||
eli $f(x)$ on pienin määrä kolikoita,
 | 
			
		||||
joilla voi muodostaa rahamäärän $x$.
 | 
			
		||||
Funktion arvot riippuvat siitä,
 | 
			
		||||
mitkä kolikot ovat käytössä.
 | 
			
		||||
Esimerkiksi jos kolikot ovat $\{1,3,4\}$,
 | 
			
		||||
funktion ensimmäiset arvot ovat:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(0) & = & 0 \\
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(2) & = & 2 \\
 | 
			
		||||
f(3) & = & 1 \\
 | 
			
		||||
f(4) & = & 1 \\
 | 
			
		||||
f(5) & = & 2 \\
 | 
			
		||||
f(6) & = & 2 \\
 | 
			
		||||
f(7) & = & 2 \\
 | 
			
		||||
f(8) & = & 2 \\
 | 
			
		||||
f(9) & = & 3 \\
 | 
			
		||||
f(10) & = & 3 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Nyt $f(0)=0$, koska jos rahamäärä on 0,
 | 
			
		||||
ei tarvita yhtään kolikkoa.
 | 
			
		||||
Vastaavasti $f(3)=1$, koska rahamäärän 3
 | 
			
		||||
voi muodostaa kolikolla 3,
 | 
			
		||||
ja $f(5)=2$, koska rahamäärän 5
 | 
			
		||||
voi muodostaa kolikoilla 1 ja 4.
 | 
			
		||||
 | 
			
		||||
Oleellinen ominaisuus funktiossa on,
 | 
			
		||||
että arvon $f(x)$ pystyy laskemaan
 | 
			
		||||
rekursiivisesti käyttäen pienempiä
 | 
			
		||||
funktion arvoja.
 | 
			
		||||
Esimerkiksi jos kolikot ovat $\{1,3,4\}$,
 | 
			
		||||
on kolme tapaa alkaa muodostaa rahamäärää $x$:
 | 
			
		||||
valitaan kolikko 1, 3 tai 4.
 | 
			
		||||
Jos valitaan kolikko 1, täytyy
 | 
			
		||||
muodostaa vielä rahamäärä $x-1$.
 | 
			
		||||
Vastaavasti jos valitaan kolikko 3 tai 4,
 | 
			
		||||
täytyy muodostaa rahamäärä $x-3$ tai $x-4$.
 | 
			
		||||
 | 
			
		||||
Niinpä rekursiivinen kaava on
 | 
			
		||||
\[f(x) = \min(f(x-1),f(x-3),f(x-4))+1,\]
 | 
			
		||||
missä funktio $\min$ valitsee pienimmän parametreistaan.
 | 
			
		||||
Yleisemmin jos kolikot ovat $\{c_1,c_2,\ldots,c_k\}$,
 | 
			
		||||
rekursiivinen kaava on
 | 
			
		||||
\[f(x) = \min(f(x-c_1),f(x-c_2),\ldots,f(x-c_k))+1.\]
 | 
			
		||||
Funktion pohjatapauksena on
 | 
			
		||||
\[f(0)=0,\]
 | 
			
		||||
koska rahamäärän 0 muodostamiseen ei tarvita
 | 
			
		||||
yhtään kolikkoa.
 | 
			
		||||
Lisäksi on hyvä määritellä
 | 
			
		||||
\[f(x)=\infty,\hspace{8px}\textrm{jos $x<0$}.\]
 | 
			
		||||
Tämä tarkoittaa, että negatiivisen rahamäärän
 | 
			
		||||
muodostaminen vaatii äärettömästi kolikoita,
 | 
			
		||||
mikä estää sen, että rekursio muodostaisi
 | 
			
		||||
ratkaisun, johon kuuluu negatiivinen rahamäärä.
 | 
			
		||||
 | 
			
		||||
Nyt voimme toteuttaa funktion C++:lla suoraan
 | 
			
		||||
rekursiivisen määritelmän perusteella:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int f(int x) {
 | 
			
		||||
    if (x == 0) return 0;
 | 
			
		||||
    if (x < 0) return 1e9;
 | 
			
		||||
    int u = 1e9;
 | 
			
		||||
    for (int i = 1; i <= k; i++) {
 | 
			
		||||
        u = min(u, f(x-c[i])+1);
 | 
			
		||||
    }
 | 
			
		||||
    return u;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Koodi olettaa, että käytettävät kolikot ovat
 | 
			
		||||
$\texttt{c}[1], \texttt{c}[2], \ldots, \texttt{c}[k]$,
 | 
			
		||||
ja arvo $10^9$ kuvastaa ääretöntä.
 | 
			
		||||
Tämä on toimiva funktio, mutta se ei ole vielä tehokas,
 | 
			
		||||
koska funktio käy läpi valtavasti erilaisia tapoja
 | 
			
		||||
muodostaa rahamäärä.
 | 
			
		||||
Seuraavaksi esiteltävä muistitaulukko tekee
 | 
			
		||||
funktiosta tehokkaan.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Muistitaulukko}
 | 
			
		||||
 | 
			
		||||
\index{muistitaulukko@muistitaulukko}
 | 
			
		||||
 | 
			
		||||
Dynaaminen ohjelmointi tehostaa
 | 
			
		||||
rekursiivisen funktion laskentaa
 | 
			
		||||
tallentamalla funktion arvoja \key{muistitaulukkoon}.
 | 
			
		||||
Taulukon avulla funktion arvo
 | 
			
		||||
tietyllä parametrilla riittää laskea
 | 
			
		||||
vain kerran, minkä jälkeen sen voi
 | 
			
		||||
hakea suoraan taulukosta.
 | 
			
		||||
Tämä muutos nopeuttaa algoritmia ratkaisevasti.
 | 
			
		||||
 | 
			
		||||
Tässä tehtävässä muistitaulukoksi sopii taulukko
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int d[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
jonka kohtaan $\texttt{d}[x]$
 | 
			
		||||
lasketaan funktion arvo $f(x)$.
 | 
			
		||||
Vakio $N$ valitaan niin, että kaikki
 | 
			
		||||
laskettavat funktion arvot mahtuvat taulukkoon.
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen funktion voi toteuttaa
 | 
			
		||||
tehokkaasti näin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int f(int x) {
 | 
			
		||||
    if (x == 0) return 0;
 | 
			
		||||
    if (x < 0) return 1e9;
 | 
			
		||||
    if (d[x]) return d[x];
 | 
			
		||||
    int u = 1e9;
 | 
			
		||||
    for (int i = 1; i <= k; i++) {
 | 
			
		||||
        u = min(u, f(x-c[i])+1);
 | 
			
		||||
    }
 | 
			
		||||
    d[x] = u;
 | 
			
		||||
    return d[x];
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio käsittelee pohjatapaukset $x=0$
 | 
			
		||||
ja $x<0$ kuten ennenkin.
 | 
			
		||||
Sitten funktio tarkastaa,
 | 
			
		||||
onko $f(x)$ laskettu jo taulukkoon $\texttt{d}[x]$.
 | 
			
		||||
Jos $f(x)$ on laskettu,
 | 
			
		||||
funktio palauttaa sen suoraan.
 | 
			
		||||
Muussa tapauksessa funktio laskee arvon rekursiivisesti
 | 
			
		||||
ja tallentaa sen kohtaan $\texttt{d}[x]$.
 | 
			
		||||
 | 
			
		||||
Muistitaulukon ansiosta funktio toimii
 | 
			
		||||
nopeasti, koska sen tarvitsee laskea
 | 
			
		||||
vastaus kullekin $x$:n arvolle
 | 
			
		||||
vain kerran rekursiivisesti.
 | 
			
		||||
Heti kun arvo $f(x)$ on tallennettu muistitaulukkoon,
 | 
			
		||||
sen saa haettua sieltä suoraan,
 | 
			
		||||
kun funktiota kutsutaan seuraavan kerran parametrilla $x$.
 | 
			
		||||
 | 
			
		||||
Tuloksena olevan algoritmin aikavaativuus on $O(xk)$,
 | 
			
		||||
kun rahamäärä on $x$ ja kolikoiden määrä on $k$.
 | 
			
		||||
Käytännössä ratkaisu on mahdollista toteuttaa,
 | 
			
		||||
jos $x$ on niin pieni, että on mahdollista varata 
 | 
			
		||||
riittävän suuri muistitaulukko.
 | 
			
		||||
 | 
			
		||||
Huomaa, että muistitaulukon voi muodostaa
 | 
			
		||||
myös suoraan silmukalla ilman rekursiota
 | 
			
		||||
laskemalla arvot pienimmästä suurimpaan:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
d[0] = 0;
 | 
			
		||||
for (int i = 1; i <= x; i++) {
 | 
			
		||||
    int u = 1e9;
 | 
			
		||||
    for (int j = 1; j <= k; j++) {
 | 
			
		||||
        if (i-c[j] < 0) continue;
 | 
			
		||||
        u = min(u, d[i-c[j]]+1);
 | 
			
		||||
    }
 | 
			
		||||
    d[i] = u;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Silmukkatoteutus on lyhyempi ja
 | 
			
		||||
hieman tehokkaampi kuin rekursiototeutus,
 | 
			
		||||
minkä vuoksi kokeneet kisakoodarit
 | 
			
		||||
toteuttavat dynaamisen ohjelmoinnin
 | 
			
		||||
usein silmukan avulla.
 | 
			
		||||
Kuitenkin silmukkatoteutuksen taustalla
 | 
			
		||||
on sama rekursiivinen idea kuin ennenkin.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisun muodostaminen}
 | 
			
		||||
 | 
			
		||||
Joskus optimiratkaisun arvon selvittämisen lisäksi
 | 
			
		||||
täytyy muodostaa näytteeksi yksi mahdollinen optimiratkaisu.
 | 
			
		||||
Tässä tehtävässä tämä tarkoittaa,
 | 
			
		||||
että ohjelman täytyy antaa esimerkki
 | 
			
		||||
tavasta valita kolikot,
 | 
			
		||||
joista muodostuu rahamäärä $x$
 | 
			
		||||
käyttäen mahdollisimman vähän kolikoita.
 | 
			
		||||
 | 
			
		||||
Ratkaisun muodostaminen onnistuu lisäämällä
 | 
			
		||||
koodiin uuden taulukon, joka kertoo
 | 
			
		||||
kullekin rahamäärälle,
 | 
			
		||||
mikä kolikko siitä tulee poistaa
 | 
			
		||||
optimiratkaisussa.
 | 
			
		||||
Seuraavassa koodissa taulukko \texttt{e}
 | 
			
		||||
huolehtii asiasta:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
d[0] = 0;
 | 
			
		||||
for (int i = 1; i <= x; i++) {
 | 
			
		||||
    d[i] = 1e9;
 | 
			
		||||
    for (int j = 1; j <= k; j++) {
 | 
			
		||||
        if (i-c[j] < 0) continue;
 | 
			
		||||
        int u = d[i-c[j]]+1;
 | 
			
		||||
        if (u < d[i]) {
 | 
			
		||||
            d[i] = u;
 | 
			
		||||
            e[i] = c[j];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen rahamäärän $x$ muodostavat
 | 
			
		||||
kolikot voi tulostaa näin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
while (x > 0) {
 | 
			
		||||
    cout << e[x] << "\n";
 | 
			
		||||
    x -= e[x];
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisuiden määrän laskeminen}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten kolikkotehtävän muunnelmaa,
 | 
			
		||||
joka on muuten samanlainen kuin ennenkin,
 | 
			
		||||
mutta laskettavana on mahdollisten ratkaisuiden yhteismäärä
 | 
			
		||||
optimaalisen ratkaisun sijasta.
 | 
			
		||||
Esimerkiksi jos kolikot ovat $\{1,3,4\}$ ja rahamäärä on 5,
 | 
			
		||||
niin ratkaisuja on kaikkiaan 6:
 | 
			
		||||
 | 
			
		||||
\begin{multicols}{2}
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $1+1+1+1+1$
 | 
			
		||||
\item $1+1+3$
 | 
			
		||||
\item $1+3+1$
 | 
			
		||||
\item $3+1+1$
 | 
			
		||||
\item $1+4$
 | 
			
		||||
\item $4+1$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
\end{multicols}
 | 
			
		||||
 | 
			
		||||
Ratkaisujen määrän laskeminen tapahtuu melko samalla tavalla
 | 
			
		||||
kuin optimiratkaisun etsiminen.
 | 
			
		||||
Erona on, että optimiratkaisun etsivässä rekursiossa
 | 
			
		||||
valitaan pienin tai suurin aiempi arvo,
 | 
			
		||||
kun taas ratkaisujen määrän laskevassa rekursiossa lasketaan
 | 
			
		||||
yhteen kaikki vaihtoehdot.
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa voimme määritellä funktion $f(x)$,
 | 
			
		||||
joka kertoo, monellako tavalla rahamäärän $x$
 | 
			
		||||
voi muodostaa kolikoista.
 | 
			
		||||
Esimerkiksi $f(5)=6$, kun kolikot ovat $\{1,3,4\}$.
 | 
			
		||||
Funktion $f(x)$ saa laskettua rekursiivisesti kaavalla
 | 
			
		||||
\[ f(x) = f(x-c_1)+f(x-c_2)+\cdots+f(x-c_k),\]
 | 
			
		||||
koska rahamäärän $x$ muodostamiseksi pitää
 | 
			
		||||
valita jokin kolikko $c_i$ ja muodostaa sen jälkeen rahamäärä $x-c_i$.
 | 
			
		||||
Pohjatapauksina ovat $f(0)=1$, koska rahamäärä 0 syntyy
 | 
			
		||||
ilman yhtään kolikkoa,
 | 
			
		||||
sekä $f(x)=0$, kun $x<0$, koska negatiivista rahamäärää
 | 
			
		||||
ei ole mahdollista muodostaa.
 | 
			
		||||
 | 
			
		||||
Yllä olevassa esimerkissä funktioksi tulee
 | 
			
		||||
\[ f(x) = f(x-1)+f(x-3)+f(x-4) \]
 | 
			
		||||
ja funktion ensimmäiset arvot ovat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(0) & = & 1 \\
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(2) & = & 1 \\
 | 
			
		||||
f(3) & = & 2 \\
 | 
			
		||||
f(4) & = & 4 \\
 | 
			
		||||
f(5) & = & 6 \\
 | 
			
		||||
f(6) & = & 9 \\
 | 
			
		||||
f(7) & = & 15 \\
 | 
			
		||||
f(8) & = & 25 \\
 | 
			
		||||
f(9) & = & 40 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Seuraava koodi laskee funktion $f(x)$ arvon
 | 
			
		||||
dynaamisella ohjelmoinnilla täyttämällä taulukon
 | 
			
		||||
\texttt{d} rahamäärille $0 \ldots x$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
d[0] = 1;
 | 
			
		||||
for (int i = 1; i <= x; i++) {
 | 
			
		||||
    for (int j = 1; j <= k; j++) {
 | 
			
		||||
        if (i-c[j] < 0) continue;
 | 
			
		||||
        d[i] += d[i-c[j]];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Usein ratkaisujen määrä on niin suuri, että sitä ei tarvitse
 | 
			
		||||
laskea kokonaan vaan riittää ilmoittaa vastaus
 | 
			
		||||
modulo $m$, missä esimerkiksi $m=10^9+7$.
 | 
			
		||||
Tämä onnistuu muokkaamalla koodia niin,
 | 
			
		||||
että kaikki laskutoimitukset lasketaan modulo $m$.
 | 
			
		||||
Tässä tapauksessa riittää lisätä rivin
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
        d[i] += d[i-c[j]];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
jälkeen rivi
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
        d[i] %= m;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Nyt olemme käyneet läpi kaikki dynaamisen
 | 
			
		||||
ohjelmoinnin perusasiat.
 | 
			
		||||
Dynaamista ohjelmointia voi soveltaa monilla
 | 
			
		||||
tavoilla erilaisissa tilanteissa,
 | 
			
		||||
minkä vuoksi tutustumme seuraavaksi
 | 
			
		||||
joukkoon tehtäviä, jotka esittelevät
 | 
			
		||||
dynaamisen ohjelmoinnin mahdollisuuksia.
 | 
			
		||||
 | 
			
		||||
\section{Pisin nouseva alijono}
 | 
			
		||||
 | 
			
		||||
\index{pisin nouseva alijono@pisin nouseva alijono}
 | 
			
		||||
 | 
			
		||||
Annettuna on taulukko, jossa on $n$
 | 
			
		||||
kokonaislukua $x_1,x_2,\ldots,x_n$.
 | 
			
		||||
Tehtävänä on selvittää,
 | 
			
		||||
kuinka pitkä on taulukon
 | 
			
		||||
\key{pisin nouseva alijono}
 | 
			
		||||
eli vasemmalta oikealle kulkeva
 | 
			
		||||
ketju taulukon alkioita,
 | 
			
		||||
jotka on valittu niin,
 | 
			
		||||
että jokainen alkio on edellistä suurempi.
 | 
			
		||||
Esimerkiksi taulukossa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$6$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$1$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
pisin nouseva alijono sisältää 4 alkiota:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (2,1);
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (3,1);
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (5,1);
 | 
			
		||||
\fill[color=lightgray] (6,0) rectangle (7,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
\node at (0.5,0.5) {$6$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$1$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (1.5,-0.25) .. controls (1.75,-1.00) and (2.25,-1.00) .. (2.4,-0.25);
 | 
			
		||||
\draw[thick,->] (2.6,-0.25) .. controls (3.0,-1.00) and (4.0,-1.00) .. (4.4,-0.25);
 | 
			
		||||
\draw[thick,->] (4.6,-0.25) .. controls (5.0,-1.00) and (6.0,-1.00) .. (6.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Merkitään $f(k)$ kohtaan $k$ päättyvän
 | 
			
		||||
pisimmän nousevan alijonon pituutta,
 | 
			
		||||
jolloin ratkaisu tehtävään on suurin
 | 
			
		||||
arvoista $f(1),f(2),\ldots,f(n)$.
 | 
			
		||||
Esimerkiksi yllä olevassa taulukossa
 | 
			
		||||
funktion arvot ovat seuraavat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(2) & = & 1 \\
 | 
			
		||||
f(3) & = & 2 \\
 | 
			
		||||
f(4) & = & 1 \\
 | 
			
		||||
f(5) & = & 3 \\
 | 
			
		||||
f(6) & = & 2 \\
 | 
			
		||||
f(7) & = & 4 \\
 | 
			
		||||
f(8) & = & 2 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Arvon $f(k)$ laskemisessa on kaksi vaihtoehtoa,
 | 
			
		||||
millainen kohtaan $k$ päättyvä pisin nouseva alijono on:
 | 
			
		||||
\begin{enumerate}
 | 
			
		||||
\item Pisin nouseva alijono sisältää vain luvun $x_k$,
 | 
			
		||||
jolloin $f(k)=1$.
 | 
			
		||||
\item Valitaan jokin kohta $i$, jolle pätee $i<k$
 | 
			
		||||
ja $x_i<x_k$.
 | 
			
		||||
Pisin nouseva alijono saadaan liittämällä
 | 
			
		||||
kohtaan $i$ päättyvän pisimmän nousevan alijonon perään luku $x_k$.
 | 
			
		||||
Tällöin $f(k)=f(i)+1$.
 | 
			
		||||
\end{enumerate}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä arvon $f(7)$ laskemista.
 | 
			
		||||
Paras ratkaisu on ottaa pohjaksi kohtaan 5
 | 
			
		||||
päättyvä pisin nouseva alijono $[2,5,7]$
 | 
			
		||||
ja lisätä sen perään luku $x_7=8$.
 | 
			
		||||
Tuloksena on alijono $[2,5,7,8]$ ja $f(7)=f(5)+1=4$.
 | 
			
		||||
 | 
			
		||||
Suoraviivainen tapa toteuttaa algoritmi on
 | 
			
		||||
käydä kussakin kohdassa $k$ läpi kaikki kohdat
 | 
			
		||||
$i=1,2,\ldots,k-1$, joissa voi olla alijonon
 | 
			
		||||
edellinen luku.
 | 
			
		||||
Tällaisen algoritmin aikavaativuus on $O(n^2)$.
 | 
			
		||||
Yllättävää kyllä, algoritmin voi toteuttaa myös
 | 
			
		||||
ajassa $O(n \log n)$, mutta tämä on vaikeampaa.
 | 
			
		||||
 | 
			
		||||
\section{Reitinhaku ruudukossa}
 | 
			
		||||
 | 
			
		||||
Seuraava tehtävämme on etsiä reitti
 | 
			
		||||
$n \times n$ -ruudukon vasemmasta yläkulmasta
 | 
			
		||||
oikeaan alakulmaan.
 | 
			
		||||
Jokaisessa ruudussa on luku, ja reitti
 | 
			
		||||
tulee muodostaa niin, että reittiin kuuluvien
 | 
			
		||||
lukujen summa on mahdollisimman suuri.
 | 
			
		||||
Rajoituksena ruudukossa on mahdollista
 | 
			
		||||
liikkua vain oikealla ja alaspäin.
 | 
			
		||||
 | 
			
		||||
Seuraavassa ruudukossa paras reitti
 | 
			
		||||
on merkitty harmaalla taustalla:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=lightgray] (0, 9) rectangle (1, 8);
 | 
			
		||||
    \fill [color=lightgray] (0, 8) rectangle (1, 7);
 | 
			
		||||
    \fill [color=lightgray] (1, 8) rectangle (2, 7);
 | 
			
		||||
    \fill [color=lightgray] (1, 7) rectangle (2, 6);
 | 
			
		||||
    \fill [color=lightgray] (2, 7) rectangle (3, 6);
 | 
			
		||||
    \fill [color=lightgray] (3, 7) rectangle (4, 6);
 | 
			
		||||
    \fill [color=lightgray] (4, 7) rectangle (5, 6);
 | 
			
		||||
    \fill [color=lightgray] (4, 6) rectangle (5, 5);
 | 
			
		||||
    \fill [color=lightgray] (4, 5) rectangle (5, 4);
 | 
			
		||||
    \draw (0, 4) grid (5, 9);
 | 
			
		||||
    \node at (0.5,8.5) {3};
 | 
			
		||||
    \node at (1.5,8.5) {7};
 | 
			
		||||
    \node at (2.5,8.5) {9};
 | 
			
		||||
    \node at (3.5,8.5) {2};
 | 
			
		||||
    \node at (4.5,8.5) {7};
 | 
			
		||||
    \node at (0.5,7.5) {9};
 | 
			
		||||
    \node at (1.5,7.5) {8};
 | 
			
		||||
    \node at (2.5,7.5) {3};
 | 
			
		||||
    \node at (3.5,7.5) {5};
 | 
			
		||||
    \node at (4.5,7.5) {5};
 | 
			
		||||
    \node at (0.5,6.5) {1};
 | 
			
		||||
    \node at (1.5,6.5) {7};
 | 
			
		||||
    \node at (2.5,6.5) {9};
 | 
			
		||||
    \node at (3.5,6.5) {8};
 | 
			
		||||
    \node at (4.5,6.5) {5};
 | 
			
		||||
    \node at (0.5,5.5) {3};
 | 
			
		||||
    \node at (1.5,5.5) {8};
 | 
			
		||||
    \node at (2.5,5.5) {6};
 | 
			
		||||
    \node at (3.5,5.5) {4};
 | 
			
		||||
    \node at (4.5,5.5) {10};
 | 
			
		||||
    \node at (0.5,4.5) {6};
 | 
			
		||||
    \node at (1.5,4.5) {3};
 | 
			
		||||
    \node at (2.5,4.5) {9};
 | 
			
		||||
    \node at (3.5,4.5) {7};
 | 
			
		||||
    \node at (4.5,4.5) {8};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tällä reitillä lukujen summa on $3+9+8+7+9+8+5+10+8=67$,
 | 
			
		||||
joka on suurin mahdollinen summa vasemmasta yläkulmasta
 | 
			
		||||
oikeaan alakulmaan.
 | 
			
		||||
 | 
			
		||||
Hyvä lähestymistapa tehtävään on laskea
 | 
			
		||||
kuhunkin ruutuun $(y,x)$ suurin summa
 | 
			
		||||
reitillä vasemmasta yläkulmasta kyseiseen ruutuun.
 | 
			
		||||
Merkitään tätä suurinta summaa $f(y,x)$,
 | 
			
		||||
jolloin $f(n,n)$ on suurin summa
 | 
			
		||||
reitillä vasemmasta yläkulmasta oikeaan alakulmaan.
 | 
			
		||||
 | 
			
		||||
Rekursio syntyy havainnosta,
 | 
			
		||||
että ruutuun $(y,x)$ saapuvan reitin
 | 
			
		||||
täytyy tulla joko vasemmalta ruudusta $(y,x-1)$
 | 
			
		||||
tai ylhäältä ruudusta $(y-1,x)$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=lightgray] (3, 7) rectangle (4, 6);
 | 
			
		||||
    \draw (0, 4) grid (5, 9);
 | 
			
		||||
    
 | 
			
		||||
    \node at (2.5,6.5) {$\rightarrow$};
 | 
			
		||||
    \node at (3.5,7.5) {$\downarrow$};
 | 
			
		||||
    
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kun $r(y,x)$
 | 
			
		||||
on ruudukon luku kohdassa $(y,x)$,
 | 
			
		||||
rekursion pohjatapaukset ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(1,1) & = & r(1,1) \\
 | 
			
		||||
f(1,x) & = & f(1,x-1)+r(1,x) \\
 | 
			
		||||
f(y,1) & = & f(y-1,1)+r(y,1)\\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Yleisessä tapauksessa valittavana on
 | 
			
		||||
kaksi reittiä,
 | 
			
		||||
joista kannattaa valita se,
 | 
			
		||||
joka tuottaa suuremman summan:
 | 
			
		||||
\[ f(y,x) = \max(f(y,x-1),f(y-1,x))+r(y,x)\]
 | 
			
		||||
 | 
			
		||||
Ratkaisun aikavaativuus on $O(n^2)$, koska jokaisessa
 | 
			
		||||
ruudussa $f(y,x)$ saadaan laskettua vakioajassa
 | 
			
		||||
viereisten ruutujen arvoista.
 | 
			
		||||
 | 
			
		||||
\section{Repunpakkaus}
 | 
			
		||||
 | 
			
		||||
\index{repunpakkaus@repunpakkaus}
 | 
			
		||||
 | 
			
		||||
\key{Repunpakkaus} on klassinen ongelma,
 | 
			
		||||
jossa annettuna on $n$ tavaraa,
 | 
			
		||||
joiden painot ovat
 | 
			
		||||
$p_1,p_2,\ldots,p_n$ ja arvot ovat
 | 
			
		||||
$a_1,a_2,\ldots,a_n$.
 | 
			
		||||
Tehtävänä on valita reppuun pakattavat tavarat
 | 
			
		||||
niin, että tavaroiden
 | 
			
		||||
painojen summa on enintään $x$
 | 
			
		||||
ja tavaroiden arvojen summa on mahdollisimman suuri.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi jos tavarat ovat
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrr}
 | 
			
		||||
tavara & paino & arvo \\
 | 
			
		||||
\hline
 | 
			
		||||
A & 5 & 1 \\
 | 
			
		||||
B & 6 & 3 \\
 | 
			
		||||
C & 8 & 5 \\
 | 
			
		||||
D & 5 & 3 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
ja suurin sallittu yhteispaino on 12,
 | 
			
		||||
niin paras ratkaisu on pakata reppuun tavarat $B$ ja $D$.
 | 
			
		||||
Niiden yhteispaino $6+5=11$ ei ylitä rajaa 12
 | 
			
		||||
ja arvojen summa
 | 
			
		||||
on $3+3=6$, mikä on paras mahdollinen tulos.
 | 
			
		||||
 | 
			
		||||
Tämä tehtävä on mahdollista ratkaista kahdella eri
 | 
			
		||||
tavalla dynaamisella ohjelmoinnilla
 | 
			
		||||
riippuen siitä, tarkastellaanko ongelmaa
 | 
			
		||||
maksimointina vai minimointina.
 | 
			
		||||
Käymme seuraavaksi läpi molemmat ratkaisut.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 1}
 | 
			
		||||
 | 
			
		||||
\textit{Maksimointi:} Merkitään $f(k,u)$
 | 
			
		||||
suurinta mahdollista tavaroiden yhteisarvoa,
 | 
			
		||||
kun reppuun pakataan jokin osajoukko
 | 
			
		||||
tavaroista $1 \ldots k$,
 | 
			
		||||
jossa tavaroiden yhteispaino on $u$.
 | 
			
		||||
Ratkaisu tehtävään on suurin arvo
 | 
			
		||||
$f(n,u)$, kun $0 \le u \le x$.
 | 
			
		||||
Rekursiivinen kaava funktion laskemiseksi on
 | 
			
		||||
\[f(k,u) = \max(f(k-1,u),f(k-1,u-p_k)+a_k),\]
 | 
			
		||||
koska kohdassa $k$ oleva tavara joko otetaan tai ei oteta
 | 
			
		||||
mukaan ratkaisuun.
 | 
			
		||||
Pohjatapauksina on $f(0,0)=0$ ja $f(0,u)=-\infty$,
 | 
			
		||||
kun $u \neq 0$. Tämän ratkaisun aikavaativuus on $O(nx)$.
 | 
			
		||||
 | 
			
		||||
Esimerkin tilanteessa optimiratkaisu on
 | 
			
		||||
$f(4,11)=6$, joka muodostuu seuraavan ketjun kautta:
 | 
			
		||||
\[f(4,11)=f(3,6)+3=f(2,6)+3=f(1,0)+3+3=f(0,0)+3+3=6.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ratkaisu 2}
 | 
			
		||||
 | 
			
		||||
\textit{Minimointi:} Merkitään $f(k,u)$
 | 
			
		||||
pienintä mahdollista tavaroiden yhteispainoa,
 | 
			
		||||
kun reppuun pakataan jokin osajoukko
 | 
			
		||||
tavaroista $1 \ldots k$,
 | 
			
		||||
jossa tavaroiden yhteisarvo on $u$.
 | 
			
		||||
Ratkaisu tehtävään on suurin arvo $u$,
 | 
			
		||||
jolle pätee $0 \le u \le s$ ja $f(n,u) \le x$,
 | 
			
		||||
missä $s=\sum_{i=1}^n a_i$.
 | 
			
		||||
Rekursiivinen kaava funktion laskemiseksi on
 | 
			
		||||
\[f(k,u) = \min(f(k-1,u),f(k-1,u-a_k)+p_k)\]
 | 
			
		||||
ratkaisua 1 vastaavasti.
 | 
			
		||||
Pohjatapauksina on $f(0,0)=0$ ja $f(0,u)=\infty$, kun $u \neq 0$.
 | 
			
		||||
Tämän ratkaisun aikavaativuus on $O(ns)$.
 | 
			
		||||
 | 
			
		||||
Esimerkin tilanteessa optimiratkaisu on
 | 
			
		||||
$f(4,6)=11$, joka muodostuu seuraavan ketjun kautta:
 | 
			
		||||
\[f(4,6)=f(3,3)+5=f(2,3)+5=f(1,0)+6+5=f(0,0)+6+5=11.\]
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
Kiinnostava seikka on, että eri asiat syötteessä
 | 
			
		||||
vaikuttavat ratkaisuiden tehokkuuteen.
 | 
			
		||||
Ratkaisussa 1 tavaroiden painot vaikuttavat tehokkuuteen
 | 
			
		||||
mutta arvoilla ei ole merkitystä.
 | 
			
		||||
Ratkaisussa 2 puolestaan tavaroiden arvot vaikuttavat
 | 
			
		||||
tehokkuuteen mutta painoilla ei ole merkitystä.
 | 
			
		||||
 | 
			
		||||
\section{Editointietäisyys}
 | 
			
		||||
 | 
			
		||||
\index{editointietxisyys@editointietäisyys}
 | 
			
		||||
\index{Levenšteinin etäisyys}
 | 
			
		||||
 | 
			
		||||
\key{Editointietäisyys} eli
 | 
			
		||||
\key{Levenšteinin etäisyys}
 | 
			
		||||
kuvaa, kuinka kaukana kaksi merkkijonoa ovat toisistaan.
 | 
			
		||||
Se on pienin määrä editointioperaatioita,
 | 
			
		||||
joilla ensimmäisen merkkijonon saa muutettua toiseksi.
 | 
			
		||||
Sallitut operaatiot ovat:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item merkin lisäys (esim. \texttt{ABC} $\rightarrow$ \texttt{ABCA})
 | 
			
		||||
\item merkin poisto (esim. \texttt{ABC} $\rightarrow$ \texttt{AC})
 | 
			
		||||
\item merkin muutos (esim. \texttt{ABC} $\rightarrow$ \texttt{ADC})
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi merkkijonojen \texttt{TALO} ja \texttt{PALLO}
 | 
			
		||||
editointietäisyys on 2, koska voimme tehdä ensin
 | 
			
		||||
operaation \texttt{TALO} $\rightarrow$ \texttt{TALLO}
 | 
			
		||||
(merkin lisäys) ja sen jälkeen operaation
 | 
			
		||||
\texttt{TALLO} $\rightarrow$ \texttt{PALLO}
 | 
			
		||||
(merkin muutos).
 | 
			
		||||
Tämä on pienin mahdollinen määrä operaatioita, koska
 | 
			
		||||
selvästikään yksi operaatio ei riitä.
 | 
			
		||||
 | 
			
		||||
Oletetaan, että annettuna on merkkijonot
 | 
			
		||||
\texttt{x} (pituus $n$ merkkiä) ja
 | 
			
		||||
\texttt{y} (pituus $m$ merkkiä),
 | 
			
		||||
ja haluamme laskea niiden editointietäisyyden.
 | 
			
		||||
Tämä onnistuu tehokkaasti dynaamisella
 | 
			
		||||
ohjelmoinnilla ajassa $O(nm)$.
 | 
			
		||||
Merkitään funktiolla $f(a,b)$
 | 
			
		||||
editointietäisyyttä \texttt{x}:n $a$
 | 
			
		||||
ensimmäisen merkin sekä
 | 
			
		||||
\texttt{y}:n $b$:n ensimmäisen merkin välillä.
 | 
			
		||||
Tätä funktiota käyttäen
 | 
			
		||||
merkkijonojen
 | 
			
		||||
\texttt{x} ja \texttt{y} editointietäisyys
 | 
			
		||||
on $f(n,m)$, ja funktio kertoo myös tarvittavat
 | 
			
		||||
editointioperaatiot.
 | 
			
		||||
 | 
			
		||||
Funktion pohjatapaukset ovat
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(0,b) & = & b \\
 | 
			
		||||
f(a,0) & = & a \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
ja yleisessä tapauksessa pätee kaava
 | 
			
		||||
\[ f(a,b) = \min(f(a,b-1)+1,f(a-1,b)+1,f(a-1,b-1)+c),\]
 | 
			
		||||
missä $c=0$, jos \texttt{x}:n merkki $a$
 | 
			
		||||
ja \texttt{y}:n merkki $b$ ovat samat,
 | 
			
		||||
ja muussa tapauksessa $c=1$.
 | 
			
		||||
Kaava käy läpi mahdollisuudet lyhentää merkkijonoja:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $f(a,b-1)$ tarkoittaa, että $x$:ään lisätään merkki
 | 
			
		||||
\item $f(a-1,b)$ tarkoittaa, että $x$:stä poistetaan merkki
 | 
			
		||||
\item $f(a-1,b-1)$ tarkoittaa, että $x$:ssä ja $y$:ssä on
 | 
			
		||||
sama merkki ($c=0$) tai $x$:n merkki muutetaan $y$:n merkiksi ($c=1$)
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Seuraava taulukko sisältää funktion $f$ arvot
 | 
			
		||||
esimerkin tapauksessa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    %\fill [color=lightgray] (5, -3) rectangle (6, -4);
 | 
			
		||||
    \draw (1, -1) grid (7, -6);
 | 
			
		||||
    
 | 
			
		||||
    \node at (0.5,-2.5) {\texttt{T}};
 | 
			
		||||
    \node at (0.5,-3.5) {\texttt{A}};
 | 
			
		||||
    \node at (0.5,-4.5) {\texttt{L}};
 | 
			
		||||
    \node at (0.5,-5.5) {\texttt{O}};
 | 
			
		||||
 | 
			
		||||
    \node at (2.5,-0.5) {\texttt{P}};
 | 
			
		||||
    \node at (3.5,-0.5) {\texttt{A}};
 | 
			
		||||
    \node at (4.5,-0.5) {\texttt{L}};
 | 
			
		||||
    \node at (5.5,-0.5) {\texttt{L}};
 | 
			
		||||
    \node at (6.5,-0.5) {\texttt{O}};
 | 
			
		||||
 | 
			
		||||
    \node at (1.5,-1.5) {$0$};
 | 
			
		||||
    \node at (1.5,-2.5) {$1$};
 | 
			
		||||
    \node at (1.5,-3.5) {$2$};
 | 
			
		||||
    \node at (1.5,-4.5) {$3$};
 | 
			
		||||
    \node at (1.5,-5.5) {$4$};
 | 
			
		||||
    \node at (2.5,-1.5) {$1$};
 | 
			
		||||
    \node at (2.5,-2.5) {$1$};
 | 
			
		||||
    \node at (2.5,-3.5) {$2$};
 | 
			
		||||
    \node at (2.5,-4.5) {$3$};
 | 
			
		||||
    \node at (2.5,-5.5) {$4$};
 | 
			
		||||
    \node at (3.5,-1.5) {$2$};
 | 
			
		||||
    \node at (3.5,-2.5) {$2$};
 | 
			
		||||
    \node at (3.5,-3.5) {$1$};
 | 
			
		||||
    \node at (3.5,-4.5) {$2$};
 | 
			
		||||
    \node at (3.5,-5.5) {$3$};
 | 
			
		||||
    \node at (4.5,-1.5) {$3$};
 | 
			
		||||
    \node at (4.5,-2.5) {$3$};
 | 
			
		||||
    \node at (4.5,-3.5) {$2$};
 | 
			
		||||
    \node at (4.5,-4.5) {$1$};
 | 
			
		||||
    \node at (4.5,-5.5) {$2$};
 | 
			
		||||
    \node at (5.5,-1.5) {$4$};
 | 
			
		||||
    \node at (5.5,-2.5) {$4$};
 | 
			
		||||
    \node at (5.5,-3.5) {$3$};
 | 
			
		||||
    \node at (5.5,-4.5) {$2$};
 | 
			
		||||
    \node at (5.5,-5.5) {$2$};
 | 
			
		||||
    \node at (6.5,-1.5) {$5$};
 | 
			
		||||
    \node at (6.5,-2.5) {$5$};
 | 
			
		||||
    \node at (6.5,-3.5) {$4$};
 | 
			
		||||
    \node at (6.5,-4.5) {$3$};
 | 
			
		||||
    \node at (6.5,-5.5) {$2$};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Taulukon oikean alanurkan ruutu
 | 
			
		||||
kertoo, että merkkijonojen \texttt{TALO}
 | 
			
		||||
ja \texttt{PALLO} editointietäisyys on 2.
 | 
			
		||||
Taulukosta pystyy myös
 | 
			
		||||
lukemaan, miten pienimmän editointietäisyyden
 | 
			
		||||
voi saavuttaa.
 | 
			
		||||
Tässä tapauksessa polku on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \draw (1, -1) grid (7, -6);
 | 
			
		||||
    
 | 
			
		||||
    \node at (0.5,-2.5) {\texttt{T}};
 | 
			
		||||
    \node at (0.5,-3.5) {\texttt{A}};
 | 
			
		||||
    \node at (0.5,-4.5) {\texttt{L}};
 | 
			
		||||
    \node at (0.5,-5.5) {\texttt{O}};
 | 
			
		||||
 | 
			
		||||
    \node at (2.5,-0.5) {\texttt{P}};
 | 
			
		||||
    \node at (3.5,-0.5) {\texttt{A}};
 | 
			
		||||
    \node at (4.5,-0.5) {\texttt{L}};
 | 
			
		||||
    \node at (5.5,-0.5) {\texttt{L}};
 | 
			
		||||
    \node at (6.5,-0.5) {\texttt{O}};
 | 
			
		||||
 | 
			
		||||
    \node at (1.5,-1.5) {$0$};
 | 
			
		||||
    \node at (1.5,-2.5) {$1$};
 | 
			
		||||
    \node at (1.5,-3.5) {$2$};
 | 
			
		||||
    \node at (1.5,-4.5) {$3$};
 | 
			
		||||
    \node at (1.5,-5.5) {$4$};
 | 
			
		||||
    \node at (2.5,-1.5) {$1$};
 | 
			
		||||
    \node at (2.5,-2.5) {$1$};
 | 
			
		||||
    \node at (2.5,-3.5) {$2$};
 | 
			
		||||
    \node at (2.5,-4.5) {$3$};
 | 
			
		||||
    \node at (2.5,-5.5) {$4$};
 | 
			
		||||
    \node at (3.5,-1.5) {$2$};
 | 
			
		||||
    \node at (3.5,-2.5) {$2$};
 | 
			
		||||
    \node at (3.5,-3.5) {$1$};
 | 
			
		||||
    \node at (3.5,-4.5) {$2$};
 | 
			
		||||
    \node at (3.5,-5.5) {$3$};
 | 
			
		||||
    \node at (4.5,-1.5) {$3$};
 | 
			
		||||
    \node at (4.5,-2.5) {$3$};
 | 
			
		||||
    \node at (4.5,-3.5) {$2$};
 | 
			
		||||
    \node at (4.5,-4.5) {$1$};
 | 
			
		||||
    \node at (4.5,-5.5) {$2$};
 | 
			
		||||
    \node at (5.5,-1.5) {$4$};
 | 
			
		||||
    \node at (5.5,-2.5) {$4$};
 | 
			
		||||
    \node at (5.5,-3.5) {$3$};
 | 
			
		||||
    \node at (5.5,-4.5) {$2$};
 | 
			
		||||
    \node at (5.5,-5.5) {$2$};
 | 
			
		||||
    \node at (6.5,-1.5) {$5$};
 | 
			
		||||
    \node at (6.5,-2.5) {$5$};
 | 
			
		||||
    \node at (6.5,-3.5) {$4$};
 | 
			
		||||
    \node at (6.5,-4.5) {$3$};
 | 
			
		||||
    \node at (6.5,-5.5) {$2$};
 | 
			
		||||
 | 
			
		||||
    \path[draw=red,thick,-,line width=2pt] (6.5,-5.5) -- (5.5,-4.5);
 | 
			
		||||
    \path[draw=red,thick,-,line width=2pt] (5.5,-4.5) -- (4.5,-4.5);
 | 
			
		||||
    \path[draw=red,thick,->,line width=2pt] (4.5,-4.5) -- (1.5,-1.5);
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Merkkijonojen \texttt{PALLO} ja \texttt{TALO} viimeinen merkki on sama,
 | 
			
		||||
joten niiden editointietäisyys on sama kuin
 | 
			
		||||
merkkijonojen \texttt{PALL} ja \texttt{TAL}.
 | 
			
		||||
Nyt voidaan poistaa viimeinen \texttt{L} merkkijonosta \texttt{PAL},
 | 
			
		||||
mistä tulee yksi operaatio.
 | 
			
		||||
Editointietäisyys on siis yhden suurempi
 | 
			
		||||
kuin merkkijonoilla \texttt{PAL} ja \texttt{TAL}, jne.
 | 
			
		||||
 | 
			
		||||
\section{Laatoitukset}
 | 
			
		||||
 | 
			
		||||
Joskus dynaamisen ohjelmoinnin tila on monimutkaisempi kuin
 | 
			
		||||
kiinteä yhdistelmä lukuja.
 | 
			
		||||
Tarkastelemme lopuksi tehtävää, jossa
 | 
			
		||||
laskettavana on, monellako tavalla
 | 
			
		||||
kokoa $1 \times 2$ ja $2 \times 1$ olevilla laatoilla
 | 
			
		||||
voi täyttää $n \times m$ -kokoisen ruudukon.
 | 
			
		||||
Esimerkiksi ruudukolle kokoa $4 \times 7$
 | 
			
		||||
yksi mahdollinen ratkaisu on
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
    \draw (0,0) grid (7,4);
 | 
			
		||||
    \draw[fill=gray] (0+0.2,0+0.2) rectangle (2-0.2,1-0.2);
 | 
			
		||||
    \draw[fill=gray] (2+0.2,0+0.2) rectangle (4-0.2,1-0.2);
 | 
			
		||||
    \draw[fill=gray] (4+0.2,0+0.2) rectangle (6-0.2,1-0.2);
 | 
			
		||||
    \draw[fill=gray] (0+0.2,1+0.2) rectangle (2-0.2,2-0.2);
 | 
			
		||||
    \draw[fill=gray] (2+0.2,1+0.2) rectangle (4-0.2,2-0.2);
 | 
			
		||||
    \draw[fill=gray] (1+0.2,2+0.2) rectangle (3-0.2,3-0.2);
 | 
			
		||||
    \draw[fill=gray] (1+0.2,3+0.2) rectangle (3-0.2,4-0.2);
 | 
			
		||||
    \draw[fill=gray] (4+0.2,3+0.2) rectangle (6-0.2,4-0.2);
 | 
			
		||||
 | 
			
		||||
    \draw[fill=gray] (0+0.2,2+0.2) rectangle (1-0.2,4-0.2);
 | 
			
		||||
    \draw[fill=gray] (3+0.2,2+0.2) rectangle (4-0.2,4-0.2);
 | 
			
		||||
    \draw[fill=gray] (6+0.2,2+0.2) rectangle (7-0.2,4-0.2);
 | 
			
		||||
    \draw[fill=gray] (4+0.2,1+0.2) rectangle (5-0.2,3-0.2);
 | 
			
		||||
    \draw[fill=gray] (5+0.2,1+0.2) rectangle (6-0.2,3-0.2);
 | 
			
		||||
    \draw[fill=gray] (6+0.2,0+0.2) rectangle (7-0.2,2-0.2);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja ratkaisujen yhteismäärä on 781.
 | 
			
		||||
 | 
			
		||||
Tehtävän voi ratkaista dynaamisella ohjelmoinnilla
 | 
			
		||||
käymällä ruudukkoa läpi rivi riviltä.
 | 
			
		||||
Jokainen ratkaisun rivi pelkistyy merkkijonoksi,
 | 
			
		||||
jossa on $m$ merkkiä joukosta $\{\sqcap, \sqcup, \sqsubset, \sqsupset \}$.
 | 
			
		||||
Esimerkiksi yllä olevassa ratkaisussa on 4 riviä,
 | 
			
		||||
jotka vastaavat merkkijonoja
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
$\sqcap \sqsubset \sqsupset \sqcap \sqsubset \sqsupset \sqcap$,
 | 
			
		||||
\item
 | 
			
		||||
$\sqcup \sqsubset \sqsupset \sqcup \sqcap \sqcap \sqcup$,
 | 
			
		||||
\item
 | 
			
		||||
$\sqsubset \sqsupset \sqsubset \sqsupset \sqcup \sqcup \sqcap$ ja
 | 
			
		||||
\item
 | 
			
		||||
$\sqsubset \sqsupset \sqsubset \sqsupset \sqsubset \sqsupset \sqcup$.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Tehtävään sopiva rekursiivinen funktio on $f(k,x)$,
 | 
			
		||||
joka laskee, montako tapaa on muodostaa ratkaisu
 | 
			
		||||
ruudukon riveille $1 \ldots k$ niin,
 | 
			
		||||
että riviä $k$ vastaa merkkijono $x$.
 | 
			
		||||
Dynaaminen ohjelmointi on mahdollista,
 | 
			
		||||
koska jokaisen rivin sisältöä
 | 
			
		||||
rajoittaa vain edellisen rivin sisältö.
 | 
			
		||||
 | 
			
		||||
Riveistä muodostuva ratkaisu on kelvollinen,
 | 
			
		||||
jos rivillä 1 ei ole merkkiä $\sqcup$,
 | 
			
		||||
rivillä $n$ ei ole merkkiä $\sqcap$
 | 
			
		||||
ja kaikki peräkkäiset rivit ovat \emph{yhteensopivat}.
 | 
			
		||||
Esimerkiksi rivit
 | 
			
		||||
$\sqcup \sqsubset \sqsupset \sqcup \sqcap \sqcap \sqcup$ ja
 | 
			
		||||
$\sqsubset \sqsupset \sqsubset \sqsupset \sqcup \sqcup \sqcap$ 
 | 
			
		||||
ovat yhteensopivat,
 | 
			
		||||
kun taas rivit
 | 
			
		||||
$\sqcap \sqsubset \sqsupset \sqcap \sqsubset \sqsupset \sqcap$ ja
 | 
			
		||||
$\sqsubset \sqsupset \sqsubset \sqsupset \sqsubset \sqsupset \sqcup$
 | 
			
		||||
eivät ole yhteensopivat.
 | 
			
		||||
 | 
			
		||||
Koska rivillä on $m$ merkkiä ja jokaiselle merkille on 4
 | 
			
		||||
vaihtoehtoa, erilaisia rivejä on korkeintaan $4^m$.
 | 
			
		||||
Niinpä ratkaisun aikavaativuus on $O(n 4^{2m})$,
 | 
			
		||||
koska joka rivillä käydään läpi $O(4^m)$
 | 
			
		||||
vaihtoehtoa rivin sisällölle
 | 
			
		||||
ja jokaista vaihtoehtoa kohden on $O(4^m)$
 | 
			
		||||
vaihtoehtoa edellisen rivin sisällölle.
 | 
			
		||||
Käytännössä ruudukko kannattaa kääntää niin
 | 
			
		||||
päin, että pienempi sivun pituus on $m$:n roolissa,
 | 
			
		||||
koska $m$:n suuruus on ratkaiseva ajankäytön kannalta.
 | 
			
		||||
 | 
			
		||||
Ratkaisua on mahdollista tehostaa parantamalla rivien esitystapaa merkkijonoina.
 | 
			
		||||
Osoittautuu, että ainoa seuraavalla rivillä tarvittava tieto on,
 | 
			
		||||
missä kohdissa riviltä lähtee laattoja alaspäin.
 | 
			
		||||
Niinpä rivin voikin tallentaa käyttäen vain merkkejä
 | 
			
		||||
$\sqcap$ ja $\Box$, missä $\Box$ kokoaa yhteen vanhat merkit
 | 
			
		||||
$\sqcup$, $\sqsubset$ ja $\sqsupset$.
 | 
			
		||||
Tällöin erilaisia rivejä on vain $2^m$
 | 
			
		||||
ja aikavaativuudeksi tulee $O(n 2^{2m})$.
 | 
			
		||||
 | 
			
		||||
Mainittakoon lopuksi, että laatoitusten määrän laskemiseen
 | 
			
		||||
on myös yllättävä suora kaava
 | 
			
		||||
\[ \prod_{a=1}^{\lceil n/2 \rceil} \prod_{b=1}^{\lceil m/2 \rceil} 4 \cdot (\cos^2 \frac{\pi a}{n + 1} + \cos^2 \frac{\pi b}{m+1}).\]
 | 
			
		||||
Tämä kaava on sinänsä hyvin tehokas,
 | 
			
		||||
koska se laskee laatoitusten määrän ajassa $O(nm)$,
 | 
			
		||||
mutta käytännön ongelma kaavan käyttämisessä
 | 
			
		||||
on, kuinka tallentaa välitulokset riittävän tarkkoina lukuina.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,839 @@
 | 
			
		|||
\chapter{Amortized analysis}
 | 
			
		||||
 | 
			
		||||
\index{tasoitettu analyysi@tasoitettu analyysi}
 | 
			
		||||
 | 
			
		||||
Monen algoritmin aikavaativuuden pystyy laskemaan
 | 
			
		||||
suoraan katsomalla algoritmin rakennetta:
 | 
			
		||||
mitä silmukoita algoritmissa on ja miten monta
 | 
			
		||||
kertaa niitä suoritetaan.
 | 
			
		||||
Joskus kuitenkaan näin suoraviivainen analyysi ei
 | 
			
		||||
riitä antamaan todellista kuvaa algoritmin tehokkuudesta.
 | 
			
		||||
 | 
			
		||||
\key{Tasoitettu analyysi} soveltuu sellaisten
 | 
			
		||||
algoritmien analyysiin, joiden osana on jokin operaatio,
 | 
			
		||||
jonka ajankäyttö vaihtelee.
 | 
			
		||||
Ideana on tarkastella yksittäisen operaation
 | 
			
		||||
sijasta kaikkia operaatioita algoritmin
 | 
			
		||||
aikana ja laskea niiden ajankäytölle yhteinen raja.
 | 
			
		||||
 | 
			
		||||
\section{Kahden osoittimen tekniikka}
 | 
			
		||||
 | 
			
		||||
\index{kahden osoittimen tekniikka}
 | 
			
		||||
 | 
			
		||||
\key{Kahden osoittimen tekniikka} on taulukon käsittelyssä
 | 
			
		||||
käytettävä menetelmä, jossa taulukkoa käydään läpi
 | 
			
		||||
kahden osoittimen avulla.
 | 
			
		||||
Molemmat osoittimet liikkuvat algoritmin aikana,
 | 
			
		||||
mutta rajoituksena on, että ne voivat liikkua vain
 | 
			
		||||
yhteen suuntaan, mikä takaa, että algoritmi toimii tehokkaasti.
 | 
			
		||||
 | 
			
		||||
Tutustumme seuraavaksi kahden osoittimen tekniikkaan
 | 
			
		||||
kahden esimerkkitehtävän kautta.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Alitaulukon summa}
 | 
			
		||||
 | 
			
		||||
Annettuna on taulukko, jossa on $n$ positiivista kokonaislukua.
 | 
			
		||||
Tehtävänä on selvittää, onko taulukossa alitaulukkoa,
 | 
			
		||||
jossa lukujen summa on $x$.
 | 
			
		||||
Esimerkiksi taulukossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on alitaulukko, jossa lukujen summa on 8:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tämän tehtävän voi ratkaista
 | 
			
		||||
ajassa $O(n)$ kahden osoittimen tekniikalla.
 | 
			
		||||
Ideana on käydä taulukkoa läpi kahden osoittimen
 | 
			
		||||
avulla, jotka rajaavat välin taulukosta.
 | 
			
		||||
Joka vuorolla vasen osoitin liikkuu
 | 
			
		||||
yhden askeleen eteenpäin ja oikea osoitin
 | 
			
		||||
liikkuu niin kauan eteenpäin kuin summa on enintään $x$.
 | 
			
		||||
Jos välin summaksi tulee tarkalleen $x$, ratkaisu on löytynyt.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä algoritmin toimintaa
 | 
			
		||||
seuraavassa taulukossa, kun tavoitteena on muodostaa summa $x=8$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Aluksi osoittimet rajaavat taulukosta välin,
 | 
			
		||||
jonka summa on $1+3+2=6$.
 | 
			
		||||
Väli ei voi olla tätä suurempi,
 | 
			
		||||
koska seuraava luku 5 veisi summan yli $x$:n.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (0,0) rectangle (3,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (0.5,-0.7) -- (0.5,-0.1);
 | 
			
		||||
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi vasen osoitin siirtyy askeleen eteenpäin.
 | 
			
		||||
Oikea osoitin säilyy paikallaan, koska muuten
 | 
			
		||||
summa kasvaisi liian suureksi.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (3,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (1.5,-0.7) -- (1.5,-0.1);
 | 
			
		||||
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Vasen osoitin siirtyy taas askeleen eteenpäin
 | 
			
		||||
ja tällä kertaa oikea osoitin siirtyy kolme askelta
 | 
			
		||||
eteenpäin. Muodostuu summa $2+5+1=8$ eli taulukosta
 | 
			
		||||
on löytynyt väli, jonka lukujen summa on $x$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$1$};
 | 
			
		||||
\node at (5.5,0.5) {$1$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
 | 
			
		||||
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
% Algoritmin toteutus näyttää seuraavalta:
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% int s = 0, b = 0;
 | 
			
		||||
% for (int a = 1; a <= n; a++) {
 | 
			
		||||
%     while (b<n && s+t[b+1] <= x) {
 | 
			
		||||
%         b++;
 | 
			
		||||
%         s += t[b];
 | 
			
		||||
%     }
 | 
			
		||||
%     if (s == x) {
 | 
			
		||||
%         // ratkaisu löytyi
 | 
			
		||||
%     }
 | 
			
		||||
%     s -= t[a];
 | 
			
		||||
% }
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% Muuttujat $a$ ja $b$ sisältävät vasemman ja oikean
 | 
			
		||||
% osoittimen kohdan.
 | 
			
		||||
% Muuttuja $s$ taas laskee lukujen summan välillä.
 | 
			
		||||
% Joka askeleella $a$ liikkuu askeleen eteenpäin
 | 
			
		||||
% ja $b$ liikkuu niin kauan kuin summa on enintään $x$.
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus riippuu siitä,
 | 
			
		||||
kauanko oikean osoittimen liikkuminen vie aikaa.
 | 
			
		||||
Tämä vaihtelee, koska oikea osoitin voi liikkua
 | 
			
		||||
minkä tahansa matkan eteenpäin taulukossa.
 | 
			
		||||
Kuitenkin oikea osoitin liikkuu \textit{yhteensä}
 | 
			
		||||
$O(n)$ askelta algoritmin aikana, koska se voi
 | 
			
		||||
liikkua vain eteenpäin.
 | 
			
		||||
 | 
			
		||||
Koska sekä vasen että oikea osoitin liikkuvat
 | 
			
		||||
$O(n)$ askelta algoritmin aikana,
 | 
			
		||||
algoritmin aikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kahden luvun summa}
 | 
			
		||||
 | 
			
		||||
\index{2SUM-ongelma}
 | 
			
		||||
 | 
			
		||||
Annettuna on taulukko, jossa on $n$ kokonaislukua,
 | 
			
		||||
sekä kokonaisluku $x$.
 | 
			
		||||
Tehtävänä on etsiä taulukosta kaksi lukua,
 | 
			
		||||
joiden summa on $x$, tai todeta,
 | 
			
		||||
että tämä ei ole mahdollista.
 | 
			
		||||
Tämä ongelma tunnetaan tunnetaan nimellä
 | 
			
		||||
\key{2SUM} ja se ratkeaa tehokkaasti
 | 
			
		||||
kahden osoittimen tekniikalla.
 | 
			
		||||
 | 
			
		||||
Taulukon luvut järjestetään ensin
 | 
			
		||||
pienimmästä suurimpaan, minkä jälkeen
 | 
			
		||||
taulukkoa aletaan käydä läpi kahdella osoittimella,
 | 
			
		||||
jotka lähtevät liikkelle taulukon molemmista päistä.
 | 
			
		||||
Vasen osoitin aloittaa taulukon alusta ja
 | 
			
		||||
liikkuu joka vaiheessa askeleen eteenpäin.
 | 
			
		||||
Oikea osoitin taas aloittaa taulukon lopusta
 | 
			
		||||
ja peruuttaa vuorollaan taaksepäin, kunnes osoitinten
 | 
			
		||||
määrittämän välin lukujen summa on enintään $x$.
 | 
			
		||||
Jos summa on tarkalleen $x$, ratkaisu on löytynyt.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan algoritmin toimintaa
 | 
			
		||||
seuraavassa taulukossa, kun tavoitteena on muodostaa
 | 
			
		||||
summa $x=12$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$4$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$9$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$10$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraavassa on algoritmin aloitustilanne.
 | 
			
		||||
Lukujen summa on $1+10=11$, joka on pienempi
 | 
			
		||||
kuin $x$:n arvo.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (0,0) rectangle (1,1);
 | 
			
		||||
\fill[color=lightgray] (7,0) rectangle (8,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$4$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$9$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$10$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (0.5,-0.7) -- (0.5,-0.1);
 | 
			
		||||
\draw[thick,->] (7.5,-0.7) -- (7.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi vasen osoitin liikkuu askeleen eteenpäin.
 | 
			
		||||
Oikea osoitin peruuttaa kolme askelta, minkä jälkeen
 | 
			
		||||
summana on $4+7=11$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (2,1);
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$4$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$9$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$10$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (1.5,-0.7) -- (1.5,-0.1);
 | 
			
		||||
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Sitten vasen osoitin siirtyy jälleen askeleen eteenpäin.
 | 
			
		||||
Oikea osoitin pysyy paikallaan ja ratkaisu $5+7=12$ on löytynyt.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (3,1);
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$4$};
 | 
			
		||||
\node at (2.5,0.5) {$5$};
 | 
			
		||||
\node at (3.5,0.5) {$6$};
 | 
			
		||||
\node at (4.5,0.5) {$7$};
 | 
			
		||||
\node at (5.5,0.5) {$9$};
 | 
			
		||||
\node at (6.5,0.5) {$9$};
 | 
			
		||||
\node at (7.5,0.5) {$10$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (2.5,-0.7) -- (2.5,-0.1);
 | 
			
		||||
\draw[thick,->] (4.5,-0.7) -- (4.5,-0.1);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmin alussa taulukon järjestäminen vie
 | 
			
		||||
aikaa $O(n \log n)$.
 | 
			
		||||
Tämän jälkeen vasen osoitin liikkuu $O(n)$ askelta
 | 
			
		||||
eteenpäin ja oikea osoitin liikkuu $O(n)$ askelta
 | 
			
		||||
taaksepäin, mihin kuluu aikaa $O(n)$.
 | 
			
		||||
Algoritmin kokonaisaikavaativuus on siis $O(n \log n)$.
 | 
			
		||||
 | 
			
		||||
Huomaa, että tehtävän voi ratkaista myös 
 | 
			
		||||
toisella tavalla ajassa
 | 
			
		||||
$O(n \log n)$ binäärihaun avulla.
 | 
			
		||||
Tässä ratkaisussa jokaiselle taulukon luvulle
 | 
			
		||||
etsitään binäärihaulla toista lukua niin,
 | 
			
		||||
että lukujen summa olisi yhteensä $x$.
 | 
			
		||||
Binäärihaku suoritetaan $n$ kertaa ja
 | 
			
		||||
jokainen binäärihaku vie aikaa $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\index{3SUM-ongelma}
 | 
			
		||||
Hieman vaikeampi ongelma on \key{3SUM},
 | 
			
		||||
jossa taulukosta tuleekin etsiä kolme lukua,
 | 
			
		||||
joiden summa on $x$.
 | 
			
		||||
Tämä ongelma on mahdollista ratkaista ajassa $O(n^2)$.
 | 
			
		||||
Keksitkö, miten se tapahtuu?
 | 
			
		||||
 | 
			
		||||
\section{Lähin pienempi edeltäjä}
 | 
			
		||||
 | 
			
		||||
\index{lzhin pienempi edeltxjx@lähin pienempi edeltäjä}
 | 
			
		||||
 | 
			
		||||
Tasoitetun analyysin avulla arvioidaan usein
 | 
			
		||||
tietorakenteeseen kohdistuvien operaatioiden määrää.
 | 
			
		||||
Algoritmin operaatiot voivat jakautua epätasaisesti
 | 
			
		||||
niin, että useimmat operaatiot tehdään tietyssä
 | 
			
		||||
algoritmin vaiheessa, mutta operaatioiden
 | 
			
		||||
yhteismäärä on kuitenkin rajoitettu.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä ongelmaa,
 | 
			
		||||
jossa tehtävänä on etsiä kullekin taulukon
 | 
			
		||||
alkiolle
 | 
			
		||||
\key{lähin pienempi edeltäjä} eli
 | 
			
		||||
lähinnä oleva pienempi alkio taulukon alkuosassa.
 | 
			
		||||
On mahdollista, ettei tällaista alkiota ole olemassa,
 | 
			
		||||
jolloin algoritmin tulee huomata asia.
 | 
			
		||||
Osoittautuu, että tehtävä on mahdollista ratkaista
 | 
			
		||||
tehokkaasti ajassa $O(n)$ sopivan tietorakenteen avulla.
 | 
			
		||||
 | 
			
		||||
Tehokas ratkaisu tehtävään on käydä
 | 
			
		||||
taulukko läpi alusta loppuun ja pitää samalla yllä ketjua,
 | 
			
		||||
jonka ensimmäinen luku on käsiteltävä taulukon luku
 | 
			
		||||
ja jokainen seuraava luku on luvun lähin
 | 
			
		||||
pienempi edeltäjä.
 | 
			
		||||
Jos ketjussa on vain yksi luku,
 | 
			
		||||
käsiteltävällä luvulla ei ole pienempää edeltäjää.
 | 
			
		||||
Joka askeleella ketjun alusta poistetaan lukuja
 | 
			
		||||
niin kauan, kunnes ketjun ensimmäinen luku on 
 | 
			
		||||
pienempi kuin käsiteltävä taulukon luku tai ketju on tyhjä.
 | 
			
		||||
Tämän jälkeen käsiteltävä luku lisätään ketjun alkuun.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä algoritmin toimintaa
 | 
			
		||||
seuraavassa taulukossa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$3$};
 | 
			
		||||
\node at (6.5,0.5) {$4$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Aluksi luvut 1, 3 ja 4 liittyvät ketjuun, koska jokainen luku on
 | 
			
		||||
edellistä suurempi. Siis luvun 4 lähin pienempi edeltäjä on luku 3,
 | 
			
		||||
jonka lähin pienempi edeltäjä on puolestaan luku 1. Tilanne näyttää tältä:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (3,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$3$};
 | 
			
		||||
\node at (6.5,0.5) {$4$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (2.5,-0.25) .. controls (2.25,-1.00) and (1.75,-1.00) .. (1.6,-0.25);
 | 
			
		||||
\draw[thick,->] (1.4,-0.25) .. controls (1.25,-1.00) and (0.75,-1.00) .. (0.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Taulukon seuraava luku 2 on pienempi kuin ketjun kaksi ensimmäistä lukua 4 ja 3.
 | 
			
		||||
Niinpä luvut 4 ja 3 poistetaan ketjusta, minkä jälkeen luku 2
 | 
			
		||||
lisätään ketjun alkuun. Sen lähin pienempi edeltäjä on luku 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (3,0) rectangle (4,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$3$};
 | 
			
		||||
\node at (6.5,0.5) {$4$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (3.5,-0.25) .. controls (3.00,-1.00) and (1.00,-1.00) .. (0.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraava luku 5 on suurempi kuin luku 2,
 | 
			
		||||
joten se lisätään suoraan ketjun alkuun ja
 | 
			
		||||
sen lähin pienempi edeltäjä on luku 2:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$3$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$5$};
 | 
			
		||||
\node at (5.5,0.5) {$3$};
 | 
			
		||||
\node at (6.5,0.5) {$4$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (3.4,-0.25) .. controls (3.00,-1.00) and (1.00,-1.00) .. (0.5,-0.25);
 | 
			
		||||
\draw[thick,->] (4.5,-0.25) .. controls (4.25,-1.00) and (3.75,-1.00) .. (3.6,-0.25);
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmi jatkaa samalla tavalla taulukon loppuun
 | 
			
		||||
ja selvittää jokaisen luvun lähimmän
 | 
			
		||||
pienemmän edeltäjän.
 | 
			
		||||
Mutta kuinka tehokas algoritmi on?
 | 
			
		||||
 | 
			
		||||
Algoritmin tehokkuus riippuu siitä,
 | 
			
		||||
kauanko ketjun käsittelyyn kuluu aikaa yhteensä.
 | 
			
		||||
Jos uusi luku on suurempi kuin ketjun ensimmäinen
 | 
			
		||||
luku, se vain lisätään ketjun alkuun,
 | 
			
		||||
mikä on tehokasta.
 | 
			
		||||
Joskus taas ketjussa voi olla useita
 | 
			
		||||
suurempia lukuja, joiden poistaminen vie aikaa.
 | 
			
		||||
Oleellista on kuitenkin, että jokainen
 | 
			
		||||
taulukossa oleva luku liittyy
 | 
			
		||||
tarkalleen kerran ketjuun ja poistuu
 | 
			
		||||
korkeintaan kerran ketjusta.
 | 
			
		||||
Niinpä jokainen luku aiheuttaa $O(1)$
 | 
			
		||||
ketjuun liittyvää operaatiota
 | 
			
		||||
ja algoritmin kokonaisaikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
\section{Liukuvan ikkunan minimi}
 | 
			
		||||
 | 
			
		||||
\index{liukuva ikkuna}
 | 
			
		||||
\index{liukuvan ikkunan minimi@liukuvan ikkunan minimi}
 | 
			
		||||
 | 
			
		||||
\key{Liukuva ikkuna} on taulukon halki kulkeva
 | 
			
		||||
aktiivinen alitaulukko, jonka pituus on vakio.
 | 
			
		||||
Jokaisessa liukuvan ikkunan sijainnissa
 | 
			
		||||
halutaan tyypillisesti laskea jotain tietoa
 | 
			
		||||
ikkunan alueelle osuvista alkioista.
 | 
			
		||||
Kiinnostava tehtävä on pitää yllä
 | 
			
		||||
\key{liukuvan ikkunan minimiä}.
 | 
			
		||||
Tämä tarkoittaa, että jokaisessa liukuvan ikkunan
 | 
			
		||||
sijainnissa tulee ilmoittaa pienin alkio
 | 
			
		||||
ikkunan alueella.
 | 
			
		||||
 | 
			
		||||
Liukuvan ikkunan minimit voi laskea
 | 
			
		||||
lähes samalla tavalla kuin lähimmät
 | 
			
		||||
pienimmät edeltäjät.
 | 
			
		||||
Ideana on pitää yllä ketjua, jonka alussa
 | 
			
		||||
on ikkunan viimeinen luku ja jossa jokainen
 | 
			
		||||
luku on edellistä pienempi. Joka vaiheessa
 | 
			
		||||
ketjun viimeinen luku on ikkunan pienin luku.
 | 
			
		||||
Kun liukuva ikkuna liikkuu eteenpäin ja välille
 | 
			
		||||
tulee uusi luku, ketjusta poistetaan kaikki luvut,
 | 
			
		||||
jotka ovat uutta lukua suurempia.
 | 
			
		||||
Tämän jälkeen uusi luku lisätään ketjun alkuun.
 | 
			
		||||
Lisäksi jos ketjun viimeinen luku ei enää kuulu
 | 
			
		||||
välille, se poistetaan ketjusta.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä, kuinka algoritmi selvittää
 | 
			
		||||
minimit seuraavassa taulukossa,
 | 
			
		||||
kun ikkunan koko $k=4$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Liukuva ikkuna aloittaa matkansa taulukon vasemmasta reunasta.
 | 
			
		||||
Ensimmäisessä ikkunan sijainnissa pienin luku on 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (0,0) rectangle (4,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (3.5,-0.25) .. controls (3.25,-1.00) and (2.75,-1.00) .. (2.6,-0.25);
 | 
			
		||||
\draw[thick,->] (2.4,-0.25) .. controls (2.25,-1.00) and (1.75,-1.00) .. (1.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kun ikkuna siirtyy eteenpäin, mukaan tulee luku 3,
 | 
			
		||||
joka on pienempi kuin luvut 5 ja 4 ketjun alussa.
 | 
			
		||||
Niinpä luvut 5 ja 4 poistuvat ketjusta ja luku 3
 | 
			
		||||
siirtyy sen alkuun. Pienin luku on edelleen 1.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (4.5,-0.25) .. controls (4.25,-1.00) and (1.75,-1.00) .. (1.5,-0.25);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ikkuna siirtyy taas eteenpäin, minkä seurauksena pienin luku 1
 | 
			
		||||
putoaa pois ikkunasta. Niinpä se poistetaan ketjun lopusta
 | 
			
		||||
ja uusi pienin luku on 3. Lisäksi uusi ikkunaan tuleva luku 4
 | 
			
		||||
lisätään ketjun alkuun.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (6,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (5.5,-0.25) .. controls (5.25,-1.00) and (4.75,-1.00) .. (4.5,-0.25);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi ikkunaan tuleva luku 1 on pienempi
 | 
			
		||||
kuin kaikki ketjussa olevat luvut.
 | 
			
		||||
Tämän seurauksena koko ketju tyhjentyy ja
 | 
			
		||||
siihen jää vain luku 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (3,0) rectangle (7,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\fill[color=black] (6.5,-0.25) circle (0.1);
 | 
			
		||||
 | 
			
		||||
%\draw[thick,->] (5.5,-0.25) .. controls (5.25,-1.00) and (4.75,-1.00) .. (4.5,-0.25);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Lopuksi ikkuna saapuu viimeiseen sijaintiinsa.
 | 
			
		||||
Luku 2 lisätään ketjun alkuun,
 | 
			
		||||
mutta ikkunan pienin luku on edelleen 1.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (8,1);
 | 
			
		||||
\draw (0,0) grid (8,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$2$};
 | 
			
		||||
\node at (1.5,0.5) {$1$};
 | 
			
		||||
\node at (2.5,0.5) {$4$};
 | 
			
		||||
\node at (3.5,0.5) {$5$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$1$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->] (7.5,-0.25) .. controls (7.25,-1.00) and (6.75,-1.00) .. (6.5,-0.25);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässäkin algoritmissa jokainen taulukon luku lisätään
 | 
			
		||||
ketjuun tarkalleen kerran ja poistetaan ketjusta korkeintaan kerran,
 | 
			
		||||
joko ketjun alusta tai ketjun lopusta.
 | 
			
		||||
Niinpä algoritmin kokonaisaikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,562 @@
 | 
			
		|||
\chapter{Bit manipulation}
 | 
			
		||||
 | 
			
		||||
Tietokone käsittelee tietoa sisäisesti bitteinä
 | 
			
		||||
eli numeroina 0 ja 1.
 | 
			
		||||
Tässä luvussa tutustumme tarkemmin kokonaisluvun
 | 
			
		||||
bittiesitykseen sekä bittioperaatioihin,
 | 
			
		||||
jotka muokkaavat luvun bittejä.
 | 
			
		||||
Osoittautuu, että näistä operaatioista on
 | 
			
		||||
monenlaista hyötyä algoritmien ohjelmoinnissa.
 | 
			
		||||
 | 
			
		||||
\section{Luvun bittiesitys}
 | 
			
		||||
 | 
			
		||||
\index{bittiesitys@bittiesitys}
 | 
			
		||||
 | 
			
		||||
Luvun \key{bittiesitys} ilmaisee, mistä 2:n potensseista
 | 
			
		||||
luku muodostuu. Esimerkiksi luvun 43 bittiesitys on 101011, koska
 | 
			
		||||
$43 = 2^5 + 2^3 + 2^1 + 2^0$
 | 
			
		||||
eli oikealta lukien bitit 0, 1, 3 ja 5 ovat ykkösiä
 | 
			
		||||
ja kaikki muut bitit ovat nollia.
 | 
			
		||||
 | 
			
		||||
Tietokoneessa luvun bittiesityksen
 | 
			
		||||
bittien määrä on kiinteä ja riippuu käytetystä tietotyypistä.
 | 
			
		||||
Esimerkiksi C++:n \texttt{int}-tyyppi on tavallisesti 32-bittinen,
 | 
			
		||||
jolloin \texttt{int}-luku tallennetaan 32 bittinä.
 | 
			
		||||
Tällöin esimerkiksi luvun 43 bittiesitys \texttt{int}-lukuna on seuraava:
 | 
			
		||||
 | 
			
		||||
\[00000000000000000000000000101011\]
 | 
			
		||||
 | 
			
		||||
Luvun bittiesitys on joko \key{etumerkillinen}
 | 
			
		||||
tai \key{etumerkitön}.
 | 
			
		||||
Etumerkillisen bittiesityksen ensimmäinen bitti on etumerkki
 | 
			
		||||
($+$ tai $-$) ja $n$ bitillä voi esittää luvut $-2^{n-1} \ldots 2^{n-1}-1$.
 | 
			
		||||
Jos taas bittiesitys on etumerkitön,
 | 
			
		||||
kaikki bitit kuuluvat lukuun ja $n$ bitillä voi esittää luvut $0 \ldots 2^n-1$.
 | 
			
		||||
 | 
			
		||||
Etumerkillisessä bittiesityksessä ei-negatiivisen luvun
 | 
			
		||||
ensimmäinen bitti on 0 ja negatiivisen luvun
 | 
			
		||||
ensimmäinen bitti on 1.
 | 
			
		||||
Bittiesityksenä on \key{kahden komplementti},
 | 
			
		||||
jossa luvun luvun vastaluvun saa laskettua
 | 
			
		||||
muuttamalla
 | 
			
		||||
kaikki bitit käänteiseksi ja lisäämällä
 | 
			
		||||
tulokseen yksi.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi luvun $-43$ esitys \texttt{int}-lukuna on seuraava:
 | 
			
		||||
 | 
			
		||||
\[11111111111111111111111111010101\]
 | 
			
		||||
 | 
			
		||||
Etumerkillisen ja etumerkittömän bittiesityksen
 | 
			
		||||
yhteys on, että etumerkillisen luvun $-x$
 | 
			
		||||
ja etumerkittömän luvun $2^n-x$ bittiesitykset ovat samat.
 | 
			
		||||
Niinpä yllä oleva bittiesitys tarkoittaa
 | 
			
		||||
etumerkittömänä lukua $2^{32}-43$.
 | 
			
		||||
 | 
			
		||||
C++:ssa luvut ovat oletuksena etumerkillisiä,
 | 
			
		||||
mutta avainsanan \texttt{unsigned} avulla
 | 
			
		||||
luvusta saa etumerkittömän.
 | 
			
		||||
Esimerkiksi koodissa
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int x = -43;
 | 
			
		||||
unsigned int y = x;
 | 
			
		||||
cout << x << "\n"; // -43
 | 
			
		||||
cout << y << "\n"; // 4294967253
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
etumerkillistä lukua $x=-43$ vastaa etumerkitön luku $y=2^{32}-43$.
 | 
			
		||||
 | 
			
		||||
Jos luvun suuruus menee käytössä
 | 
			
		||||
olevan bittiesityksen ulkopuolelle,
 | 
			
		||||
niin luku pyörähtää ympäri.
 | 
			
		||||
Etumerkillisessä bittiesityksessä
 | 
			
		||||
luvusta $2^{n-1}-1$ seuraava luku on $-2^{n-1}$
 | 
			
		||||
ja vastaavasti etumerkittömässä bittiesityksessä
 | 
			
		||||
luvusta $2^n-1$ seuraava luku on $0$.
 | 
			
		||||
Esimerkiksi koodissa
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int x = 2147483647
 | 
			
		||||
cout << x << "\n"; // 2147483647
 | 
			
		||||
x++;
 | 
			
		||||
cout << x << "\n"; // -2147483648
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
muuttuja $x$ pyörähtää ympäri luvusta $2^{31}-1$ lukuun $-2^{31}$.
 | 
			
		||||
 | 
			
		||||
\section{Bittioperaatiot}
 | 
			
		||||
 | 
			
		||||
\newcommand\XOR{\mathbin{\char`\^}}
 | 
			
		||||
 | 
			
		||||
\subsubsection{And-operaatio}
 | 
			
		||||
 | 
			
		||||
\index{and-operaatio}
 | 
			
		||||
 | 
			
		||||
And-operaatio $x$ \& $y$ tuottaa luvun,
 | 
			
		||||
jossa on ykkösbitti niissä kohdissa,
 | 
			
		||||
joissa molemmissa luvuissa $x$ ja $y$ on ykkösbitti.
 | 
			
		||||
Esimerkiksi $22$ \& $26$ = 18, koska
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrr}
 | 
			
		||||
& 10110 & (22)\\
 | 
			
		||||
\& & 11010 & (26) \\
 | 
			
		||||
\hline
 | 
			
		||||
 = & 10010 & (18) \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
And-operaation avulla voi tarkastaa luvun parillisuuden,
 | 
			
		||||
koska $x$ \& $1$ = 0, jos luku on parillinen,
 | 
			
		||||
ja $x$ \& $1$ = 1, jos luku on pariton.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Or-operaatio}
 | 
			
		||||
 | 
			
		||||
\index{or-operaatio}
 | 
			
		||||
 | 
			
		||||
Or-operaatio $x$ | $y$ tuottaa luvun,
 | 
			
		||||
jossa on ykkösbitti niissä kohdissa,
 | 
			
		||||
joissa ainakin toisessa luvuista $x$ ja $y$ on ykkösbitti.
 | 
			
		||||
Esimerkiksi $22$ | $26$ = 30, koska
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrr}
 | 
			
		||||
& 10110 & (22)\\
 | 
			
		||||
| & 11010 & (26) \\
 | 
			
		||||
\hline
 | 
			
		||||
 = & 11110 & (30) \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Xor-operaatio}
 | 
			
		||||
 | 
			
		||||
\index{xor-operaatio}
 | 
			
		||||
 | 
			
		||||
Xor-operaatio $x$ $\XOR$ $y$ tuottaa luvun,
 | 
			
		||||
jossa on ykkösbitti niissä kohdissa,
 | 
			
		||||
joissa tarkalleen toisessa luvuista $x$ ja $y$ on ykkösbitti.
 | 
			
		||||
Esimerkiksi $22$ $\XOR$ $26$ = 12, koska
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrr}
 | 
			
		||||
& 10110 & (22)\\
 | 
			
		||||
$\XOR$ & 11010 & (26) \\
 | 
			
		||||
\hline
 | 
			
		||||
 = & 01100 & (12) \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Not-operaatio}
 | 
			
		||||
 | 
			
		||||
\index{not-operaatio}
 | 
			
		||||
 | 
			
		||||
Not-operaatio \textasciitilde$x$ tuottaa luvun,
 | 
			
		||||
jossa kaikki $x$:n bitit on muutettu käänteisiksi.
 | 
			
		||||
Operaatiolle pätee kaava \textasciitilde$x = -x-1$,
 | 
			
		||||
esimerkiksi \textasciitilde$29 = -30$.
 | 
			
		||||
 | 
			
		||||
Not-operaation toiminta bittitasolla riippuu siitä,
 | 
			
		||||
montako bittiä luvun bittiesityksessä on,
 | 
			
		||||
koska operaatio vaikuttaa kaikkiin luvun bitteihin.
 | 
			
		||||
Esimerkiksi 32-bittisenä \texttt{int}-lukuna
 | 
			
		||||
tilanne on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrrr}
 | 
			
		||||
$x$ & = & 29 &   00000000000000000000000000011101 \\
 | 
			
		||||
\textasciitilde$x$ & = & $-30$ & 11111111111111111111111111100010 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Bittisiirrot}
 | 
			
		||||
 | 
			
		||||
\index{bittisiirto@bittisiirto}
 | 
			
		||||
 | 
			
		||||
Vasen bittisiirto $x < < k$ tuottaa luvun, jossa luvun $x$ bittejä
 | 
			
		||||
on siirretty $k$ askelta vasemmalle eli
 | 
			
		||||
luvun loppuun tulee $k$ nollabittiä.
 | 
			
		||||
Oikea bittisiirto $x > > k$ tuottaa puolestaan
 | 
			
		||||
luvun, jossa luvun $x$ bittejä
 | 
			
		||||
on siirretty $k$ askelta oikealle eli
 | 
			
		||||
luvun lopusta lähtee pois $k$ viimeistä bittiä.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi $14 < < 2 = 56$,
 | 
			
		||||
koska $14$ on bitteinä 1110,
 | 
			
		||||
josta tulee bittisiirron jälkeen 111000 eli $56$.
 | 
			
		||||
Vastaavasti $49 > > 3 = 6$,
 | 
			
		||||
koska $49$ on bitteinä 110001,
 | 
			
		||||
josta tulee bittisiirron jälkeen 110 eli $6$.
 | 
			
		||||
 | 
			
		||||
Huomaa, että vasen bittisiirto $x < < k$
 | 
			
		||||
vastaa luvun $x$ kertomista $2^k$:lla
 | 
			
		||||
ja oikea bittisiirto $x > > k$
 | 
			
		||||
vastaa luvun $x$ jakamista $2^k$:lla
 | 
			
		||||
alaspäin pyöristäen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Bittien käsittely}
 | 
			
		||||
 | 
			
		||||
Luvun bitit indeksoidaan oikealta vasemmalle
 | 
			
		||||
nollasta alkaen.
 | 
			
		||||
Luvussa $1 < < k$ on yksi ykkösbitti
 | 
			
		||||
kohdassa $k$ ja kaikki muut bitit ovat nollia, joten sen avulla voi käsitellä
 | 
			
		||||
muiden lukujen yksittäisiä bittejä.
 | 
			
		||||
 | 
			
		||||
Luvun $x$ bitti $k$ on ykkösbitti, jos
 | 
			
		||||
$x$ \& $(1 < < k) = (1 < < k)$.
 | 
			
		||||
Lauseke $x$ | $(1 < < k)$ asettaa luvun $x$ bitin $k$
 | 
			
		||||
ykköseksi, lauseke
 | 
			
		||||
$x$ \& \textasciitilde $(1 < < k)$
 | 
			
		||||
asettaa luvun $x$ bitin $k$ nollaksi ja
 | 
			
		||||
lauseke $x$ $\XOR$ $(1 < < k)$
 | 
			
		||||
muuttaa luvun $x$ bitin $k$ käänteiseksi.
 | 
			
		||||
% 
 | 
			
		||||
% Seuraava koodi muuttaa luvun bittejä:
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% int x = 181; // 10110101
 | 
			
		||||
% cout << (x|(1<<2)) << "\n"; // 181 = 10110101
 | 
			
		||||
% cout << (x|(1<<3)) << "\n"; // 189 = 10111101
 | 
			
		||||
% cout << (x&~(1<<2)) << "\n"; // 177 = 10110001
 | 
			
		||||
% cout << (x&~(1<<3)) << "\n"; // 181 = 10110101
 | 
			
		||||
% cout << (x^(1<<2)) << "\n"; // 177 = 10110001
 | 
			
		||||
% cout << (x^(1<<3)) << "\n"; // 189 = 10111101
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% % Bittiesityksen vasemmanpuoleisin bitti on eniten merkitsevä
 | 
			
		||||
% % (\textit{most significant}) ja
 | 
			
		||||
% % oikeanpuoleisin bitti on vähiten merkitsevä (\textit{least significant}).
 | 
			
		||||
 | 
			
		||||
Lauseke $x$ \& $(x-1)$ muuttaa luvun $x$ viimeisen
 | 
			
		||||
ykkösbitin nollaksi, ja lauseke $x$ \& $-x$ nollaa
 | 
			
		||||
luvun $x$ kaikki bitit paitsi viimeisen ykkösbitin.
 | 
			
		||||
Lauseke $x$ | $(x-1)$ vuorostaan muuttaa kaikki
 | 
			
		||||
viimeisen ykkösbitin jälkeiset bitit ykkösiksi.
 | 
			
		||||
 | 
			
		||||
Huomaa myös, että positiivinen luku $x$ on muotoa $2^k$,
 | 
			
		||||
jos $x$ \& $(x-1) = 0$.
 | 
			
		||||
% 
 | 
			
		||||
% Seuraava koodi esittelee operaatioita:
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% int x = 168; // 10101000
 | 
			
		||||
% cout << (x&(x-1)) << "\n"; // 160 = 10100000
 | 
			
		||||
% cout << (x&-x) << "\n"; // 8 = 00001000
 | 
			
		||||
% cout << (x|(x-1)) << "\n"; // 175 = 10101111
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Lisäfunktiot}
 | 
			
		||||
 | 
			
		||||
Kääntäjä g++ sisältää mm. seuraavat funktiot
 | 
			
		||||
bittien käsittelyyn:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
$\texttt{\_\_builtin\_clz}(x)$:
 | 
			
		||||
nollien määrä bittiesityksen alussa
 | 
			
		||||
\item
 | 
			
		||||
$\texttt{\_\_builtin\_ctz}(x)$:
 | 
			
		||||
nollien määrä bittiesityksen lopussa
 | 
			
		||||
\item
 | 
			
		||||
$\texttt{\_\_builtin\_popcount}(x)$:
 | 
			
		||||
ykkösten määrä bittiesityksessä
 | 
			
		||||
\item
 | 
			
		||||
$\texttt{\_\_builtin\_parity}(x)$:
 | 
			
		||||
ykkösten määrän parillisuus
 | 
			
		||||
\end{itemize}
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Nämä funktiot käsittelevät \texttt{int}-lukuja,
 | 
			
		||||
mutta funktioista on myös \texttt{long long} -versiot,
 | 
			
		||||
joiden lopussa on pääte \texttt{ll}.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi esittelee funktioiden käyttöä:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int x = 5328; // 00000000000000000001010011010000
 | 
			
		||||
cout << __builtin_clz(x) << "\n"; // 19
 | 
			
		||||
cout << __builtin_ctz(x) << "\n"; // 4
 | 
			
		||||
cout << __builtin_popcount(x) << "\n"; // 5
 | 
			
		||||
cout << __builtin_parity(x) << "\n"; // 1
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\section{Joukon bittiesitys}
 | 
			
		||||
 | 
			
		||||
Joukon $\{0,1,2,\ldots,n-1\}$
 | 
			
		||||
jokaista osajoukkoa
 | 
			
		||||
vastaa $n$-bittinen luku,
 | 
			
		||||
jossa ykkösbitit ilmaisevat,
 | 
			
		||||
mitkä alkiot ovat mukana osajoukossa.
 | 
			
		||||
Esimerkiksi joukkoa $\{1,3,4,8\}$
 | 
			
		||||
vastaa bittiesitys 100011010 eli luku
 | 
			
		||||
$2^8+2^4+2^3+2^1=282$.
 | 
			
		||||
 | 
			
		||||
Joukon bittiesitys vie vähän muistia,
 | 
			
		||||
koska tieto kunkin alkion kuulumisesta
 | 
			
		||||
osajoukkoon vie vain yhden bitin tilaa.
 | 
			
		||||
Lisäksi bittimuodossa tallennettua joukkoa
 | 
			
		||||
on tehokasta käsitellä bittioperaatioilla.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Joukon käsittely}
 | 
			
		||||
 | 
			
		||||
Seuraavan koodin muuttuja $x$
 | 
			
		||||
sisältää joukon $\{0,1,2,\ldots,31\}$
 | 
			
		||||
osajoukon.
 | 
			
		||||
Koodi lisää luvut 1, 3, 4 ja 8
 | 
			
		||||
joukkoon ja tulostaa
 | 
			
		||||
joukon sisällön.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
// x on tyhjä joukko
 | 
			
		||||
int x = 0;
 | 
			
		||||
// lisätään luvut 1, 3, 4 ja 8 joukkoon
 | 
			
		||||
x |= (1<<1);
 | 
			
		||||
x |= (1<<3);
 | 
			
		||||
x |= (1<<4);
 | 
			
		||||
x |= (1<<8);
 | 
			
		||||
// tulostetaan joukon sisältö
 | 
			
		||||
for (int i = 0; i < 32; i++) {
 | 
			
		||||
    if (x&(1<<i)) cout << i << " ";
 | 
			
		||||
}
 | 
			
		||||
cout << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Koodin tulostus on seuraava:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
1 3 4 8
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Kun joukko on tallennettu bittiesityksenä,
 | 
			
		||||
niin joukko-operaatiot voi toteuttaa 
 | 
			
		||||
tehokkaasti bittioperaatioiden avulla:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $a$ \& $b$ on joukkojen $a$ ja $b$ leikkaus $a \cap b$
 | 
			
		||||
(tämä sisältää alkiot,
 | 
			
		||||
jotka ovat kummassakin joukossa)
 | 
			
		||||
\item $a$ | $b$ on joukkojen $a$ ja $b$ yhdiste $a \cup b$
 | 
			
		||||
(tämä sisältää alkiot,
 | 
			
		||||
jotka ovat ainakin toisessa joukossa)
 | 
			
		||||
\item $a$ \& (\textasciitilde$b$) on joukkojen $a$ ja $b$ erotus
 | 
			
		||||
$a \setminus b$ (tämä sisältää alkiot,
 | 
			
		||||
jotka ovat joukossa $a$ mutta eivät joukossa $b$)
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi muodostaa
 | 
			
		||||
joukkojen $\{1,3,4,8\}$ ja $\{3,6,8,9\}$ yhdisteen:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
// joukko {1,3,4,8}
 | 
			
		||||
int x = (1<<1)+(1<<3)+(1<<4)+(1<<8);
 | 
			
		||||
// joukko {3,6,8,9}
 | 
			
		||||
int y = (1<<3)+(1<<6)+(1<<8)+(1<<9);
 | 
			
		||||
// joukkojen yhdiste
 | 
			
		||||
int z = x|y;
 | 
			
		||||
// tulostetaan yhdisteen sisältö
 | 
			
		||||
for (int i = 0; i < 32; i++) {
 | 
			
		||||
    if (z&(1<<i)) cout << i << " ";
 | 
			
		||||
}
 | 
			
		||||
cout << "\n";
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Koodin tulostus on seuraava:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
1 3 4 6 8 9
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Osajoukkojen läpikäynti}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi käy läpi joukon $\{0,1,\ldots,n-1\}$ osajoukot:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int b = 0; b < (1<<n); b++) {
 | 
			
		||||
    // osajoukon b käsittely
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Seuraava koodi käy läpi
 | 
			
		||||
osajoukot, joissa on $k$ alkiota:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int b = 0; b < (1<<n); b++) {
 | 
			
		||||
    if (__builtin_popcount(b) == k) {
 | 
			
		||||
        // osajoukon b käsittely
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Seuraava koodi käy läpi joukon $x$ osajoukot:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int b = 0;
 | 
			
		||||
do {
 | 
			
		||||
    // osajoukon b käsittely
 | 
			
		||||
} while (b=(b-x)&x);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Esimerkiksi jos $x$ esittää joukkoa $\{2,5,7\}$,
 | 
			
		||||
niin koodi käy läpi osajoukot
 | 
			
		||||
$\emptyset$, $\{2\}$, $\{5\}$, $\{7\}$,
 | 
			
		||||
$\{2,5\}$, $\{2,7\}$, $\{5,7\}$ ja $\{2,5,7\}$.
 | 
			
		||||
 | 
			
		||||
\section{Dynaaminen ohjelmointi}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Permutaatioista osajoukoiksi}
 | 
			
		||||
 | 
			
		||||
Dynaamisen ohjelmoinnin avulla on usein mahdollista
 | 
			
		||||
muuttaa permutaatioiden läpikäynti osajoukkojen läpikäynniksi.
 | 
			
		||||
Tällöin dynaamisen ohjelmoinnin tilana on
 | 
			
		||||
joukon osajoukko sekä mahdollisesti muuta tietoa.
 | 
			
		||||
 | 
			
		||||
Tekniikan hyötynä on,
 | 
			
		||||
että $n$-alkioisen joukon permutaatioiden määrä $n!$
 | 
			
		||||
on selvästi suurempi kuin osajoukkojen määrä $2^n$.
 | 
			
		||||
Esimerkiksi jos $n=20$, niin $n!=2432902008176640000$,
 | 
			
		||||
kun taas $2^n=1048576$.
 | 
			
		||||
Niinpä tietyillä $n$:n arvoilla permutaatioita ei ehdi
 | 
			
		||||
käydä läpi mutta osajoukot ehtii käydä läpi.
 | 
			
		||||
 | 
			
		||||
Lasketaan esimerkkinä, monessako
 | 
			
		||||
joukon $\{0,1,\ldots,n-1\}$
 | 
			
		||||
permutaatiossa ei ole 
 | 
			
		||||
missään kohdassa kahta peräkkäistä lukua.
 | 
			
		||||
Esimerkiksi tapauksessa $n=4$ ratkaisuja on kaksi:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $(1,3,0,2)$
 | 
			
		||||
\item $(2,0,3,1)$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Merkitään $f(x,k)$:llä,
 | 
			
		||||
monellako tavalla osajoukon
 | 
			
		||||
$x$ luvut voi järjestää niin,
 | 
			
		||||
että viimeinen luku on $k$ ja missään kohdassa
 | 
			
		||||
ei ole kahta peräkkäistä lukua.
 | 
			
		||||
Esimerkiksi $f(\{0,1,3\},1)=1$,
 | 
			
		||||
koska voidaan muodostaa permutaatio $(0,3,1)$,
 | 
			
		||||
ja $f(\{0,1,3\},3)=0$, koska 0 ja 1 eivät
 | 
			
		||||
voi olla peräkkäin alussa.
 | 
			
		||||
 | 
			
		||||
Funktion $f$ avulla ratkaisu tehtävään
 | 
			
		||||
on summa
 | 
			
		||||
 | 
			
		||||
\[ \sum_{i=0}^{n-1} f(\{0,1,\ldots,n-1\},i). \]
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Dynaamisen ohjelmoinnin tilat voi
 | 
			
		||||
tallentaa seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
long long d[1<<n][n];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Perustapauksena $f(\{k\},k)=1$ kaikilla $k$:n arvoilla:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 0; i < n; i++) d[1<<i][i] = 1;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Tämän jälkeen muut funktion arvot
 | 
			
		||||
saa laskettua seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int b = 0; b < (1<<n); b++) {
 | 
			
		||||
    for (int i = 0; i < n; i++) {
 | 
			
		||||
        for (int j = 0; j < n; j++) {
 | 
			
		||||
            if (abs(i-j) > 1 && (b&(1<<i)) && (b&(1<<j))) {
 | 
			
		||||
                d[b][i] += d[b^(1<<i)][j];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Muuttujassa $b$ on osajoukon bittiesitys,
 | 
			
		||||
ja osajoukon luvuista muodostettu
 | 
			
		||||
permutaatio on muotoa $(\ldots,j,i)$.
 | 
			
		||||
Vaatimukset ovat, että lukujen $i$ ja $j$
 | 
			
		||||
etäisyyden tulee olla yli 1
 | 
			
		||||
ja lukujen tulee olla osajoukossa $b$.
 | 
			
		||||
 | 
			
		||||
Lopuksi ratkaisujen määrän saa laskettua näin
 | 
			
		||||
muuttujaan $s$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
long long s = 0;
 | 
			
		||||
for (int i = 0; i < n; i++) {
 | 
			
		||||
    s += d[(1<<n)-1][i];
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Osajoukkojen summat}
 | 
			
		||||
 | 
			
		||||
Oletetaan sitten, että jokaista
 | 
			
		||||
joukon $\{0,1,\ldots,n-1\}$
 | 
			
		||||
osajoukkoa $x$ vastaa arvo $c(x)$ ja
 | 
			
		||||
tehtävänä on laskea kullekin
 | 
			
		||||
osajoukolle $x$ summa
 | 
			
		||||
\[s(x)=\sum_{y \subset x} c(y) \]
 | 
			
		||||
eli bittimuodossa ilmaistuna
 | 
			
		||||
\[s(x)=\sum_{y \& x = y} c(y). \]
 | 
			
		||||
Seuraavassa on esimerkki funktioiden arvoista,
 | 
			
		||||
kun $n=3$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{rrr}
 | 
			
		||||
$x$ & $c(x)$ & $s(x)$ \\
 | 
			
		||||
\hline
 | 
			
		||||
000 & 2 & 2 \\
 | 
			
		||||
001 & 0 & 2 \\
 | 
			
		||||
010 & 1 & 3 \\
 | 
			
		||||
011 & 3 & 6 \\
 | 
			
		||||
100 & 0 & 2 \\
 | 
			
		||||
101 & 4 & 6 \\
 | 
			
		||||
110 & 2 & 5 \\
 | 
			
		||||
111 & 0 & 12 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
Esimerkiksi $s(110)=c(000)+c(010)+c(100)+c(110)=5$. 
 | 
			
		||||
 | 
			
		||||
Tehtävä on mahdollista ratkaista ajassa $O(2^n n)$
 | 
			
		||||
laskemalla arvoja funktiolle $f(x,k)$:
 | 
			
		||||
mikä on lukujen $c(y)$ summa, missä $x$:stä saa $y$:n
 | 
			
		||||
muuttamalla millä tahansa tavalla bittien $0,1,\ldots,k$
 | 
			
		||||
joukossa ykkösbittejä nollabiteiksi.
 | 
			
		||||
Tämän funktion avulla ilmaistuna $s(x)=f(x,n-1)$.
 | 
			
		||||
 | 
			
		||||
Funktion pohjatapaukset ovat:
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    f(x,0) = \begin{cases}
 | 
			
		||||
               c(x)          & \textrm{jos $x$:n bitti 0 on 0}\\
 | 
			
		||||
               c(x)+c(x \XOR 1) & \textrm{jos $x$:n bitti 0 on 1}\\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
Suuremmille $k$:n arvoille pätee seuraava rekursio:
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    f(x,k) = \begin{cases}
 | 
			
		||||
               f(x,k-1)          & \textrm{jos $x$:n bitti $k$ on 0}\\
 | 
			
		||||
               f(x,k-1)+f(x \XOR (1 < < k),k-1)    & \textrm{jos $x$:n bitti $k$ on 1}\\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
 | 
			
		||||
Niinpä funktion arvot voi laskea seuraavasti
 | 
			
		||||
dynaamisella ohjelmoinnilla.
 | 
			
		||||
Koodi olettaa, että taulukko \texttt{c} sisältää
 | 
			
		||||
funktion $c$ arvot ja muodostaa taulukon \texttt{s},
 | 
			
		||||
jossa on funktion $s$ arvot.
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int x = 0; x < (1<<n); x++) {
 | 
			
		||||
    f[x][0] = c[x];
 | 
			
		||||
    if (x&1) f[x][0] += c[x^1];
 | 
			
		||||
}
 | 
			
		||||
for (int k = 1; k < n; k++) {
 | 
			
		||||
    for (int x = 0; x < (1<<n); x++) {
 | 
			
		||||
        f[x][k] = f[x][k-1];
 | 
			
		||||
        if (b&(1<<k)) f[x][k] += f[x^(1<<k)][k-1];
 | 
			
		||||
    }
 | 
			
		||||
    if (k == n-1) s[x] = f[x][k];
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Itse asiassa saman laskennan voi toteuttaa lyhyemmin
 | 
			
		||||
seuraavasti niin, että tulokset lasketaan
 | 
			
		||||
suoraan taulukkoon \texttt{s}:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int x = 0; x < (1<<n); x++) s[x] = c[x];
 | 
			
		||||
for (int k = 0; k < n; k++) {
 | 
			
		||||
    for (int x = 0; x < (1<<n); x++) {
 | 
			
		||||
        if (x&(1<<k)) s[x] += s[x^(1<<k)];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,688 @@
 | 
			
		|||
\chapter{Basics of graphs}
 | 
			
		||||
 | 
			
		||||
Monen ohjelmointitehtävän voi ratkaista tulkitsemalla
 | 
			
		||||
tehtävän verkko-on\-gel\-ma\-na ja käyttämällä
 | 
			
		||||
sopivaa verkkoalgoritmia.
 | 
			
		||||
Tyypillinen esimerkki verkosta on tieverkosto,
 | 
			
		||||
jonka rakenne muistuttaa luonnostaan verkkoa.
 | 
			
		||||
Joskus taas verkko kätkeytyy syvemmälle ongelmaan
 | 
			
		||||
ja sitä voi olla vaikeaa huomata.
 | 
			
		||||
 | 
			
		||||
Tässä kirjan osassa tutustumme verkkojen käsittelyyn
 | 
			
		||||
liittyviin tekniikoihin ja kisakoodauksessa
 | 
			
		||||
keskeisiin verkkoalgoritmeihin.
 | 
			
		||||
Aloitamme aiheeseen perehtymisen
 | 
			
		||||
käymällä läpi verkkoihin liittyviä käsitteitä
 | 
			
		||||
sekä erilaisia tapoja pitää verkkoa muistissa algoritmeissa.
 | 
			
		||||
 | 
			
		||||
\section{Käsitteitä}
 | 
			
		||||
 | 
			
		||||
\index{verkko@verkko}
 | 
			
		||||
\index{solmu@solmu}
 | 
			
		||||
\index{kaari@kaari}
 | 
			
		||||
 | 
			
		||||
\key{Verkko} muodostuu \key{solmuista}
 | 
			
		||||
ja niiden välisistä \key{kaarista}.
 | 
			
		||||
Merkitsemme tässä kirjassa
 | 
			
		||||
verkon solmujen määrää
 | 
			
		||||
muuttujalla $n$ ja kaarten määrää muuttujalla $m$.
 | 
			
		||||
Lisäksi numeroimme verkon solmut kokonaisluvuin
 | 
			
		||||
$1,2,\ldots,n$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa verkossa on 5 solmua ja 7 kaarta:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{polku@polku}
 | 
			
		||||
 | 
			
		||||
\key{Polku} on solmusta $a$ solmuun $b$
 | 
			
		||||
johtava reitti, joka kulkee verkon kaaria pitkin.
 | 
			
		||||
Polun \key{pituus} on kaarten määrä polulla.
 | 
			
		||||
Esimerkiksi yllä olevassa verkossa
 | 
			
		||||
polkuja solmusta 1 solmuun 5 ovat:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $1 \rightarrow 2 \rightarrow 5$ (pituus 2)
 | 
			
		||||
\item $1 \rightarrow 4 \rightarrow 5$ (pituus 2)
 | 
			
		||||
\item $1 \rightarrow 2 \rightarrow 4 \rightarrow 5$ (pituus 3)
 | 
			
		||||
\item $1 \rightarrow 3 \rightarrow 4 \rightarrow 5$ (pituus 3)
 | 
			
		||||
\item $1 \rightarrow 4 \rightarrow 2 \rightarrow 5$ (pituus 3)
 | 
			
		||||
\item $1 \rightarrow 3 \rightarrow 4 \rightarrow 2 \rightarrow 5$ (pituus 4)
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yhtenäisyys}
 | 
			
		||||
 | 
			
		||||
\index{yhtenxinen verkko@yhtenäinen verkko}
 | 
			
		||||
 | 
			
		||||
Verkko on \key{yhtenäinen}, jos siinä on polku
 | 
			
		||||
mistä tahansa solmusta mihin tahansa solmuun.
 | 
			
		||||
Esimerkiksi seuraava verkko on yhtenäinen:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraava verkko taas ei ole yhtenäinen,
 | 
			
		||||
koska solmusta 4 ei pääse muihin verkon solmuihin.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Verkon yhtenäiset osat muodostavat sen
 | 
			
		||||
\key{komponentit}. \index{komponentti@komponentti}
 | 
			
		||||
Esimerkiksi seuraavassa verkossa on
 | 
			
		||||
kolme komponenttia:
 | 
			
		||||
$\{1,\,2,\,3\}$,
 | 
			
		||||
$\{4,\,5,\,6,\,7\}$ ja
 | 
			
		||||
$\{8\}$.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.8]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (6) at (6,1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (9,1) {$7$};
 | 
			
		||||
\node[draw, circle] (4) at (6,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (9,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (8) at (11,2) {$8$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (7);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
\path[draw,thick,-] (6) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{puu@puu}
 | 
			
		||||
 | 
			
		||||
\key{Puu} on yhtenäinen verkko,
 | 
			
		||||
jossa on $n$ solmua ja $n-1$ kaarta.
 | 
			
		||||
Puussa minkä tahansa kahden solmun välillä
 | 
			
		||||
on yksikäsitteinen polku.
 | 
			
		||||
Esimerkiksi seuraava verkko on puu:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
%\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
%\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kaarten suunnat}
 | 
			
		||||
 | 
			
		||||
\index{suunnattu verkko@suunnattu verkko}
 | 
			
		||||
 | 
			
		||||
Verkko on \key{suunnattu},
 | 
			
		||||
jos verkon kaaria pystyy
 | 
			
		||||
kulkemaan vain niiden merkittyyn suuntaan.
 | 
			
		||||
Esimerkiki seuraava verkko on suunnattu:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Yllä olevassa verkossa on polku solmusta
 | 
			
		||||
3 solmuun 5, joka kulkee kaaria
 | 
			
		||||
$3 \rightarrow 1 \rightarrow 2 \rightarrow 5$.
 | 
			
		||||
Sen sijaan verkossa ei ole polkua
 | 
			
		||||
solmusta 5 solmuun 3.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\index{sykli@sykli}
 | 
			
		||||
\index{syklitzn verkko@syklitön verkko}
 | 
			
		||||
 | 
			
		||||
\key{Sykli} on polku, jonka ensimmäinen
 | 
			
		||||
ja viimeinen solmu on sama.
 | 
			
		||||
Esimerkiksi yllä olevassa verkossa on sykli
 | 
			
		||||
$1 \rightarrow 2 \rightarrow 4 \rightarrow 1$.
 | 
			
		||||
Jos verkossa ei ole yhtään sykliä, se on \key{syklitön}.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kaarten painot}
 | 
			
		||||
 | 
			
		||||
\index{painotettu verkko@painotettu verkko}
 | 
			
		||||
 | 
			
		||||
\key{Painotetussa} verkossa
 | 
			
		||||
jokaiseen kaareen liittyy \key{paino}.
 | 
			
		||||
Usein painot kuvaavat kaarien pituuksia.
 | 
			
		||||
Esimerkiksi seuraava verkko on painotettu:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:5] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:1] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:7] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:3] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Nyt polun pituus on 
 | 
			
		||||
polulla olevien kaarten painojen summa.
 | 
			
		||||
Esimerkiksi yllä olevassa verkossa
 | 
			
		||||
polun $1 \rightarrow 2 \rightarrow 5$
 | 
			
		||||
pituus on $5+7=12$ ja polun
 | 
			
		||||
$1 \rightarrow 3 \rightarrow 4 \rightarrow 5$ pituus on $1+7+3=11$.
 | 
			
		||||
Jälkimmäinen on lyhin polku solmusta 1 solmuun 5.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Naapurit ja asteet}
 | 
			
		||||
 | 
			
		||||
\index{naapuri@naapuri}
 | 
			
		||||
\index{aste@aste}
 | 
			
		||||
 | 
			
		||||
Kaksi solmua ovat \key{naapureita},
 | 
			
		||||
jos ne ovat verkossa
 | 
			
		||||
\key{vierekkäin} eli niiden välillä on kaari.
 | 
			
		||||
Solmun \key{aste} on
 | 
			
		||||
sen naapurien määrä.
 | 
			
		||||
Esimerkiksi seuraavassa verkossa
 | 
			
		||||
solmun 2 naapurit ovat 1, 4 ja 5,
 | 
			
		||||
joten sen aste on 3.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
%\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Verkon solmujen asteiden summa on aina $2m$,
 | 
			
		||||
missä $m$ on kaarten määrä.
 | 
			
		||||
Tämä johtuu siitä, että jokainen kaari lisää
 | 
			
		||||
kahden solmun astetta yhdellä.
 | 
			
		||||
Niinpä solmujen asteiden summa on aina parillinen.
 | 
			
		||||
 | 
			
		||||
\index{szznnollinen verkko@säännöllinen verkko}
 | 
			
		||||
\index{tzydellinen verkko@täydellinen verkko}
 | 
			
		||||
 | 
			
		||||
Verkko on \key{säännöllinen},
 | 
			
		||||
jos jokaisen solmun aste on vakio $d$.
 | 
			
		||||
Verkko on \key{täydellinen},
 | 
			
		||||
jos jokaisen solmun aste on $n-1$ eli
 | 
			
		||||
verkossa on kaikki mahdolliset kaaret
 | 
			
		||||
solmujen välillä.
 | 
			
		||||
 | 
			
		||||
\index{lzhtzaste@lähtöaste}
 | 
			
		||||
\index{tuloaste@tuloaste}
 | 
			
		||||
 | 
			
		||||
Suunnatussa verkossa \key{lähtöaste}
 | 
			
		||||
on solmusta lähtevien kaarten määrä ja
 | 
			
		||||
\key{tuloaste} on solmuun tulevien
 | 
			
		||||
kaarten määrä.
 | 
			
		||||
Esimerkiksi seuraavassa verkossa solmun 2
 | 
			
		||||
lähtöaste on 1 ja tuloaste on 2.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (4);
 | 
			
		||||
\path[draw,thick,<-,>=latex] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Väritykset}
 | 
			
		||||
 | 
			
		||||
\index{vxritys@väritys}
 | 
			
		||||
\index{kaksijakoinen verkko@kaksijakoinen verkko}
 | 
			
		||||
 | 
			
		||||
Verkon \key{värityksessä} jokaiselle solmulle valitaan
 | 
			
		||||
tietty väri niin, että millään kahdella
 | 
			
		||||
vierekkäisellä solmulla ei ole samaa väriä.
 | 
			
		||||
 | 
			
		||||
Verkko on \key{kaksijakoinen},
 | 
			
		||||
jos se on mahdollista värittää kahdella värillä.
 | 
			
		||||
Osoittautuu, että verkko on kaksijakoinen tarkalleen silloin,
 | 
			
		||||
kun siinä ei ole sykliä, johon kuuluu
 | 
			
		||||
pariton määrä solmuja.
 | 
			
		||||
Esimerkiksi verkko
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$3$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$1$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on kaksijakoinen, koska sen voi värittää seuraavasti:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle, fill=blue!40] (1) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle, fill=red!40] (2) at (4,3) {$3$};
 | 
			
		||||
\node[draw, circle, fill=red!40] (3) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle, fill=blue!40] (4) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle, fill=red!40] (5) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle, fill=blue!40] (6) at (-2,3) {$1$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yksinkertaisuus}
 | 
			
		||||
 | 
			
		||||
\index{yksinkertainen verkko@yksinkertainen verkko}
 | 
			
		||||
 | 
			
		||||
Verkko on \key{yksinkertainen},
 | 
			
		||||
jos mistään solmusta ei ole kaarta itseensä
 | 
			
		||||
eikä minkään kahden solmun välillä ole
 | 
			
		||||
monta kaarta samaan suuntaan.
 | 
			
		||||
Usein oletuksena on, että verkko on yksinkertainen.
 | 
			
		||||
Esimerkiksi verkko
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$3$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) edge [bend right=20] (2);
 | 
			
		||||
\path[draw,thick,-] (2) edge [bend right=20] (1);
 | 
			
		||||
%\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
 | 
			
		||||
\tikzset{every loop/.style={in=135,out=190}}
 | 
			
		||||
\path[draw,thick,-] (5) edge [loop left] (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\emph{ei} ole yksinkertainen, koska solmusta 4 on kaari itseensä
 | 
			
		||||
ja solmujen 2 ja 3 välillä on kaksi kaarta.
 | 
			
		||||
 | 
			
		||||
\section{Verkko muistissa}
 | 
			
		||||
 | 
			
		||||
On monia tapoja pitää verkkoa muistissa algoritmissa.
 | 
			
		||||
Sopiva tietorakenne riippuu siitä,
 | 
			
		||||
kuinka suuri verkko on ja
 | 
			
		||||
millä tavoin algoritmi käsittelee sitä.
 | 
			
		||||
Seuraavaksi käymme läpi kolme tavallista vaihtoehtoa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Vieruslistaesitys}
 | 
			
		||||
 | 
			
		||||
\index{vieruslista@vieruslista}
 | 
			
		||||
 | 
			
		||||
Tavallisin tapa pitää verkkoa muistissa on
 | 
			
		||||
luoda jokaisesta solmusta \key{vieruslista},
 | 
			
		||||
joka sisältää kaikki solmut,
 | 
			
		||||
joihin solmusta pystyy siirtymään kaarta pitkin.
 | 
			
		||||
Vieruslistaesitys on tavallisin verkon esitysmuoto, ja
 | 
			
		||||
useimmat algoritmit pystyy toteuttamaan
 | 
			
		||||
tehokkaasti sitä käyttäen.
 | 
			
		||||
 | 
			
		||||
Kätevä tapa tallentaa verkon vieruslistaesitys on luoda taulukko,
 | 
			
		||||
jossa jokainen alkio on vektori:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Taulukossa solmun $s$ vieruslista on kohdassa $\texttt{v}[s]$.
 | 
			
		||||
Vakio $N$ on valittu niin suureksi,
 | 
			
		||||
että kaikki vieruslistat mahtuvat taulukkoon.
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
voi tallentaa seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v[1].push_back(2);
 | 
			
		||||
v[2].push_back(3);
 | 
			
		||||
v[2].push_back(4);
 | 
			
		||||
v[3].push_back(4);
 | 
			
		||||
v[4].push_back(1);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Jos verkko on suuntaamaton, sen voi tallentaa samalla tavalla,
 | 
			
		||||
mutta silloin jokainen kaari lisätään kumpaankin suuntaan.
 | 
			
		||||
 | 
			
		||||
Painotetun verkon tapauksessa rakennetta voi laajentaa näin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<pair<int,int>> v[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Nyt vieruslistalla on pareja, joiden ensimmäinen kenttä on
 | 
			
		||||
kaaren kohdesolmu ja toinen kenttä on kaaren paino.
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- node[font=\small,label=above:5] {} (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=above:7] {} (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=left:6] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=right:5] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- node[font=\small,label=left:2] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
voi tallentaa seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v[1].push_back({2,5});
 | 
			
		||||
v[2].push_back({3,7});
 | 
			
		||||
v[2].push_back({4,6});
 | 
			
		||||
v[3].push_back({4,5});
 | 
			
		||||
v[4].push_back({1,2});
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Vieruslistaesityksen etuna on, että sen avulla on nopeaa selvittää,
 | 
			
		||||
mihin solmuihin tietystä solmusta pääsee kulkemaan.
 | 
			
		||||
Esimerkiksi seuraava silmukka käy läpi kaikki solmut,
 | 
			
		||||
joihin pääsee solmusta $s$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (auto u : v[s]) {
 | 
			
		||||
    // käsittele solmu u
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Vierusmatriisiesitys}
 | 
			
		||||
 | 
			
		||||
\index{vierusmatriisi@vierusmatriisi}
 | 
			
		||||
 | 
			
		||||
\key{Vierusmatriisi} on kaksiulotteinen taulukko,
 | 
			
		||||
joka kertoo jokaisesta mahdollisesta kaaresta,
 | 
			
		||||
onko se mukana verkossa.
 | 
			
		||||
Vierusmatriisista on nopeaa tarkistaa,
 | 
			
		||||
onko kahden solmun välillä kaari.
 | 
			
		||||
Toisaalta matriisi vie paljon tilaa,
 | 
			
		||||
jos verkko on suuri.
 | 
			
		||||
Vierusmatriisi tallennetaan taulukkona
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int v[N][N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
jossa arvo $\texttt{v}[a][b]$ ilmaisee,
 | 
			
		||||
onko kaari solmusta $a$ solmuun $b$ mukana verkossa.
 | 
			
		||||
Jos kaari on mukana verkossa,
 | 
			
		||||
niin $\texttt{v}[a][b]=1$,
 | 
			
		||||
ja muussa tapauksessa $\texttt{v}[a][b]=0$.
 | 
			
		||||
Nyt esimerkiksi verkkoa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
vastaa seuraava vierusmatriisi:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (4,4);
 | 
			
		||||
\node at (0.5,0.5) {1};
 | 
			
		||||
\node at (1.5,0.5) {0};
 | 
			
		||||
\node at (2.5,0.5) {0};
 | 
			
		||||
\node at (3.5,0.5) {0};
 | 
			
		||||
\node at (0.5,1.5) {0};
 | 
			
		||||
\node at (1.5,1.5) {0};
 | 
			
		||||
\node at (2.5,1.5) {0};
 | 
			
		||||
\node at (3.5,1.5) {1};
 | 
			
		||||
\node at (0.5,2.5) {0};
 | 
			
		||||
\node at (1.5,2.5) {0};
 | 
			
		||||
\node at (2.5,2.5) {1};
 | 
			
		||||
\node at (3.5,2.5) {1};
 | 
			
		||||
\node at (0.5,3.5) {0};
 | 
			
		||||
\node at (1.5,3.5) {1};
 | 
			
		||||
\node at (2.5,3.5) {0};
 | 
			
		||||
\node at (3.5,3.5) {0};
 | 
			
		||||
\node at (-0.5,0.5) {4};
 | 
			
		||||
\node at (-0.5,1.5) {3};
 | 
			
		||||
\node at (-0.5,2.5) {2};
 | 
			
		||||
\node at (-0.5,3.5) {1};
 | 
			
		||||
\node at (0.5,4.5) {1};
 | 
			
		||||
\node at (1.5,4.5) {2};
 | 
			
		||||
\node at (2.5,4.5) {3};
 | 
			
		||||
\node at (3.5,4.5) {4};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Jos verkko on painotettu, vierusmatriisiesitystä voi 
 | 
			
		||||
laajentaa luontevasti niin, että matriisissa kerrotaan
 | 
			
		||||
kaaren paino, jos kaari on olemassa.
 | 
			
		||||
Tätä esitystapaa käyttäen esimerkiksi verkkoa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- node[font=\small,label=above:5] {} (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=above:7] {} (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=left:6] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=right:5] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- node[font=\small,label=left:2] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
vastaa seuraava vierusmatriisi:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (4,4);
 | 
			
		||||
\node at (0.5,0.5) {2};
 | 
			
		||||
\node at (1.5,0.5) {0};
 | 
			
		||||
\node at (2.5,0.5) {0};
 | 
			
		||||
\node at (3.5,0.5) {0};
 | 
			
		||||
\node at (0.5,1.5) {0};
 | 
			
		||||
\node at (1.5,1.5) {0};
 | 
			
		||||
\node at (2.5,1.5) {0};
 | 
			
		||||
\node at (3.5,1.5) {5};
 | 
			
		||||
\node at (0.5,2.5) {0};
 | 
			
		||||
\node at (1.5,2.5) {0};
 | 
			
		||||
\node at (2.5,2.5) {7};
 | 
			
		||||
\node at (3.5,2.5) {6};
 | 
			
		||||
\node at (0.5,3.5) {0};
 | 
			
		||||
\node at (1.5,3.5) {5};
 | 
			
		||||
\node at (2.5,3.5) {0};
 | 
			
		||||
\node at (3.5,3.5) {0};
 | 
			
		||||
\node at (-0.5,0.5) {4};
 | 
			
		||||
\node at (-0.5,1.5) {3};
 | 
			
		||||
\node at (-0.5,2.5) {2};
 | 
			
		||||
\node at (-0.5,3.5) {1};
 | 
			
		||||
\node at (0.5,4.5) {1};
 | 
			
		||||
\node at (1.5,4.5) {2};
 | 
			
		||||
\node at (2.5,4.5) {3};
 | 
			
		||||
\node at (3.5,4.5) {4};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kaarilistaesitys}
 | 
			
		||||
 | 
			
		||||
\index{kaarilista@kaarilista}
 | 
			
		||||
 | 
			
		||||
\key{Kaarilista} sisältää kaikki verkon kaaret.
 | 
			
		||||
Kaarilista on hyvä tapa tallentaa verkko,
 | 
			
		||||
jos algoritmissa täytyy käydä läpi
 | 
			
		||||
kaikki verkon kaaret eikä ole tarvetta
 | 
			
		||||
etsiä kaarta alkusolmun perusteella.
 | 
			
		||||
 | 
			
		||||
Kaarilistan voi tallentaa vektoriin
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<pair<int,int>> v;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
jossa jokaisessa solmussa on parina kaaren
 | 
			
		||||
alku- ja loppusolmu.
 | 
			
		||||
Tällöin esimerkiksi verkon
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
voi tallentaa seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v.push_back({1,2});
 | 
			
		||||
v.push_back({2,3});
 | 
			
		||||
v.push_back({2,4});
 | 
			
		||||
v.push_back({3,4});
 | 
			
		||||
v.push_back({4,1});
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Painotetun verkon tapauksessa rakennetta voi laajentaa
 | 
			
		||||
esimerkiksi näin:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<pair<pair<int,int>,int>> v;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Nyt listalla on pareja, joiden ensimmäinen jäsen
 | 
			
		||||
sisältää parina kaaren alku- ja loppusolmun,
 | 
			
		||||
ja toinen jäsen on kaaren paino.
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- node[font=\small,label=above:5] {} (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=above:7] {} (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=left:6] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=right:5] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- node[font=\small,label=left:2] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
voi tallentaa seuraavasti:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
v.push_back({{1,2},5});
 | 
			
		||||
v.push_back({{2,3},7});
 | 
			
		||||
v.push_back({{2,4},6});
 | 
			
		||||
v.push_back({{3,4},5});
 | 
			
		||||
v.push_back({{4,1},2});
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,640 @@
 | 
			
		|||
\chapter{Graph search}
 | 
			
		||||
 | 
			
		||||
Tässä luvussa tutustumme
 | 
			
		||||
syvyyshakuun ja leveyshakuun, jotka
 | 
			
		||||
ovat keskeisiä menetelmiä verkon läpikäyntiin.
 | 
			
		||||
Molemmat algoritmit lähtevät liikkeelle
 | 
			
		||||
tietystä alkusolmusta ja 
 | 
			
		||||
käyvät läpi kaikki solmut,
 | 
			
		||||
joihin alkusolmusta pääsee.
 | 
			
		||||
Algoritmien erona on,
 | 
			
		||||
missä järjestyksessä ne kulkevat verkossa.
 | 
			
		||||
 | 
			
		||||
\section{Syvyyshaku}
 | 
			
		||||
 | 
			
		||||
\index{syvyyshaku@syvyyshaku}
 | 
			
		||||
 | 
			
		||||
\key{Syvyyshaku}
 | 
			
		||||
on suoraviivainen menetelmä verkon läpikäyntiin.
 | 
			
		||||
Algoritmi lähtee liikkeelle tietystä
 | 
			
		||||
verkon solmusta ja etenee siitä
 | 
			
		||||
kaikkiin solmuihin, jotka ovat
 | 
			
		||||
saavutettavissa kaaria kulkemalla.
 | 
			
		||||
 | 
			
		||||
Syvyyshaku etenee verkossa syvyyssuuntaisesti
 | 
			
		||||
eli kulkee eteenpäin verkossa niin kauan
 | 
			
		||||
kuin vastaan tulee uusia solmuja.
 | 
			
		||||
Tämän jälkeen haku perääntyy kokeilemaan
 | 
			
		||||
muita suuntia.
 | 
			
		||||
Algoritmi pitää kirjaa vierailemistaan solmuista,
 | 
			
		||||
jotta se käsittelee kunkin solmun vain kerran.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan syvyyshaun toimintaa
 | 
			
		||||
seuraavassa verkossa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Syvyyshaku voi lähteä liikkeelle
 | 
			
		||||
mistä tahansa solmusta,
 | 
			
		||||
mutta oletetaan nyt,
 | 
			
		||||
että haku lähtee liikkeelle solmusta 1.
 | 
			
		||||
 | 
			
		||||
Solmun 1 naapurit ovat solmut 2 ja 4,
 | 
			
		||||
joista haku etenee ensin solmuun 2:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen haku etenee vastaavasti
 | 
			
		||||
solmuihin 3 ja 5:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Solmun 5 naapurit ovat 2 ja 3,
 | 
			
		||||
mutta haku on käynyt jo molemmissa,
 | 
			
		||||
joten on aika peruuttaa taaksepäin.
 | 
			
		||||
Myös solmujen 3 ja 2 naapurit on käyty,
 | 
			
		||||
joten haku peruuttaa solmuun 1 asti.
 | 
			
		||||
Siitä lähtee kaari, josta pääsee
 | 
			
		||||
solmuun 4:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen haku päättyy,
 | 
			
		||||
koska se on käynyt kaikissa solmuissa.
 | 
			
		||||
 | 
			
		||||
Syvyyshaun aikavaativuus on $O(n+m)$,
 | 
			
		||||
missä $n$ on solmujen määrä ja $m$ on kaarten määrä,
 | 
			
		||||
koska haku käsittelee kerran jokaisen solmun ja kaaren.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Toteutus}
 | 
			
		||||
 | 
			
		||||
Syvyyshaku on yleensä mukavinta toteuttaa
 | 
			
		||||
rekursiolla.
 | 
			
		||||
Seuraava funktio \texttt{haku}
 | 
			
		||||
suorittaa syvyyshaun sille parametrina
 | 
			
		||||
annetusta solmusta lähtien.
 | 
			
		||||
Funktio olettaa, että
 | 
			
		||||
verkko on tallennettu vieruslistoina
 | 
			
		||||
taulukkoon
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> v[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
ja pitää lisäksi yllä taulukkoa
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int z[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
joka kertoo, missä solmuissa haku on käynyt.
 | 
			
		||||
Alussa taulukon jokainen arvo on 0,
 | 
			
		||||
ja kun haku saapuu solmuun $s$,
 | 
			
		||||
kohtaan \texttt{z}[$s$] merkitään 1.
 | 
			
		||||
Funktion toteutus on seuraavanlainen:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku(int s) {
 | 
			
		||||
    if (z[s]) return;
 | 
			
		||||
    z[s] = 1;
 | 
			
		||||
    // solmun s käsittely tähän
 | 
			
		||||
    for (auto u: v[s]) {
 | 
			
		||||
        haku(u);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Leveyshaku}
 | 
			
		||||
 | 
			
		||||
\index{leveyshaku@leveyshaku}
 | 
			
		||||
 | 
			
		||||
\key{Leveyshaku}
 | 
			
		||||
käy solmut läpi järjestyksessä sen mukaan,
 | 
			
		||||
kuinka kaukana ne ovat alkusolmusta.
 | 
			
		||||
Niinpä leveyshaun avulla pystyy laskemaan
 | 
			
		||||
etäisyyden alkusolmusta kaikkiin
 | 
			
		||||
muihin solmuihin.
 | 
			
		||||
Leveyshaku on kuitenkin vaikeampi
 | 
			
		||||
toteuttaa kuin syvyyshaku.
 | 
			
		||||
 | 
			
		||||
Leveyshakua voi ajatella niin,
 | 
			
		||||
että se käy solmuja läpi kerros kerrallaan.
 | 
			
		||||
Ensin haku käy läpi solmut,
 | 
			
		||||
joihin pääsee yhdellä kaarella
 | 
			
		||||
alkusolmusta.
 | 
			
		||||
Tämän jälkeen vuorossa ovat
 | 
			
		||||
solmut, joihin pääsee kahdella
 | 
			
		||||
kaarella alkusolmusta, jne.
 | 
			
		||||
Sama jatkuu, kunnes uusia käsiteltäviä
 | 
			
		||||
solmuja ei enää ole.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan leveyshaun toimintaa
 | 
			
		||||
seuraavassa verkossa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Oletetaan jälleen,
 | 
			
		||||
että haku alkaa solmusta 1.
 | 
			
		||||
Haku etenee ensin kaikkiin solmuihin,
 | 
			
		||||
joihin pääsee alkusolmusta:
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Seuraavaksi haku etenee solmuihin 3 ja 5:
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Viimeisenä haku etenee solmuun 6:
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Leveyshaun tuloksena selviää etäisyys
 | 
			
		||||
kuhunkin verkon solmuun alkusolmusta.
 | 
			
		||||
Etäisyys on sama kuin kerros,
 | 
			
		||||
jossa solmu käsiteltiin haun aikana:
 | 
			
		||||
 | 
			
		||||
\begin{tabular}{ll}
 | 
			
		||||
\\
 | 
			
		||||
solmu & etäisyys \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 \\
 | 
			
		||||
2 & 1 \\
 | 
			
		||||
3 & 2 \\
 | 
			
		||||
4 & 1 \\
 | 
			
		||||
5 & 2 \\
 | 
			
		||||
6 & 3 \\
 | 
			
		||||
\\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
 | 
			
		||||
Leveyshaun aikavaativuus on syvyyshaun tavoin $O(n+m)$,
 | 
			
		||||
missä $n$ on solmujen määrä ja $m$ on kaarten määrä.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Toteutus}
 | 
			
		||||
 | 
			
		||||
Leveyshaku on syvyyshakua hankalampi toteuttaa,
 | 
			
		||||
koska haku käy läpi solmuja verkon eri
 | 
			
		||||
puolilta niiden etäisyyden mukaan.
 | 
			
		||||
Tyypillinen toteutus on pitää yllä jonoa
 | 
			
		||||
käsiteltävistä solmuista.
 | 
			
		||||
Joka askeleella otetaan käsittelyyn seuraava
 | 
			
		||||
solmu jonosta ja uudet solmut lisätään
 | 
			
		||||
jonon perälle.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi toteuttaa leveyshaun
 | 
			
		||||
solmusta $x$ lähtien.
 | 
			
		||||
Koodi olettaa, että verkko on tallennettu
 | 
			
		||||
vieruslistoina, ja pitää yllä jonoa
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
queue<int> q;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
joka sisältää solmut käsittelyjärjestyksessä.
 | 
			
		||||
Koodi lisää aina uudet vastaan tulevat solmut
 | 
			
		||||
jonon perään ja ottaa seuraavaksi käsiteltävän
 | 
			
		||||
solmun jonon alusta,
 | 
			
		||||
minkä ansiosta solmut käsitellään
 | 
			
		||||
kerroksittain alkusolmusta lähtien.
 | 
			
		||||
 | 
			
		||||
Lisäksi koodi käyttää taulukoita
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int z[N], e[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
niin, että taulukko \texttt{z} sisältää tiedon,
 | 
			
		||||
missä solmuissa haku on käynyt,
 | 
			
		||||
ja taulukkoon \texttt{e} lasketaan lyhin
 | 
			
		||||
etäisyys alkusolmusta kaikkiin verkon solmuihin.
 | 
			
		||||
Toteutuksesta tulee seuraavanlainen:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
z[s] = 1; e[x] = 0;
 | 
			
		||||
q.push(x);
 | 
			
		||||
while (!q.empty()) {
 | 
			
		||||
    int s = q.front(); q.pop();
 | 
			
		||||
    // solmun s käsittely tähän     
 | 
			
		||||
    for (auto u : v[s]) {
 | 
			
		||||
        if (z[u]) continue;
 | 
			
		||||
        z[u] = 1; e[u] = e[s]+1;
 | 
			
		||||
        q.push(u);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Sovelluksia}
 | 
			
		||||
 | 
			
		||||
Verkon läpikäynnin avulla
 | 
			
		||||
saa selville monia asioita
 | 
			
		||||
verkon rakenteesta.
 | 
			
		||||
Läpikäynnin voi yleensä aina toteuttaa
 | 
			
		||||
joko syvyyshaulla tai leveyshaulla,
 | 
			
		||||
mutta käytännössä syvyyshaku on parempi valinta,
 | 
			
		||||
koska sen toteutus on helpompi.
 | 
			
		||||
Oletamme seuraavaksi, että käsiteltävänä on
 | 
			
		||||
suuntaamaton verkko.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yhtenäisyyden tarkastaminen}
 | 
			
		||||
 | 
			
		||||
\index{yhtenxisyys@yhtenäisyys}
 | 
			
		||||
 | 
			
		||||
Verkko on yhtenäinen,
 | 
			
		||||
jos mistä tahansa solmuista
 | 
			
		||||
pääsee kaikkiin muihin solmuihin.
 | 
			
		||||
Niinpä verkon yhtenäisyys selviää
 | 
			
		||||
aloittamalla läpikäynti
 | 
			
		||||
jostakin verkon solmusta ja
 | 
			
		||||
tarkastamalla, pääseekö siitä kaikkiin solmuihin.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (2) at (7,5) {$2$};
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (7,3) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (3,3) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmusta $1$ alkava syvyyshaku löytää seuraavat
 | 
			
		||||
solmut:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (2) at (7,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (7,3) {$5$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (4) at (3,3) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (4);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Koska syvyyshaku ei pääse kaikkiin solmuihin,
 | 
			
		||||
tämä tarkoittaa, että verkko ei ole yhtenäinen.
 | 
			
		||||
Vastaavalla tavalla voi etsiä myös verkon komponentit
 | 
			
		||||
käymällä solmut läpi ja aloittamalla uuden syvyyshaun
 | 
			
		||||
aina, jos käsiteltävä solmu ei kuulu vielä mihinkään komponenttiin.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Syklin etsiminen}
 | 
			
		||||
 | 
			
		||||
\index{sykli@sykli}
 | 
			
		||||
 | 
			
		||||
Verkossa on sykli,
 | 
			
		||||
jos jonkin komponentin läpikäynnin
 | 
			
		||||
aikana tulee vastaan solmu,
 | 
			
		||||
jonka naapuri on jo käsitelty
 | 
			
		||||
ja solmuun ei ole saavuttu kyseisen naapurin kautta.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=lightgray] (2) at (7,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (5) at (7,3) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (3,3) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (5);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on sykli, koska tultaessa solmusta 2 solmuun 5
 | 
			
		||||
havaitaan, että naapurina oleva solmu 3 on jo käsitelty.
 | 
			
		||||
Niinpä verkossa täytyy olla solmun 3 kautta
 | 
			
		||||
kulkeva sykli.
 | 
			
		||||
Tällainen sykli on esimerkiksi
 | 
			
		||||
$3 \rightarrow 2 \rightarrow 5 \rightarrow 3$.
 | 
			
		||||
 | 
			
		||||
Syklin olemassaolon voi myös päätellä laskemalla,
 | 
			
		||||
montako solmua ja kaarta komponentissa on.
 | 
			
		||||
Jos komponentissa on $c$ solmua ja siinä ei ole sykliä,
 | 
			
		||||
niin siinä on oltava tarkalleen $c-1$ kaarta.
 | 
			
		||||
Jos kaaria on $c$ tai enemmän, niin komponentissa
 | 
			
		||||
on varmasti sykli.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kaksijakoisuuden tarkastaminen}
 | 
			
		||||
 | 
			
		||||
\index{kaksijakoisuus@kaksijakoisuus}
 | 
			
		||||
 | 
			
		||||
Verkko on kaksijakoinen,
 | 
			
		||||
jos sen solmut voi värittää
 | 
			
		||||
kahdella värillä
 | 
			
		||||
niin, että kahta samanväristä
 | 
			
		||||
solmua ei ole vierekkäin.
 | 
			
		||||
On yllättävän helppoa selvittää
 | 
			
		||||
verkon läpikäynnin avulla,
 | 
			
		||||
onko verkko kaksijakoinen.
 | 
			
		||||
 | 
			
		||||
Ideana on värittää alkusolmu
 | 
			
		||||
siniseksi, sen kaikki naapurit
 | 
			
		||||
punaiseksi, niiden kaikki naapurit
 | 
			
		||||
siniseksi, jne.
 | 
			
		||||
Jos jossain vaiheessa
 | 
			
		||||
ilmenee ristiriita
 | 
			
		||||
(saman solmun tulisi olla sekä
 | 
			
		||||
sininen että punainen),
 | 
			
		||||
verkko ei ole kaksijakoinen.
 | 
			
		||||
Muuten verkko on kaksijakoinen
 | 
			
		||||
ja yksi väritys on muodostunut.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkko
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (2) at (5,5) {$2$};
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (7,4) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (5,3) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (3,3) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (4);
 | 
			
		||||
\path[draw,thick,-] (4) -- (1);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (5) -- (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ei ole kaksijakoinen, koska
 | 
			
		||||
läpikäynti solmusta 1 alkaen
 | 
			
		||||
aiheuttaa seuraavan ristiriidan:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle,fill=red!40] (2) at (5,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=blue!40] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=blue!40] (3) at (7,4) {$3$};
 | 
			
		||||
\node[draw, circle,fill=red!40] (5) at (5,3) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (3,3) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (4);
 | 
			
		||||
\path[draw,thick,-] (4) -- (1);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (5) -- (3);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tässä vaiheessa havaitaan,
 | 
			
		||||
että sekä solmun 2 että solmun 5 väri on punainen,
 | 
			
		||||
vaikka solmut ovat vierekkäin verkossa,
 | 
			
		||||
joten verkko ei ole kaksijakoinen.
 | 
			
		||||
 | 
			
		||||
Tämä algoritmi on luotettava tapa selvittää
 | 
			
		||||
verkon kaksijakoisuus,
 | 
			
		||||
koska kun värejä on vain kaksi,
 | 
			
		||||
ensimmäisen solmun värin valinta
 | 
			
		||||
määrittää kaikkien muiden
 | 
			
		||||
samassa komponentissa olevien
 | 
			
		||||
solmujen värin.
 | 
			
		||||
Ei ole merkitystä,
 | 
			
		||||
kumman värin ensimmäinen
 | 
			
		||||
solmu saa.
 | 
			
		||||
 | 
			
		||||
Huomaa, että yleensä ottaen on vaikeaa
 | 
			
		||||
selvittää, voiko verkon solmut
 | 
			
		||||
värittää $k$ värillä niin,
 | 
			
		||||
ettei missään kohtaa ole vierekkäin
 | 
			
		||||
kahta samanväristä solmua.
 | 
			
		||||
Edes tapaukseen $k=3$ ei tunneta
 | 
			
		||||
mitään tehokasta algoritmia,
 | 
			
		||||
vaan kyseessä on NP-vaikea ongelma.
 | 
			
		||||
% 
 | 
			
		||||
% \section{Labyrintin käsittely}
 | 
			
		||||
% 
 | 
			
		||||
% Labyrintti on ruudukko, joka muodostuu lattia- ja seinäruuduista,
 | 
			
		||||
% ja labyrintissa on sallittua kulkea lattiaruutuja pitkin.
 | 
			
		||||
% Labyrinttia
 | 
			
		||||
% \begin{center}
 | 
			
		||||
% \begin{tikzpicture}[scale=0.7]
 | 
			
		||||
% \fill[color=gray] (0,0) rectangle (8,1);
 | 
			
		||||
% \fill[color=gray] (0,5) rectangle (8,6);
 | 
			
		||||
% \fill[color=gray] (0,0) rectangle (1,6);
 | 
			
		||||
% \fill[color=gray] (7,0) rectangle (8,6);
 | 
			
		||||
% 
 | 
			
		||||
% \fill[color=gray] (2,0) rectangle (3,4);
 | 
			
		||||
% \fill[color=gray] (4,2) rectangle (6,4);
 | 
			
		||||
% 
 | 
			
		||||
% \draw (0,0) grid (8,6);
 | 
			
		||||
% 
 | 
			
		||||
% \node at (1.5,1.5) {$a$};
 | 
			
		||||
% \node at (6.5,3.5) {$b$};
 | 
			
		||||
% \end{tikzpicture}
 | 
			
		||||
% \end{center}
 | 
			
		||||
% vastaa luontevasti verkko
 | 
			
		||||
% \begin{center}
 | 
			
		||||
% \begin{tikzpicture}[scale=0.7]
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (a) at (1,1) {$a$};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (b) at (1,2.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (c) at (1,4) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (d) at (1,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (e) at (2.5,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (f) at (4,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (g) at (5.5,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (h) at (7,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (i) at (8.5,5.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (j) at (8.5,4) {$b$};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (k) at (8.5,2.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (l) at (8.5,1) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (m) at (7,1) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (n) at (5.5,1) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (o) at (4,1) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (p) at (4,2.5) {};
 | 
			
		||||
% \node[draw,circle,minimum size=20pt] (q) at (4,4) {};
 | 
			
		||||
% 
 | 
			
		||||
% \path[draw,thick,-] (a) -- (b);
 | 
			
		||||
% \path[draw,thick,-] (b) -- (c);
 | 
			
		||||
% \path[draw,thick,-] (c) -- (d);
 | 
			
		||||
% \path[draw,thick,-] (d) -- (e);
 | 
			
		||||
% \path[draw,thick,-] (e) -- (f);
 | 
			
		||||
% \path[draw,thick,-] (f) -- (g);
 | 
			
		||||
% \path[draw,thick,-] (g) -- (h);
 | 
			
		||||
% \path[draw,thick,-] (h) -- (i);
 | 
			
		||||
% \path[draw,thick,-] (i) -- (j);
 | 
			
		||||
% \path[draw,thick,-] (j) -- (k);
 | 
			
		||||
% \path[draw,thick,-] (k) -- (l);
 | 
			
		||||
% \path[draw,thick,-] (l) -- (m);
 | 
			
		||||
% \path[draw,thick,-] (m) -- (n);
 | 
			
		||||
% \path[draw,thick,-] (n) -- (o);
 | 
			
		||||
% \path[draw,thick,-] (o) -- (p);
 | 
			
		||||
% \path[draw,thick,-] (p) -- (q);
 | 
			
		||||
% \path[draw,thick,-] (q) -- (f);
 | 
			
		||||
% \end{tikzpicture}
 | 
			
		||||
% \end{center}
 | 
			
		||||
% jossa verkon solmuja ovat labyrintin lattiaruudut
 | 
			
		||||
% ja solmujen välillä on kaari, jos lattiaruudusta
 | 
			
		||||
% toiseen pääsee kulkemaan yhdellä askeleella.
 | 
			
		||||
% Niinpä erilaiset labyrinttiin liittyvät ongelmat
 | 
			
		||||
% palautuvat verkko-ongelmiksi.
 | 
			
		||||
% 
 | 
			
		||||
% Esimerkiksi syvyyshaulla pystyy selvittämään,
 | 
			
		||||
% onko ruudusta $a$ reittiä ruutuun $b$
 | 
			
		||||
% ja leveyshaku kertoo lisäksi,
 | 
			
		||||
% mikä on pienin mahdollinen askelten määrä reitillä.
 | 
			
		||||
% Samoin voi esimerkiksi vaikkapa, kuinka monta
 | 
			
		||||
% toisistaan erillistä huonetta labyrintissa on
 | 
			
		||||
% sekä kuinka monta ruutua huoneissa on.
 | 
			
		||||
% 
 | 
			
		||||
% Labyrintin tapauksessa ei kannata muodostaa erikseen
 | 
			
		||||
% verkkoa, vaan syvyyshaun ja leveyshaun voi toteuttaa
 | 
			
		||||
% suoraan labyrintin ruudukkoon.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,818 @@
 | 
			
		|||
\chapter{Shortest paths}
 | 
			
		||||
 | 
			
		||||
\index{lyhin polku@lyhin polku}
 | 
			
		||||
 | 
			
		||||
Lyhimmän polun etsiminen alkusolmusta loppusolmuun
 | 
			
		||||
on keskeinen verkko-ongelma, joka esiintyy usein
 | 
			
		||||
käytännön tilanteissa.
 | 
			
		||||
Esimerkiksi tieverkostossa
 | 
			
		||||
luonteva ongelma on selvittää,
 | 
			
		||||
mikä on lyhin reitti kahden kaupungin välillä,
 | 
			
		||||
kun tiedossa ovat kaupunkien väliset tiet ja niiden pituudet.
 | 
			
		||||
 | 
			
		||||
Jos verkon kaarilla ei ole painoja,
 | 
			
		||||
polun pituus on sama kuin kaarten
 | 
			
		||||
määrä polulla, jolloin lyhimmän polun
 | 
			
		||||
voi etsiä leveyshaulla.
 | 
			
		||||
Tässä luvussa keskitymme kuitenkin
 | 
			
		||||
tapaukseen, jossa kaarilla on painot.
 | 
			
		||||
Tällöin lyhimpien polkujen etsimiseen
 | 
			
		||||
tarvitaan kehittyneempiä algoritmeja.
 | 
			
		||||
 | 
			
		||||
\section{Bellman–Fordin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Bellman–Fordin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Bellman–Fordin algoritmi} etsii
 | 
			
		||||
lyhimmän polun alkusolmusta
 | 
			
		||||
kaikkiin muihin verkon solmuihin.
 | 
			
		||||
Algoritmi toimii kaikenlaisissa verkoissa,
 | 
			
		||||
kunhan verkossa ei ole sykliä,
 | 
			
		||||
jonka kaarten yhteispaino on negatiivinen.
 | 
			
		||||
Jos verkossa on negatiivinen sykli,
 | 
			
		||||
algoritmi huomaa tilanteen.
 | 
			
		||||
 | 
			
		||||
Algoritmi pitää yllä etäisyysarvioita
 | 
			
		||||
alkusolmusta kaikkiin muihin verkon solmuihin.
 | 
			
		||||
Alussa alkusolmun etäisyysarvio on 0
 | 
			
		||||
ja muiden solmujen etäisyys\-arvio on ääretön.
 | 
			
		||||
Algoritmi parantaa arvioita
 | 
			
		||||
etsimällä verkosta kaaria,
 | 
			
		||||
jotka lyhentävät polkuja,
 | 
			
		||||
kunnes mitään arviota ei voi enää parantaa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan Bellman–Fordin
 | 
			
		||||
algoritmin toimintaa seuraavassa verkossa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {1};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {2};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {3};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {4};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
\node[color=red] at (1,3+0.55) {$0$};
 | 
			
		||||
\node[color=red] at (4,3+0.55) {$\infty$};
 | 
			
		||||
\node[color=red] at (1,1-0.55) {$\infty$};
 | 
			
		||||
\node[color=red] at (4,1-0.55) {$\infty$};
 | 
			
		||||
\node[color=red] at (6,2-0.55) {$\infty$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Verkon jokaiseen solmun viereen on merkitty etäisyysarvio.
 | 
			
		||||
Alussa alkusolmun etäisyysarvio on 0
 | 
			
		||||
ja muiden solmujen etäisyysarvio on
 | 
			
		||||
ääretön.
 | 
			
		||||
 | 
			
		||||
Algoritmi etsii verkosta kaaria,
 | 
			
		||||
jotka parantavat etäisyysarvioita.
 | 
			
		||||
Aluksi kaikki solmusta 0 lähtevät kaaret
 | 
			
		||||
parantavat arvioita:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {1};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {2};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {3};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {4};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
\node[color=red] at (1,3+0.55) {$0$};
 | 
			
		||||
\node[color=red] at (4,3+0.55) {$2$};
 | 
			
		||||
\node[color=red] at (1,1-0.55) {$3$};
 | 
			
		||||
\node[color=red] at (4,1-0.55) {$7$};
 | 
			
		||||
\node[color=red] at (6,2-0.55) {$\infty$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Sitten kaaret $2 \rightarrow 5$ ja $3 \rightarrow 4$
 | 
			
		||||
parantavat arvioita:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {1};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {2};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {3};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {4};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
\node[color=red] at (1,3+0.55) {$0$};
 | 
			
		||||
\node[color=red] at (4,3+0.55) {$2$};
 | 
			
		||||
\node[color=red] at (1,1-0.55) {$3$};
 | 
			
		||||
\node[color=red] at (4,1-0.55) {$1$};
 | 
			
		||||
\node[color=red] at (6,2-0.55) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Lopuksi tulee vielä yksi parannus:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {1};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {2};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {3};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {4};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
\node[color=red] at (1,3+0.55) {$0$};
 | 
			
		||||
\node[color=red] at (4,3+0.55) {$2$};
 | 
			
		||||
\node[color=red] at (1,1-0.55) {$3$};
 | 
			
		||||
\node[color=red] at (4,1-0.55) {$1$};
 | 
			
		||||
\node[color=red] at (6,2-0.55) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen mikään kaari
 | 
			
		||||
ei paranna etäisyysarvioita.
 | 
			
		||||
Tämä tarkoittaa, että etäisyydet
 | 
			
		||||
ovat lopulliset, eli joka solmussa
 | 
			
		||||
on nyt pienin etäisyys alkusolmusta
 | 
			
		||||
kyseiseen solmuun.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi pienin etäisyys 3
 | 
			
		||||
solmusta 1 solmuun 5 toteutuu käyttämällä
 | 
			
		||||
seuraavaa polkua:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {1};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {2};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {3};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {4};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
\node[color=red] at (1,3+0.55) {$0$};
 | 
			
		||||
\node[color=red] at (4,3+0.55) {$2$};
 | 
			
		||||
\node[color=red] at (1,1-0.55) {$3$};
 | 
			
		||||
\node[color=red] at (4,1-0.55) {$1$};
 | 
			
		||||
\node[color=red] at (6,2-0.55) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:3] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-2$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (4);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Seuraava Bellman–Fordin algoritmin toteutus
 | 
			
		||||
etsii lyhimmät polut solmusta $x$
 | 
			
		||||
kaikkiin muihin verkon solmuihin.
 | 
			
		||||
Koodi olettaa, että verkko on tallennettuna
 | 
			
		||||
vieruslistoina taulukossa
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<pair<int,int>> v[N];
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
niin, että parissa on ensin kaaren kohdesolmu
 | 
			
		||||
ja sitten kaaren paino.
 | 
			
		||||
 | 
			
		||||
Algoritmi muodostuu $n-1$ kierroksesta,
 | 
			
		||||
joista jokaisella algoritmi käy läpi kaikki
 | 
			
		||||
verkon kaaret ja koettaa parantaa etäisyysarvioita.
 | 
			
		||||
Algoritmi laskee taulukkoon \texttt{e}
 | 
			
		||||
etäisyyden solmusta $x$ kuhunkin verkon solmuun.
 | 
			
		||||
Koodissa oleva alkuarvo $10^9$ kuvastaa
 | 
			
		||||
ääretöntä.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) e[i] = 1e9;
 | 
			
		||||
e[x] = 0;
 | 
			
		||||
for (int i = 1; i <= n-1; i++) {
 | 
			
		||||
    for (int a = 1; a <= n; a++) {
 | 
			
		||||
        for (auto b : v[a]) {
 | 
			
		||||
            e[b.first] = min(e[b.first],e[a]+b.second);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on $O(nm)$,
 | 
			
		||||
koska se muodostuu $n-1$ kierroksesta ja
 | 
			
		||||
käy läpi jokaisen kierroksen aikana kaikki $m$ kaarta.
 | 
			
		||||
Jos verkossa ei ole negatiivista sykliä,
 | 
			
		||||
kaikki etäisyysarviot ovat lopulliset $n-1$
 | 
			
		||||
kierroksen jälkeen, koska jokaisessa lyhimmässä
 | 
			
		||||
polussa on enintään $n-1$ kaarta.
 | 
			
		||||
 | 
			
		||||
Käytännössä kaikki lopulliset etäisyysarviot
 | 
			
		||||
saadaan usein laskettua selvästi alle $n-1$ kierroksessa,
 | 
			
		||||
joten mahdollinen tehostus algoritmiin on lopettaa heti,
 | 
			
		||||
kun mikään etäisyysarvio ei parane kierroksen aikana.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Negatiivinen sykli}
 | 
			
		||||
 | 
			
		||||
\index{negatiivinen sykli@negatiivinen sykli}
 | 
			
		||||
 | 
			
		||||
Bellman–Fordin algoritmin avulla voi myös tarkastaa,
 | 
			
		||||
onko verkossa sykliä,
 | 
			
		||||
jonka pituus on negatiivinen.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (2,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:$3$] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:$1$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:$5$] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-7$] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=right:$2$] {} (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\noindent
 | 
			
		||||
on negatiivinen sykli $2 \rightarrow 3 \rightarrow 4 \rightarrow 2$,
 | 
			
		||||
jonka pituus on $-4$.
 | 
			
		||||
 | 
			
		||||
Jos verkossa on negatiivinen sykli,
 | 
			
		||||
sen kautta kulkevaa polkua voi lyhentää äärettömästi
 | 
			
		||||
toistamalla negatiivista sykliä uudestaan ja uudestaan,
 | 
			
		||||
minkä vuoksi lyhimmän polun käsite ei ole mielekäs.
 | 
			
		||||
 | 
			
		||||
Negatiivisen syklin voi tunnistaa
 | 
			
		||||
Bellman–Fordin algoritmilla
 | 
			
		||||
suorittamalla algoritmia $n$ kierrosta.
 | 
			
		||||
Jos viimeinen kierros parantaa jotain
 | 
			
		||||
etäisyysarviota, verkossa on negatiivinen sykli.
 | 
			
		||||
Huomaa, että algoritmi etsii negatiivista sykliä
 | 
			
		||||
koko verkon alueelta alkusolmusta välittämättä.
 | 
			
		||||
 | 
			
		||||
\subsubsection{SPFA-algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{SPFA-algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{SPFA-algoritmi} (''Shortest Path Faster Algorithm'')
 | 
			
		||||
on Bellman–Fordin algoritmin muunnelma,
 | 
			
		||||
joka on usein alkuperäistä algoritmia tehokkaampi.
 | 
			
		||||
Se ei tutki joka kierroksella koko verkkoa läpi
 | 
			
		||||
parantaakseen etäisyysarvioita, vaan valitsee
 | 
			
		||||
tutkittavat kaaret älykkäämmin.
 | 
			
		||||
 | 
			
		||||
Algoritmi pitää yllä jonoa solmuista,
 | 
			
		||||
joiden kautta saattaa pystyä parantamaan etäisyysarvioita.
 | 
			
		||||
Algoritmi lisää jonoon aluksi alkusolmun $x$
 | 
			
		||||
ja valitsee aina seuraavan
 | 
			
		||||
tutkittavan solmun $a$ jonon alusta.
 | 
			
		||||
Aina kun kaari $a \rightarrow b$ parantaa
 | 
			
		||||
etäisyysarviota, algoritmi lisää jonoon solmun $b$.
 | 
			
		||||
 | 
			
		||||
Seuraavassa toteutuksessa jonona on \texttt{queue}-rakenne
 | 
			
		||||
\texttt{q}. Lisäksi taulukko \texttt{z} kertoo,
 | 
			
		||||
onko solmu valmiina jonossa, jolloin algoritmi ei
 | 
			
		||||
lisää solmua jonoon uudestaan.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) e[i] = 1e9;
 | 
			
		||||
e[x] = 0;
 | 
			
		||||
q.push(x);
 | 
			
		||||
while (!q.empty()) {
 | 
			
		||||
    int a = q.front(); q.pop();
 | 
			
		||||
    z[a] = 0;
 | 
			
		||||
    for (auto b : v[a]) {
 | 
			
		||||
        if (e[a]+b.second < e[b.first]) {
 | 
			
		||||
            e[b.first] = e[a]+b.second;
 | 
			
		||||
            if (!z[b]) {q.push(b); z[b] = 1;}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
SPFA-algoritmin tehokkuus riippuu verkon rakenteesta:
 | 
			
		||||
algoritmi on keskimäärin hyvin tehokas, mutta
 | 
			
		||||
sen pahimman tapauksen aikavaativuus on edelleen
 | 
			
		||||
$O(nm)$ ja on mahdollista
 | 
			
		||||
laatia syötteitä, jotka saavat algoritmin yhtä hitaaksi
 | 
			
		||||
kuin tavallisen Bellman–Fordin algoritmin.
 | 
			
		||||
 | 
			
		||||
\section{Dijkstran algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Dijkstran algoritmi@Dijkstran algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Dijkstran algoritmi} etsii Bellman–Fordin
 | 
			
		||||
algoritmin tavoin lyhimmät polut
 | 
			
		||||
alkusolmusta kaikkiin muihin solmuihin.
 | 
			
		||||
Dijkstran algoritmi on tehokkaampi kuin
 | 
			
		||||
Bellman–Fordin algoritmi,
 | 
			
		||||
minkä ansiosta se soveltuu suurten
 | 
			
		||||
verkkojen käsittelyyn.
 | 
			
		||||
Algoritmi vaatii kuitenkin,
 | 
			
		||||
ettei verkossa ole negatiivisia kaaria.
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmi vastaa
 | 
			
		||||
Bellman–Fordin algoritmia siinä,
 | 
			
		||||
että se pitää
 | 
			
		||||
yllä etäisyysarvioita solmuihin
 | 
			
		||||
ja parantaa niitä algoritmin aikana.
 | 
			
		||||
Algoritmin tehokkuus perustuu
 | 
			
		||||
siihen, että sen riittää käydä läpi
 | 
			
		||||
verkon kaaret vain kerran
 | 
			
		||||
hyödyntäen tietoa,
 | 
			
		||||
ettei verkossa ole negatiivisia kaaria.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan Dijkstran algoritmin toimintaa
 | 
			
		||||
seuraavassa verkossa, kun alkusolmuna
 | 
			
		||||
on solmu 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {3};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {4};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {2};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {1};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,3+0.6) {$\infty$};
 | 
			
		||||
\node[color=red] at (4,3+0.6) {$\infty$};
 | 
			
		||||
\node[color=red] at (1,1-0.6) {$\infty$};
 | 
			
		||||
\node[color=red] at (4,1-0.6) {$0$};
 | 
			
		||||
\node[color=red] at (6,2-0.6) {$\infty$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Bellman–Fordin algoritmin tavoin
 | 
			
		||||
alkusolmun etäisyysarvio on 0
 | 
			
		||||
ja kaikissa muissa solmuissa etäisyysarvio
 | 
			
		||||
on aluksi ääretön.
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmi
 | 
			
		||||
ottaa joka askeleella käsittelyyn
 | 
			
		||||
sellaisen solmun,
 | 
			
		||||
jota ei ole vielä käsitelty
 | 
			
		||||
ja jonka etäisyysarvio on
 | 
			
		||||
mahdollisimman pieni.
 | 
			
		||||
Alussa tällainen solmu on solmu 1,
 | 
			
		||||
jonka etäisyysarvio on 0.
 | 
			
		||||
 | 
			
		||||
Kun solmu tulee käsittelyyn,
 | 
			
		||||
algoritmi käy läpi kaikki
 | 
			
		||||
siitä lähtevät kaaret ja
 | 
			
		||||
parantaa etäisyysarvioita
 | 
			
		||||
niiden avulla:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {3};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {4};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {2};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (4) at (4,1) {1};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {5};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,3+0.6) {$\infty$};
 | 
			
		||||
\node[color=red] at (4,3+0.6) {$9$};
 | 
			
		||||
\node[color=red] at (1,1-0.6) {$5$};
 | 
			
		||||
\node[color=red] at (4,1-0.6) {$0$};
 | 
			
		||||
\node[color=red] at (6,2-0.6) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Solmun 1 käsittely paransi etäisyysarvioita
 | 
			
		||||
solmuihin 2, 4 ja 5,
 | 
			
		||||
joiden uudet etäisyydet ovat nyt 5, 9 ja 1.
 | 
			
		||||
 | 
			
		||||
Seuraavaksi käsittelyyn tulee solmu 5,
 | 
			
		||||
jonka etäisyys on 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {3};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {4};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {2};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (4) at (4,1) {1};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (5) at (6,2) {5};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,3+0.6) {$\infty$};
 | 
			
		||||
\node[color=red] at (4,3+0.6) {$3$};
 | 
			
		||||
\node[color=red] at (1,1-0.6) {$5$};
 | 
			
		||||
\node[color=red] at (4,1-0.6) {$0$};
 | 
			
		||||
\node[color=red] at (6,2-0.6) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen vuorossa on solmu 4:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {3};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (2) at (4,3) {4};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {2};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (4) at (4,1) {1};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (5) at (6,2) {5};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,3+0.6) {$9$};
 | 
			
		||||
\node[color=red] at (4,3+0.6) {$3$};
 | 
			
		||||
\node[color=red] at (1,1-0.6) {$5$};
 | 
			
		||||
\node[color=red] at (4,1-0.6) {$0$};
 | 
			
		||||
\node[color=red] at (6,2-0.6) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmissa on hienoutena,
 | 
			
		||||
että aina kun solmu tulee käsittelyyn,
 | 
			
		||||
sen etäisyysarvio on siitä lähtien lopullinen.
 | 
			
		||||
Esimerkiksi tässä vaiheessa
 | 
			
		||||
etäisyydet 0, 1 ja 3 ovat lopulliset
 | 
			
		||||
etäisyydet solmuihin 1, 5 ja 4.
 | 
			
		||||
 | 
			
		||||
Algoritmi käsittelee vastaavasti
 | 
			
		||||
vielä kaksi viimeistä solmua,
 | 
			
		||||
minkä jälkeen algoritmin päätteeksi
 | 
			
		||||
etäisyydet ovat:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle, fill=lightgray] (1) at (1,3) {3};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (2) at (4,3) {4};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (3) at (1,1) {2};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (4) at (4,1) {1};
 | 
			
		||||
\node[draw, circle, fill=lightgray] (5) at (6,2) {5};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,3+0.6) {$7$};
 | 
			
		||||
\node[color=red] at (4,3+0.6) {$3$};
 | 
			
		||||
\node[color=red] at (1,1-0.6) {$5$};
 | 
			
		||||
\node[color=red] at (4,1-0.6) {$0$};
 | 
			
		||||
\node[color=red] at (6,2-0.6) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:6] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Negatiiviset kaaret}
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmin tehokkuus perustuu siihen,
 | 
			
		||||
että verkossa ei ole negatiivisia kaaria.
 | 
			
		||||
Jos verkossa on negatiivinen kaari,
 | 
			
		||||
algoritmi ei välttämättä toimi oikein.
 | 
			
		||||
Tarkastellaan esimerkkinä seuraavaa verkkoa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (2,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:2] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:3] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:6] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:$-5$] {} (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\noindent
 | 
			
		||||
Lyhin polku solmusta 1 solmuun 4 on
 | 
			
		||||
$1 \rightarrow 3 \rightarrow 4$,
 | 
			
		||||
ja sen pituus on 1.
 | 
			
		||||
Dijkstran algoritmi löytää
 | 
			
		||||
kuitenkin keveimpiä kaaria seuraten
 | 
			
		||||
polun $1 \rightarrow 2 \rightarrow 4$.
 | 
			
		||||
Algoritmi ei pysty ottamaan huomioon,
 | 
			
		||||
että alemmalla polulla kaaren paino $-5$
 | 
			
		||||
kumoaa aiemman suuren kaaren painon $6$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Seuraava Dijkstran algoritmin toteutus laskee
 | 
			
		||||
pienimmän etäisyyden solmusta $x$ kaikkiin muihin solmuihin.
 | 
			
		||||
Verkko on tallennettu taulukkoon \texttt{v}
 | 
			
		||||
vieruslistoina, joissa on pareina kohdesolmu
 | 
			
		||||
ja kaaren pituus.
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmin tehokas toteutus vaatii,
 | 
			
		||||
että verkosta pystyy löytämään
 | 
			
		||||
nopeasti vielä käsittelemättömän solmun,
 | 
			
		||||
jonka etäisyysarvio on pienin.
 | 
			
		||||
Sopiva tietorakenne tähän on prioriteettijono,
 | 
			
		||||
jossa solmut ovat järjestyksessä etäisyys\-arvioiden mukaan.
 | 
			
		||||
Prioriteettijonon avulla
 | 
			
		||||
seuraavaksi käsiteltävän solmun saa selville logaritmisessa ajassa.
 | 
			
		||||
 | 
			
		||||
Seuraavassa toteutuksessa prioriteettijono sisältää
 | 
			
		||||
pareja, joiden ensimmäinen kenttä on etäisyysarvio
 | 
			
		||||
ja toinen kenttä on solmun tunniste:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
priority_queue<pair<int,int>> q;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
Pieni hankaluus on,
 | 
			
		||||
että Dijkstran algoritmissa täytyy saada selville
 | 
			
		||||
\emph{pienimmän} etäisyysarvion solmu,
 | 
			
		||||
kun taas C++:n prioriteettijono antaa oletuksena
 | 
			
		||||
\emph{suurimman} alkion.
 | 
			
		||||
Helppo ratkaisu on tallentaa etäisyysarviot
 | 
			
		||||
\emph{negatiivisina}, jolloin C++:n prioriteettijonoa
 | 
			
		||||
voi käyttää suoraan.
 | 
			
		||||
 | 
			
		||||
Koodi merkitsee taulukkoon \texttt{z},
 | 
			
		||||
onko solmu käsitelty,
 | 
			
		||||
ja pitää yllä etäisyysarvioita taulukossa \texttt{e}.
 | 
			
		||||
Alussa alkusolmun etäisyysarvio on 0
 | 
			
		||||
ja jokaisen muun solmun etäisyysarviona
 | 
			
		||||
on ääretöntä vastaava $10^9$.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) e[i] = 1e9;
 | 
			
		||||
e[x] = 0;
 | 
			
		||||
q.push({0,x});
 | 
			
		||||
while (!q.empty()) {
 | 
			
		||||
    int a = q.top().second; q.pop();
 | 
			
		||||
    if (z[a]) continue;
 | 
			
		||||
    z[a] = 1;
 | 
			
		||||
    for (auto b : v[a]) {
 | 
			
		||||
        if (e[a]+b.second < e[b]) {
 | 
			
		||||
            e[b] = e[a]+b.second;
 | 
			
		||||
            q.push({-e[b],b});
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Yllä olevan toteutuksen aikavaativuus on $O(n+m \log m)$,
 | 
			
		||||
koska algoritmi käy läpi kaikki verkon solmut
 | 
			
		||||
ja lisää jokaista kaarta kohden korkeintaan
 | 
			
		||||
yhden etäisyysarvion prioriteettijonoon.
 | 
			
		||||
 | 
			
		||||
\section{Floyd–Warshallin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Floyd–Warshallin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Floyd–Warshallin algoritmi}
 | 
			
		||||
on toisenlainen lähestymistapa
 | 
			
		||||
lyhimpien polkujen etsintään.
 | 
			
		||||
Toisin kuin muut tämän luvun algoritmit,
 | 
			
		||||
se  etsii yhdellä kertaa lyhimmät polut kaikkien
 | 
			
		||||
verkon solmujen välillä.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Algoritmi ylläpitää kaksiulotteista
 | 
			
		||||
taulukkoa etäisyyksistä solmujen
 | 
			
		||||
välillä.
 | 
			
		||||
Ensin taulukkoon on merkitty
 | 
			
		||||
etäisyydet käyttäen vain solmujen
 | 
			
		||||
välisiä kaaria.
 | 
			
		||||
Tämän jälkeen algoritmi
 | 
			
		||||
päivittää etäisyyksiä,
 | 
			
		||||
kun verkon solmut saavat yksi kerrallaan
 | 
			
		||||
toimia välisolmuina poluilla.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan Floyd–Warshallin
 | 
			
		||||
algoritmin toimintaa seuraavassa verkossa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$3$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$1$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmi merkitsee aluksi taulukkoon
 | 
			
		||||
etäisyyden 0 jokaisesta solmusta itseensä
 | 
			
		||||
sekä etäisyyden $x$, jos solmuparin välillä
 | 
			
		||||
on kaari, jonka pituus on $x$.
 | 
			
		||||
Muiden solmuparien etäisyys on aluksi ääretön.
 | 
			
		||||
 | 
			
		||||
Tässä verkossa taulukosta tulee:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrr}
 | 
			
		||||
 & 1 & 2 & 3 & 4 & 5 \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 & 5 & $\infty$ & 9 & 1 \\
 | 
			
		||||
2 & 5 & 0 & 2 & $\infty$ & $\infty$ \\
 | 
			
		||||
3 & $\infty$ & 2 & 0 & 7 & $\infty$ \\
 | 
			
		||||
4 & 9 & $\infty$ & 7 & 0 & 2 \\
 | 
			
		||||
5 & 1 & $\infty$ & $\infty$ & 2 & 0 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\vspace{10pt}
 | 
			
		||||
Algoritmin toiminta muodostuu peräkkäisistä kierroksista.
 | 
			
		||||
Jokaisella kierroksella valitaan yksi uusi solmu,
 | 
			
		||||
joka saa toimia välisolmuna poluilla,
 | 
			
		||||
ja algoritmi parantaa taulukon
 | 
			
		||||
etäisyyksiä muodostaen polkuja tämän solmun avulla.
 | 
			
		||||
 | 
			
		||||
Ensimmäisellä kierroksella solmu 1 on välisolmu.
 | 
			
		||||
Tämän ansiosta solmujen 2 ja 4 välille muodostuu
 | 
			
		||||
polku, jonka pituus on 14,
 | 
			
		||||
koska solmu 1 yhdistää ne toisiinsa.
 | 
			
		||||
Vastaavasti solmut 2 ja 5 yhdistyvät polulla,
 | 
			
		||||
jonka pituus on 6.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrr}
 | 
			
		||||
 & 1 & 2 & 3 & 4 & 5 \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 & 5 & $\infty$ & 9 & 1 \\
 | 
			
		||||
2 & 5 & 0 & 2 & \textbf{14} & \textbf{6} \\
 | 
			
		||||
3 & $\infty$ & 2 & 0 & 7 & $\infty$ \\
 | 
			
		||||
4 & 9 & \textbf{14} & 7 & 0 & 2 \\
 | 
			
		||||
5 & 1 & \textbf{6} & $\infty$ & 2 & 0 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\vspace{10pt}
 | 
			
		||||
 | 
			
		||||
Toisella kierroksella solmu 2 saa toimia välisolmuna.
 | 
			
		||||
Tämä mahdollistaa uudet polut solmuparien 1 ja 3
 | 
			
		||||
sekä 3 ja 5 välille:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrr}
 | 
			
		||||
 & 1 & 2 & 3 & 4 & 5 \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 & 5 & \textbf{7} & 9 & 1 \\
 | 
			
		||||
2 & 5 & 0 & 2 & 14 & 6 \\
 | 
			
		||||
3 & \textbf{7} & 2 & 0 & 7 & \textbf{8} \\
 | 
			
		||||
4 & 9 & 14 & 7 & 0 & 2 \\
 | 
			
		||||
5 & 1 & 6 & \textbf{8} & 2 & 0 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\vspace{10pt}
 | 
			
		||||
 | 
			
		||||
Kolmannella kierroksella solmu 3 saa toimia välisolmuna,
 | 
			
		||||
jolloin syntyy uusi polku solmuparin 2 ja 4 välille:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrr}
 | 
			
		||||
 & 1 & 2 & 3 & 4 & 5 \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 & 5 & 7 & 9 & 1 \\
 | 
			
		||||
2 & 5 & 0 & 2 & \textbf{9} & 6 \\
 | 
			
		||||
3 & 7 & 2 & 0 & 7 & 8 \\
 | 
			
		||||
4 & 9 & \textbf{9} & 7 & 0 & 2 \\
 | 
			
		||||
5 & 1 & 6 & 8 & 2 & 0 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\vspace{10pt}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Algoritmin toiminta jatkuu samalla tavalla
 | 
			
		||||
niin, että kukin solmu tulee vuorollaan
 | 
			
		||||
välisolmuksi.
 | 
			
		||||
Algoritmin päätteeksi taulukko sisältää
 | 
			
		||||
lyhimmän etäisyyden minkä tahansa
 | 
			
		||||
solmuparin välillä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrr}
 | 
			
		||||
 & 1 & 2 & 3 & 4 & 5 \\
 | 
			
		||||
\hline
 | 
			
		||||
1 & 0 & 5 & 7 & 3 & 1 \\
 | 
			
		||||
2 & 5 & 0 & 2 & 9 & 6 \\
 | 
			
		||||
3 & 7 & 2 & 0 & 7 & 8 \\
 | 
			
		||||
4 & 3 & 9 & 7 & 0 & 2 \\
 | 
			
		||||
5 & 1 & 6 & 8 & 2 & 0 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi taulukosta selviää, että lyhin polku
 | 
			
		||||
solmusta 2 solmuun 4 on pituudeltaan 8.
 | 
			
		||||
Tämä vastaa seuraavaa polkua:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$3$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$1$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:7] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:2] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:5] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Floyd–Warshallin algoritmin etuna on,
 | 
			
		||||
että se on helppoa toteuttaa.
 | 
			
		||||
Seuraava toteutus muodostaa etäisyysmatriisin
 | 
			
		||||
\texttt{d}, jossa $\texttt{d}[a][b]$
 | 
			
		||||
on pienin etäisyys polulla solmusta $a$ solmuun $b$.
 | 
			
		||||
Aluksi algoritmi alustaa matriisin \texttt{d}
 | 
			
		||||
verkon vierusmatriisin \texttt{v} perusteella
 | 
			
		||||
(arvo $10^9$ kuvastaa ääretöntä):
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) {
 | 
			
		||||
    for (int j = 1; j <= n; j++) {
 | 
			
		||||
        if (i == j) d[i][j] = 0;
 | 
			
		||||
        else if (v[i][j]) d[i][j] = v[i][j];
 | 
			
		||||
        else d[i][j] = 1e9;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen lyhimmät polut löytyvät seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int k = 1; k <= n; k++) {
 | 
			
		||||
    for (int i = 1; i <= n; i++) {
 | 
			
		||||
        for (int j = 1; j <= n; j++) {
 | 
			
		||||
            d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on
 | 
			
		||||
$O(n^3)$, koska siinä on kolme sisäkkäistä
 | 
			
		||||
silmukkaa,
 | 
			
		||||
jotka käyvät läpi verkon solmut.
 | 
			
		||||
 | 
			
		||||
Koska Floyd–Warshallin
 | 
			
		||||
algoritmin toteutus on yksinkertainen,
 | 
			
		||||
algoritmi voi olla hyvä valinta jopa silloin,
 | 
			
		||||
kun haettavana on yksittäinen
 | 
			
		||||
lyhin polku verkossa.
 | 
			
		||||
Tämä on kuitenkin mahdollista vain silloin,
 | 
			
		||||
kun verkko on niin pieni,
 | 
			
		||||
että kuutiollinen aikavaativuus on riittävä.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,555 @@
 | 
			
		|||
\chapter{Puiden käsittely}
 | 
			
		||||
 | 
			
		||||
\index{puu@puu}
 | 
			
		||||
 | 
			
		||||
\key{Puu} on yhtenäinen, syklitön verkko,
 | 
			
		||||
jossa on $n$ solmua ja $n-1$ kaarta.
 | 
			
		||||
Jos puusta poistaa yhden kaaren, se ei ole enää yhtenäinen,
 | 
			
		||||
ja jos puuhun lisää yhden kaaren, se ei ole enää syklitön.
 | 
			
		||||
Puussa pätee myös aina, että
 | 
			
		||||
jokaisen kahden puun solmun välillä on yksikäsitteinen polku.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa puussa on 7 solmua ja 6 kaarta:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (2,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$7$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,1) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{lehti@lehti}
 | 
			
		||||
 | 
			
		||||
Puun \key{lehdet} ovat solmut,
 | 
			
		||||
joiden aste on 1 eli joista lähtee vain yksi kaari.
 | 
			
		||||
Esimerkiksi yllä olevan puun lehdet ovat
 | 
			
		||||
solmut 3, 5, 6 ja 7.
 | 
			
		||||
 | 
			
		||||
\index{juuri@juuri}
 | 
			
		||||
\index{juurellinen puu@juurellinen puu}
 | 
			
		||||
 | 
			
		||||
Jos puu on \key{juurellinen}, yksi solmuista
 | 
			
		||||
on puun \key{juuri},
 | 
			
		||||
jonka alapuolelle muut solmut asettuvat.
 | 
			
		||||
Esimerkiksi jos yllä olevassa puussa valitaan
 | 
			
		||||
juureksi solmu 1, solmut asettuvat seuraavaan järjestykseen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{lapsi@lapsi}
 | 
			
		||||
\index{vanhempi@vanhempi}
 | 
			
		||||
 | 
			
		||||
Juurellisessa puussa solmun \key{lapset}
 | 
			
		||||
ovat sen alemman tason naapurit
 | 
			
		||||
ja solmun \key{vanhempi}
 | 
			
		||||
on sen ylemmän tason naapuri.
 | 
			
		||||
Jokaisella solmulla on tasan yksi vanhempi,
 | 
			
		||||
paitsi juurella ei ole vanhempaa.
 | 
			
		||||
Esimerkiksi yllä olevassa puussa solmun 4
 | 
			
		||||
lapset ovat solmut 3 ja 7 ja solmun 4 vanhempi on solmu 1.
 | 
			
		||||
 | 
			
		||||
\index{alipuu@alipuu}
 | 
			
		||||
 | 
			
		||||
Juurellisen puun rakenne on \emph{rekursiivinen}:
 | 
			
		||||
jokaisesta puun solmusta alkaa \key{alipuu},
 | 
			
		||||
jonka juurena on solmu itse ja johon kuuluvat
 | 
			
		||||
kaikki solmut, joihin solmusta pääsee kulkemalla alaspäin puussa.
 | 
			
		||||
Esimerkiksi solmun 4 alipuussa
 | 
			
		||||
ovat solmut 4, 3 ja 7.
 | 
			
		||||
 | 
			
		||||
\section{Puun läpikäynti}
 | 
			
		||||
 | 
			
		||||
Puun läpikäyntiin voi käyttää syvyyshakua ja
 | 
			
		||||
leveyshakua samaan
 | 
			
		||||
tapaan kuin yleisen verkon läpikäyntiin.
 | 
			
		||||
Erona on kuitenkin, että puussa ei ole silmukoita,
 | 
			
		||||
minkä ansiosta ei tarvitse huolehtia siitä,
 | 
			
		||||
että läpikäynti päätyisi tiettyyn
 | 
			
		||||
solmuun monesta eri suunnasta.
 | 
			
		||||
 | 
			
		||||
Tavallisin menetelmä puun läpikäyntiin on
 | 
			
		||||
valita tietty solmu juureksi ja aloittaa
 | 
			
		||||
siitä syvyyshaku.
 | 
			
		||||
Seuraava rekursiivinen funktio toteuttaa sen:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku(int s, int e) {
 | 
			
		||||
    // solmun s käsittely tähän
 | 
			
		||||
    for (auto u : v[s]) {
 | 
			
		||||
        if (u != e) haku(u, s);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktion parametrit ovat käsiteltävä solmu $s$
 | 
			
		||||
sekä edellinen käsitelty solmu $e$.
 | 
			
		||||
Parametrin $e$ ideana on varmistaa, että
 | 
			
		||||
läpikäynti etenee vain alaspäin puussa
 | 
			
		||||
sellaisiin solmuihin, joita ei ole vielä käsitelty.
 | 
			
		||||
 | 
			
		||||
Seuraava kutsu käy läpi puun aloittaen juuresta $x$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
haku(x, 0);
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Ensimmäisessä kutsussa $e=0$, koska läpikäynti
 | 
			
		||||
saa edetä juuresta kaikkiin suuntiin alaspäin.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Dynaaminen ohjelmointi}
 | 
			
		||||
 | 
			
		||||
Puun läpikäyntiin voi myös yhdistää dynaamista
 | 
			
		||||
ohjelmointia ja laskea sen avulla jotakin tietoa puusta.
 | 
			
		||||
Dynaamisen ohjelmoinnin avulla voi esimerkiksi
 | 
			
		||||
laskea ajassa $O(n)$ jokaiselle solmulle,
 | 
			
		||||
montako solmua sen alipuussa
 | 
			
		||||
on tai kuinka pitkä on pisin solmusta
 | 
			
		||||
alaspäin jatkuva polku puussa.
 | 
			
		||||
 | 
			
		||||
Lasketaan esimerkiksi jokaiselle solmulle $s$
 | 
			
		||||
sen alipuun solmujen määrä $\texttt{c}[s]$.
 | 
			
		||||
Solmun alipuuhun kuuluvat solmu itse
 | 
			
		||||
sekä kaikki sen lasten alipuut.
 | 
			
		||||
Niinpä solmun alipuun solmujen määrä on
 | 
			
		||||
yhden suurempi kuin summa lasten
 | 
			
		||||
alipuiden solmujen määristä.
 | 
			
		||||
Laskennan voi toteuttaa seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void haku(int s, int e) {
 | 
			
		||||
    c[s] = 1;
 | 
			
		||||
    for (auto u : v[s]) {
 | 
			
		||||
        if (u == e) continue;
 | 
			
		||||
        haku(u, s);
 | 
			
		||||
        c[s] += c[u];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Läpimitta}
 | 
			
		||||
 | 
			
		||||
\index{lzpimitta@läpimitta}
 | 
			
		||||
 | 
			
		||||
Puun \key{läpimitta} on pisin polku
 | 
			
		||||
kahden puussa olevan solmun välillä.
 | 
			
		||||
Esimerkiksi puussa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (2,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$7$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,1) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
läpimitta on 4, jota vastaa kaksi polkua:
 | 
			
		||||
solmujen 3 ja 6 välinen polku sekä
 | 
			
		||||
solmujen 7 ja 6 välinen polku.
 | 
			
		||||
 | 
			
		||||
Käymme seuraavaksi läpi kaksi tehokasta
 | 
			
		||||
algoritmia puun läpimitan laskeminen.
 | 
			
		||||
Molemmat algoritmit laskevat läpimitan ajassa
 | 
			
		||||
$O(n)$.
 | 
			
		||||
Ensimmäinen algoritmi perustuu dynaamiseen
 | 
			
		||||
ohjelmointiin, ja toinen algoritmi
 | 
			
		||||
laskee läpimitan kahden syvyyshaun avulla.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Algoritmi 1}
 | 
			
		||||
 | 
			
		||||
Algoritmin alussa
 | 
			
		||||
yksi solmuista valitaan puun juureksi.
 | 
			
		||||
Tämän jälkeen algoritmi laskee
 | 
			
		||||
jokaiseen solmuun,
 | 
			
		||||
kuinka pitkä on pisin polku,
 | 
			
		||||
joka alkaa jostakin lehdestä,
 | 
			
		||||
nousee kyseiseen solmuun asti
 | 
			
		||||
ja laskeutuu toiseen lehteen.
 | 
			
		||||
Pisin tällainen polku vastaa puun läpimittaa.
 | 
			
		||||
 | 
			
		||||
Esimerkissä pisin polku alkaa lehdestä 7,
 | 
			
		||||
nousee solmuun 1 asti ja laskeutuu
 | 
			
		||||
sitten alas lehteen 6:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (7) -- (3);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (3) -- (1);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmi laskee ensin dynaamisella ohjelmoinnilla
 | 
			
		||||
jokaiselle solmulle, kuinka pitkä on pisin polku,
 | 
			
		||||
joka lähtee solmusta alaspäin.
 | 
			
		||||
Esimerkiksi yllä olevassa puussa pisin polku
 | 
			
		||||
solmusta 1 alaspäin on pituudeltaan 2
 | 
			
		||||
(vaihtoehdot $1 \rightarrow 4 \rightarrow 3$,
 | 
			
		||||
$1 \rightarrow 4 \rightarrow 7$ ja $1 \rightarrow 2 \rightarrow 6$).
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen algoritmi laskee kullekin solmulle,
 | 
			
		||||
kuinka pitkä on pisin polku, jossa solmu on käännekohtana.
 | 
			
		||||
Pisin tällainen polku syntyy valitsemalla kaksi lasta,
 | 
			
		||||
joista lähtee alaspäin mahdollisimman pitkä polku.
 | 
			
		||||
Esimerkiksi yllä olevassa puussa solmun 1 lapsista valitaan solmut 2 ja 4.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Algoritmi 2}
 | 
			
		||||
 | 
			
		||||
Toinen tehokas tapa laskea puun läpimitta 
 | 
			
		||||
perustuu kahteen syvyyshakuun.
 | 
			
		||||
Ensin valitaan mikä tahansa solmu $a$ puusta
 | 
			
		||||
ja etsitään siitä kaukaisin solmu $b$
 | 
			
		||||
syvyyshaulla.
 | 
			
		||||
Tämän jälkeen etsitään $b$:stä kaukaisin
 | 
			
		||||
solmu $c$ syvyyshaulla.
 | 
			
		||||
Puun läpimitta on etäisyys $b$:n ja $c$:n välillä.
 | 
			
		||||
 | 
			
		||||
Esimerkissä $a$, $b$ ja $c$ voisivat olla:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (2,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$7$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,1) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\node[color=red] at (2,1.6) {$a$};
 | 
			
		||||
\node[color=red] at (-1.4,3) {$b$};
 | 
			
		||||
\node[color=red] at (4,1.6) {$c$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (6) -- (3);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (3) -- (1);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Menetelmä on tyylikäs, mutta miksi se toimii?
 | 
			
		||||
 | 
			
		||||
Tässä auttaa tarkastella puuta niin,
 | 
			
		||||
että puun läpimittaa vastaava polku on
 | 
			
		||||
levitetty vaakatasoon ja muut puun osat
 | 
			
		||||
riippuvat siitä alaspäin:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (2,1) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (2,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (6,1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (0,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\node[color=red] at (2,-1.6) {$a$};
 | 
			
		||||
\node[color=red] at (-2,1.6) {$b$};
 | 
			
		||||
\node[color=red] at (6,1.6) {$c$};
 | 
			
		||||
\node[color=red] at (2,1.6) {$x$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (7) -- (3);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (3) -- (1);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (2) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Solmu $x$ on kohta,
 | 
			
		||||
jossa polku solmusta $a$ liittyy
 | 
			
		||||
läpimittaa vastaavaan polkuun.
 | 
			
		||||
Kaukaisin solmu $a$:sta
 | 
			
		||||
on solmu $b$, solmu $c$
 | 
			
		||||
tai jokin muu solmu, joka
 | 
			
		||||
on ainakin yhtä kaukana solmusta $x$.
 | 
			
		||||
Niinpä tämä solmu on aina sopiva
 | 
			
		||||
valinta läpimittaa vastaavan polun
 | 
			
		||||
toiseksi päätesolmuksi.
 | 
			
		||||
 | 
			
		||||
\section{Solmujen etäisyydet}
 | 
			
		||||
 | 
			
		||||
Vaikeampi tehtävä on laskea
 | 
			
		||||
jokaiselle puun solmulle
 | 
			
		||||
jokaiseen suuntaan, mikä on suurin
 | 
			
		||||
etäisyys johonkin kyseisessä suunnassa
 | 
			
		||||
olevaan solmuun.
 | 
			
		||||
Osoittautuu, että tämäkin tehtävä ratkeaa
 | 
			
		||||
ajassa $O(n)$ dynaamisella ohjelmoinnilla.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkkipuussa etäisyydet ovat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (2,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (4,1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-2,3) {$7$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,1) {$3$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\node[color=red] at (0.5,3.2) {$2$};
 | 
			
		||||
\node[color=red] at (0.3,2.4) {$1$};
 | 
			
		||||
\node[color=red] at (-0.2,2.4) {$2$};
 | 
			
		||||
\node[color=red] at (-0.2,1.5) {$3$};
 | 
			
		||||
\node[color=red] at (-0.5,1.2) {$1$};
 | 
			
		||||
\node[color=red] at (-1.7,2.4) {$4$};
 | 
			
		||||
\node[color=red] at (-0.5,0.8) {$1$};
 | 
			
		||||
\node[color=red] at (-1.5,0.8) {$4$};
 | 
			
		||||
\node[color=red] at (1.5,3.2) {$3$};
 | 
			
		||||
\node[color=red] at (1.5,1.2) {$3$};
 | 
			
		||||
\node[color=red] at (3.5,1.2) {$4$};
 | 
			
		||||
\node[color=red] at (2.2,2.4) {$1$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
Esimerkiksi solmussa 4
 | 
			
		||||
kaukaisin solmu ylöspäin mentäessä
 | 
			
		||||
on solmu 6, johon etäisyys on 3 käyttäen
 | 
			
		||||
polkua $4 \rightarrow 1 \rightarrow 2 \rightarrow 6$.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Tässäkin tehtävässä hyvä lähtökohta on
 | 
			
		||||
valita jokin solmu puun juureksi,
 | 
			
		||||
jolloin kaikki etäisyydet alaspäin
 | 
			
		||||
saa laskettua dynaamisella ohjelmoinnilla:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (-2.5,0.7) {$1$};
 | 
			
		||||
\node[color=red] at (-1.5,0.7) {$1$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (2.2,0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (-0.5,2.8) {$2$};
 | 
			
		||||
\node[color=red] at (0.2,2.5) {$1$};
 | 
			
		||||
\node[color=red] at (0.5,2.8) {$2$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Jäljelle jäävä tehtävä on laskea etäisyydet ylöspäin.
 | 
			
		||||
Tämä onnistuu tekemällä puuhun toinen läpikäynti,
 | 
			
		||||
joka pitää mukana tietoa,
 | 
			
		||||
mikä on suurin etäisyys solmun vanhemmasta
 | 
			
		||||
johonkin toisessa suunnassa olevaan solmuun.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi solmun 2
 | 
			
		||||
suurin etäisyys ylöspäin on yhtä suurempi
 | 
			
		||||
kuin solmun 1 suurin etäisyys
 | 
			
		||||
johonkin muuhun suuntaan kuin solmuun 2:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-,color=red,line width=2pt] (3) -- (7);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Lopputuloksena on etäisyydet kaikista solmuista
 | 
			
		||||
kaikkiin suuntiin:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (-2.5,0.7) {$1$};
 | 
			
		||||
\node[color=red] at (-1.5,0.7) {$1$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (2.2,0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (-0.5,2.8) {$2$};
 | 
			
		||||
\node[color=red] at (0.2,2.5) {$1$};
 | 
			
		||||
\node[color=red] at (0.5,2.8) {$2$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (-3,-0.4) {$4$};
 | 
			
		||||
\node[color=red] at (-1,-0.4) {$4$};
 | 
			
		||||
\node[color=red] at (-2,1.6) {$3$};
 | 
			
		||||
\node[color=red] at (2,1.6) {$3$};
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (2.2,-0.4) {$4$};
 | 
			
		||||
\node[color=red] at (0.2,1.6) {$3$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
% 
 | 
			
		||||
% Kummankin läpikäynnin aikavaativuus on $O(n)$,
 | 
			
		||||
% joten algoritmin kokonais\-aikavaativuus on $O(n)$.
 | 
			
		||||
 | 
			
		||||
\section{Binääripuut}
 | 
			
		||||
 | 
			
		||||
\index{binxxripuu@binääripuu}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
\key{Binääripuu} on juurellinen puu,
 | 
			
		||||
jonka jokaisella solmulla on vasen ja oikea alipuu.
 | 
			
		||||
On mahdollista, että alipuu on tyhjä,
 | 
			
		||||
jolloin puu ei jatku siitä pidemmälle alaspäin.
 | 
			
		||||
Binääripuun jokaisella solmulla on 0, 1 tai 2 lasta.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraava puu on binääripuu:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-1.5,-1.5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1.5,-1.5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-3,-3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (0,-3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-1.5,-4.5) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (3,-3) {$7$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Binääripuun solmuilla on kolme luontevaa järjestystä,
 | 
			
		||||
jotka syntyvät rekursiivisesta läpikäynnistä:
 | 
			
		||||
 | 
			
		||||
\index{esijxrjestys@esijärjestys}
 | 
			
		||||
\index{sisxjxrjestys@sisäjärjestys}
 | 
			
		||||
\index{jxlkijxrjestys@jälkijärjestys}
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item \key{esijärjestys}: juuri, vasen alipuu, oikea alipuu
 | 
			
		||||
\item \key{sisäjärjestys}: vasen alipuu, juuri, oikea alipuu
 | 
			
		||||
\item \key{jälkijärjestys}: vasen alipuu, oikea alipuu, juuri
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Esimerkissä kuvatun puun esijärjestys on
 | 
			
		||||
$[1,2,4,5,6,3,7]$,
 | 
			
		||||
sisäjärjestys on $[4,2,6,5,1,3,7]$
 | 
			
		||||
ja jälkijärjestys on $[4,6,5,2,7,3,1]$.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tietämällä puun esijärjestyksen
 | 
			
		||||
ja sisäjärjestyksen voi päätellä puun koko rakenteen.
 | 
			
		||||
Esimerkiksi yllä oleva puu on ainoa mahdollinen
 | 
			
		||||
puu, jossa esijärjestys on
 | 
			
		||||
$[1,2,4,5,6,3,7]$ ja sisäjärjestys on $[4,2,6,5,1,3,7]$.
 | 
			
		||||
Vastaavasti myös jälkijärjestys ja sisäjärjestys
 | 
			
		||||
määrittävät puun rakenteen.
 | 
			
		||||
 | 
			
		||||
Tilanne on toinen, jos tiedossa on vain
 | 
			
		||||
esijärjestys ja jälkijärjestys.
 | 
			
		||||
Nämä järjestykset eivät
 | 
			
		||||
kuvaa välttämättä puuta yksikäsitteisesti.
 | 
			
		||||
Esimerkiksi molemmissa puissa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-1.5,-1.5) {$2$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (1b) at (0+4,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2b) at (1.5+4,-1.5) {$2$};
 | 
			
		||||
\path[draw,thick,-] (1b) -- (2b);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
esijärjestys on $(1,2)$ ja jälkijärjestys on $(2,1)$,
 | 
			
		||||
mutta siitä huolimatta puiden rakenteet eivät ole samat.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,745 @@
 | 
			
		|||
\chapter{Spanning trees}
 | 
			
		||||
 | 
			
		||||
\index{virittxvx puu@virittävä puu}
 | 
			
		||||
 | 
			
		||||
\key{Virittävä puu} on kokoelma
 | 
			
		||||
verkon kaaria,
 | 
			
		||||
joka kytkee kaikki
 | 
			
		||||
verkon solmut toisiinsa.
 | 
			
		||||
Kuten puut yleensäkin,
 | 
			
		||||
virittävä puu on yhtenäinen ja syklitön.
 | 
			
		||||
Virittävän puun muodostamiseen
 | 
			
		||||
on yleensä monia tapoja.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
yksi mahdollinen virittävä puu on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Virittävän puun paino on siihen kuuluvien kaarten painojen summa.
 | 
			
		||||
Esimerkiksi yllä olevan puun paino on $3+5+9+3+2=22$.
 | 
			
		||||
 | 
			
		||||
\key{Pienin virittävä puu}
 | 
			
		||||
on virittävä puu, jonka paino on mahdollisimman pieni.
 | 
			
		||||
Yllä olevan verkon pienin virittävä puu
 | 
			
		||||
on painoltaan 20, ja sen voi muodostaa seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Vastaavasti \key{suurin virittävä puu}
 | 
			
		||||
on virittävä puu, jonka paino on mahdollisimman suuri.
 | 
			
		||||
Yllä olevan verkon suurin virittävä puu on
 | 
			
		||||
painoltaan 32:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Huomaa, että voi olla monta erilaista
 | 
			
		||||
tapaa muodostaa pienin tai
 | 
			
		||||
suurin virittävä puu, eli puut eivät ole yksikäsitteisiä.
 | 
			
		||||
 | 
			
		||||
Tässä luvussa tutustumme algoritmeihin,
 | 
			
		||||
jotka muodostavat verkon pienimmän tai suurimman
 | 
			
		||||
virittävän puun.
 | 
			
		||||
Osoittautuu, että virittävien puiden etsiminen
 | 
			
		||||
on siinä mielessä helppo ongelma,
 | 
			
		||||
että monenlaiset ahneet menetelmät tuottavat
 | 
			
		||||
optimaalisen ratkaisun.
 | 
			
		||||
 | 
			
		||||
Käymme läpi kaksi algoritmia, jotka molemmat valitsevat
 | 
			
		||||
puuhun mukaan kaaria painojärjestyksessä.
 | 
			
		||||
Keskitymme pienimmän virittävän puun etsimiseen,
 | 
			
		||||
mutta samoilla algoritmeilla voi muodostaa myös suurimman virittävän
 | 
			
		||||
puun käsittelemällä kaaret käänteisessä järjestyksessä.
 | 
			
		||||
 | 
			
		||||
\section{Kruskalin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Kruskalin algoritmi@Kruskalin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Kruskalin algoritmi} aloittaa pienimmän
 | 
			
		||||
virittävän
 | 
			
		||||
puun muodostamisen tilanteesta,
 | 
			
		||||
jossa puussa ei ole yhtään kaaria.
 | 
			
		||||
Sitten algoritmi alkaa lisätä
 | 
			
		||||
puuhun kaaria järjestyksessä
 | 
			
		||||
kevyimmästä raskaimpaan.
 | 
			
		||||
Kunkin kaaren kohdalla
 | 
			
		||||
algoritmi ottaa kaaren mukaan puuhun,
 | 
			
		||||
jos tämä ei aiheuta sykliä.
 | 
			
		||||
 | 
			
		||||
Kruskalin algoritmi pitää yllä
 | 
			
		||||
tietoa verkon komponenteista.
 | 
			
		||||
Aluksi jokainen solmu on omassa
 | 
			
		||||
komponentissaan,
 | 
			
		||||
ja komponentit yhdistyvät pikkuhiljaa
 | 
			
		||||
algoritmin aikana puuhun tulevista kaarista.
 | 
			
		||||
Lopulta kaikki solmut ovat samassa
 | 
			
		||||
komponentissa, jolloin pienin virittävä puu on valmis.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Tarkastellaan Kruskalin algoritmin toimintaa
 | 
			
		||||
seuraavassa verkossa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Algoritmin ensimmäinen vaihe on
 | 
			
		||||
järjestää verkon kaaret niiden painon mukaan.
 | 
			
		||||
Tuloksena on seuraava lista:
 | 
			
		||||
 | 
			
		||||
\begin{tabular}{ll}
 | 
			
		||||
\\
 | 
			
		||||
kaari & paino \\
 | 
			
		||||
\hline
 | 
			
		||||
5--6 & 2 \\
 | 
			
		||||
1--2 & 3 \\
 | 
			
		||||
3--6 & 3 \\
 | 
			
		||||
1--5 & 5 \\
 | 
			
		||||
2--3 & 5 \\
 | 
			
		||||
2--5 & 6 \\
 | 
			
		||||
4--6 & 7 \\
 | 
			
		||||
3--4 & 9 \\
 | 
			
		||||
\\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen algoritmi käy listan läpi
 | 
			
		||||
ja lisää kaaren puuhun,
 | 
			
		||||
jos se yhdistää kaksi erillistä komponenttia.
 | 
			
		||||
 | 
			
		||||
Aluksi jokainen solmu on omassa komponentissaan:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Ensimmäinen virittävään puuhun lisättävä
 | 
			
		||||
kaari on 5--6, joka yhdistää
 | 
			
		||||
komponentit $\{5\}$ ja $\{6\}$ komponentiksi $\{5,6\}$:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen algoritmi lisää puuhun vastaavasti
 | 
			
		||||
kaaret 1--2, 3--6 ja 1--5:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Näiden lisäysten jälkeen monet
 | 
			
		||||
komponentit ovat yhdistyneet ja verkossa on kaksi
 | 
			
		||||
komponenttia: $\{1,2,3,5,6\}$ ja $\{4\}$.
 | 
			
		||||
 | 
			
		||||
Seuraavaksi käsiteltävä kaari on 2--3,
 | 
			
		||||
mutta tämä kaari ei tule mukaan puuhun,
 | 
			
		||||
koska solmut 2 ja 3 ovat jo samassa komponentissa.
 | 
			
		||||
Vastaavasta syystä myöskään kaari 2--5 ei tule mukaan puuhun.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Lopuksi puuhun tulee kaari 4--6,
 | 
			
		||||
joka luo yhden komponentin:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Tämän lisäyksen jälkeen algoritmi päättyy,
 | 
			
		||||
koska kaikki solmut on kytketty toisiinsa kaarilla
 | 
			
		||||
ja verkko on yhtenäinen.
 | 
			
		||||
Tuloksena on verkon pienin virittävä puu,
 | 
			
		||||
jonka paino on $2+3+3+5+7=20$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Miksi algoritmi toimii?}
 | 
			
		||||
 | 
			
		||||
On hyvä kysymys, miksi Kruskalin algoritmi
 | 
			
		||||
toimii aina eli miksi ahne strategia tuottaa
 | 
			
		||||
varmasti pienimmän mahdollisen virittävän puun.
 | 
			
		||||
 | 
			
		||||
Voimme perustella algoritmin toimivuuden
 | 
			
		||||
tekemällä vastaoletuksen, että pienimmässä
 | 
			
		||||
virittävässä puussa ei olisi verkon keveintä kaarta.
 | 
			
		||||
Oletetaan esimerkiksi, että äskeisen verkon
 | 
			
		||||
pienimmässä virittävässä puussa ei olisi
 | 
			
		||||
2:n painoista kaarta solmujen 5 ja 6 välillä.
 | 
			
		||||
Emme tiedä tarkalleen, millainen uusi pienin
 | 
			
		||||
virittävä puu olisi, mutta siinä täytyy olla
 | 
			
		||||
kuitenkin joukko kaaria.
 | 
			
		||||
Oletetaan, että virittävä puu olisi
 | 
			
		||||
vaikkapa seuraavanlainen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,dashed] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,dashed] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-,dashed] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-,dashed] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-,dashed] (4) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ei ole kuitenkaan mahdollista,
 | 
			
		||||
että yllä oleva virittävä puu olisi todellisuudessa
 | 
			
		||||
verkon pienin virittävä puu.
 | 
			
		||||
Tämä johtuu siitä, että voimme poistaa siitä
 | 
			
		||||
jonkin kaaren ja korvata sen 2:n painoisella kaarella.
 | 
			
		||||
Tuloksena on virittävä puu, jonka paino on \emph{pienempi}:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-,dashed] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-,dashed] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-,dashed] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-,dashed] (4) -- (6);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Niinpä on aina optimaalinen ratkaisu valita pienimpään
 | 
			
		||||
virittävään puuhun verkon kevein kaari.
 | 
			
		||||
Vastaavalla tavalla voimme perustella
 | 
			
		||||
seuraavaksi keveimmän kaaren valinnan, jne.
 | 
			
		||||
Niinpä Kruskalin algoritmi toimii oikein ja
 | 
			
		||||
tuottaa aina pienimmän virittävän puun.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Kruskalin algoritmi on mukavinta toteuttaa
 | 
			
		||||
kaarilistan avulla. Algoritmin ensimmäinen vaihe
 | 
			
		||||
on järjestää kaaret painojärjestykseen,
 | 
			
		||||
missä kuluu aikaa $O(m \log m)$.
 | 
			
		||||
Tämän jälkeen seuraa algoritmin toinen vaihe,
 | 
			
		||||
jossa listalta valitaan kaaret mukaan puuhun.
 | 
			
		||||
 | 
			
		||||
Algoritmin toinen vaihe rakentuu seuraavanlaisen silmukan ympärille:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (...) {
 | 
			
		||||
  if (!sama(a,b)) liita(a,b);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Silmukka käy läpi kaikki listan kaaret
 | 
			
		||||
niin, että muuttujat $a$ ja $b$ ovat kulloinkin kaaren
 | 
			
		||||
päissä olevat solmut.
 | 
			
		||||
Koodi käyttää kahta funktiota:
 | 
			
		||||
funktio \texttt{sama} tutkii,
 | 
			
		||||
ovatko solmut samassa komponentissa,
 | 
			
		||||
ja funktio \texttt{liita}
 | 
			
		||||
yhdistää kaksi komponenttia toisiinsa.
 | 
			
		||||
 | 
			
		||||
Ongelmana on, kuinka toteuttaa tehokkaasti
 | 
			
		||||
funktiot \texttt{sama} ja \texttt{liita}.
 | 
			
		||||
Yksi mahdollisuus on pitää yllä verkkoa tavallisesti
 | 
			
		||||
ja toteuttaa funktio \texttt{sama} verkon läpikäyntinä.
 | 
			
		||||
Tällöin kuitenkin funktion \texttt{sama}
 | 
			
		||||
suoritus veisi aikaa $O(n+m)$,
 | 
			
		||||
mikä on hidasta, koska funktiota kutsutaan
 | 
			
		||||
jokaisen kaaren kohdalla.
 | 
			
		||||
 | 
			
		||||
Seuraavaksi esiteltävä union-find-rakenne
 | 
			
		||||
ratkaisee asian.
 | 
			
		||||
Se toteuttaa molemmat funktiot
 | 
			
		||||
ajassa $O(\log n)$,
 | 
			
		||||
jolloin Kruskalin algoritmin
 | 
			
		||||
aikavaativuus on vain $O(m \log n)$
 | 
			
		||||
kaarilistan järjestämisen jälkeen.
 | 
			
		||||
 | 
			
		||||
\section{Union-find-rakenne}
 | 
			
		||||
 | 
			
		||||
\index{union-find-rakenne}
 | 
			
		||||
 | 
			
		||||
\key{Union-find-rakenne} pitää yllä
 | 
			
		||||
alkiojoukkoja.
 | 
			
		||||
Joukot ovat erillisiä,
 | 
			
		||||
eli tietty alkio on tarkalleen
 | 
			
		||||
yhdessä joukossa.
 | 
			
		||||
Rakenne tarjoaa kaksi operaatiota,
 | 
			
		||||
jotka toimivat ajassa $O(\log n)$.
 | 
			
		||||
Ensimmäinen operaatio tarkistaa,
 | 
			
		||||
ovatko kaksi alkiota samassa joukossa.
 | 
			
		||||
Toinen operaatio yhdistää kaksi
 | 
			
		||||
joukkoa toisiinsa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Rakenne}
 | 
			
		||||
 | 
			
		||||
Union-find-rakenteessa jokaisella
 | 
			
		||||
joukolla on edustaja-alkio.
 | 
			
		||||
Kaikki muut joukon alkiot osoittavat
 | 
			
		||||
edustajaan joko suoraan tai
 | 
			
		||||
muiden alkioiden kautta.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos joukot ovat
 | 
			
		||||
$\{1,4,7\}$, $\{5\}$ ja $\{2,3,6,8\}$,
 | 
			
		||||
tilanne voisi olla:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (0,-1) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (7,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (7,-1.5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (4,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (6,-2.5) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (8,-2.5) {$8$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (4);
 | 
			
		||||
\path[draw,thick,->] (7) -- (4);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->] (6) -- (3);
 | 
			
		||||
\path[draw,thick,->] (8) -- (3);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tässä tapauksessa alkiot 4, 5 ja 2
 | 
			
		||||
ovat joukkojen edustajat.
 | 
			
		||||
Minkä tahansa alkion edustaja
 | 
			
		||||
löytyy kulkemalla alkiosta lähtevää polkua
 | 
			
		||||
eteenpäin niin kauan, kunnes polku päättyy.
 | 
			
		||||
Esimerkiksi alkion 6 edustaja on 2,
 | 
			
		||||
koska alkiosta 6 lähtevä
 | 
			
		||||
polku on $6 \rightarrow 3 \rightarrow 2$.
 | 
			
		||||
Tämän avulla voi selvittää,
 | 
			
		||||
ovatko kaksi alkiota samassa joukossa:
 | 
			
		||||
jos kummankin alkion edustaja on sama,
 | 
			
		||||
alkiot ovat samassa joukossa,
 | 
			
		||||
ja muuten ne ovat eri joukoissa.
 | 
			
		||||
 | 
			
		||||
Kahden joukon yhdistäminen tapahtuu
 | 
			
		||||
valitsemalla toinen edustaja
 | 
			
		||||
joukkojen yhteiseksi edustajaksi
 | 
			
		||||
ja kytkemällä toinen edustaja siihen.
 | 
			
		||||
Esimerkiksi joukot $\{1,4,7\}$ ja $\{2,3,6,8\}$
 | 
			
		||||
voi yhdistää näin joukoksi $\{1,2,3,4,6,7,8\}$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (2,-1) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (7,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (7,-1.5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,0) {$4$};
 | 
			
		||||
\node[draw, circle] (6) at (6,-2.5) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (4,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (8,-2.5) {$8$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (4);
 | 
			
		||||
\path[draw,thick,->] (7) -- (4);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->] (6) -- (3);
 | 
			
		||||
\path[draw,thick,->] (8) -- (3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (4) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Joukkojen yhteiseksi edustajaksi valitaan alkio 2,
 | 
			
		||||
minkä vuoksi alkio 4 yhdistetään siihen.
 | 
			
		||||
Tästä lähtien alkio 2 edustaa kaikkia joukon alkioita.
 | 
			
		||||
 | 
			
		||||
Tehokkuuden kannalta oleellista on,
 | 
			
		||||
miten yhdistäminen tapahtuu.
 | 
			
		||||
Osoittautuu, että ratkaisu on yksinkertainen:
 | 
			
		||||
riittää yhdistää aina pienempi joukko suurempaan,
 | 
			
		||||
tai kummin päin tahansa,
 | 
			
		||||
jos joukot ovat yhtä suuret.
 | 
			
		||||
Tällöin pisin ketju
 | 
			
		||||
alkiosta edustajaan on aina luokkaa $O(\log n)$,
 | 
			
		||||
koska jokainen askel eteenpäin
 | 
			
		||||
ketjussa kaksinkertaistaa
 | 
			
		||||
vastaavan joukon koon.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Union-find-rakenne on kätevää toteuttaa
 | 
			
		||||
taulukoiden avulla.
 | 
			
		||||
Seuraavassa toteutuksessa taulukko \texttt{k}
 | 
			
		||||
viittaa seuraavaan alkioon ketjussa
 | 
			
		||||
tai alkioon itseensä, jos alkio on edustaja.
 | 
			
		||||
Taulukko \texttt{s} taas kertoo jokaiselle edustajalle,
 | 
			
		||||
kuinka monta alkiota niiden joukossa on.
 | 
			
		||||
 | 
			
		||||
Aluksi jokainen alkio on omassa joukossaan,
 | 
			
		||||
jonka koko on 1:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int i = 1; i <= n; i++) k[i] = i;
 | 
			
		||||
for (int i = 1; i <= n; i++) s[i] = 1;
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{id} kertoo alkion $x$
 | 
			
		||||
joukon edustajan. Alkion edustaja löytyy
 | 
			
		||||
käymällä ketju läpi alkiosta $x$ alkaen.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int id(int x) {
 | 
			
		||||
    while (x != k[x]) x = k[x];
 | 
			
		||||
    return x;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{sama} kertoo,
 | 
			
		||||
ovatko alkiot $a$ ja $b$ samassa joukossa.
 | 
			
		||||
Tämä onnistuu helposti funktion
 | 
			
		||||
\texttt{id} avulla.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bool sama(int a, int b) {
 | 
			
		||||
    return id(a) == id(b);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Funktio \texttt{liita} yhdistää
 | 
			
		||||
puolestaan alkioiden $a$ ja $b$ osoittamat
 | 
			
		||||
joukot yhdeksi joukoksi.
 | 
			
		||||
Funktio etsii ensin joukkojen edustajat
 | 
			
		||||
ja yhdistää sitten pienemmän joukon suurempaan.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
void liita(int a, int b) {
 | 
			
		||||
    a = id(a);
 | 
			
		||||
    b = id(b);
 | 
			
		||||
    if (s[b] > s[a]) swap(a,b);
 | 
			
		||||
    s[a] += s[b];
 | 
			
		||||
    k[b] = a;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Funktion \texttt{id} aikavaativuus on $O(\log n)$
 | 
			
		||||
olettaen, että ketjun pituus on luokkaa $O(\log n)$.
 | 
			
		||||
Niinpä myös funktioiden \texttt{sama} ja \texttt{liita}
 | 
			
		||||
aikavaativuus on $O(\log n)$.
 | 
			
		||||
Funktio \texttt{liita} varmistaa,
 | 
			
		||||
että ketjun pituus on luokkaa $O(\log n)$
 | 
			
		||||
yhdistämällä pienemmän joukon suurempaan.
 | 
			
		||||
 | 
			
		||||
% Funktiota \texttt{id} on mahdollista vielä tehostaa
 | 
			
		||||
% seuraavasti:
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% int id(int x) {
 | 
			
		||||
%   if (x == k[x]) return x;
 | 
			
		||||
%   return k[x] = id(x);
 | 
			
		||||
% }
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% Nyt joukon edustajan etsimisen yhteydessä kaikki ketjun
 | 
			
		||||
% alkiot laitetaan osoittamaan suoraan edustajaan.
 | 
			
		||||
% On mahdollista osoittaa, että tämän avulla
 | 
			
		||||
% funktioiden \texttt{sama} ja \texttt{liita}
 | 
			
		||||
% aikavaativuus on tasoitetusti
 | 
			
		||||
% vain $O(\alpha(n))$, missä $\alpha(n)$ on
 | 
			
		||||
% hyvin hitaasti kasvava käänteinen Ackermannin funktio.
 | 
			
		||||
 | 
			
		||||
\section{Primin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Primin algoritmi@Primin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Primin algoritmi} on vaihtoehtoinen menetelmä
 | 
			
		||||
verkon pienimmän virittävän puun muodostamiseen.
 | 
			
		||||
Algoritmi aloittaa puun muodostamisen jostakin
 | 
			
		||||
verkon solmusta ja lisää puuhun aina kaaren,
 | 
			
		||||
joka on mahdollisimman kevyt ja joka
 | 
			
		||||
liittää puuhun uuden solmun.
 | 
			
		||||
Lopulta kaikki solmut on lisätty puuhun
 | 
			
		||||
ja pienin virittävä puu on valmis.
 | 
			
		||||
 | 
			
		||||
Primin algoritmin toiminta on lähellä
 | 
			
		||||
Dijkstran algoritmia.
 | 
			
		||||
Erona on, että Dijkstran algoritmissa valitaan
 | 
			
		||||
kaari, jonka kautta syntyy lyhin polku alkusolmusta
 | 
			
		||||
uuteen solmuun, mutta Primin algoritmissa
 | 
			
		||||
valitaan vain kevein kaari, joka johtaa uuteen solmuun.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan Primin algoritmin toimintaa
 | 
			
		||||
seuraavassa verkossa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
 | 
			
		||||
%\path[draw=red,thick,-,line width=2pt] (5) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Aluksi solmujen välillä ei ole mitään kaaria:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Puun muodostuksen voi aloittaa mistä tahansa solmusta,
 | 
			
		||||
ja aloitetaan se nyt solmusta 1.
 | 
			
		||||
Kevein kaari on painoltaan 3 ja se johtaa solmuun 2:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Nyt kevein uuteen solmuun johtavan
 | 
			
		||||
kaaren paino on 5,
 | 
			
		||||
ja voimme laajentaa joko solmuun 3 tai 5.
 | 
			
		||||
Valitaan solmu 3:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
%\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Sama jatkuu, kunnes kaikki solmut ovat mukana puussa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1.5,2) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6.5,2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:5] {} (3);
 | 
			
		||||
%\path[draw,thick,-] (3) -- node[font=\small,label=above:9] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (1) -- node[font=\small,label=below:5] {} (5);
 | 
			
		||||
\path[draw,thick,-] (5) -- node[font=\small,label=below:2] {} (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- node[font=\small,label=below:7] {} (4);
 | 
			
		||||
%\path[draw,thick,-] (2) -- node[font=\small,label=left:6] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=left:3] {} (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Toteutus}
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmin tavoin Primin algoritmin voi toteuttaa
 | 
			
		||||
tehokkaasti käyttämällä prioriteettijonoa.
 | 
			
		||||
Primin algoritmin tapauksessa jono sisältää kaikki solmut,
 | 
			
		||||
jotka voi yhdistää nykyiseen komponentiin kaarella,
 | 
			
		||||
järjestyksessä kaaren painon mukaan kevyimmästä raskaimpaan.
 | 
			
		||||
 | 
			
		||||
Primin algoritmin aikavaativuus on $O(n + m \log m)$
 | 
			
		||||
eli sama kuin Dijkstran algoritmissa.
 | 
			
		||||
Käytännössä Primin algoritmi on suunnilleen
 | 
			
		||||
yhtä nopea kuin Kruskalin algoritmi,
 | 
			
		||||
ja onkin makuasia, kumpaa algoritmia käyttää.
 | 
			
		||||
Useimmat kisakoodarit käyttävät kuitenkin Kruskalin algoritmia.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,753 @@
 | 
			
		|||
\chapter{Directed graphs}
 | 
			
		||||
 | 
			
		||||
Tässä luvussa tutustumme kahteen suunnattujen
 | 
			
		||||
verkkojen alaluokkaan:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item \key{Syklitön verkko}:
 | 
			
		||||
Tällaisessa verkossa ei ole yhtään sykliä,
 | 
			
		||||
eli mistään solmusta ei ole polkua takaisin
 | 
			
		||||
solmuun itseensä.
 | 
			
		||||
\item \key{Seuraajaverkko}:
 | 
			
		||||
Tällaisessa verkossa jokaisen solmun lähtöaste on 1
 | 
			
		||||
eli kullakin solmulla on yksikäsitteinen seuraaja.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Osoittautuu, että kummassakin tapauksessa
 | 
			
		||||
verkon käsittely on yleistä verkkoa helpompaa
 | 
			
		||||
ja voimme käyttää tehokkaita algoritmeja, jotka
 | 
			
		||||
hyödyntävät oletuksia verkon rakenteesta.
 | 
			
		||||
 | 
			
		||||
\section{Topologinen järjestys}
 | 
			
		||||
 | 
			
		||||
\index{topologinen jxrjestys@topologinen järjestys}
 | 
			
		||||
\index{sykli@sykli}
 | 
			
		||||
 | 
			
		||||
\key{Topologinen järjestys} on tapa
 | 
			
		||||
järjestää suunnatun verkon solmut niin,
 | 
			
		||||
että jos solmusta $a$ pääsee solmuun $b$,
 | 
			
		||||
niin $a$ on ennen $b$:tä järjestyksessä.
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
yksi topologinen järjestys on
 | 
			
		||||
$[4,1,5,2,3,6]$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (-6,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-1.5,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-7.5,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (-4.5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-0,0) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) edge [bend right=30] (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend left=30]  (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Topologinen järjestys on olemassa
 | 
			
		||||
aina silloin, kun verkko on syklitön.
 | 
			
		||||
Jos taas verkossa on sykli,
 | 
			
		||||
topologista järjestystä ei voi muodostaa,
 | 
			
		||||
koska mitään syklissä olevaa solmua ei voi
 | 
			
		||||
valita ensimmäisenä topologiseen järjestykseen.
 | 
			
		||||
Seuraavaksi näemme, miten syvyyshaun avulla
 | 
			
		||||
voi muodostaa topologisen järjestyksen tai
 | 
			
		||||
todeta, että tämä ei ole mahdollista syklin takia.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Algoritmi}
 | 
			
		||||
 | 
			
		||||
Ideana on käydä läpi verkon solmut ja aloittaa
 | 
			
		||||
solmusta uusi syvyyshaku aina silloin, kun solmua
 | 
			
		||||
ei ole vielä käsitelty.
 | 
			
		||||
Kunkin syvyyshaun aikana solmuilla on
 | 
			
		||||
kolme mahdollista tilaa:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item tila 0: solmua ei ole käsitelty (valkoinen)
 | 
			
		||||
\item tila 1: solmun käsittely on alkanut (vaaleanharmaa)
 | 
			
		||||
\item tila 2: solmu on käsitelty (tummanharmaa)
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Aluksi jokaisen verkon solmun tila on 0.
 | 
			
		||||
Kun syvyyshaku saapuu solmuun, sen tilaksi tulee 1.
 | 
			
		||||
Lopuksi kun syvyyshaku on käsitellyt kaikki
 | 
			
		||||
solmun naapurit, solmun tilaksi tulee 2.
 | 
			
		||||
 | 
			
		||||
Jos verkossa on sykli, tämä selviää syvyyshaun aikana siitä,
 | 
			
		||||
että jossain vaiheessa haku saapuu solmuun,
 | 
			
		||||
jonka tila on 1. Tässä tapauksessa topologista
 | 
			
		||||
järjestystä ei voi muodostaa.
 | 
			
		||||
 | 
			
		||||
Jos verkossa ei ole sykliä, topologinen järjestys
 | 
			
		||||
saadaan muodostamalla lista, johon kukin solmu lisätään
 | 
			
		||||
silloin, kun sen tilaksi tulee 2.
 | 
			
		||||
Tämä lista käänteisenä on yksi verkon
 | 
			
		||||
topologinen järjestys.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki 1}
 | 
			
		||||
 | 
			
		||||
Esimerkkiverkossa syvyyshaku etenee ensin solmusta 1
 | 
			
		||||
solmuun 6 asti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle,fill=gray!20] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
%\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässä vaiheessa solmu 6 on käsitelty, joten se lisätään listalle.
 | 
			
		||||
Sen jälkeen haku palaa takaisinpäin:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle,fill=gray!80] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen listan sisältönä on $[6,3,2,1]$.
 | 
			
		||||
Sitten seuraava syvyyshaku alkaa solmusta 4:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle,fill=gray!80] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle,fill=gray!80] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
%\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän seurauksena listaksi tulee $[6,3,2,1,5,4]$.
 | 
			
		||||
Kaikki solmut on käyty läpi, joten topologinen järjestys on valmis.
 | 
			
		||||
Topologinen järjestys on lista käänteisenä eli $[4,5,1,2,3,6]$:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4.5,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (6,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (0,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1.5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (7.5,0) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend right=30] (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend right=40] (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Huomaa, että topologinen järjestys ei ole yksikäsitteinen,
 | 
			
		||||
vaan verkolla voi olla useita topologisia järjestyksiä.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki 2}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten tilannetta, jossa topologista järjestystä
 | 
			
		||||
ei voi muodostaa syklin takia. Näin on seuraavassa verkossa:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Nyt syvyyshaun aikana tapahtuu näin:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle,fill=gray!20] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle,fill=gray!20] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Syvyyshaku saapuu tilassa 1 olevaan solmuun 2,
 | 
			
		||||
mikä tarkoittaa, että verkossa on sykli.
 | 
			
		||||
Tässä tapauksessa sykli on $2 \rightarrow 3 \rightarrow 5 \rightarrow 2$.
 | 
			
		||||
 | 
			
		||||
\section{Dynaaminen ohjelmointi}
 | 
			
		||||
 | 
			
		||||
Jos suunnattu verkko on syklitön,
 | 
			
		||||
siihen voi soveltaa dynaamista ohjelmointia.
 | 
			
		||||
Tämän avulla voi ratkaista tehokkaasti
 | 
			
		||||
ajassa $O(n+m)$ esimerkiksi seuraavat
 | 
			
		||||
ongelmat koskien verkossa olevia polkuja
 | 
			
		||||
alkusolmusta loppusolmuun:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item montako erilaista polkua on olemassa?
 | 
			
		||||
\item mikä on lyhin/pisin polku?
 | 
			
		||||
\item mikä on pienin/suurin määrä kaaria polulla?
 | 
			
		||||
\item mitkä solmut esiintyvät varmasti polulla?
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Polkujen määrän laskeminen}
 | 
			
		||||
 | 
			
		||||
Lasketaan esimerkkinä polkujen määrä
 | 
			
		||||
alkusolmusta loppusolmuun suunnatussa,
 | 
			
		||||
syklittömässä verkossa.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on 3 polkua solmusta 4 solmuun 6:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $4 \rightarrow 1 \rightarrow 2 \rightarrow 3 \rightarrow 6$
 | 
			
		||||
\item $4 \rightarrow 5 \rightarrow 2 \rightarrow 3 \rightarrow 6$
 | 
			
		||||
\item $4 \rightarrow 5 \rightarrow 3 \rightarrow 6$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Ideana on käydä läpi verkon solmut topologisessa järjestyksessä
 | 
			
		||||
ja laskea kunkin solmun kohdalla yhteen eri suunnista
 | 
			
		||||
tulevien polkujen määrät.
 | 
			
		||||
Verkon topologinen järjestys on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4.5,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (6,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (0,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1.5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (7.5,0) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) edge [bend left=30] (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend right=30] (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend right=40] (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tuloksena ovat seuraavat lukumäärät:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,5) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,3) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (1,2.3) {$1$};
 | 
			
		||||
\node[color=red] at (3,2.3) {$1$};
 | 
			
		||||
\node[color=red] at (5,2.3) {$3$};
 | 
			
		||||
\node[color=red] at (1,5.7) {$1$};
 | 
			
		||||
\node[color=red] at (3,5.7) {$2$};
 | 
			
		||||
\node[color=red] at (5,5.7) {$3$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi solmuun 2 pääsee solmuista 1 ja 5.
 | 
			
		||||
Kumpaankin solmuun päättyy yksi polku solmusta 4 alkaen,
 | 
			
		||||
joten solmuun 2 päättyy kaksi polkua solmusta 4 alkaen.
 | 
			
		||||
Vastaavasti solmuun 3 pääsee solmuista 2 ja 5,
 | 
			
		||||
joiden kautta tulee kaksi ja yksi polkua solmusta 4 alkaen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Dijkstran algoritmin sovellukset}
 | 
			
		||||
 | 
			
		||||
\index{Dijkstran algoritmi@Dijkstran algoritmi}
 | 
			
		||||
 | 
			
		||||
Dijkstran algoritmin sivutuotteena syntyy suunnattu, syklitön verkko,
 | 
			
		||||
joka kertoo jokaiselle alkuperäisen verkon solmulle,
 | 
			
		||||
mitä tapoja alkusolmusta on päästä kyseiseen solmuun lyhintä
 | 
			
		||||
polkua käyttäen.
 | 
			
		||||
Tähän verkkoon voi soveltaa edelleen dynaamista ohjelmointia.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,-2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (2,-2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (4,-1) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- node[font=\small,label=left:5] {} (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=right:4] {} (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:8] {} (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- node[font=\small,label=below:2] {} (4);
 | 
			
		||||
\path[draw,thick,-] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- node[font=\small,label=above:2] {} (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmusta 1 lähteviin lyhimpiin polkuihin kuuluvat
 | 
			
		||||
seuraavat kaaret:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,-2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (2,-2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (4,-1) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3);
 | 
			
		||||
\path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Koska kyseessä on suunnaton, syklitön verkko,
 | 
			
		||||
siihen voi soveltaa dynaamista ohjelmointia.
 | 
			
		||||
Niinpä voi esimerkiksi laskea, montako lyhintä polkua
 | 
			
		||||
on olemassa solmusta 1 solmuun 5:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (0,-2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (2,-2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (4,-1) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- node[font=\small,label=above:3] {} (2);
 | 
			
		||||
\path[draw,thick,->] (1) -- node[font=\small,label=left:5] {} (3);
 | 
			
		||||
\path[draw,thick,->] (2) -- node[font=\small,label=right:4] {} (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- node[font=\small,label=below:2] {} (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- node[font=\small,label=below:1] {} (5);
 | 
			
		||||
\path[draw,thick,->] (2) -- node[font=\small,label=above:2] {} (3);
 | 
			
		||||
 | 
			
		||||
\node[color=red] at (0,0.7) {$1$};
 | 
			
		||||
\node[color=red] at (2,0.7) {$1$};
 | 
			
		||||
\node[color=red] at (0,-2.7) {$2$};
 | 
			
		||||
\node[color=red] at (2,-2.7) {$3$};
 | 
			
		||||
\node[color=red] at (4,-1.7) {$3$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ongelman esitys verkkona}
 | 
			
		||||
 | 
			
		||||
Itse asiassa mikä tahansa dynaamisen ohjelmoinnin
 | 
			
		||||
ongelma voidaan esittää suunnattuna, syklittömänä verkkona.
 | 
			
		||||
Tällaisessa verkossa solmuja ovat dynaamisen
 | 
			
		||||
ohjelmoinnin tilat ja kaaret kuvaavat,
 | 
			
		||||
miten tilat riippuvat toisistaan.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tuttua tehtävää,
 | 
			
		||||
jossa annettuna on rahamäärä $x$
 | 
			
		||||
ja tehtävänä on muodostaa se kolikoista
 | 
			
		||||
$\{c_1,c_2,\ldots,c_k\}$.
 | 
			
		||||
Tässä tapauksessa voimme muodostaa verkon, jonka solmut
 | 
			
		||||
vastaavat rahamääriä ja kaaret kuvaavat
 | 
			
		||||
kolikkojen valintoja.
 | 
			
		||||
Esimerkiksi jos kolikot ovat $\{1,3,4\}$
 | 
			
		||||
ja $x=6$, niin verkosta tulee seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (0) at (0,0) {$0$};
 | 
			
		||||
\node[draw, circle] (1) at (2,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (6,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (8,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (10,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (12,0) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (0) -- (1);
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (6);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (0) edge [bend right=30] (3);
 | 
			
		||||
\path[draw,thick,->] (1) edge [bend right=30] (4);
 | 
			
		||||
\path[draw,thick,->] (2) edge [bend right=30] (5);
 | 
			
		||||
\path[draw,thick,->] (3) edge [bend right=30] (6);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (0) edge [bend left=30] (4);
 | 
			
		||||
\path[draw,thick,->] (1) edge [bend left=30] (5);
 | 
			
		||||
\path[draw,thick,->] (2) edge [bend left=30] (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tätä verkkoesitystä käyttäen
 | 
			
		||||
lyhin polku solmusta 0 solmuun $x$
 | 
			
		||||
vastaa ratkaisua, jossa kolikoita on
 | 
			
		||||
mahdollisimman vähän, ja polkujen yhteismäärä
 | 
			
		||||
solmusta 0 solmuun $x$
 | 
			
		||||
on sama kuin ratkaisujen yhteismäärä.
 | 
			
		||||
 | 
			
		||||
\section{Tehokas eteneminen}
 | 
			
		||||
 | 
			
		||||
\index{seuraajaverkko@seuraajaverkko}
 | 
			
		||||
\index{funktionaalinen verkko@funktionaalinen verkko}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi oletamme, että
 | 
			
		||||
suunnaton verkko on \key{seuraajaverkko},
 | 
			
		||||
jolloin jokaisen solmun lähtöaste on 1
 | 
			
		||||
eli siitä lähtee tasan yksi kaari ulospäin.
 | 
			
		||||
Niinpä verkko muodostuu yhdestä tai useammasta
 | 
			
		||||
komponentista, joista jokaisessa on yksi sykli
 | 
			
		||||
ja joukko siihen johtavia polkuja.
 | 
			
		||||
 | 
			
		||||
Seuraajaverkosta käytetään joskus nimeä
 | 
			
		||||
\key{funktionaalinen verkko}.
 | 
			
		||||
Tämä johtuu siitä, että jokaista seuraajaverkkoa
 | 
			
		||||
vastaa funktio $f$, joka määrittelee verkon kaaret.
 | 
			
		||||
Funktion parametrina on verkon solmu ja
 | 
			
		||||
se palauttaa solmusta lähtevän kaaren kohdesolmun.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi funktio
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrrrrrr}
 | 
			
		||||
$x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
 | 
			
		||||
\hline
 | 
			
		||||
$f(x)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
määrittelee seuraavan verkon:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,-3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (4,0) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (2,-1.5) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (-2,-1.5) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (3,-3) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (-4,0) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->] (2)  edge [bend left=40] (5);
 | 
			
		||||
\path[draw,thick,->] (3) -- (7);
 | 
			
		||||
\path[draw,thick,->] (4) -- (6);
 | 
			
		||||
\path[draw,thick,->] (5)  edge [bend left=40] (2);
 | 
			
		||||
\path[draw,thick,->] (6) -- (2);
 | 
			
		||||
\path[draw,thick,->] (7) -- (1);
 | 
			
		||||
\path[draw,thick,->] (8) -- (6);
 | 
			
		||||
\path[draw,thick,->] (9) -- (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Koska seuraajaverkon jokaisella solmulla
 | 
			
		||||
on yksikäsitteinen seuraaja, voimme määritellä funktion $f(x,k)$,
 | 
			
		||||
joka kertoo solmun, johon päätyy solmusta $x$
 | 
			
		||||
kulkemalla $k$ askelta.
 | 
			
		||||
Esimerkiksi yllä olevassa verkossa $f(4,6)=2$,
 | 
			
		||||
koska solmusta 4 päätyy solmuun 2 kulkemalla 6 askelta:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$4$};
 | 
			
		||||
\node[draw, circle] (2) at (1.5,0) {$6$};
 | 
			
		||||
\node[draw, circle] (3) at (3,0) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (4.5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (6,0) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (7.5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (9,0) {$2$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (6);
 | 
			
		||||
\path[draw,thick,->] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Suoraviivainen tapa laskea arvo $f(x,k)$
 | 
			
		||||
on käydä läpi polku askel askeleelta, mihin kuluu aikaa $O(k)$.
 | 
			
		||||
Sopivan esikäsittelyn avulla voimme laskea kuitenkin
 | 
			
		||||
minkä tahansa arvon $f(x,k)$ ajassa $O(\log k)$.
 | 
			
		||||
 | 
			
		||||
Ideana on laskea etukäteen kaikki arvot $f(x,k)$, kun $k$ on 2:n potenssi
 | 
			
		||||
ja enintään $u$, missä $u$ on suurin mahdollinen määrä
 | 
			
		||||
askeleita, joista olemme kiinnostuneita.
 | 
			
		||||
Tämä onnistuu tehokkaasti, koska voimme käyttää rekursiota
 | 
			
		||||
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    f(x,k) = \begin{cases}
 | 
			
		||||
               f(x)              & k = 1\\
 | 
			
		||||
               f(f(x,k/2),k/2)   & k > 1\\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
 | 
			
		||||
Arvojen $f(x,k)$ esilaskenta vie aikaa $O(n \log u)$,
 | 
			
		||||
koska jokaisesta solmusta lasketaan $O(\log u)$ arvoa.
 | 
			
		||||
Esimerkin tapauksessa taulukko alkaa muodostua seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrrrrrr}
 | 
			
		||||
$x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \\
 | 
			
		||||
\hline
 | 
			
		||||
$f(x,1)$ & 3 & 5 & 7 & 6 & 2 & 2 & 1 & 6 & 3 \\
 | 
			
		||||
$f(x,2)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\
 | 
			
		||||
$f(x,4)$ & 3 & 2 & 7 & 2 & 5 & 5 & 1 & 2 & 3 \\
 | 
			
		||||
$f(x,8)$ & 7 & 2 & 1 & 2 & 5 & 5 & 3 & 2 & 7 \\
 | 
			
		||||
$\cdots$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen funktion $f(x,k)$ arvon saa laskettua
 | 
			
		||||
esittämällä luvun $k$ summana 2:n potensseja.
 | 
			
		||||
Esimerkiksi jos haluamme laskea arvon $f(x,11)$,
 | 
			
		||||
muodostamme ensin esityksen $11=8+2+1$.
 | 
			
		||||
Tämän ansiosta
 | 
			
		||||
\[f(x,11)=f(f(f(x,8),2),1).\]
 | 
			
		||||
Esimerkiksi yllä olevassa verkossa 
 | 
			
		||||
\[f(4,11)=f(f(f(4,8),2),1)=5.\]
 | 
			
		||||
 | 
			
		||||
Tällaisessa esityksessä on aina
 | 
			
		||||
$O(\log k)$ osaa, joten arvon $f(x,k)$ laskemiseen
 | 
			
		||||
kuluu aikaa $O(\log k)$.
 | 
			
		||||
 | 
			
		||||
\section{Syklin tunnistaminen}
 | 
			
		||||
 | 
			
		||||
\index{sykli@sykli}
 | 
			
		||||
\index{syklin tunnistaminen@syklin tunnistaminen}
 | 
			
		||||
 | 
			
		||||
Kiinnostavia kysymyksiä seuraajaverkossa ovat,
 | 
			
		||||
minkä solmun kohdalla saavutaan sykliin
 | 
			
		||||
solmusta $x$ lähdettäessä
 | 
			
		||||
ja montako solmua kyseiseen sykliin kuuluu.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (5) at (0,0) {$5$};
 | 
			
		||||
\node[draw, circle] (4) at (-2,0) {$4$};
 | 
			
		||||
\node[draw, circle] (6) at (-1,1.5) {$6$};
 | 
			
		||||
\node[draw, circle] (3) at (-4,0) {$3$};
 | 
			
		||||
\node[draw, circle] (2) at (-6,0) {$2$};
 | 
			
		||||
\node[draw, circle] (1) at (-8,0) {$1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (6);
 | 
			
		||||
\path[draw,thick,->] (6) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmusta 1 lähdettäessä ensimmäinen sykliin kuuluva
 | 
			
		||||
solmu on solmu 4 ja syklissä on kolme solmua
 | 
			
		||||
(solmut 4, 5 ja 6).
 | 
			
		||||
 | 
			
		||||
Helppo tapa tunnistaa sykli on alkaa kulkea verkossa
 | 
			
		||||
solmusta $x$ alkaen ja pitää kirjaa kaikista vastaan tulevista
 | 
			
		||||
solmuista. Kun jokin solmu tulee vastaan toista kertaa,
 | 
			
		||||
sykli on löytynyt. Tämän menetelmän aikavaativuus on $O(n)$
 | 
			
		||||
ja muistia kuluu myös $O(n)$.
 | 
			
		||||
 | 
			
		||||
Osoittautuu kuitenkin, että syklin tunnistamiseen on
 | 
			
		||||
olemassa parempia algoritmeja.
 | 
			
		||||
Niissä aikavaativuus on edelleen $O(n)$,
 | 
			
		||||
mutta muistia kuluu vain $O(1)$.
 | 
			
		||||
Tästä on merkittävää hyötyä, jos $n$ on suuri.
 | 
			
		||||
Tutustumme seuraavaksi Floydin algoritmiin,
 | 
			
		||||
joka saavuttaa nämä ominaisuudet.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Floydin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Floydin algoritmi@Floydin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Floydin algoritmi} kulkee verkossa eteenpäin rinnakkain
 | 
			
		||||
kahdesta kohdasta.
 | 
			
		||||
Algoritmissa on kaksi osoitinta $a$ ja $b$,
 | 
			
		||||
jotka molemmat ovat ensin alkusolmussa.
 | 
			
		||||
Osoitin $a$ liikkuu joka vuorolla askeleen eteenpäin,
 | 
			
		||||
kun taas osoitin $b$ liikkuu kaksi askelta eteenpäin.
 | 
			
		||||
Haku jatkuu, kunnes osoittimet kohtaavat:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
a = f(x);
 | 
			
		||||
b = f(f(x));
 | 
			
		||||
while (a != b) {
 | 
			
		||||
    a = f(a);
 | 
			
		||||
    b = f(f(b));
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tässä vaiheessa osoitin $a$ on kulkenut $k$ askelta
 | 
			
		||||
ja osoitin $b$ on kulkenut $2k$ askelta,
 | 
			
		||||
missä $k$ on jaollinen syklin pituudella.
 | 
			
		||||
Niinpä ensimmäinen sykliin kuuluva solmu löytyy siirtämällä
 | 
			
		||||
osoitin $a$ alkuun ja liikuttamalla osoittimia
 | 
			
		||||
rinnakkain eteenpäin, kunnes ne kohtaavat:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
a = x;
 | 
			
		||||
while (a != b) {
 | 
			
		||||
    a = f(a);
 | 
			
		||||
    b = f(b);
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Nyt $a$ ja $b$ osoittavat ensimmäiseen syklin
 | 
			
		||||
solmuun, joka tulee vastaan solmusta $x$ lähdettäessä.
 | 
			
		||||
Lopuksi syklin pituus $c$ voidaan laskea näin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
b = f(a);
 | 
			
		||||
c = 1;
 | 
			
		||||
while (a != b) {
 | 
			
		||||
    b = f(b);
 | 
			
		||||
    c++;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% \subsubsection{Algoritmi 2 (Brent)}
 | 
			
		||||
% 
 | 
			
		||||
% \index{Brentin algoritmi@Brentin algoritmi}
 | 
			
		||||
% 
 | 
			
		||||
% Brentin algoritmi
 | 
			
		||||
% muodostuu peräkkäisistä vaiheista, joissa osoitin $a$ pysyy
 | 
			
		||||
% paikallaan ja osoitin $b$ liikkuu $k$ askelta
 | 
			
		||||
% Alussa $k=1$ ja $k$:n arvo kaksinkertaistuu
 | 
			
		||||
% joka vaiheen alussa.
 | 
			
		||||
% Lisäksi $a$ siirretään $b$:n kohdalle vaiheen alussa.
 | 
			
		||||
% Näin jatketaan, kunnes osoittimet kohtaavat.
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% a = x;
 | 
			
		||||
% b = f(x);
 | 
			
		||||
% c = k = 1;
 | 
			
		||||
% while (a != b) {
 | 
			
		||||
%     if (c == k) {
 | 
			
		||||
%         a = b;
 | 
			
		||||
%         c = 0;
 | 
			
		||||
%         k *= 2;
 | 
			
		||||
%     }
 | 
			
		||||
%     b = f(b);
 | 
			
		||||
%     c++;
 | 
			
		||||
% }
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% Nyt tiedossa on, että syklin pituus on $c$.
 | 
			
		||||
% Tämän jälkeen ensimmäinen sykliin kuuluva solmu löytyy
 | 
			
		||||
% palauttamalla ensin osoittimet alkuun,
 | 
			
		||||
% siirtämällä sitten osoitinta $b$ eteenpäin $c$ askelta
 | 
			
		||||
% ja liikuttamalla lopuksi osoittimia rinnakkain,
 | 
			
		||||
% kunnes ne osoittavat samaan solmuun.
 | 
			
		||||
% 
 | 
			
		||||
% \begin{lstlisting}
 | 
			
		||||
% a = b = x;
 | 
			
		||||
% for (int i = 0; i < c; i++) b = f(b);
 | 
			
		||||
% while (a != b) {
 | 
			
		||||
%     a = f(a);
 | 
			
		||||
%     b = f(b);
 | 
			
		||||
% }
 | 
			
		||||
% \end{lstlisting}
 | 
			
		||||
% 
 | 
			
		||||
% Nyt $a$ ja $b$ osoittavat ensimmäiseen sykliin kuuluvaan solmuun.
 | 
			
		||||
% 
 | 
			
		||||
% Brentin algoritmin etuna Floydin algoritmiin verrattuna on,
 | 
			
		||||
% että se kutsuu funktiota $f$ harvemmin.
 | 
			
		||||
% Floydin algoritmi kutsuu funktiota $f$ ensimmäisessä silmukassa
 | 
			
		||||
% kolmesti joka kierroksella, kun taas Brentin algoritmi
 | 
			
		||||
% kutsuu funktiota $f$ vain kerran kierrosta kohden.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,548 @@
 | 
			
		|||
\chapter{Strongly connectivity}
 | 
			
		||||
 | 
			
		||||
\index{vahvasti yhtenxinen verkko@vahvasti yhtenäinen verkko}
 | 
			
		||||
 | 
			
		||||
Suunnatussa verkossa yhtenäisyys ei takaa sitä,
 | 
			
		||||
että kahden solmun välillä olisi olemassa polkua,
 | 
			
		||||
koska kaarten suunnat rajoittavat liikkumista.
 | 
			
		||||
Niinpä suunnattuja verkkoja varten on mielekästä
 | 
			
		||||
määritellä uusi käsite,
 | 
			
		||||
joka vaatii enemmän verkon yhtenäisyydeltä.
 | 
			
		||||
 | 
			
		||||
Verkko on \key{vahvasti yhtenäinen},
 | 
			
		||||
jos mistä tahansa solmusta on olemassa polku
 | 
			
		||||
kaikkiin muihin solmuihin.
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa vasen
 | 
			
		||||
verkko on vahvasti yhtenäinen,
 | 
			
		||||
kun taas oikea verkko ei ole.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,1) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,-1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (1);
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (1b) at (6,1) {$1$};
 | 
			
		||||
\node[draw, circle] (2b) at (8,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3b) at (6,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (4b) at (8,-1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1b) -- (2b);
 | 
			
		||||
\path[draw,thick,->] (2b) -- (4b);
 | 
			
		||||
\path[draw,thick,->] (4b) -- (3b);
 | 
			
		||||
\path[draw,thick,->] (1b) -- (3b);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Oikea verkko ei ole vahvasti yhtenäinen,
 | 
			
		||||
koska esimerkiksi solmusta 2 ei ole
 | 
			
		||||
polkua solmuun 1.
 | 
			
		||||
 | 
			
		||||
\index{vahvasti yhtenxinen komponentti@vahvasti yhtenäinen komponentti}
 | 
			
		||||
\index{komponenttiverkko@komponenttiverkko}
 | 
			
		||||
 | 
			
		||||
Verkon \key{vahvasti yhtenäiset komponentit}
 | 
			
		||||
jakavat verkon solmut mahdollisimman
 | 
			
		||||
suuriin vahvasti yhtenäisiin osiin.
 | 
			
		||||
Verkon vahvasti yhtenäiset komponentit
 | 
			
		||||
muodostavat syklittömän \key{komponenttiverkon},
 | 
			
		||||
joka kuvaa alkuperäisen verkon syvärakennetta.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,->] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (7);
 | 
			
		||||
\path[draw,thick,->] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
vahvasti yhtenäiset komponentit ovat
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,->] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (7);
 | 
			
		||||
\path[draw,thick,->] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-0.5,2.5) rectangle (-3.5,-0.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-4.5,2.5) rectangle (-7.5,1.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-4.5,0.5) rectangle (-5.5,-0.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-6.5,0.5) rectangle (-7.5,-0.5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja ne muodostavat seuraavan komponenttiverkon:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (-3,1) {$B$};
 | 
			
		||||
\node[draw, circle] (2) at (-6,2) {$A$};
 | 
			
		||||
\node[draw, circle] (3) at (-5,0) {$D$};
 | 
			
		||||
\node[draw, circle] (4) at (-7,0) {$C$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Komponentit ovat $A=\{1,2\}$,
 | 
			
		||||
$B=\{3,6,7\}$, $C=\{4\}$ sekä $D=\{5\}$.
 | 
			
		||||
 | 
			
		||||
Komponenttiverkko on syklitön, suunnattu verkko,
 | 
			
		||||
jonka käsittely on alkuperäistä verkkoa
 | 
			
		||||
helpompaa, koska siinä ei ole syklejä.
 | 
			
		||||
Niinpä komponenttiverkolle voi muodostaa
 | 
			
		||||
luvun 16 tapaan topologisen järjestyksen
 | 
			
		||||
ja soveltaa sen jälkeen dynaamista ohjelmintia
 | 
			
		||||
verkon käsittelyyn.
 | 
			
		||||
 | 
			
		||||
\section{Kosarajun algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Kosarajun algoritmi@Kosarajun algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Kosarajun algoritmi} on tehokas
 | 
			
		||||
menetelmä verkon
 | 
			
		||||
vahvasti yhtenäisten komponenttien etsimiseen.
 | 
			
		||||
Se suorittaa verkkoon
 | 
			
		||||
kaksi syvyyshakua, joista ensimmäinen
 | 
			
		||||
kerää solmut listaan verkon rakenteen perusteella
 | 
			
		||||
ja toinen muodostaa vahvasti yhtenäiset komponentit.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Syvyyshaku 1}
 | 
			
		||||
 | 
			
		||||
Algoritmin ensimmäinen vaihe muodostaa listan solmuista
 | 
			
		||||
syvyyshaun käsittelyjärjestyksessä.
 | 
			
		||||
Algoritmi käy solmut läpi yksi kerrallaan,
 | 
			
		||||
ja jos solmua ei ole vielä käsitelty, algoritmi suorittaa
 | 
			
		||||
solmusta alkaen syvyyshaun.
 | 
			
		||||
Solmu lisätään listalle, kun syvyyshaku on 
 | 
			
		||||
käsitellyt kaikki siitä lähtevät kaaret.
 | 
			
		||||
 | 
			
		||||
Esimerkkiverkossa solmujen käsittelyjärjestys on:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\node at (-7,2.75) {$1/8$};
 | 
			
		||||
\node at (-5,2.75) {$2/7$};
 | 
			
		||||
\node at (-3,2.75) {$9/14$};
 | 
			
		||||
\node at (-7,-0.75) {$4/5$};
 | 
			
		||||
\node at (-5,-0.75) {$3/6$};
 | 
			
		||||
\node at (-3,-0.75) {$11/12$};
 | 
			
		||||
\node at (-1,1.75) {$10/13$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->] (1) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (4);
 | 
			
		||||
\path[draw,thick,->] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,->] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (5) -- (7);
 | 
			
		||||
\path[draw,thick,->] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Solmun kohdalla oleva merkintä $x/y$ tarkoittaa, että solmun
 | 
			
		||||
käsittely syvyyshaussa alkoi hetkellä $x$ ja päättyi hetkellä $y$.
 | 
			
		||||
Kun solmut järjestetään käsittelyn päättymisajan
 | 
			
		||||
mukaan, tuloksena on seuraava järjestys:
 | 
			
		||||
 | 
			
		||||
\begin{tabular}{ll}
 | 
			
		||||
\\
 | 
			
		||||
solmu & päättymisaika \\
 | 
			
		||||
\hline
 | 
			
		||||
4 & 5 \\
 | 
			
		||||
5 & 6 \\
 | 
			
		||||
2 & 7 \\
 | 
			
		||||
1 & 8 \\
 | 
			
		||||
6 & 12 \\
 | 
			
		||||
7 & 13 \\
 | 
			
		||||
3 & 14 \\
 | 
			
		||||
\\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
 | 
			
		||||
Solmujen käsittelyjärjestys algoritmin seuraavassa vaiheessa
 | 
			
		||||
tulee olemaan tämä järjestys käänteisenä eli $[3,7,6,1,2,5,4]$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Syvyyshaku 2}
 | 
			
		||||
 | 
			
		||||
Algoritmin toinen vaihe muodostaa verkon vahvasti
 | 
			
		||||
yhtenäiset komponentit.
 | 
			
		||||
Ennen toista syvyyshakua algoritmi muuttaa
 | 
			
		||||
jokaisen kaaren suunnan käänteiseksi.
 | 
			
		||||
Tämä varmistaa,
 | 
			
		||||
että toisen syvyyshaun aikana löydetään
 | 
			
		||||
joka kerta vahvasti yhtenäinen komponentti,
 | 
			
		||||
johon ei kuulu ylimääräisiä solmuja.
 | 
			
		||||
 | 
			
		||||
Esimerkkiverkko on käännettynä seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,<-] (2) -- (1);
 | 
			
		||||
\path[draw,thick,<-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (2);
 | 
			
		||||
\path[draw,thick,<-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,<-] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,<-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (5) -- (7);
 | 
			
		||||
\path[draw,thick,<-] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän jälkeen algoritmi käy läpi
 | 
			
		||||
solmut käänteisessä ensimmäisen syvyyshaun
 | 
			
		||||
tuottamassa järjestyksessä.
 | 
			
		||||
Jos solmu ei kuulu vielä komponenttiin,
 | 
			
		||||
siitä alkaa uusi syvyyshaku.
 | 
			
		||||
Solmun komponenttiin liitetään kaikki aiemmin
 | 
			
		||||
käsittelemättömät solmut,
 | 
			
		||||
joihin syvyyshaku pääsee solmusta.
 | 
			
		||||
 | 
			
		||||
Esimerkkiverkossa muodostuu ensin komponentti
 | 
			
		||||
solmusta 3 alkaen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,<-] (2) -- (1);
 | 
			
		||||
\path[draw,thick,<-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (2);
 | 
			
		||||
\path[draw,thick,<-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,<-] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,<-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (5) -- (7);
 | 
			
		||||
\path[draw,thick,<-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-0.5,2.5) rectangle (-3.5,-0.5);
 | 
			
		||||
%\draw [red,thick,dashed,line width=2pt] (-4.5,2.5) rectangle (-7.5,1.5);
 | 
			
		||||
%\draw [red,thick,dashed,line width=2pt] (-4.5,0.5) rectangle (-5.5,-0.5);
 | 
			
		||||
%\draw [red,thick,dashed,line width=2pt] (-6.5,0.5) rectangle (-7.5,-0.5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Huomaa, että kaarten kääntämisen ansiosta komponentti
 | 
			
		||||
ei pääse ''vuotamaan'' muihin verkon osiin.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Sitten listalla ovat solmut 7 ja 6, mutta ne on jo liitetty
 | 
			
		||||
komponenttiin.
 | 
			
		||||
Seuraava uusi solmu on 1, josta muodostuu uusi komponentti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,<-] (2) -- (1);
 | 
			
		||||
\path[draw,thick,<-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (2);
 | 
			
		||||
\path[draw,thick,<-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,<-] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,<-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (5) -- (7);
 | 
			
		||||
\path[draw,thick,<-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-0.5,2.5) rectangle (-3.5,-0.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-4.5,2.5) rectangle (-7.5,1.5);
 | 
			
		||||
%\draw [red,thick,dashed,line width=2pt] (-4.5,0.5) rectangle (-5.5,-0.5);
 | 
			
		||||
%\draw [red,thick,dashed,line width=2pt] (-6.5,0.5) rectangle (-7.5,-0.5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Viimeisenä algoritmi käsittelee solmut 5 ja 4,
 | 
			
		||||
jotka tuottavat loput vahvasti yhtenäiset komponentit:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9,label distance=-2mm]
 | 
			
		||||
\node[draw, circle] (1) at (-1,1) {$7$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,2) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (-5,2) {$2$};
 | 
			
		||||
\node[draw, circle] (6) at (-7,2) {$1$};
 | 
			
		||||
\node[draw, circle] (3) at (-3,0) {$6$};
 | 
			
		||||
\node[draw, circle] (5) at (-5,0) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-7,0) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,<-] (2) -- (1);
 | 
			
		||||
\path[draw,thick,<-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (2);
 | 
			
		||||
\path[draw,thick,<-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,<-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (4) edge [bend left] (6);
 | 
			
		||||
\path[draw,thick,<-] (6) edge [bend left] (4);
 | 
			
		||||
\path[draw,thick,<-] (4) -- (5);
 | 
			
		||||
\path[draw,thick,<-] (5) -- (7);
 | 
			
		||||
\path[draw,thick,<-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-0.5,2.5) rectangle (-3.5,-0.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-4.5,2.5) rectangle (-7.5,1.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-4.5,0.5) rectangle (-5.5,-0.5);
 | 
			
		||||
\draw [red,thick,dashed,line width=2pt] (-6.5,0.5) rectangle (-7.5,-0.5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on $O(n+m)$,
 | 
			
		||||
missä $n$ on solmujen määrä ja $m$ on kaarten määrä.
 | 
			
		||||
Tämä johtuu siitä,
 | 
			
		||||
että algoritmi suorittaa kaksi syvyyshakua ja
 | 
			
		||||
kummankin haun aikavaativuus on $O(n+m)$.
 | 
			
		||||
 | 
			
		||||
\section{2SAT-ongelma}
 | 
			
		||||
 | 
			
		||||
\index{2SAT-ongelma}
 | 
			
		||||
 | 
			
		||||
Vahvasti yhtenäisyys liittyy myös \key{2SAT-ongelman} ratkaisemiseen.
 | 
			
		||||
Siinä annettuna on looginen lauseke muotoa
 | 
			
		||||
\[
 | 
			
		||||
(a_1 \lor b_1) \land (a_2 \lor b_2) \land \cdots \land (a_m \lor b_m),
 | 
			
		||||
\]
 | 
			
		||||
jossa jokainen $a_i$ ja $b_i$ on joko looginen muuttuja
 | 
			
		||||
($x_1,x_2,\ldots,x_n$)
 | 
			
		||||
tai loogisen muuttujan negaatio ($\lnot x_1, \lnot x_2, \ldots, \lnot x_n$).
 | 
			
		||||
Merkit ''$\land$'' ja ''$\lor$'' tarkoittavat
 | 
			
		||||
loogisia operaatioita ''ja'' ja ''tai''.
 | 
			
		||||
Tehtävänä on valita muuttujille arvot niin,
 | 
			
		||||
että lauseke on tosi,
 | 
			
		||||
tai todeta, että tämä ei ole mahdollista.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi lauseke
 | 
			
		||||
\[
 | 
			
		||||
L_1 = (x_2 \lor \lnot x_1) \land
 | 
			
		||||
      (\lnot x_1 \lor \lnot x_2) \land
 | 
			
		||||
      (x_1 \lor x_3) \land
 | 
			
		||||
      (\lnot x_2 \lor \lnot x_3) \land
 | 
			
		||||
      (x_1 \lor x_4)
 | 
			
		||||
\]
 | 
			
		||||
on tosi, kun $x_1$ ja $x_2$ ovat epätosia
 | 
			
		||||
ja $x_3$ ja $x_4$ ovat tosia.
 | 
			
		||||
Vastaavasti lauseke
 | 
			
		||||
\[
 | 
			
		||||
L_2 = (x_1 \lor x_2) \land
 | 
			
		||||
      (x_1 \lor \lnot x_2) \land
 | 
			
		||||
      (\lnot x_1 \lor x_3) \land
 | 
			
		||||
      (\lnot x_1 \lor \lnot x_3)
 | 
			
		||||
\]
 | 
			
		||||
on epätosi riippumatta muuttujien valinnasta.
 | 
			
		||||
Tämän näkee siitä, että muuttujalle $x_1$
 | 
			
		||||
ei ole mahdollista arvoa, joka ei tuottaisi ristiriitaa.
 | 
			
		||||
Jos $x_1$ on epätosi, pitäisi päteä sekä $x_2$ että $\lnot x_2$,
 | 
			
		||||
mikä on mahdotonta.
 | 
			
		||||
Jos taas $x_1$ on tosi,
 | 
			
		||||
pitäisi päteä sekä $x_3$ että $\lnot x_3$,
 | 
			
		||||
mikä on myös mahdotonta.
 | 
			
		||||
 | 
			
		||||
2SAT-ongelman saa muutettua verkoksi niin,
 | 
			
		||||
että jokainen muuttuja $x_i$ ja negaatio $\lnot x_i$
 | 
			
		||||
on yksi verkon solmuista
 | 
			
		||||
ja muuttujien riippuvuudet ovat kaaria.
 | 
			
		||||
Jokaisesta parista $(a_i \lor b_i)$ tulee kaksi
 | 
			
		||||
kaarta: $\lnot a_i \to b_i$ sekä $\lnot b_i \to a_i$.
 | 
			
		||||
Nämä tarkoittavat, että jos $a_i$ ei päde,
 | 
			
		||||
niin $b_i$:n on pakko päteä, ja päinvastoin.
 | 
			
		||||
 | 
			
		||||
Lausekkeen $L_1$ verkosta tulee nyt:
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=1.0,minimum size=2pt]
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (1) at (1,2) {$\lnot x_3$};
 | 
			
		||||
\node[draw, circle] (2) at (3,2) {$x_2$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (3) at (1,0) {$\lnot x_4$};
 | 
			
		||||
\node[draw, circle] (4) at (3,0) {$x_1$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (5) at (5,2) {$\lnot x_1$};
 | 
			
		||||
\node[draw, circle] (6) at (7,2) {$x_4$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (7) at (5,0) {$\lnot x_2$};
 | 
			
		||||
\node[draw, circle] (8) at (7,0) {$x_3$};
 | 
			
		||||
 
 | 
			
		||||
\path[draw,thick,->] (1) -- (4);
 | 
			
		||||
\path[draw,thick,->] (4) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\path[draw,thick,->] (2) -- (5);
 | 
			
		||||
\path[draw,thick,->] (4) -- (7);
 | 
			
		||||
\path[draw,thick,->] (5) -- (6);
 | 
			
		||||
\path[draw,thick,->] (5) -- (8);
 | 
			
		||||
\path[draw,thick,->] (8) -- (7);
 | 
			
		||||
\path[draw,thick,->] (7) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Lausekkeen $L_2$ verkosta taas tulee:
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=1.0,minimum size=2pt]
 | 
			
		||||
\node[draw, circle] (1) at (1,2) {$x_3$};
 | 
			
		||||
\node[draw, circle] (2) at (3,2) {$x_2$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (3) at (5,2) {$\lnot x_2$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (4) at (7,2) {$\lnot x_3$};
 | 
			
		||||
\node[draw, circle, inner sep=1.3pt] (5) at (4,3.5) {$\lnot x_1$};
 | 
			
		||||
\node[draw, circle] (6) at (4,0.5) {$x_1$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (5);
 | 
			
		||||
\path[draw,thick,->] (4) -- (5);
 | 
			
		||||
\path[draw,thick,->] (6) -- (1);
 | 
			
		||||
\path[draw,thick,->] (6) -- (4);
 | 
			
		||||
\path[draw,thick,->] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->] (5) -- (3);
 | 
			
		||||
\path[draw,thick,->] (2) -- (6);
 | 
			
		||||
\path[draw,thick,->] (3) -- (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Verkon rakenne kertoo, onko 2SAT-ongelmalla ratkaisua.
 | 
			
		||||
Jos on jokin muuttuja $x_i$ niin,
 | 
			
		||||
että $x_i$ ja $\lnot x_i$ ovat samassa
 | 
			
		||||
vahvasti yhtenäisessä komponentissa,
 | 
			
		||||
niin ratkaisua ei ole olemassa.
 | 
			
		||||
Tällöin verkossa on polku sekä $x_i$:stä
 | 
			
		||||
$\lnot x_i$:ään että $\lnot x_i$:stä $x_i$:ään,
 | 
			
		||||
eli kumman tahansa arvon valitseminen
 | 
			
		||||
muuttujalle $x_i$ pakottaisi myös valitsemaan
 | 
			
		||||
vastakkaisen arvon, mikä on ristiriita.
 | 
			
		||||
Jos taas verkossa ei ole tällaista
 | 
			
		||||
solmua $x_i$, ratkaisu on olemassa.
 | 
			
		||||
 | 
			
		||||
Lausekkeen $L_1$ verkossa
 | 
			
		||||
mitkään solmut $x_i$ ja $\lnot x_i$
 | 
			
		||||
eivät ole samassa vahvasti yhtenäisessä komponentissa,
 | 
			
		||||
mikä tarkoittaa, että ratkaisu on olemassa.
 | 
			
		||||
Lausekkeen $L_2$ verkossa taas kaikki solmut
 | 
			
		||||
ovat samassa vahvasti yhtenäisessä komponentissa,
 | 
			
		||||
eli ratkaisua ei ole olemassa.
 | 
			
		||||
 | 
			
		||||
Jos ratkaisu on olemassa, muuttujien arvot saa selville
 | 
			
		||||
käymällä komponenttiverkko läpi käänteisessä
 | 
			
		||||
topologisessa järjestyksessä.
 | 
			
		||||
Verkosta otetaan käsittelyyn ja poistetaan
 | 
			
		||||
joka vaiheessa komponentti,
 | 
			
		||||
josta ei lähde kaaria muihin jäljellä
 | 
			
		||||
oleviin komponentteihin.
 | 
			
		||||
Jos komponentin muuttujille ei ole vielä valittu arvoja,
 | 
			
		||||
ne saavat komponentin mukaiset arvot.
 | 
			
		||||
Jos taas arvot on jo valittu, niitä ei muuteta.
 | 
			
		||||
Näin jatketaan, kunnes jokainen lausekkeen muuttuja on saanut arvon.
 | 
			
		||||
 | 
			
		||||
Lausekkeen $L_1$ verkon komponenttiverkko on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=1.0]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$A$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$B$};
 | 
			
		||||
\node[draw, circle] (3) at (4,0) {$C$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$D$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->] (3) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Komponentit ovat
 | 
			
		||||
$A = \{\lnot x_4\}$,
 | 
			
		||||
$B = \{x_1, x_2, \lnot x_3\}$,
 | 
			
		||||
$C = \{\lnot x_1, \lnot x_2, x_3\}$ sekä
 | 
			
		||||
$D = \{x_4\}$.
 | 
			
		||||
Ratkaisun muodostuksessa
 | 
			
		||||
käsitellään ensin komponentti $D$,
 | 
			
		||||
josta $x_4$ saa arvon tosi.
 | 
			
		||||
Sitten käsitellään komponentti $C$,
 | 
			
		||||
josta $x_1$ ja $x_2$ tulevat epätodeksi
 | 
			
		||||
ja $x_3$ tulee todeksi.
 | 
			
		||||
Kaikki muuttujat ovat saaneet arvon,
 | 
			
		||||
joten myöhemmin käsiteltävät
 | 
			
		||||
komponentit $B$ ja $A$ eivät vaikuta enää ratkaisuun.
 | 
			
		||||
 | 
			
		||||
Huomaa, että tämän menetelmän toiminta
 | 
			
		||||
perustuu verkon erityiseen rakenteeseen.
 | 
			
		||||
Jos solmusta $x_i$ pääsee
 | 
			
		||||
solmuun $x_j$,
 | 
			
		||||
josta pääsee solmuun $\lnot x_j$,
 | 
			
		||||
niin $x_i$ ei saa koskaan arvoa tosi.
 | 
			
		||||
Tämä johtuu siitä, että
 | 
			
		||||
solmusta $\lnot x_j$ täytyy
 | 
			
		||||
päästä myös solmuun $\lnot x_i$,
 | 
			
		||||
koska kaikki riippuvuudet
 | 
			
		||||
ovat verkossa molempiin suuntiin.
 | 
			
		||||
Niinpä sekä $x_i$ että $x_j$
 | 
			
		||||
saavat varmasti arvokseen epätosi.
 | 
			
		||||
 | 
			
		||||
\index{3SAT-ongelma}
 | 
			
		||||
 | 
			
		||||
2SAT-ongelman vaikeampi versio on \key{3SAT-ongelma},
 | 
			
		||||
jossa jokainen lausekkeen osa on muotoa
 | 
			
		||||
$(a_i \lor b_i \lor c_i)$.
 | 
			
		||||
Tämän ongelman ratkaisemiseen \textit{ei}
 | 
			
		||||
tunneta tehokasta menetelmää,
 | 
			
		||||
vaan kyseessä on NP-vaikea ongelma.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,912 @@
 | 
			
		|||
\chapter{Tree queries}
 | 
			
		||||
 | 
			
		||||
\index{puukysely@puukysely}
 | 
			
		||||
 | 
			
		||||
Tässä luvussa tutustumme algoritmeihin,
 | 
			
		||||
joiden avulla voi toteuttaa tehokkaasti kyselyitä
 | 
			
		||||
juurelliseen puuhun.
 | 
			
		||||
Kyselyt liittyvät puussa oleviin polkuihin
 | 
			
		||||
ja alipuihin.
 | 
			
		||||
Esimerkkejä kyselyistä ovat:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item mikä solmu on $k$ askelta ylempänä solmua $x$?
 | 
			
		||||
\item mikä on solmun $x$ alipuun arvojen summa?
 | 
			
		||||
\item mikä on solmujen $a$ ja $b$ välisen polun arvojen summa?
 | 
			
		||||
\item mikä on solmujen $a$ ja $b$ alin yhteinen esivanhempi?
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\section{Tehokas nouseminen}
 | 
			
		||||
 | 
			
		||||
Tehokas nouseminen puussa tarkoittaa,
 | 
			
		||||
että voimme selvittää nopeasti,
 | 
			
		||||
mihin solmuun päätyy kulkemalla
 | 
			
		||||
$k$ askelta ylöspäin solmusta $x$ alkaen.
 | 
			
		||||
Merkitään $f(x,k)$ solmua,
 | 
			
		||||
joka on $k$ askelta ylempänä solmua $x$.
 | 
			
		||||
Esimerkiksi seuraavassa puussa
 | 
			
		||||
$f(2,1)=1$ ja $f(8,2)=4$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$5$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (8) edge [bend left] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right] (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Suoraviivainen tapa laskea funktion $f(x,k)$
 | 
			
		||||
arvo on kulkea puussa $k$ askelta ylöspäin
 | 
			
		||||
solmusta $x$ alkaen.
 | 
			
		||||
Tämän aikavaativuus on kuitenkin $O(n)$,
 | 
			
		||||
koska on mahdollista, että puussa on
 | 
			
		||||
ketju, jossa on $O(n)$ solmua.
 | 
			
		||||
 | 
			
		||||
Kuten luvussa 16.3, funktion $f(x,k)$
 | 
			
		||||
arvo on mahdollista laskea tehokkaasti ajassa
 | 
			
		||||
$O(\log k)$ sopivan esikäsittelyn avulla.
 | 
			
		||||
Ideana on laskea etukäteen kaikki arvot
 | 
			
		||||
$f(x,k)$, joissa $k=1,2,4,8,\ldots$ eli 2:n potenssi.
 | 
			
		||||
Esimerkiksi yllä olevassa puussa muodostuu seuraava taulukko:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|rrrrrrrrr}
 | 
			
		||||
$x$ & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 \\
 | 
			
		||||
\hline
 | 
			
		||||
$f(x,1)$ & 0 & 1 & 4 & 1 & 1 & 2 & 4 & 7 \\
 | 
			
		||||
$f(x,2)$ & 0 & 0 & 1 & 0 & 0 & 1 & 1 & 4 \\
 | 
			
		||||
$f(x,4)$ & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
$\cdots$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Taulukossa arvo 0 tarkoittaa, että nousemalla $k$
 | 
			
		||||
askelta päätyy puun ulkopuolelle juurisolmun yläpuolelle.
 | 
			
		||||
 | 
			
		||||
Esilaskenta vie aikaa $O(n \log n)$, koska jokaisesta
 | 
			
		||||
solmusta voi nousta korkeintaan $n$ askelta ylöspäin.
 | 
			
		||||
Tämän jälkeen minkä tahansa funktion $f(x,k)$ arvon saa
 | 
			
		||||
laskettua ajassa $O(\log k)$ jakamalla nousun 2:n
 | 
			
		||||
potenssin osiin.
 | 
			
		||||
 | 
			
		||||
\section{Solmutaulukko}
 | 
			
		||||
 | 
			
		||||
\index{solmutaulukko@solmutaulukko}
 | 
			
		||||
 | 
			
		||||
\key{Solmutaulukko} sisältää juurellisen puun solmut siinä
 | 
			
		||||
järjestyksessä kuin juuresta alkava syvyyshaku
 | 
			
		||||
vierailee solmuissa.
 | 
			
		||||
Esimerkiksi puussa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (-0.5,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (1,-1) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (2.5,-1) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (4) -- (8);
 | 
			
		||||
\path[draw,thick,-] (4) -- (9);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
syvyyshaku etenee
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (-0.5,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (1,-1) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (2.5,-1) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (4) -- (8);
 | 
			
		||||
\path[draw,thick,-] (4) -- (9);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) edge [bend right=15] (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) edge [bend right=15] (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (7);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (7) edge [bend right=15] (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (8);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (8) edge [bend right=15] (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (9);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (9) edge [bend right=15] (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) edge [bend right=15] (1);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja solmutaulukoksi tulee:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (9,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Alipuiden käsittely}
 | 
			
		||||
 | 
			
		||||
Solmutaulukossa jokaisen alipuun kaikki solmut ovat peräkkäin
 | 
			
		||||
niin, että ensin on alipuun juurisolmu ja sitten
 | 
			
		||||
kaikki muut alipuun solmut.
 | 
			
		||||
Esimerkiksi äskeisessä taulukossa solmun $4$
 | 
			
		||||
alipuuta vastaa seuraava taulukon osa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (8,1);
 | 
			
		||||
\draw (0,0) grid (9,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän ansiosta solmutaulukon avulla voi käsitellä tehokkaasti
 | 
			
		||||
puun alipuihin liittyviä kyselyitä.
 | 
			
		||||
Ratkaistaan esimerkkinä tehtävä,
 | 
			
		||||
jossa kuhunkin puun solmuun liittyy
 | 
			
		||||
arvo ja toteutettavana on seuraavat kyselyt:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item muuta solmun $x$ arvoa
 | 
			
		||||
\item laske arvojen summa solmun $x$ alipuussa
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan seuraavaa puuta,
 | 
			
		||||
jossa siniset luvut ovat solmujen arvoja.
 | 
			
		||||
Esimerkiksi solmun $4$ alipuun arvojen summa on $3+4+3+1=11$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (-0.5,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (1,-1) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (2.5,-1) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (4) -- (8);
 | 
			
		||||
\path[draw,thick,-] (4) -- (9);
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (0,3+0.65) {2};
 | 
			
		||||
\node[color=blue] at (-3-0.65,1) {3};
 | 
			
		||||
\node[color=blue] at (-1-0.65,1) {5};
 | 
			
		||||
\node[color=blue] at (1+0.65,1) {3};
 | 
			
		||||
\node[color=blue] at (3+0.65,1) {1};
 | 
			
		||||
\node[color=blue] at (-3,-1-0.65) {4};
 | 
			
		||||
\node[color=blue] at (-0.5,-1-0.65) {4};
 | 
			
		||||
\node[color=blue] at (1,-1-0.65) {3};
 | 
			
		||||
\node[color=blue] at (2.5,-1-0.65) {1};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ideana on luoda solmutaulukko, joka sisältää jokaisesta solmusta
 | 
			
		||||
kolme tietoa: (1) solmun tunnus, (2) alipuun koko ja (3) solmun arvo.
 | 
			
		||||
Esimerkiksi yllä olevasta puusta syntyy seuraava taulukko:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,1) grid (9,-2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-0.5) {$9$};
 | 
			
		||||
\node at (1.5,-0.5) {$2$};
 | 
			
		||||
\node at (2.5,-0.5) {$1$};
 | 
			
		||||
\node at (3.5,-0.5) {$1$};
 | 
			
		||||
\node at (4.5,-0.5) {$4$};
 | 
			
		||||
\node at (5.5,-0.5) {$1$};
 | 
			
		||||
\node at (6.5,-0.5) {$1$};
 | 
			
		||||
\node at (7.5,-0.5) {$1$};
 | 
			
		||||
\node at (8.5,-0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-1.5) {$2$};
 | 
			
		||||
\node at (1.5,-1.5) {$3$};
 | 
			
		||||
\node at (2.5,-1.5) {$4$};
 | 
			
		||||
\node at (3.5,-1.5) {$5$};
 | 
			
		||||
\node at (4.5,-1.5) {$3$};
 | 
			
		||||
\node at (5.5,-1.5) {$4$};
 | 
			
		||||
\node at (6.5,-1.5) {$3$};
 | 
			
		||||
\node at (7.5,-1.5) {$1$};
 | 
			
		||||
\node at (8.5,-1.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tästä taulukosta alipuun solmujen arvojen summa selviää
 | 
			
		||||
lukemalla ensin alipuun koko ja sitten sitä vastaavat solmut.
 | 
			
		||||
Esimerkiksi solmun $4$ alipuun arvojen summa selviää näin:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (4,1) rectangle (5,0);
 | 
			
		||||
\fill[color=lightgray] (4,0) rectangle (5,-1);
 | 
			
		||||
\fill[color=lightgray] (4,-1) rectangle (8,-2);
 | 
			
		||||
\draw (0,1) grid (9,-2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-0.5) {$9$};
 | 
			
		||||
\node at (1.5,-0.5) {$2$};
 | 
			
		||||
\node at (2.5,-0.5) {$1$};
 | 
			
		||||
\node at (3.5,-0.5) {$1$};
 | 
			
		||||
\node at (4.5,-0.5) {$4$};
 | 
			
		||||
\node at (5.5,-0.5) {$1$};
 | 
			
		||||
\node at (6.5,-0.5) {$1$};
 | 
			
		||||
\node at (7.5,-0.5) {$1$};
 | 
			
		||||
\node at (8.5,-0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-1.5) {$2$};
 | 
			
		||||
\node at (1.5,-1.5) {$3$};
 | 
			
		||||
\node at (2.5,-1.5) {$4$};
 | 
			
		||||
\node at (3.5,-1.5) {$5$};
 | 
			
		||||
\node at (4.5,-1.5) {$3$};
 | 
			
		||||
\node at (5.5,-1.5) {$4$};
 | 
			
		||||
\node at (6.5,-1.5) {$3$};
 | 
			
		||||
\node at (7.5,-1.5) {$1$};
 | 
			
		||||
\node at (8.5,-1.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Viimeinen tarvittava askel on tallentaa solmujen arvot
 | 
			
		||||
binääri-indeksi\-puuhun tai segmenttipuuhun.
 | 
			
		||||
Tällöin sekä alipuun arvojen summan laskeminen
 | 
			
		||||
että solmun arvon muuttaminen onnistuvat ajassa $O(\log n)$,
 | 
			
		||||
eli pystymme vastaamaan kyselyihin tehokkaasti.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Polkujen käsittely}
 | 
			
		||||
 | 
			
		||||
Solmutaulukon avulla voi myös käsitellä tehokkaasti
 | 
			
		||||
polkuja, jotka kulkevat juuresta tiettyyn solmuun puussa.
 | 
			
		||||
Ratkaistaan seuraavaksi tehtävä,
 | 
			
		||||
jossa toteutettavana on seuraavat kyselyt:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item muuta solmun $x$ arvoa
 | 
			
		||||
\item laske arvojen summa juuresta solmuun $x$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa puussa polulla solmusta 1
 | 
			
		||||
solmuun 8 arvojen summa on $4+5+3=12$.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (-3,1) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (-1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (-0.5,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (1,-1) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (2.5,-1) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (4) -- (8);
 | 
			
		||||
\path[draw,thick,-] (4) -- (9);
 | 
			
		||||
 | 
			
		||||
\node[color=blue] at (0,3+0.65) {4};
 | 
			
		||||
\node[color=blue] at (-3-0.65,1) {5};
 | 
			
		||||
\node[color=blue] at (-1-0.65,1) {3};
 | 
			
		||||
\node[color=blue] at (1+0.65,1) {5};
 | 
			
		||||
\node[color=blue] at (3+0.65,1) {2};
 | 
			
		||||
\node[color=blue] at (-3,-1-0.65) {3};
 | 
			
		||||
\node[color=blue] at (-0.5,-1-0.65) {5};
 | 
			
		||||
\node[color=blue] at (1,-1-0.65) {3};
 | 
			
		||||
\node[color=blue] at (2.5,-1-0.65) {1};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ideana on muodostaa samanlainen taulukko kuin
 | 
			
		||||
alipuiden käsittelyssä mutta tallentaa
 | 
			
		||||
solmujen arvot erikoisella tavalla:
 | 
			
		||||
kun taulukon kohdassa $k$ olevan solmun arvo on $a$,
 | 
			
		||||
kohdan $k$ arvoon lisätään $a$ ja kohdan $k+c$ arvosta
 | 
			
		||||
vähennetään $a$, missä $c$ on alipuun koko.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi yllä olevaa puuta vastaa seuraava taulukko:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,1) grid (10,-2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
\node at (9.5,0.5) {--};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-0.5) {$9$};
 | 
			
		||||
\node at (1.5,-0.5) {$2$};
 | 
			
		||||
\node at (2.5,-0.5) {$1$};
 | 
			
		||||
\node at (3.5,-0.5) {$1$};
 | 
			
		||||
\node at (4.5,-0.5) {$4$};
 | 
			
		||||
\node at (5.5,-0.5) {$1$};
 | 
			
		||||
\node at (6.5,-0.5) {$1$};
 | 
			
		||||
\node at (7.5,-0.5) {$1$};
 | 
			
		||||
\node at (8.5,-0.5) {$1$};
 | 
			
		||||
\node at (9.5,-0.5) {--};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-1.5) {$4$};
 | 
			
		||||
\node at (1.5,-1.5) {$5$};
 | 
			
		||||
\node at (2.5,-1.5) {$3$};
 | 
			
		||||
\node at (3.5,-1.5) {$-5$};
 | 
			
		||||
\node at (4.5,-1.5) {$2$};
 | 
			
		||||
\node at (5.5,-1.5) {$5$};
 | 
			
		||||
\node at (6.5,-1.5) {$-2$};
 | 
			
		||||
\node at (7.5,-1.5) {$-2$};
 | 
			
		||||
\node at (8.5,-1.5) {$-4$};
 | 
			
		||||
\node at (9.5,-1.5) {$-4$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\node at (9.5,1.4) {$10$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi solmun $3$ arvona on $-5$, koska
 | 
			
		||||
se on solmujen $2$ ja $6$ alipuiden jälkeinen solmu,
 | 
			
		||||
mistä tulee arvoa $-5-3$, ja sen oma arvo on $3$.
 | 
			
		||||
Yhteensä solmun 3 arvo on siis $-5-3+3=-5$.
 | 
			
		||||
Huomaa, että taulukossa on ylimääräinen kohta 10,
 | 
			
		||||
johon on tallennettu vain juuren arvon vastaluku.
 | 
			
		||||
 | 
			
		||||
Nyt solmujen arvojen summa polulla juuresta alkaen
 | 
			
		||||
selviää laskemalla kaikkien taulukon arvojen summa
 | 
			
		||||
taulukon alusta solmuun asti.
 | 
			
		||||
Esimerkiksi summa solmusta $1$ solmuun $8$ selviää näin:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (6,1) rectangle (7,0);
 | 
			
		||||
\fill[color=lightgray] (0,-1) rectangle (7,-2);
 | 
			
		||||
\draw (0,1) grid (10,-2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$6$};
 | 
			
		||||
\node at (3.5,0.5) {$3$};
 | 
			
		||||
\node at (4.5,0.5) {$4$};
 | 
			
		||||
\node at (5.5,0.5) {$7$};
 | 
			
		||||
\node at (6.5,0.5) {$8$};
 | 
			
		||||
\node at (7.5,0.5) {$9$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
\node at (9.5,0.5) {--};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-0.5) {$9$};
 | 
			
		||||
\node at (1.5,-0.5) {$2$};
 | 
			
		||||
\node at (2.5,-0.5) {$1$};
 | 
			
		||||
\node at (3.5,-0.5) {$1$};
 | 
			
		||||
\node at (4.5,-0.5) {$4$};
 | 
			
		||||
\node at (5.5,-0.5) {$1$};
 | 
			
		||||
\node at (6.5,-0.5) {$1$};
 | 
			
		||||
\node at (7.5,-0.5) {$1$};
 | 
			
		||||
\node at (8.5,-0.5) {$1$};
 | 
			
		||||
\node at (9.5,-0.5) {--};
 | 
			
		||||
 | 
			
		||||
\node at (0.5,-1.5) {$4$};
 | 
			
		||||
\node at (1.5,-1.5) {$5$};
 | 
			
		||||
\node at (2.5,-1.5) {$3$};
 | 
			
		||||
\node at (3.5,-1.5) {$-5$};
 | 
			
		||||
\node at (4.5,-1.5) {$2$};
 | 
			
		||||
\node at (5.5,-1.5) {$5$};
 | 
			
		||||
\node at (6.5,-1.5) {$-2$};
 | 
			
		||||
\node at (7.5,-1.5) {$-2$};
 | 
			
		||||
\node at (8.5,-1.5) {$-4$};
 | 
			
		||||
\node at (9.5,-1.5) {$-4$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$1$};
 | 
			
		||||
\node at (1.5,1.4) {$2$};
 | 
			
		||||
\node at (2.5,1.4) {$3$};
 | 
			
		||||
\node at (3.5,1.4) {$4$};
 | 
			
		||||
\node at (4.5,1.4) {$5$};
 | 
			
		||||
\node at (5.5,1.4) {$6$};
 | 
			
		||||
\node at (6.5,1.4) {$7$};
 | 
			
		||||
\node at (7.5,1.4) {$8$};
 | 
			
		||||
\node at (8.5,1.4) {$9$};
 | 
			
		||||
\node at (9.5,1.4) {$10$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Summaksi tulee
 | 
			
		||||
\[4+5+3-5+2+5-2=12,\]
 | 
			
		||||
mikä vastaa polun summaa $4+5+3=12$.
 | 
			
		||||
Tämä laskentatapa toimii, koska jokaisen solmun arvo
 | 
			
		||||
lisätään summaan, kun se tulee vastaan syvyyshaussa,
 | 
			
		||||
ja vähennetään summasta, kun sen käsittely päättyy.
 | 
			
		||||
 | 
			
		||||
Alipuiden käsittelyä vastaavasti voimme tallentaa
 | 
			
		||||
arvot binääri-indeksi\-puuhun tai segmenttipuuhun ja
 | 
			
		||||
sekä polun summan laskeminen että arvon muuttaminen
 | 
			
		||||
onnistuvat ajassa $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\section{Alin yhteinen esivanhempi}
 | 
			
		||||
 | 
			
		||||
\index{alin yhteinen esivanhempi@alin yhteinen esivanhempi}
 | 
			
		||||
 | 
			
		||||
Kahden puun solmun
 | 
			
		||||
\key{alin yhteinen esivanhempi}
 | 
			
		||||
on mahdollisimman matalalla puussa oleva solmu,
 | 
			
		||||
jonka alipuuhun kumpikin solmu kuuluu.
 | 
			
		||||
Tyypillinen tehtävä on vastata tehokkaasti
 | 
			
		||||
joukkoon kyselyitä, jossa selvitettävänä on
 | 
			
		||||
kahden solmun alin yhteinen esivanhempi.
 | 
			
		||||
Esimerkiksi puussa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmujen 5 ja 8 alin yhteinen esivanhempi on solmu 2
 | 
			
		||||
ja solmujen 3 ja 4 alin yhteinen esivanhempi on solmu 1.
 | 
			
		||||
 | 
			
		||||
Tutustumme seuraavaksi kahteen tehokkaaseen menetelmään
 | 
			
		||||
alimman yhteisen esivanhemman selvittämiseen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 1}
 | 
			
		||||
 | 
			
		||||
Yksi tapa ratkaista tehtävä on hyödyntää
 | 
			
		||||
tehokasta nousemista puussa.
 | 
			
		||||
Tällöin alimman yhteisen esivanhemman etsiminen
 | 
			
		||||
muodostuu kahdesta vaiheesta.
 | 
			
		||||
Ensin noustaan alemmasta solmusta samalle tasolle
 | 
			
		||||
ylemmän solmun kanssa,
 | 
			
		||||
sitten noustaan rinnakkain kohti
 | 
			
		||||
alinta yhteistä esivanhempaa.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä solmujen 5 ja 8
 | 
			
		||||
alimman yhteisen esivanhemman etsimistä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (6) at (-3,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$6$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Solmu 5 on tasolla 3, kun taas solmu 8 on tasolla 4.
 | 
			
		||||
Niinpä nousemme ensin solmusta 8 yhden tason ylemmäs solmuun 6.
 | 
			
		||||
Tämän jälkeen nousemme rinnakkain solmuista 5 ja 6
 | 
			
		||||
lähtien yhden tason, jolloin päädymme solmuun 2:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (6) at (-3,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$6$};
 | 
			
		||||
\node[draw, circle,fill=lightgray] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) edge [bend left] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (8) edge [bend right] (7);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (7) edge [bend right] (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Menetelmä vaatii $O(n \log n)$-aikaisen esikäsittelyn,
 | 
			
		||||
jonka jälkeen minkä tahansa kahden solmun alin yhteinen
 | 
			
		||||
esivanhempi selviää ajassa $O(\log n)$,
 | 
			
		||||
koska kumpikin vaihe nousussa vie aikaa $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Menetelmä 2}
 | 
			
		||||
 | 
			
		||||
Toinen tapa ratkaista tehtävä perustuu solmutaulukon
 | 
			
		||||
käyttämiseen.
 | 
			
		||||
Ideana on jälleen järjestää solmut syvyyshaun mukaan:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) edge [bend right=15] (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) edge [bend right=15] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) edge [bend right=15] (7);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (7) edge [bend right=15] (8);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (8) edge [bend right=15] (7);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (7) edge [bend right=15] (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) edge [bend right=15] (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) edge [bend right=15] (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Erona aiempaan solmu lisätään kuitenkin solmutaulukkoon
 | 
			
		||||
mukaan \textit{aina}, kun syvyyshaku käy solmussa,
 | 
			
		||||
eikä vain ensimmäisellä kerralla.
 | 
			
		||||
Niinpä solmu esiintyy solmutaulukossa $k+1$ kertaa,
 | 
			
		||||
missä $k$ on solmun lasten määrä,
 | 
			
		||||
ja solmutaulukossa on yhteensä $2n-1$ solmua.
 | 
			
		||||
 | 
			
		||||
Tallennamme solmutaulukkoon kaksi tietoa:
 | 
			
		||||
(1) solmun tunnus ja (2) solmun taso puussa.
 | 
			
		||||
Esimerkkipuuta vastaavat taulukot ovat:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
 | 
			
		||||
\draw (0,1) grid (15,2);
 | 
			
		||||
%\node at (-1.1,1.5) {\texttt{node}};
 | 
			
		||||
\node at (0.5,1.5) {$1$};
 | 
			
		||||
\node at (1.5,1.5) {$2$};
 | 
			
		||||
\node at (2.5,1.5) {$5$};
 | 
			
		||||
\node at (3.5,1.5) {$2$};
 | 
			
		||||
\node at (4.5,1.5) {$6$};
 | 
			
		||||
\node at (5.5,1.5) {$8$};
 | 
			
		||||
\node at (6.5,1.5) {$6$};
 | 
			
		||||
\node at (7.5,1.5) {$2$};
 | 
			
		||||
\node at (8.5,1.5) {$1$};
 | 
			
		||||
\node at (9.5,1.5) {$3$};
 | 
			
		||||
\node at (10.5,1.5) {$1$};
 | 
			
		||||
\node at (11.5,1.5) {$4$};
 | 
			
		||||
\node at (12.5,1.5) {$7$};
 | 
			
		||||
\node at (13.5,1.5) {$4$};
 | 
			
		||||
\node at (14.5,1.5) {$1$};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\draw (0,0) grid (15,1);
 | 
			
		||||
%\node at (-1.1,0.5) {\texttt{depth}};
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$3$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$3$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
\node at (8.5,0.5) {$1$};
 | 
			
		||||
\node at (9.5,0.5) {$2$};
 | 
			
		||||
\node at (10.5,0.5) {$1$};
 | 
			
		||||
\node at (11.5,0.5) {$2$};
 | 
			
		||||
\node at (12.5,0.5) {$3$};
 | 
			
		||||
\node at (13.5,0.5) {$2$};
 | 
			
		||||
\node at (14.5,0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,2.5) {$1$};
 | 
			
		||||
\node at (1.5,2.5) {$2$};
 | 
			
		||||
\node at (2.5,2.5) {$3$};
 | 
			
		||||
\node at (3.5,2.5) {$4$};
 | 
			
		||||
\node at (4.5,2.5) {$5$};
 | 
			
		||||
\node at (5.5,2.5) {$6$};
 | 
			
		||||
\node at (6.5,2.5) {$7$};
 | 
			
		||||
\node at (7.5,2.5) {$8$};
 | 
			
		||||
\node at (8.5,2.5) {$9$};
 | 
			
		||||
\node at (9.5,2.5) {$10$};
 | 
			
		||||
\node at (10.5,2.5) {$11$};
 | 
			
		||||
\node at (11.5,2.5) {$12$};
 | 
			
		||||
\node at (12.5,2.5) {$13$};
 | 
			
		||||
\node at (13.5,2.5) {$14$};
 | 
			
		||||
\node at (14.5,2.5) {$15$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän taulukon avulla solmujen $a$ ja $b$ alin yhteinen esivanhempi
 | 
			
		||||
selviää etsimällä taulukosta alimman tason solmu
 | 
			
		||||
solmujen $a$ ja $b$ välissä.
 | 
			
		||||
Esimerkiksi solmujen 5 ja 8 alin yhteinen esivanhempi
 | 
			
		||||
löytyy seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,1) rectangle (3,2);
 | 
			
		||||
\fill[color=lightgray] (5,1) rectangle (6,2);
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (6,1);
 | 
			
		||||
 | 
			
		||||
\node at (3.5,-0.5) {$\uparrow$};
 | 
			
		||||
 | 
			
		||||
\draw (0,1) grid (15,2);
 | 
			
		||||
\node at (0.5,1.5) {$1$};
 | 
			
		||||
\node at (1.5,1.5) {$2$};
 | 
			
		||||
\node at (2.5,1.5) {$5$};
 | 
			
		||||
\node at (3.5,1.5) {$2$};
 | 
			
		||||
\node at (4.5,1.5) {$6$};
 | 
			
		||||
\node at (5.5,1.5) {$8$};
 | 
			
		||||
\node at (6.5,1.5) {$6$};
 | 
			
		||||
\node at (7.5,1.5) {$2$};
 | 
			
		||||
\node at (8.5,1.5) {$1$};
 | 
			
		||||
\node at (9.5,1.5) {$3$};
 | 
			
		||||
\node at (10.5,1.5) {$1$};
 | 
			
		||||
\node at (11.5,1.5) {$4$};
 | 
			
		||||
\node at (12.5,1.5) {$7$};
 | 
			
		||||
\node at (13.5,1.5) {$4$};
 | 
			
		||||
\node at (14.5,1.5) {$1$};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\draw (0,0) grid (15,1);
 | 
			
		||||
\node at (0.5,0.5) {$1$};
 | 
			
		||||
\node at (1.5,0.5) {$2$};
 | 
			
		||||
\node at (2.5,0.5) {$3$};
 | 
			
		||||
\node at (3.5,0.5) {$2$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$4$};
 | 
			
		||||
\node at (6.5,0.5) {$3$};
 | 
			
		||||
\node at (7.5,0.5) {$2$};
 | 
			
		||||
\node at (8.5,0.5) {$1$};
 | 
			
		||||
\node at (9.5,0.5) {$2$};
 | 
			
		||||
\node at (10.5,0.5) {$1$};
 | 
			
		||||
\node at (11.5,0.5) {$2$};
 | 
			
		||||
\node at (12.5,0.5) {$3$};
 | 
			
		||||
\node at (13.5,0.5) {$2$};
 | 
			
		||||
\node at (14.5,0.5) {$1$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,2.5) {$1$};
 | 
			
		||||
\node at (1.5,2.5) {$2$};
 | 
			
		||||
\node at (2.5,2.5) {$3$};
 | 
			
		||||
\node at (3.5,2.5) {$4$};
 | 
			
		||||
\node at (4.5,2.5) {$5$};
 | 
			
		||||
\node at (5.5,2.5) {$6$};
 | 
			
		||||
\node at (6.5,2.5) {$7$};
 | 
			
		||||
\node at (7.5,2.5) {$8$};
 | 
			
		||||
\node at (8.5,2.5) {$9$};
 | 
			
		||||
\node at (9.5,2.5) {$10$};
 | 
			
		||||
\node at (10.5,2.5) {$11$};
 | 
			
		||||
\node at (11.5,2.5) {$12$};
 | 
			
		||||
\node at (12.5,2.5) {$13$};
 | 
			
		||||
\node at (13.5,2.5) {$14$};
 | 
			
		||||
\node at (14.5,2.5) {$15$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Solmu 5 on taulukossa kohdassa 3,
 | 
			
		||||
solmu 8 on taulukossa kohdassa 6
 | 
			
		||||
ja alimman tason solmu välillä $3 \ldots 6$
 | 
			
		||||
on kohdassa 4 oleva solmu 2,
 | 
			
		||||
jonka taso on 2.
 | 
			
		||||
Niinpä solmujen 5 ja 8 alin yhteinen esivanhempi
 | 
			
		||||
on solmu 2.
 | 
			
		||||
 | 
			
		||||
Alimman tason solmu välillä selviää
 | 
			
		||||
ajassa $O(\log n)$, kun taulukon sisältö on
 | 
			
		||||
tallennettu segmenttipuuhun.
 | 
			
		||||
Myös aikavaativuus $O(1)$ on mahdollinen,
 | 
			
		||||
koska taulukko on staattinen, mutta tälle on harvoin tarvetta.
 | 
			
		||||
Kummassakin tapauksessa esikäsittely vie aikaa $O(n \log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Solmujen etäisyydet}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan lopuksi tehtävää,
 | 
			
		||||
jossa kyselyissä tulee laskea tehokkaasti
 | 
			
		||||
kahden solmun etäisyys eli solmujen välisen polun pituus puussa.
 | 
			
		||||
Osoittautuu, että tämä tehtävä
 | 
			
		||||
palautuu alimman yhteisen esivanhemman etsimiseen.
 | 
			
		||||
 | 
			
		||||
Valitaan ensin mikä tahansa
 | 
			
		||||
solmu puun juureksi.
 | 
			
		||||
Tämän jälkeen solmujen $a$ ja $b$
 | 
			
		||||
etäisyys on $d(a)+d(b)-2 \cdot d(c)$,
 | 
			
		||||
missä $c$ on solmujen alin yhteinen esivanhempi
 | 
			
		||||
ja $d(s)$ on etäisyys puun juuresta solmuun $s$.
 | 
			
		||||
Esimerkiksi puussa
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (-2,1) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (0,1) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (2,-1) {$7$};
 | 
			
		||||
\node[draw, circle] (6) at (-3,-1) {$5$};
 | 
			
		||||
\node[draw, circle] (7) at (-1,-1) {$6$};
 | 
			
		||||
\node[draw, circle] (8) at (-1,-3) {$8$};
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (7);
 | 
			
		||||
\path[draw,thick,-] (7) -- (8);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,-,line width=2pt] (8) -- node[font=\small] {} (7);
 | 
			
		||||
\path[draw=red,thick,-,line width=2pt] (7) -- node[font=\small] {} (3);
 | 
			
		||||
\path[draw=red,thick,-,line width=2pt] (6) -- node[font=\small] {} (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmujen 5 ja 8 alin yhteinen esivanhempi on 2.
 | 
			
		||||
Polku solmusta 5 solmuun 8
 | 
			
		||||
kulkee ensin ylöspäin solmusta 5
 | 
			
		||||
solmuun 2 ja sitten alaspäin
 | 
			
		||||
solmusta 2 solmuun 8.
 | 
			
		||||
Solmujen etäisyydet juuresta ovat $d(5)=3$,
 | 
			
		||||
$d(8)=4$ ja $d(2)=2$,
 | 
			
		||||
joten solmujen 5 ja 8 etäisyys
 | 
			
		||||
on $3+4-2\cdot2=3$.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,664 @@
 | 
			
		|||
\chapter{Paths and circuits}
 | 
			
		||||
 | 
			
		||||
Tämä luku käsittelee kahdenlaisia polkuja verkossa:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item \key{Eulerin polku} on verkossa oleva
 | 
			
		||||
polku, joka kulkee tasan kerran jokaista
 | 
			
		||||
verkon kaarta pitkin.
 | 
			
		||||
\item \key{Hamiltonin polku} on verkossa
 | 
			
		||||
oleva polku, joka käy tasan kerran
 | 
			
		||||
jokaisessa verkon solmussa.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Vaikka Eulerin ja Hamiltonin polut
 | 
			
		||||
näyttävät päältä päin
 | 
			
		||||
samantapaisilta käsitteiltä,
 | 
			
		||||
niihin liittyy hyvin erilaisia laskennallisia ongelmia.
 | 
			
		||||
Osoittautuu, että yksinkertainen verkon solmujen
 | 
			
		||||
asteisiin liittyvä sääntö ratkaisee, onko verkossa
 | 
			
		||||
Eulerin polkua, ja polun muodostamiseen on myös
 | 
			
		||||
olemassa tehokas algoritmi.
 | 
			
		||||
Sen sijaan Hamiltonin polun etsimiseen ei tunneta
 | 
			
		||||
mitään tehokasta algoritmia, vaan kyseessä on
 | 
			
		||||
NP-vaikea ongelma.
 | 
			
		||||
 | 
			
		||||
\section{Eulerin polku}
 | 
			
		||||
 | 
			
		||||
\index{Eulerin polku@Eulerin polku}
 | 
			
		||||
 | 
			
		||||
\key{Eulerin polku} on verkossa oleva
 | 
			
		||||
polku, joka kulkee tarkalleen kerran jokaista kaarta pitkin.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on Eulerin polku solmusta 2 solmuun 5:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:1.}] {} (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]left:2.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]south:3.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]left:4.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:5.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]south:6.}] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\index{Eulerin kierros@Eulerin kierros}
 | 
			
		||||
\key{Eulerin kierros}
 | 
			
		||||
on puolestaan Eulerin polku,
 | 
			
		||||
jonka alku- ja loppusolmu ovat samat.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on Eulerin kierros, jonka alku- ja loppusolmu on 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]left:1.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]south:2.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]right:3.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]south:4.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]north:5.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:6.}] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Olemassaolo}
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että Eulerin polun ja kierroksen olemassaolo
 | 
			
		||||
riippuu verkon solmujen asteista.
 | 
			
		||||
Solmun aste on sen naapurien määrä eli niiden solmujen määrä,
 | 
			
		||||
jotka ovat yhteydessä solmuun kaarella.
 | 
			
		||||
 | 
			
		||||
Suuntaamattomassa verkossa on Eulerin polku,
 | 
			
		||||
jos kaikki kaaret ovat samassa yhtenäisessä komponentissa ja
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item jokaisen solmun aste on parillinen \textit{tai}
 | 
			
		||||
\item tarkalleen kahden solmun aste on pariton ja kaikkien
 | 
			
		||||
muiden solmujen aste on parillinen.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Ensimmäisessä tapauksessa Eulerin polku on samalla myös Eulerin kierros.
 | 
			
		||||
Jälkimmäisessä tapauksessa Eulerin polun alku- ja loppusolmu ovat
 | 
			
		||||
paritonasteiset solmut ja se ei ole Eulerin kierros.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
solmujen 1, 3 ja 4 aste on 2 ja solmujen 2 ja 5 aste on 3.
 | 
			
		||||
Tarkalleen kahden solmun aste on pariton,
 | 
			
		||||
joten verkossa on Eulerin polku solmujen 2 ja 5 välillä,
 | 
			
		||||
mutta verkossa ei ole Eulerin kierrosta.
 | 
			
		||||
 | 
			
		||||
Jos verkko on suunnattu, tilanne on hieman hankalampi.
 | 
			
		||||
Silloin Eulerin polun ja kierroksen olemassaoloon
 | 
			
		||||
vaikuttavat solmujen lähtö- ja tuloasteet.
 | 
			
		||||
Solmun lähtöaste on solmusta lähtevien kaarten määrä,
 | 
			
		||||
ja vastaavasti solmun tuloaste on solmuun tulevien kaarten määrä.
 | 
			
		||||
 | 
			
		||||
Suunnatussa verkossa on Eulerin polku, jos
 | 
			
		||||
kaikki kaaret ovat samassa vahvasti yhtenäisessä
 | 
			
		||||
komponentissa ja
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item jokaisen solmun lähtö- ja tuloaste on sama \textit{tai}
 | 
			
		||||
\item yhdessä solmussa lähtöaste on yhden suurempi kuin tuloaste,
 | 
			
		||||
toisessa solmussa tuloaste on yhden suurempi kuin lähtöaste
 | 
			
		||||
ja kaikissa muissa solmuissa lähtö- ja tuloaste on sama.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Tilanne on vastaava kuin suuntaamattomassa verkossa:
 | 
			
		||||
ensimmäisessä tapauksessa Eulerin polku on myös Eulerin kierros,
 | 
			
		||||
ja toisessa tapauksessa verkossa on vain Eulerin polku,
 | 
			
		||||
jonka lähtösolmussa lähtöaste on suurempi ja
 | 
			
		||||
päätesolmussa tuloaste on suurempi.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
solmuissa 1, 3 ja 4 sekä lähtöaste että tuloaste on 1.
 | 
			
		||||
Solmussa 2 tuloaste on 1 ja lähtöaste on 2,
 | 
			
		||||
kun taas solmussa 5 tulosate on 2 ja lähtöaste on 1.
 | 
			
		||||
Niinpä verkossa on Eulerin polku solmusta 2 solmuun 5:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:1.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]south:2.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]south:3.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]left:4.}] {} (1);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]north:5.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]left:6.}] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Hierholzerin algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Hierholzerin algoritmi@Hierholzerin algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Hierholzerin algoritmi} muodostaa Eulerin kierroksen
 | 
			
		||||
suuntaamattomassa verkossa.
 | 
			
		||||
Algoritmi olettaa, että kaikki kaaret ovat samassa
 | 
			
		||||
komponentissa ja jokaisen solmun aste on parillinen.
 | 
			
		||||
Algoritmi on mahdollista toteuttaa niin, että sen
 | 
			
		||||
aikavaativuus on $O(n+m)$.
 | 
			
		||||
 | 
			
		||||
Hierholzerin algoritmi muodostaa ensin verkkoon jonkin kierroksen,
 | 
			
		||||
johon kuuluu osa verkon kaarista.
 | 
			
		||||
Sen jälkeen algoritmi alkaa laajentaa kierrosta
 | 
			
		||||
lisäämällä sen osaksi uusia alikierroksia.
 | 
			
		||||
Tämä jatkuu niin kauan, kunnes kaikki kaaret kuuluvat
 | 
			
		||||
kierrokseen ja siitä on tullut Eulerin kierros.
 | 
			
		||||
 | 
			
		||||
Algoritmi laajentaa kierrosta valitsemalla jonkin
 | 
			
		||||
kierrokseen kuuluvan solmun $x$,
 | 
			
		||||
jonka kaikki kaaret eivät ole vielä mukana kierroksessa.
 | 
			
		||||
Algoritmi muodostaa solmusta $x$ alkaen uuden polun
 | 
			
		||||
kulkien vain sellaisia kaaria, jotka eivät ole
 | 
			
		||||
mukana kierroksessa.
 | 
			
		||||
Koska jokaisen solmun aste on parillinen,
 | 
			
		||||
ennemmin tai myöhemmin polku palaa takaisin lähtösolmuun $x$.
 | 
			
		||||
 | 
			
		||||
Jos verkossa on kaksi paritonasteista solmua,
 | 
			
		||||
Hierholzerin algoritmilla voi myös muodostaa
 | 
			
		||||
Eulerin polun lisäämällä kaaren
 | 
			
		||||
paritonasteisten solmujen välille.
 | 
			
		||||
Tämän jälkeen verkosta voi etsiä ensin
 | 
			
		||||
Eulerin kierroksen ja poistaa siitä sitten
 | 
			
		||||
ylimääräisen kaaren, jolloin tuloksena on Eulerin polku.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Esimerkki}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Tarkastellaan algoritmin toimintaa seuraavassa verkossa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (3,1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (5,1) {$7$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Oletetaan, että algoritmi aloittaa
 | 
			
		||||
ensimmäisen kierroksen solmusta 1.
 | 
			
		||||
Siitä syntyy kierros $1 \rightarrow 2 \rightarrow 3 \rightarrow 1$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (3,1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (5,1) {$7$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]north:1.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:2.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]east:3.}] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi algoritmi lisää mukaan kierroksen
 | 
			
		||||
$2 \rightarrow 5 \rightarrow 6 \rightarrow 2$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (3,1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (5,1) {$7$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]north:1.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]west:2.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]south:3.}] {} (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) -- node[font=\small,label={[red]north:4.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:5.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]east:6.}] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Lopuksi algoritmi lisää mukaan kierroksen
 | 
			
		||||
$6 \rightarrow 3 \rightarrow 4 \rightarrow 7 \rightarrow 6$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (3,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (1,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (3,1) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (5,1) {$7$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (6);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (6);
 | 
			
		||||
\path[draw,thick,-] (4) -- (7);
 | 
			
		||||
\path[draw,thick,-] (5) -- (6);
 | 
			
		||||
\path[draw,thick,-] (6) -- (7);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]north:1.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]west:2.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]south:3.}] {} (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) -- node[font=\small,label={[red]east:4.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]north:5.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]east:6.}] {} (7);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (7) -- node[font=\small,label={[red]south:7.}] {} (6);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (6) -- node[font=\small,label={[red]right:8.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:9.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]east:10.}] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Nyt kaikki kaaret ovat kierroksessa,
 | 
			
		||||
joten Eulerin kierros on valmis.
 | 
			
		||||
 | 
			
		||||
\section{Hamiltonin polku}
 | 
			
		||||
 | 
			
		||||
\index{Hamiltonin polku@Hamiltonin polku}
 | 
			
		||||
\key{Hamiltonin polku}
 | 
			
		||||
on verkossa oleva polku,
 | 
			
		||||
joka kulkee tarkalleen kerran jokaisen solmun kautta.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on Hamiltonin polku solmusta 1 solmuun 3:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]left:1.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]south:2.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]left:3.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:4.}] {} (3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{Hamiltonin kierros@Hamiltonin kierros}
 | 
			
		||||
Jos Hamiltonin polun alku- ja loppusolmu on sama,
 | 
			
		||||
kyseessä on \key{Hamiltonin kierros}.
 | 
			
		||||
Äskeisessä verkossa on myös
 | 
			
		||||
Hamiltonin kierros, jonka alku- ja loppusolmu on solmu 1:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,5) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,5) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (5,4) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1,3) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,3) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (2) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (5);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (1) -- node[font=\small,label={[red]north:1.}] {} (2);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (2) -- node[font=\small,label={[red]north:2.}] {} (3);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (3) -- node[font=\small,label={[red]south:3.}] {} (5);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (5) -- node[font=\small,label={[red]south:4.}] {} (4);
 | 
			
		||||
\path[draw=red,thick,->,line width=2pt] (4) -- node[font=\small,label={[red]left:5.}] {} (1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Olemassaolo}
 | 
			
		||||
 | 
			
		||||
Hamiltonin polun olemassaoloon ei tiedetä
 | 
			
		||||
mitään verkon rakenteeseen liittyvää ehtoa,
 | 
			
		||||
jonka voisi tarkistaa tehokkaasti.
 | 
			
		||||
Joissakin erikoistapauksissa voidaan silti sanoa
 | 
			
		||||
varmasti, että verkossa on Hamiltonin polku.
 | 
			
		||||
 | 
			
		||||
Yksinkertainen havainto on, että jos verkko on täydellinen
 | 
			
		||||
eli jokaisen solmun välillä on kaari,
 | 
			
		||||
niin siinä on Hamiltonin polku.
 | 
			
		||||
Myös vahvempia tuloksia on saatu aikaan:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
\index{Diracin lause@Diracin lause}
 | 
			
		||||
\key{Diracin lause}:
 | 
			
		||||
Jos jokaisen verkon solmun aste on $n/2$ tai suurempi,
 | 
			
		||||
niin verkossa on Hamiltonin polku.
 | 
			
		||||
\item
 | 
			
		||||
\index{Oren lause@Oren lause}
 | 
			
		||||
\key{Oren lause}:
 | 
			
		||||
Jos jokaisen ei-vierekkäisen solmuparin asteiden summa
 | 
			
		||||
on $n$ tai suurempi,
 | 
			
		||||
niin verkossa on Hamiltonin polku.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Yhteistä näissä ja muissa tuloksissa on,
 | 
			
		||||
että ne takaavat Hamiltonin polun olemassaolon,
 | 
			
		||||
jos verkossa on \textit{paljon} kaaria.
 | 
			
		||||
Tämä on ymmärrettävää, koska mitä enemmän
 | 
			
		||||
kaaria verkossa on, sitä enemmän mahdollisuuksia
 | 
			
		||||
Hamiltonin polun muodostamiseen on olemassa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Muodostaminen}
 | 
			
		||||
 | 
			
		||||
Koska Hamiltonin polun olemassaoloa ei voi tarkastaa tehokkaasti,
 | 
			
		||||
on selvää, että polkua ei voi myöskään muodostaa tehokkaasti,
 | 
			
		||||
koska muuten polun olemassaolon voisi selvittää yrittämällä
 | 
			
		||||
muodostaa sen.
 | 
			
		||||
 | 
			
		||||
Yksinkertaisin tapa etsiä Hamiltonin polkua on käyttää
 | 
			
		||||
peruuttavaa hakua, joka käy läpi kaikki vaihtoehdot
 | 
			
		||||
polun muodostamiseen.
 | 
			
		||||
Tällaisen algoritmin aikavaativuus on ainakin luokkaa $O(n!)$,
 | 
			
		||||
koska $n$ solmusta voidaan muodostaa $n!$ järjestystä,
 | 
			
		||||
jossa ne voivat esiintyä polulla.
 | 
			
		||||
 | 
			
		||||
Tehokkaampi tapa perustuu dynaamiseen ohjelmointiin
 | 
			
		||||
luvun 10.4 tapaan.
 | 
			
		||||
Ideana on määritellä funktio $f(s,x)$,
 | 
			
		||||
jossa $s$ on verkon solmujen osajoukko ja
 | 
			
		||||
$x$ on yksi osajoukon solmuista.
 | 
			
		||||
Funktio kertoo, onko olemassa Hamiltonin polkua,
 | 
			
		||||
joka käy läpi joukon $s$ solmut päätyen solmuun $x$.
 | 
			
		||||
Tällainen ratkaisu on mahdollista toteuttaa ajassa $O(2^n n^2)$.
 | 
			
		||||
 | 
			
		||||
\section{De Bruijnin jono}
 | 
			
		||||
 | 
			
		||||
\index{de Bruijnin jono@de Bruijnin jono}
 | 
			
		||||
 | 
			
		||||
\key{De Bruijnin jono}
 | 
			
		||||
on merkkijono, jonka osajonona on tarkalleen
 | 
			
		||||
kerran jokainen $k$-merkkisen aakkoston
 | 
			
		||||
$n$ merkin yhdistelmä.
 | 
			
		||||
Tällaisen merkkijonon pituus on
 | 
			
		||||
$k^n+n-1$ merkkiä.
 | 
			
		||||
Esimerkiksi kun $k=2$ ja $n=3$,
 | 
			
		||||
niin yksi mahdollinen de Bruijnin jono on
 | 
			
		||||
\[0001011100.\]
 | 
			
		||||
Tämän merkkijono osajonot ovat kaikki 
 | 
			
		||||
kolmen bitin yhdistelmät
 | 
			
		||||
000, 001, 010, 011, 100, 101, 110 ja 111.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että de Bruijnin jono
 | 
			
		||||
vastaa Eulerin kierrosta sopivasti
 | 
			
		||||
muodostetussa verkossa.
 | 
			
		||||
Ideana on muodostaa verkko niin,
 | 
			
		||||
että jokaisessa solmussa on $n-1$
 | 
			
		||||
merkin yhdistelmä ja liikkuminen
 | 
			
		||||
kaarta pitkin muodostaa uuden
 | 
			
		||||
$n$ merkin yhdistelmän.
 | 
			
		||||
Esimerkin tapauksessa verkosta tulee seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\node[draw, circle] (00) at (-3,0) {00};
 | 
			
		||||
\node[draw, circle] (11) at (3,0) {11};
 | 
			
		||||
\node[draw, circle] (01) at (0,2) {01};
 | 
			
		||||
\node[draw, circle] (10) at (0,-2) {10};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (00) edge [bend left=20] node[font=\small,label=1] {} (01);
 | 
			
		||||
\path[draw,thick,->] (01) edge [bend left=20] node[font=\small,label=1] {} (11);
 | 
			
		||||
\path[draw,thick,->] (11) edge [bend left=20] node[font=\small,label=below:0] {} (10);
 | 
			
		||||
\path[draw,thick,->] (10) edge [bend left=20] node[font=\small,label=below:0] {} (00);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (01) edge [bend left=30] node[font=\small,label=right:0] {} (10);
 | 
			
		||||
\path[draw,thick,->] (10) edge [bend left=30] node[font=\small,label=left:1] {} (01);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (00) edge [loop left] node[font=\small,label=below:0] {} (00);
 | 
			
		||||
\path[draw,thick,-] (11) edge [loop right] node[font=\small,label=below:1] {} (11);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Eulerin kierros tässä verkossa tuottaa merkkijonon,
 | 
			
		||||
joka sisältää kaikki $n$ merkin yhdistelmät,
 | 
			
		||||
kun mukaan otetaan aloitussolmun merkit sekä
 | 
			
		||||
kussakin kaaressa olevat merkit.
 | 
			
		||||
Alkusolmussa on $n-1$ merkkiä ja kaarissa
 | 
			
		||||
on $k^n$ merkkiä, joten tuloksena on
 | 
			
		||||
lyhin mahdollinen merkkijono.
 | 
			
		||||
 | 
			
		||||
\section{Ratsun kierros}
 | 
			
		||||
 | 
			
		||||
\index{ratsun kierros@ratsun kierros}
 | 
			
		||||
 | 
			
		||||
\key{Ratsun kierros} on tapa liikuttaa ratsua
 | 
			
		||||
shakin sääntöjen mukaisesti $n \times n$ -kokoisella
 | 
			
		||||
shakkilaudalla niin,
 | 
			
		||||
että ratsu käy tarkalleen kerran jokaisessa ruudussa.
 | 
			
		||||
Ratsun kierros on \key{suljettu}, jos ratsu palaa lopuksi alkuruutuun,
 | 
			
		||||
ja muussa tapauksessa kierros on \key{avoin}.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi tapauksessa $5 \times 5$ yksi ratsun kierros on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (5,5);
 | 
			
		||||
\node at (0.5,4.5) {$1$};
 | 
			
		||||
\node at (1.5,4.5) {$4$};
 | 
			
		||||
\node at (2.5,4.5) {$11$};
 | 
			
		||||
\node at (3.5,4.5) {$16$};
 | 
			
		||||
\node at (4.5,4.5) {$25$};
 | 
			
		||||
\node at (0.5,3.5) {$12$};
 | 
			
		||||
\node at (1.5,3.5) {$17$};
 | 
			
		||||
\node at (2.5,3.5) {$2$};
 | 
			
		||||
\node at (3.5,3.5) {$5$};
 | 
			
		||||
\node at (4.5,3.5) {$10$};
 | 
			
		||||
\node at (0.5,2.5) {$3$};
 | 
			
		||||
\node at (1.5,2.5) {$20$};
 | 
			
		||||
\node at (2.5,2.5) {$7$};
 | 
			
		||||
\node at (3.5,2.5) {$24$};
 | 
			
		||||
\node at (4.5,2.5) {$15$};
 | 
			
		||||
\node at (0.5,1.5) {$18$};
 | 
			
		||||
\node at (1.5,1.5) {$13$};
 | 
			
		||||
\node at (2.5,1.5) {$22$};
 | 
			
		||||
\node at (3.5,1.5) {$9$};
 | 
			
		||||
\node at (4.5,1.5) {$6$};
 | 
			
		||||
\node at (0.5,0.5) {$21$};
 | 
			
		||||
\node at (1.5,0.5) {$8$};
 | 
			
		||||
\node at (2.5,0.5) {$19$};
 | 
			
		||||
\node at (3.5,0.5) {$14$};
 | 
			
		||||
\node at (4.5,0.5) {$23$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Ratsun kierros shakkilaudalla vastaa Hamiltonin polkua verkossa,
 | 
			
		||||
jonka solmut ovat ruutuja ja kahden solmun välillä on kaari,
 | 
			
		||||
jos ratsu pystyy siirtymään solmusta toiseen shakin sääntöjen mukaisesti.
 | 
			
		||||
 | 
			
		||||
Peruuttava haku on luonteva menetelmä ratsun kierroksen muodostamiseen.
 | 
			
		||||
Hakua voi tehostaa erilaisilla \key{heuristiikoilla},
 | 
			
		||||
jotka pyrkivät ohjaamaan ratsua niin, että kokonainen kierros
 | 
			
		||||
tulee valmiiksi nopeasti.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Warnsdorffin sääntö}
 | 
			
		||||
 | 
			
		||||
\index{heuristiikka@heuristiikka}
 | 
			
		||||
\index{Warnsdorffin sxxntz@Warnsdorffin sääntö}
 | 
			
		||||
 | 
			
		||||
\key{Warnsdorffin sääntö} on yksinkertainen
 | 
			
		||||
ja hyvä heuristiikka
 | 
			
		||||
ratsun kierroksen etsimiseen.
 | 
			
		||||
Sen avulla on mahdollista löytää nopeasti ratsun kierros
 | 
			
		||||
suurestakin ruudukosta.
 | 
			
		||||
Ideana on siirtää ratsua aina niin,
 | 
			
		||||
että se päätyy ruutuun, josta on mahdollisimman \emph{vähän}
 | 
			
		||||
mahdollisuuksia jatkaa kierrosta.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa tilanteessa on valittavana
 | 
			
		||||
viisi ruutua, joihin ratsu voi siirtyä:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (5,5);
 | 
			
		||||
\node at (0.5,4.5) {$1$};
 | 
			
		||||
\node at (2.5,3.5) {$2$};
 | 
			
		||||
\node at (4.5,4.5) {$a$};
 | 
			
		||||
\node at (0.5,2.5) {$b$};
 | 
			
		||||
\node at (4.5,2.5) {$e$};
 | 
			
		||||
\node at (1.5,1.5) {$c$};
 | 
			
		||||
\node at (3.5,1.5) {$d$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tässä tapauksessa Warnsdorffin sääntö valitsee ruudun $a$,
 | 
			
		||||
koska tämän valinnan jälkeen on vain yksi mahdollisuus
 | 
			
		||||
jatkaa kierrosta. Muissa valinnoissa mahdollisuuksia olisi kolme.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,718 @@
 | 
			
		|||
\chapter{Number theory}
 | 
			
		||||
 | 
			
		||||
\index{lukuteoria@lukuteoria}
 | 
			
		||||
 | 
			
		||||
\key{Lukuteoria} on kokonaislukuja tutkiva
 | 
			
		||||
matematiikan ala, jonka keskeinen
 | 
			
		||||
käsite on lukujen jaollisuus.
 | 
			
		||||
Lukuteoriassa on kiehtovaa, että monet kokonaislukuihin
 | 
			
		||||
liittyvät kysymykset ovat hyvin vaikeita ratkaista,
 | 
			
		||||
vaikka ne saattavat näyttää päältä päin yksinkertaisilta.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä seuraavaa yhtälöä:
 | 
			
		||||
\[x^3 + y^3 + z^3 = 33\]
 | 
			
		||||
On helppoa löytää kolme reaalilukua $x$, $y$ ja $z$,
 | 
			
		||||
jotka toteuttavat yhtälön. Voimme valita
 | 
			
		||||
esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
x = 3, \\
 | 
			
		||||
y = \sqrt[3]{3}, \\
 | 
			
		||||
z = \sqrt[3]{3}.\\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Sen sijaan kukaan ei tiedä, onko olemassa
 | 
			
		||||
kolmea \emph{kokonaislukua} $x$, $y$ ja $z$,
 | 
			
		||||
jotka toteuttaisivat yhtälön, vaan kyseessä
 | 
			
		||||
on avoin lukuteorian ongelma.
 | 
			
		||||
 | 
			
		||||
Tässä luvussa tutustumme lukuteorian peruskäsitteisiin ja
 | 
			
		||||
-algoritmeihin.
 | 
			
		||||
Lähdemme liikkeelle lukujen jaollisuudesta,
 | 
			
		||||
johon liittyvät keskeiset algoritmit ovat
 | 
			
		||||
alkuluvun tarkastaminen sekä luvun jakaminen tekijöihin.
 | 
			
		||||
 | 
			
		||||
\section{Alkuluvut ja tekijät}
 | 
			
		||||
 | 
			
		||||
\index{jaollisuus@jaollisuus}
 | 
			
		||||
\index{jakaja@jakaja}
 | 
			
		||||
\index{tekijx@tekijä}
 | 
			
		||||
 | 
			
		||||
Luku $a$ on luvun $b$ \key{jakaja} eli \key{tekijä},
 | 
			
		||||
jos $b$ on jaollinen $a$:lla.
 | 
			
		||||
Jos $a$ on $b$:n jakaja,
 | 
			
		||||
niin merkitään $a \mid b$,
 | 
			
		||||
ja muuten merkitään $a \nmid b$.
 | 
			
		||||
Esimerkiksi luvun 24 jakajat ovat 1, 2, 3, 4, 6, 8, 12 ja 24.
 | 
			
		||||
 | 
			
		||||
\index{alkuluku@alkuluku}
 | 
			
		||||
\index{alkutekijxhajotelma@alkutekijähajotelma}
 | 
			
		||||
 | 
			
		||||
Luku $n$ on \key{alkuluku}, jos sen ainoat 
 | 
			
		||||
positiiviset jakajat ovat 1 ja $n$.
 | 
			
		||||
Esimerkiksi luvut 7, 19 ja 41 ovat alkulukuja.
 | 
			
		||||
Luku 35 taas ei ole alkuluku, koska se voidaan
 | 
			
		||||
jakaa tekijöihin $5 \cdot 7 = 35$.
 | 
			
		||||
Jokaiselle luvulle $n>1$ on olemassa yksikäsitteinen
 | 
			
		||||
\key{alkutekijähajotelma}
 | 
			
		||||
\[ n = p_1^{\alpha_1} p_2^{\alpha_2} \cdots p_k^{\alpha_k},\]
 | 
			
		||||
missä $p_1,p_2,\ldots,p_k$ ovat alkulukuja
 | 
			
		||||
ja $\alpha_1,\alpha_2,\ldots,\alpha_k$ ovat positiivisia
 | 
			
		||||
lukuja. Esimerkiksi luvun 84 alkutekijähajotelma on
 | 
			
		||||
\[84 = 2^2 \cdot 3^1 \cdot 7^1.\]
 | 
			
		||||
 | 
			
		||||
Luvun $n$ \key{jakajien määrä} on
 | 
			
		||||
\[\tau(n)=\prod_{i=1}^k (\alpha_i+1),\]
 | 
			
		||||
koska alkutekijän $p_i$ kohdalla on $\alpha_i+1$
 | 
			
		||||
tapaa valita, montako kertaa alkutekijä
 | 
			
		||||
esiintyy jakajassa.
 | 
			
		||||
Esimerkiksi luvun 84 jakajien määrä
 | 
			
		||||
on $\tau(84)=3 \cdot 2 \cdot 2 = 12$.
 | 
			
		||||
Jakajat ovat
 | 
			
		||||
1, 2, 3, 4, 6, 7, 12, 14, 21, 28, 42 ja 84.
 | 
			
		||||
 | 
			
		||||
Luvun $n$ \key{jakajien summa} on
 | 
			
		||||
\[\sigma(n)=\prod_{i=1}^k (1+p_i+\ldots+p_i^{\alpha_i}) = \prod_{i=1}^k \frac{p_i^{a_i+1}-1}{p_i-1},\]
 | 
			
		||||
missä jälkimmäinen muoto perustuu geometriseen summaan.
 | 
			
		||||
Esimerkiksi luvun 84 jakajien summa on
 | 
			
		||||
\[\sigma(84)=\frac{2^3-1}{2-1} \cdot \frac{3^2-1}{3-1} \cdot \frac{7^2-1}{7-1} = 7 \cdot 4 \cdot 8 = 224.\]
 | 
			
		||||
 | 
			
		||||
Luvun $n$ \key{jakajien tulo} on
 | 
			
		||||
\[\mu(n)=n^{\tau(n)/2},\]
 | 
			
		||||
koska jakajista voidaan muodostaa
 | 
			
		||||
$\tau(n)/2$ paria, joiden jokaisen tulona on $n$.
 | 
			
		||||
Esimerkiksi luvun 84 jakajista muodostuvat parit
 | 
			
		||||
$1 \cdot 84$, $2 \cdot 42$, $3 \cdot 28$, jne.,
 | 
			
		||||
ja jakajien tulo on $\mu(84)=84^6=351298031616$.
 | 
			
		||||
 | 
			
		||||
%\index{tzydellinen luku@täydellinen luku}
 | 
			
		||||
\index{txydellinen luku@täydellinen luku}
 | 
			
		||||
 | 
			
		||||
Luku $n$ on \key{täydellinen}, jos $n=\sigma(n)-n$
 | 
			
		||||
eli luku on yhtä suuri kuin summa sen jakajista
 | 
			
		||||
välillä $1 \ldots n-1$.
 | 
			
		||||
Esimerkiksi luku 28 on täydellinen, koska
 | 
			
		||||
se muodostuu summana $1+2+4+7+14$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Alkulukujen määrä}
 | 
			
		||||
 | 
			
		||||
On helppoa osoittaa, että alkulukuja on äärettömästi.
 | 
			
		||||
Jos nimittäin alkulukuja olisi äärellinen määrä,
 | 
			
		||||
voisimme muodostaa joukon $P=\{p_1,p_2,\ldots,p_n\}$,
 | 
			
		||||
joka sisältää kaikki alkuluvut.
 | 
			
		||||
Esimerkiksi $p_1=2$, $p_2=3$, $p_3=5$, jne.
 | 
			
		||||
Nyt kuitenkin voisimme muodostaa uuden alkuluvun
 | 
			
		||||
\[p_1 p_2 \cdots p_n+1,\]
 | 
			
		||||
joka on kaikkia $P$:n lukuja suurempi.
 | 
			
		||||
Koska tätä lukua ei ole joukossa $P$,
 | 
			
		||||
syntyy ristiriita ja alkulukujen määrän on
 | 
			
		||||
pakko olla ääretön.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Alkulukujen tiheys}
 | 
			
		||||
 | 
			
		||||
Alkulukujen tiheys tarkoittaa, kuinka usein alkulukuja
 | 
			
		||||
esiintyy muiden lukujen joukossa.
 | 
			
		||||
Merkitään funktiolla $\pi(n)$, 
 | 
			
		||||
montako alkulukua on välillä $1 \ldots n$.
 | 
			
		||||
Esimerkiksi $\pi(10)=4$, koska välillä $1 \ldots 10$
 | 
			
		||||
on alkuluvut 2, 3, 5 ja 7.
 | 
			
		||||
 | 
			
		||||
On mahdollista osoittaa, että
 | 
			
		||||
\[\pi(n) \approx \frac{n}{\ln n},\]
 | 
			
		||||
mikä tarkoittaa, että alkulukuja esiintyy
 | 
			
		||||
varsin usein. Esimerkiksi alkulukujen määrä
 | 
			
		||||
välillä $1 \ldots 10^6$ on $\pi(10^6)=78498$
 | 
			
		||||
ja $10^6 / \ln 10^6 \approx 72382$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Konjektuureja}
 | 
			
		||||
 | 
			
		||||
Alkulukuihin liittyy useita \emph{konjektuureja}
 | 
			
		||||
eli lauseita, joiden uskotaan olevan tosia mutta
 | 
			
		||||
joita kukaan ei ole onnistunut todistamaan tähän mennessä.
 | 
			
		||||
Kuuluisia konjektuureja ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\index{Goldbachin konjektuuri@Goldbachin konjektuuri}
 | 
			
		||||
\item \key{Goldbachin konjektuuri}:
 | 
			
		||||
Jokainen parillinen kokonaisluku $n>2$ voidaan esittää
 | 
			
		||||
muodossa $n=a+b$ niin, että $a$ ja $b$
 | 
			
		||||
ovat alkulukuja.
 | 
			
		||||
\index{alkulukupari@alkulukupari}
 | 
			
		||||
\item \key{Alkulukuparit}:
 | 
			
		||||
On olemassa äärettömästi pareja muotoa $\{p,p+2\}$,
 | 
			
		||||
joissa sekä $p$ että $p+2$ on alkuluku.
 | 
			
		||||
\index{Legendren konjektuuri@Legendren konjektuuri}
 | 
			
		||||
\item \key{Legendren konjektuuri}:
 | 
			
		||||
Lukujen $n^2$ ja $(n+1)^2$ välillä on aina alkuluku,
 | 
			
		||||
kun $n$ on mikä tahansa positiivinen kokonaisluku.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Perusalgoritmit}
 | 
			
		||||
 | 
			
		||||
Jos luku $n$ ei ole alkuluku,
 | 
			
		||||
niin sen voi esittää muodossa $a \cdot b$,
 | 
			
		||||
missä $a \le \sqrt n$ tai $b \le \sqrt n$,
 | 
			
		||||
minkä ansiosta sillä on varmasti
 | 
			
		||||
tekijä välillä $2 \ldots \sqrt n$.
 | 
			
		||||
Tämän havainnon avulla voi tarkastaa ajassa $O(\sqrt n)$,
 | 
			
		||||
onko luku alkuluku,
 | 
			
		||||
sekä myös selvittää ajassa $O(\sqrt n)$
 | 
			
		||||
luvun alkutekijät.
 | 
			
		||||
 | 
			
		||||
Seuraava funktio \texttt{alkuluku} tutkii,
 | 
			
		||||
onko annettu luku $n$ alkuluku.
 | 
			
		||||
Funktio koettaa jakaa lukua kaikilla luvuilla
 | 
			
		||||
välillä $2 \ldots \sqrt n$, ja jos mikään
 | 
			
		||||
luvuista ei jaa $n$:ää, niin $n$ on alkuluku.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
bool alkuluku(int n) {
 | 
			
		||||
    if (n < 2) return false;
 | 
			
		||||
    for (int x = 2; x*x <= n; x++) {
 | 
			
		||||
        if (n%x == 0) return false;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Seuraava funktio \texttt{tekijat} muodostaa
 | 
			
		||||
vektorin, joka sisältää luvun $n$
 | 
			
		||||
alkutekijät.
 | 
			
		||||
Funktio jakaa $n$:ää sen alkutekijöillä ja lisää
 | 
			
		||||
niitä samaan aikaan vektoriin.
 | 
			
		||||
Prosessi päättyy, kun jäljellä on luku $n$,
 | 
			
		||||
jolla ei ole tekijää välillä $2 \ldots \sqrt n$.
 | 
			
		||||
Jos $n>1$, se on alkuluku ja viimeinen tekijä.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
vector<int> tekijat(int n) {
 | 
			
		||||
    vector<int> f;
 | 
			
		||||
    for (int x = 2; x*x <= n; x++) {
 | 
			
		||||
        while (n%x == 0) {
 | 
			
		||||
            f.push_back(x);
 | 
			
		||||
            n /= x;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (n > 1) f.push_back(n);
 | 
			
		||||
    return f;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Huomaa, että funktio lisää jokaisen
 | 
			
		||||
alkutekijän vektoriin
 | 
			
		||||
niin monta kertaa, kuin kyseinen
 | 
			
		||||
alkutekijä jakaa luvun.
 | 
			
		||||
Esimerkiksi $24=2^3 \cdot 3$,
 | 
			
		||||
joten funktio muodostaa vektorin $[2,2,2,3]$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Eratostheneen seula}
 | 
			
		||||
 | 
			
		||||
\index{Eratostheneen seula@Eratostheneen seula}
 | 
			
		||||
 | 
			
		||||
\key{Eratostheneen seula} on esilaskenta-algoritmi,
 | 
			
		||||
jonka suorituksen jälkeen mistä tahansa
 | 
			
		||||
välin $2 \ldots n$ luvusta pystyy tarkastamaan
 | 
			
		||||
nopeasti, onko se alkuluku,
 | 
			
		||||
sekä etsimään yhden luvun alkutekijän,
 | 
			
		||||
jos luku ei ole alkuluku.
 | 
			
		||||
 | 
			
		||||
Algoritmi luo taulukon $\texttt{a}$,
 | 
			
		||||
jossa on käytössä indeksit $2,3,\ldots,n$.
 | 
			
		||||
Taulukossa $\texttt{a}[k]=0$ tarkoittaa,
 | 
			
		||||
että $k$ on alkuluku,
 | 
			
		||||
ja $\texttt{a}[k] \neq 0$ tarkoittaa,
 | 
			
		||||
että $k$ ei ole alkuluku.
 | 
			
		||||
Jälkimmäisessä tapauksessa $\texttt{a}[k]$
 | 
			
		||||
on yksi $k$:n alkutekijöistä.
 | 
			
		||||
 | 
			
		||||
Algoritmi käy läpi välin
 | 
			
		||||
$2 \ldots n$ luvut yksi kerrallaan.
 | 
			
		||||
Aina kun vastaan tulee uusi alkuluku $x$,
 | 
			
		||||
niin algoritmi merkitsee taulukkoon, että $x$:n moninkerrat
 | 
			
		||||
$2x,3x,4x,\ldots$ eivät ole alkulukuja,
 | 
			
		||||
koska niillä on alkutekijä $x$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos $n=20$,
 | 
			
		||||
taulukosta tulee:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (19,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$0$};
 | 
			
		||||
\node at (1.5,0.5) {$0$};
 | 
			
		||||
\node at (2.5,0.5) {$2$};
 | 
			
		||||
\node at (3.5,0.5) {$0$};
 | 
			
		||||
\node at (4.5,0.5) {$3$};
 | 
			
		||||
\node at (5.5,0.5) {$0$};
 | 
			
		||||
\node at (6.5,0.5) {$2$};
 | 
			
		||||
\node at (7.5,0.5) {$3$};
 | 
			
		||||
\node at (8.5,0.5) {$5$};
 | 
			
		||||
\node at (9.5,0.5) {$0$};
 | 
			
		||||
\node at (10.5,0.5) {$3$};
 | 
			
		||||
\node at (11.5,0.5) {$0$};
 | 
			
		||||
\node at (12.5,0.5) {$7$};
 | 
			
		||||
\node at (13.5,0.5) {$5$};
 | 
			
		||||
\node at (14.5,0.5) {$2$};
 | 
			
		||||
\node at (15.5,0.5) {$0$};
 | 
			
		||||
\node at (16.5,0.5) {$3$};
 | 
			
		||||
\node at (17.5,0.5) {$0$};
 | 
			
		||||
\node at (18.5,0.5) {$5$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
 | 
			
		||||
\node at (0.5,1.5) {$2$};
 | 
			
		||||
\node at (1.5,1.5) {$3$};
 | 
			
		||||
\node at (2.5,1.5) {$4$};
 | 
			
		||||
\node at (3.5,1.5) {$5$};
 | 
			
		||||
\node at (4.5,1.5) {$6$};
 | 
			
		||||
\node at (5.5,1.5) {$7$};
 | 
			
		||||
\node at (6.5,1.5) {$8$};
 | 
			
		||||
\node at (7.5,1.5) {$9$};
 | 
			
		||||
\node at (8.5,1.5) {$10$};
 | 
			
		||||
\node at (9.5,1.5) {$11$};
 | 
			
		||||
\node at (10.5,1.5) {$12$};
 | 
			
		||||
\node at (11.5,1.5) {$13$};
 | 
			
		||||
\node at (12.5,1.5) {$14$};
 | 
			
		||||
\node at (13.5,1.5) {$15$};
 | 
			
		||||
\node at (14.5,1.5) {$16$};
 | 
			
		||||
\node at (15.5,1.5) {$17$};
 | 
			
		||||
\node at (16.5,1.5) {$18$};
 | 
			
		||||
\node at (17.5,1.5) {$19$};
 | 
			
		||||
\node at (18.5,1.5) {$20$};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi toteuttaa
 | 
			
		||||
Eratostheneen seulan.
 | 
			
		||||
Koodi olettaa, että jokainen taulukon \texttt{a}
 | 
			
		||||
alkio on aluksi 0.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
for (int x = 2; x <= n; x++) {
 | 
			
		||||
    if (a[x]) continue;
 | 
			
		||||
    for (int u = 2*x; u <= n; u += x) {
 | 
			
		||||
        a[u] = x;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Algoritmin sisäsilmukka suoritetaan
 | 
			
		||||
$n/x$ kertaa tietyllä $x$:n arvolla,
 | 
			
		||||
joten yläraja algoritmin ajankäytölle
 | 
			
		||||
on harmoninen summa
 | 
			
		||||
 | 
			
		||||
\index{harmoninen summa@harmoninen summa}
 | 
			
		||||
 | 
			
		||||
\[\sum_{x=2}^n n/x = n/2 + n/3 + n/4 + \cdots + n/n = O(n \log n).\]
 | 
			
		||||
 | 
			
		||||
Todellisuudessa algoritmi on vielä nopeampi,
 | 
			
		||||
koska sisäsilmukka suoritetaan vain,
 | 
			
		||||
jos luku $x$ on alkuluku.
 | 
			
		||||
Voidaan osoittaa, että algoritmin aikavaativuus
 | 
			
		||||
on vain $O(n \log \log n)$ eli hyvin lähellä vaativuutta $O(n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Eukleideen algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{suurin yhteinen tekijx@suurin yhteinen tekijä}
 | 
			
		||||
\index{pienin yhteinen moninkerta@pienin yhteinen moninkerta}
 | 
			
		||||
\index{Eukleideen algoritmi@Eukleideen algoritmi}
 | 
			
		||||
 | 
			
		||||
Lukujen $a$ ja $b$ \key{suurin yhteinen tekijä} eli $\textrm{syt}(a,b)$
 | 
			
		||||
on suurin luku, jolla sekä $a$ että $b$ on jaollinen.
 | 
			
		||||
Lukujen $a$ ja $b$ \key{pienin yhteinen moninkerta} eli $\textrm{pym}(a,b)$
 | 
			
		||||
on puolestaan pienin luku, joka on jaollinen sekä $a$:lla että $b$:llä.
 | 
			
		||||
Esimerkiksi $\textrm{syt}(24,36)=12$ ja
 | 
			
		||||
$\textrm{pym}(24,36)=72$.
 | 
			
		||||
 | 
			
		||||
Suurimman yhteisen tekijän ja pienimmän yhteisen
 | 
			
		||||
moninkerran välillä on yhteys
 | 
			
		||||
\[\textrm{pym}(a,b)=\frac{ab}{\textrm{syt}(a,b)}.\]
 | 
			
		||||
 | 
			
		||||
\key{Eukleideen algoritmi} on tehokas tapa etsiä
 | 
			
		||||
suurin yhteinen tekijä.
 | 
			
		||||
Se laskee suurimman yhteisen tekijän kaavalla
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    \textrm{syt}(a,b) = \begin{cases}
 | 
			
		||||
               a        & b = 0\\
 | 
			
		||||
               \textrm{syt}(b,a \bmod b) & b \neq 0\\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[\textrm{syt}(24,36) = \textrm{syt}(36,24)
 | 
			
		||||
= \textrm{syt}(24,12) = \textrm{syt}(12,0)=12.\]
 | 
			
		||||
Eukleideen algoritmin aikavaativuus
 | 
			
		||||
on $O(\log n)$, kun $n=\min(a,b)$.
 | 
			
		||||
Pahin tapaus algoritmille on, jos luvut ovat
 | 
			
		||||
peräkkäiset Fibonaccin luvut.
 | 
			
		||||
Silloin algoritmi käy läpi kaikki pienemmät
 | 
			
		||||
peräkkäiset Fibonaccin luvut.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[\textrm{syt}(13,8)=\textrm{syt}(8,5)
 | 
			
		||||
=\textrm{syt}(5,3)=\textrm{syt}(3,2)=\textrm{syt}(2,1)=\textrm{syt}(1,0)=1.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Eulerin totienttifunktio}
 | 
			
		||||
 | 
			
		||||
\index{suhteellinen alkuluku@suhteellinen alkuluku}
 | 
			
		||||
\index{Eulerin totienttifunktio@Eulerin totienttifunktio}
 | 
			
		||||
 | 
			
		||||
Luvut $a$ ja $b$ ovat suhteelliset alkuluvut,
 | 
			
		||||
jos $\textrm{syt}(a,b)=1$.
 | 
			
		||||
\key{Eulerin totienttifunktio} $\varphi(n)$
 | 
			
		||||
laskee luvun $n$ suhteellisten alkulukujen
 | 
			
		||||
määrän välillä $1 \ldots n$.
 | 
			
		||||
Esimerkiksi $\varphi(12)=4$,
 | 
			
		||||
koska luvut 1, 5, 7 ja 11 ovat suhteellisia
 | 
			
		||||
alkulukuja luvun 12:n kanssa.
 | 
			
		||||
 | 
			
		||||
Totienttifunktion arvon $\varphi(n)$ pystyy laskemaan
 | 
			
		||||
luvun $n$ alkutekijähajotelmasta kaavalla
 | 
			
		||||
\[ \varphi(n) = \prod_{i=1}^k p_i^{\alpha_i-1}(p_i-1). \]
 | 
			
		||||
Esimerkiksi $\varphi(12)=2^1 \cdot (2-1) \cdot 3^0 \cdot (3-1)=4$.
 | 
			
		||||
Huomaa myös, että $\varphi(n)=n-1$,
 | 
			
		||||
jos $n$ on alkuluku.
 | 
			
		||||
 | 
			
		||||
\section{Modulolaskenta}
 | 
			
		||||
 | 
			
		||||
\index{modulolaskenta@modulolaskenta}
 | 
			
		||||
 | 
			
		||||
\key{Modulolaskennassa} lukualuetta rajoitetaan
 | 
			
		||||
niin, että käytössä ovat vain
 | 
			
		||||
kokonaisluvut $0,1,2,\ldots,m-1$,
 | 
			
		||||
missä $m$ on vakio.
 | 
			
		||||
Ideana on, että lukua $x$ vastaa luku $x \bmod m$
 | 
			
		||||
eli luvun $x$ jakojäännös luvulla $m$.
 | 
			
		||||
Esimerkiksi jos $m=17$, niin lukua $75$ vastaa luku
 | 
			
		||||
$75 \bmod 17 = 7$.
 | 
			
		||||
 | 
			
		||||
Useissa laskutoimituksissa jakojäännöksen voi laskea
 | 
			
		||||
ennen laskutoimitusta, minkä ansiosta saadaan seuraavat kaavat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{rcl}
 | 
			
		||||
(x+y) \bmod m & = & (x \bmod m + y \bmod m) \bmod m \\
 | 
			
		||||
(x-y) \bmod m & = & (x \bmod m - y \bmod m) \bmod m \\
 | 
			
		||||
(x \cdot y) \bmod m & = & (x \bmod m \cdot y \bmod m) \bmod m \\
 | 
			
		||||
(x^k) \bmod m & = & (x \bmod m)^k \bmod m \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tehokas potenssilasku}
 | 
			
		||||
 | 
			
		||||
Modulolaskennassa tulee usein tarvetta laskea
 | 
			
		||||
tehokkaasti potenssilasku $x^n$.
 | 
			
		||||
Tämä onnistuu ajassa $O(\log n)$
 | 
			
		||||
seuraavan rekursion avulla:
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    x^n = \begin{cases}
 | 
			
		||||
               1        & n = 0\\
 | 
			
		||||
               x^{n/2} \cdot x^{n/2} & \text{$n$ on parillinen}\\
 | 
			
		||||
               x^{n-1} \cdot x & \text{$n$ on pariton}
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
 | 
			
		||||
Oleellista on, että parillisen $n$:n
 | 
			
		||||
tapauksessa luku $x^{n/2}$ lasketaan vain kerran.
 | 
			
		||||
Tämän ansiosta potenssilaskun aikavaativuus on $O(\log n)$,
 | 
			
		||||
koska $n$:n koko puolittuu aina silloin,
 | 
			
		||||
kun $n$ on parillinen.
 | 
			
		||||
 | 
			
		||||
Seuraava funktio laskee luvun $x^n \bmod m$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
int pot(int x, int n, int m) {
 | 
			
		||||
    if (n == 0) return 1%m;
 | 
			
		||||
    int u = pot(x,n/2,m);
 | 
			
		||||
    u = (u*u)%m;
 | 
			
		||||
    if (n%2 == 1) u = (u*x)%m;
 | 
			
		||||
    return u;
 | 
			
		||||
}
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Fermat'n pieni lause ja Eulerin lause}
 | 
			
		||||
 | 
			
		||||
\index{Fermat'n pieni lause}
 | 
			
		||||
\index{Eulerin lause@Eulerin lause}
 | 
			
		||||
 | 
			
		||||
\key{Fermat'n pienen lauseen} mukaan
 | 
			
		||||
\[x^{m-1} \bmod m = 1,\]
 | 
			
		||||
kun $m$ on alkuluku ja $x$ ja $m$ ovat suhteelliset alkuluvut.
 | 
			
		||||
Tällöin myös
 | 
			
		||||
\[x^k \bmod m = x^{k \bmod (m-1)} \bmod m.\]
 | 
			
		||||
Yleisemmin \key{Eulerin lauseen} mukaan
 | 
			
		||||
\[x^{\varphi(m)} \bmod m = 1,\]
 | 
			
		||||
kun $x$ ja $m$ ovat suhteelliset alkuluvut.
 | 
			
		||||
Fermat'n pieni lause seuraa Eulerin lauseesta,
 | 
			
		||||
koska jos $m$ on alkuluku, niin $\varphi(m)=m-1$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Modulon käänteisluku}
 | 
			
		||||
 | 
			
		||||
\index{modulon kxxnteisluku@modulon käänteisluku}
 | 
			
		||||
 | 
			
		||||
Luvun $x$ käänteisluku modulo $m$
 | 
			
		||||
tarkoittaa sellaista lukua $x^{-1}$,
 | 
			
		||||
että
 | 
			
		||||
\[ x x^{-1} \bmod m = 1. \]
 | 
			
		||||
Esimerkiksi jos $x=6$ ja $m=17$,
 | 
			
		||||
niin $x^{-1}=3$, koska $6\cdot3 \bmod 17=1$.
 | 
			
		||||
 | 
			
		||||
Modulon käänteisluku mahdollistaa
 | 
			
		||||
jakolaskun laskemisen modulossa,
 | 
			
		||||
koska jakolasku luvulla $x$ vastaa
 | 
			
		||||
kertolaskua luvulla $x^{-1}$.
 | 
			
		||||
Esimerkiksi jos haluamme laskea
 | 
			
		||||
jakolaskun $36/6 \bmod 17$,
 | 
			
		||||
voimme muuttaa sen muotoon  $2 \cdot 3 \bmod 17$,
 | 
			
		||||
koska $36 \bmod 17 = 2$ ja $6^{-1} \bmod 17 = 3$.
 | 
			
		||||
 | 
			
		||||
Modulon käänteislukua ei
 | 
			
		||||
kuitenkaan ole aina olemassa.
 | 
			
		||||
Esimerkiksi jos $x=2$ ja $m=4$,
 | 
			
		||||
yhtälölle
 | 
			
		||||
\[ x x^{-1} \bmod m = 1. \]
 | 
			
		||||
ei ole ratkaisua, koska kaikki luvun 2
 | 
			
		||||
moninkerrat ovat parillisia eikä jakojäännös
 | 
			
		||||
4:llä voi koskaan olla 1.
 | 
			
		||||
Osoittautuu, että $x^{-1} \bmod m$
 | 
			
		||||
on olemassa tarkalleen silloin,
 | 
			
		||||
kun $x$ ja $m$ ovat suhteelliset alkuluvut.
 | 
			
		||||
 | 
			
		||||
Jos modulon käänteisluku on olemassa,
 | 
			
		||||
sen saa laskettua kaavalla
 | 
			
		||||
\[
 | 
			
		||||
x^{-1} = x^{\varphi(m)-1}.
 | 
			
		||||
\]
 | 
			
		||||
Erityisesti jos $m$ on alkuluku, kaavasta tulee
 | 
			
		||||
\[
 | 
			
		||||
x^{-1} = x^{m-2}.
 | 
			
		||||
\]
 | 
			
		||||
Esimerkiksi jos $x=6$ ja $m=17$, niin
 | 
			
		||||
\[x^{-1}=6^{17-2} \bmod 17 = 3.\]
 | 
			
		||||
Tämän kaavan ansiosta modulon käänteisluvun pystyy
 | 
			
		||||
laskemaan nopeasti tehokkaan potenssilaskun avulla.
 | 
			
		||||
 | 
			
		||||
Modulon käänteisluvun kaavan voi perustella Eulerin lauseen avulla.
 | 
			
		||||
Ensinnäkin käänteisluvulle täytyy päteä
 | 
			
		||||
\[
 | 
			
		||||
x x^{-1} \bmod m = 1.
 | 
			
		||||
\]
 | 
			
		||||
Toisaalta Eulerin lauseen mukaan
 | 
			
		||||
\[
 | 
			
		||||
x^{\varphi(m)} \bmod m =  xx^{\varphi(m)-1} \bmod m = 1,
 | 
			
		||||
\]
 | 
			
		||||
joten lukujen $x^{-1}$ ja $x^{\varphi(m)-1}$ on oltava samat.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Modulot tietokoneessa}
 | 
			
		||||
 | 
			
		||||
Tietokone käsittelee etumerkittömiä kokonaislukuja
 | 
			
		||||
modulo $2^k$, missä $k$ on luvun bittien määrä.
 | 
			
		||||
Usein näkyvä seuraus tästä on luvun arvon pyörähtäminen
 | 
			
		||||
ympäri, jos luku kasvaa liian suureksi.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi C++:ssa \texttt{unsigned int} -tyyppinen
 | 
			
		||||
arvo lasketaan modulo $2^{32}$.
 | 
			
		||||
Seuraava koodi määrittelee muuttujan
 | 
			
		||||
tyyppiä \texttt{unsigned int},
 | 
			
		||||
joka saa arvon $123456789$.
 | 
			
		||||
Sitten muuttujan arvo kerrotaan itsellään,
 | 
			
		||||
jolloin tuloksena on luku 
 | 
			
		||||
$123456789^2 \bmod 2^{32} = 2537071545$.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
unsigned int x = 123456789;
 | 
			
		||||
cout << x*x << "\n"; // 2537071545
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Yhtälönratkaisu}
 | 
			
		||||
 | 
			
		||||
\index{Diofantoksen yhtxlz@Diofantoksen yhtälö}
 | 
			
		||||
 | 
			
		||||
\key{Diofantoksen yhtälö} on muotoa
 | 
			
		||||
\[ ax + by = c, \]
 | 
			
		||||
missä $a$, $b$ ja $c$ ovat vakioita
 | 
			
		||||
ja tehtävänä on ratkaista muuttujat $x$ ja $y$.
 | 
			
		||||
Jokaisen yhtälössä esiintyvän luvun tulee
 | 
			
		||||
olla kokonaisluku.
 | 
			
		||||
Esimerkiksi jos yhtälö on $5x+2y=11$, yksi ratkaisu
 | 
			
		||||
on valita $x=3$ ja $y=-2$.
 | 
			
		||||
 | 
			
		||||
\index{Eukleideen algoritmi@Eukleideen algoritmi}
 | 
			
		||||
 | 
			
		||||
Diofantoksen yhtälön voi ratkaista
 | 
			
		||||
tehokkaasti Eukleideen algoritmin avulla,
 | 
			
		||||
koska Eukleideen algoritmia laajentamalla
 | 
			
		||||
pystyy löytämään luvun $\textrm{syt}(a,b)$
 | 
			
		||||
lisäksi luvut $x$ ja $y$,
 | 
			
		||||
jotka toteuttavat yhtälön
 | 
			
		||||
\[
 | 
			
		||||
ax + by = \textrm{syt}(a,b).
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Diofantoksen yhtälön ratkaisu on olemassa, jos $c$ on
 | 
			
		||||
jaollinen $\textrm{syt}(a,b)$:llä,
 | 
			
		||||
ja muussa tapauksessa yhtälöllä ei ole ratkaisua.
 | 
			
		||||
 | 
			
		||||
\index{laajennettu Eukleideen algoritmi@laajennettu Eukleideen algoritmi}
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Laajennettu Eukleideen algoritmi}
 | 
			
		||||
 | 
			
		||||
Etsitään esimerkkinä luvut $x$ ja $y$,
 | 
			
		||||
jotka toteuttavat yhtälön
 | 
			
		||||
\[
 | 
			
		||||
39x + 15y = 12.
 | 
			
		||||
\]
 | 
			
		||||
Yhtälöllä on ratkaisu, koska $\textrm{syt}(39,15)=3$
 | 
			
		||||
ja $3 \mid 12$.
 | 
			
		||||
Kun Eukleideen algoritmi laskee lukujen
 | 
			
		||||
39 ja 15 suurimman
 | 
			
		||||
yhteisen tekijän, syntyy ketju
 | 
			
		||||
\[
 | 
			
		||||
\textrm{syt}(39,15) = \textrm{syt}(15,9)
 | 
			
		||||
= \textrm{syt}(9,6) = \textrm{syt}(6,3)
 | 
			
		||||
= \textrm{syt}(3,0) = 3. \]
 | 
			
		||||
Algoritmin aikana muodostuvat jakoyhtälöt ovat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
39 - 2 \cdot 15 & = & 9 \\
 | 
			
		||||
15 - 1 \cdot 9 & = & 6 \\
 | 
			
		||||
9 - 1 \cdot 6 & = & 3 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Näiden yhtälöiden avulla saadaan
 | 
			
		||||
\[
 | 
			
		||||
39 \cdot 2 + 15 \cdot (-5) = 3
 | 
			
		||||
\]
 | 
			
		||||
ja kertomalla yhtälö 4:lla tuloksena on
 | 
			
		||||
\[
 | 
			
		||||
39 \cdot 8 + 15 \cdot (-20) = 12,
 | 
			
		||||
\]
 | 
			
		||||
joten alkuperäisen yhtälön ratkaisu on $x=8$ ja $y=-20$.
 | 
			
		||||
 | 
			
		||||
Diofantoksen yhtälön ratkaisu ei ole yksikäsitteinen,
 | 
			
		||||
vaan yhdestä ratkaisusta on mahdollista muodostaa
 | 
			
		||||
äärettömästi muita ratkaisuja.
 | 
			
		||||
Kun yhtälön ratkaisu on $(x,y)$,
 | 
			
		||||
niin myös
 | 
			
		||||
\[(x+\frac{kb}{\textrm{syt}(a,b)},y-\frac{ka}{\textrm{syt}(a,b)})\]
 | 
			
		||||
on ratkaisu, missä $k$ on mikä tahansa kokonaisluku.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kiinalainen jäännöslause}
 | 
			
		||||
 | 
			
		||||
\index{kiinalainen jxxnnzslause@kiinalainen jäännöslause}
 | 
			
		||||
 | 
			
		||||
\key{Kiinalainen jäännöslause} ratkaisee yhtälöryhmän muotoa
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
x & = & a_1 \bmod m_1 \\
 | 
			
		||||
x & = & a_2 \bmod m_2 \\
 | 
			
		||||
\cdots \\
 | 
			
		||||
x & = & a_n \bmod m_n \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
missä kaikki parit luvuista $m_1,m_2,\ldots,m_n$
 | 
			
		||||
ovat suhteellisia alkulukuja.
 | 
			
		||||
 | 
			
		||||
Olkoon $x^{-1}_m$ luvun $x$ käänteisluku
 | 
			
		||||
modulo $m$ ja
 | 
			
		||||
\[ X_k = \frac{m_1 m_2 \cdots m_n}{m_k}.\]
 | 
			
		||||
Näitä merkintöjä käyttäen yhtälöryhmän ratkaisu on
 | 
			
		||||
\[x = a_1 X_1 {X_1}^{-1}_{m_1} + a_2 X_2 {X_2}^{-1}_{m_2} + \cdots + a_n X_n {X_n}^{-1}_{m_n}.\]
 | 
			
		||||
Tässä ratkaisussa jokaiselle luvulle $k=1,2,\ldots,n$
 | 
			
		||||
pätee, että
 | 
			
		||||
\[a_k X_k {X_k}^{-1}_{m_k} \bmod m_k = a_k,\]
 | 
			
		||||
sillä
 | 
			
		||||
\[X_k {X_k}^{-1}_{m_k} \bmod m_k = 1.\]
 | 
			
		||||
Koska kaikki muut summan osat ovat jaollisia luvulla
 | 
			
		||||
$m_k$, ne eivät vaikuta jakojäännökseen ja
 | 
			
		||||
koko summan jakojäännös $m_k$:lla on $a_k$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi yhtälöryhmän
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
x & = & 3 \bmod 5 \\
 | 
			
		||||
x & = & 4 \bmod 7 \\
 | 
			
		||||
x & = & 2 \bmod 3 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
ratkaisu on
 | 
			
		||||
\[ 3 \cdot 21 \cdot 1 + 4 \cdot 15 \cdot 1 + 2 \cdot 35 \cdot 2 = 263.\]
 | 
			
		||||
 | 
			
		||||
Kun yksi ratkaisu $x$ on löytynyt,
 | 
			
		||||
sen avulla voi muodostaa äärettömästi
 | 
			
		||||
erilaisia ratkaisuja, koska kaikki luvut muotoa
 | 
			
		||||
\[x+m_1 m_2 \cdots m_n\]
 | 
			
		||||
ovat ratkaisuja.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\section{Muita tuloksia}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Lagrangen lause}
 | 
			
		||||
 | 
			
		||||
\index{Lagrangen lause@Lagrangen lause}
 | 
			
		||||
 | 
			
		||||
\key{Lagrangen lauseen} mukaan jokainen positiivinen kokonaisluku voidaan
 | 
			
		||||
esittää neljän neliöluvun summana eli muodossa $a^2+b^2+c^2+d^2$.
 | 
			
		||||
Esimerkiksi luku 123 voidaan esittää muodossa $8^2+5^2+5^2+3^2$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Zeckendorfin lause}
 | 
			
		||||
 | 
			
		||||
\index{Zeckendorfin lause@Zeckendorfin lause}
 | 
			
		||||
\index{Fibonaccin luku@Fibonaccin luku}
 | 
			
		||||
 | 
			
		||||
\key{Zeckendorfin lauseen} mukaan
 | 
			
		||||
jokaiselle positiiviselle kokonaisluvulle
 | 
			
		||||
on olemassa yksikäsitteinen esitys
 | 
			
		||||
Fibonaccin lukujen summana niin, että
 | 
			
		||||
mitkään kaksi lukua eivät ole samat eivätkä peräkkäiset
 | 
			
		||||
Fibonaccin luvut.
 | 
			
		||||
Esimerkiksi luku 74 voidaan esittää muodossa
 | 
			
		||||
$55+13+5+1$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pythagoraan kolmikot}
 | 
			
		||||
 | 
			
		||||
\index{Pythagoraan kolmikko@Pythagoraan kolmikko}
 | 
			
		||||
\index{Eukleideen kaava@Eukleideen kaava}
 | 
			
		||||
 | 
			
		||||
\key{Pythagoraan kolmikko} on lukukolmikko $(a,b,c)$,
 | 
			
		||||
joka toteuttaa Pythagoraan lauseen $a^2+b^2=c^2$
 | 
			
		||||
eli $a$, $b$ ja $c$ voivat olla suorakulmaisen
 | 
			
		||||
kolmion sivujen pituudet.
 | 
			
		||||
Esimerkiksi $(3,4,5)$ on Pythagoraan kolmikko.
 | 
			
		||||
 | 
			
		||||
Jos $(a,b,c)$ on Pythagoraan kolmikko,
 | 
			
		||||
niin myös kaikki kolmikot muotoa $(ka,kb,kc)$
 | 
			
		||||
ovat Pythagoraan kolmikoita,
 | 
			
		||||
missä $k>1$.
 | 
			
		||||
Pythagoraan kolmikko on \key{primitiivinen},
 | 
			
		||||
jos $a$, $b$ ja $c$ ovat suhteellisia alkulukuja,
 | 
			
		||||
ja primitiivisistä kolmikoista voi muodostaa
 | 
			
		||||
kaikki muut kolmikot kertoimen $k$ avulla.
 | 
			
		||||
 | 
			
		||||
\key{Eukleideen kaavan} mukaan jokainen primitiivinen
 | 
			
		||||
Pythagoraan kolmikko on muotoa
 | 
			
		||||
\[(n^2-m^2,2nm,n^2+m^2),\]
 | 
			
		||||
missä $0<m<n$, $n$ ja $m$ ovat suhteelliset
 | 
			
		||||
alkuluvut ja ainakin toinen luvuista $n$ ja $m$ on parillinen.
 | 
			
		||||
Esimerkiksi valitsemalla $m=1$ ja $n=2$ syntyy
 | 
			
		||||
pienin mahdollinen Pythagoraan kolmikko
 | 
			
		||||
\[(2^2-1^2,2\cdot2\cdot1,2^2+1^2)=(3,4,5).\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Wilsonin lause}
 | 
			
		||||
 | 
			
		||||
\index{Wilsonin lause@Wilsonin lause}
 | 
			
		||||
 | 
			
		||||
\key{Wilsonin lauseen} mukaan luku $n$ on alkuluku
 | 
			
		||||
tarkalleen silloin, kun
 | 
			
		||||
\[(n-1)! \bmod n = n-1.\]
 | 
			
		||||
Esimerkiksi luku 11 on alkuluku, koska
 | 
			
		||||
\[10! \bmod 11 = 10,\]
 | 
			
		||||
ja luku 12 ei ole alkuluku, koska
 | 
			
		||||
\[11! \bmod 12 = 0 \neq 11.\]
 | 
			
		||||
 | 
			
		||||
Wilsonin lauseen avulla voi siis tutkia, onko luku alkuluku.
 | 
			
		||||
Tämä ei ole kuitenkaan käytännössä hyvä tapa,
 | 
			
		||||
koska luvun $(n-1)!$ laskeminen on työlästä,
 | 
			
		||||
jos $n$ on suuri luku.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,831 @@
 | 
			
		|||
\chapter{Combinatorics}
 | 
			
		||||
 | 
			
		||||
\index{kombinatoriikka@kombinatoriikka}
 | 
			
		||||
 | 
			
		||||
\key{Kombinatoriikka} tarkoittaa yhdistelmien määrän laskemista.
 | 
			
		||||
Yleensä tavoitteena on toteuttaa laskenta
 | 
			
		||||
tehokkaasti niin, että jokaista yhdistelmää
 | 
			
		||||
ei tarvitse muodostaa erikseen.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää,
 | 
			
		||||
jossa laskettavana on,
 | 
			
		||||
monellako tavalla luvun $n$ voi esittää positiivisten
 | 
			
		||||
kokonaislukujen summana.
 | 
			
		||||
Esimerkiksi luvun 4 voi esittää 8 tavalla
 | 
			
		||||
seuraavasti:
 | 
			
		||||
\begin{multicols}{2}
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $1+1+1+1$
 | 
			
		||||
\item $1+1+2$
 | 
			
		||||
\item $1+2+1$
 | 
			
		||||
\item $2+1+1$
 | 
			
		||||
\item $2+2$
 | 
			
		||||
\item $3+1$
 | 
			
		||||
\item $1+3$
 | 
			
		||||
\item $4$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
\end{multicols}
 | 
			
		||||
 | 
			
		||||
Kombinatorisen tehtävän ratkaisun voi usein
 | 
			
		||||
laskea rekursiivisen funktion avulla.
 | 
			
		||||
Tässä tapauksessa voimme määritellä funktion $f(n)$,
 | 
			
		||||
joka laskee luvun $n$ esitystapojen määrän.
 | 
			
		||||
Esimerkiksi $f(4)=8$ yllä olevan esimerkin mukaisesti.
 | 
			
		||||
Funktion voi laskea rekursiivisesti seuraavasti:
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    f(n) = \begin{cases}
 | 
			
		||||
               1               & n = 1\\
 | 
			
		||||
               f(1)+f(2)+\ldots+f(n-1)+1 & n > 1\\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
Pohjatapauksena on $f(1)=1$,
 | 
			
		||||
koska luvun 1 voi esittää vain yhdellä tavalla.
 | 
			
		||||
Rekursiivinen tapaus käy läpi
 | 
			
		||||
kaikki vaihtoehdot,
 | 
			
		||||
mikä on summan viimeinen luku.
 | 
			
		||||
Esimerkiksi tapauksessa $n=4$ summa voi päättyä
 | 
			
		||||
$+1$, $+2$ tai $+3$.
 | 
			
		||||
Tämän lisäksi lasketaan mukaan esitystapa,
 | 
			
		||||
jossa on pelkkä luku $n$.
 | 
			
		||||
 | 
			
		||||
Funktion ensimmäiset arvot ovat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(2) & = & 2 \\
 | 
			
		||||
f(3) & = & 4 \\
 | 
			
		||||
f(4) & = & 8 \\
 | 
			
		||||
f(5) & = & 16 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Osoittautuu, että funktiolle on myös suljettu muoto
 | 
			
		||||
\[
 | 
			
		||||
f(n)=2^{n-1},
 | 
			
		||||
\]
 | 
			
		||||
mikä johtuu siitä, että summassa on $n-1$ mahdollista
 | 
			
		||||
kohtaa +-merkille ja niistä valitaan mikä tahansa osajoukko.
 | 
			
		||||
 | 
			
		||||
\section{Binomikerroin}
 | 
			
		||||
 | 
			
		||||
\index{binomikerroin@binomikerroin}
 | 
			
		||||
 | 
			
		||||
\key{Binomikerroin} ${n \choose k}$ ilmaisee,
 | 
			
		||||
monellako tavalla $n$ alkion joukosta
 | 
			
		||||
voidaan muodostaa $k$ alkion osajoukko.
 | 
			
		||||
Esimerkiksi ${5 \choose 3}=10$,
 | 
			
		||||
koska joukosta $\{1,2,3,4,5\}$
 | 
			
		||||
voidaan valita $10$ tavalla $3$ alkiota:
 | 
			
		||||
\[ \{1,2,3\}, \{1,2,4\}, \{1,2,5\}, \{1,3,4\}, \{1,3,5\}, 
 | 
			
		||||
\{1,4,5\}, \{2,3,4\}, \{2,3,5\}, \{2,4,5\}, \{3,4,5\} \]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laskutapa 1}
 | 
			
		||||
 | 
			
		||||
Binomikertoimen voi laskea rekursiivisesti seuraavasti:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
{n \choose k}  =  {n-1 \choose k-1} + {n-1 \choose k}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Ideana rekursiossa on tarkastella tiettyä
 | 
			
		||||
joukon alkiota $x$.
 | 
			
		||||
Jos alkio $x$ valitaan osajoukkoon,
 | 
			
		||||
täytyy vielä valita $n-1$ alkiosta $k-1$ alkiota.
 | 
			
		||||
Jos taas alkiota $x$ ei valita osajoukkoon,
 | 
			
		||||
täytyy vielä valita $n-1$ alkiosta $k$ alkiota.
 | 
			
		||||
 | 
			
		||||
Rekursion pohjatapaukset ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
{n \choose 0}  =  {n \choose n} = 1
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Tämä johtuu siitä, että on aina yksi tapa
 | 
			
		||||
muodostaa tyhjä osajoukko,
 | 
			
		||||
samoin kuin valita kaikki alkiot osajoukkoon.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laskutapa 2}
 | 
			
		||||
 | 
			
		||||
Toinen tapa laskea binomikerroin on seuraava:
 | 
			
		||||
\[
 | 
			
		||||
{n \choose k}  =  \frac{n!}{k!(n-k)!}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Kaavassa $n!$ on $n$ alkion permutaatioiden määrä.
 | 
			
		||||
Ideana on käydä läpi kaikki permutaatiot
 | 
			
		||||
ja valita kussakin tapauksessa
 | 
			
		||||
permutaation $k$ ensimmäistä alkiota osajoukkoon.
 | 
			
		||||
Koska ei ole merkitystä,
 | 
			
		||||
missä järjestyksessä osajoukon alkiot
 | 
			
		||||
ja ulkopuoliset alkiot ovat,
 | 
			
		||||
tulos jaetaan luvuilla $k!$ ja $(n-k)!$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ominaisuuksia}
 | 
			
		||||
 | 
			
		||||
Binomikertoimelle pätee
 | 
			
		||||
\[
 | 
			
		||||
{n \choose k}  =  {n \choose n-k},
 | 
			
		||||
\]
 | 
			
		||||
koska $k$ alkion valinta osajoukkoon
 | 
			
		||||
tarkoittaa samaa kuin että valitaan
 | 
			
		||||
$n-k$ alkiota osajoukon ulkopuolelle.
 | 
			
		||||
 | 
			
		||||
Binomikerrointen summa on
 | 
			
		||||
\[
 | 
			
		||||
{n \choose 0}+{n \choose 1}+{n \choose 2}+\ldots+{n \choose n}=2^n.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Nimi ''binomikerroin'' tulee siitä, että
 | 
			
		||||
 | 
			
		||||
\[ (a+b)^n =
 | 
			
		||||
{n \choose 0} a^n b^0 + 
 | 
			
		||||
{n \choose 1} a^{n-1} b^1 +
 | 
			
		||||
\ldots + 
 | 
			
		||||
{n \choose n-1} a^1 b^{n-1} +
 | 
			
		||||
{n \choose n} a^0 b^n. \]
 | 
			
		||||
 | 
			
		||||
\index{Pascalin kolmio}
 | 
			
		||||
 | 
			
		||||
Binomikertoimet esiintyvät myös \key{Pascalin
 | 
			
		||||
kolmiossa}, jonka reunoilla on lukua 1
 | 
			
		||||
ja jokainen luku saadaan
 | 
			
		||||
kahden yllä olevan luvun summana:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}{0.9}
 | 
			
		||||
\node at (0,0) {1};
 | 
			
		||||
\node at (-0.5,-0.5) {1};
 | 
			
		||||
\node at (0.5,-0.5) {1};
 | 
			
		||||
\node at (-1,-1) {1};
 | 
			
		||||
\node at (0,-1) {2};
 | 
			
		||||
\node at (1,-1) {1};
 | 
			
		||||
\node at (-1.5,-1.5) {1};
 | 
			
		||||
\node at (-0.5,-1.5) {3};
 | 
			
		||||
\node at (0.5,-1.5) {3};
 | 
			
		||||
\node at (1.5,-1.5) {1};
 | 
			
		||||
\node at (-2,-2) {1};
 | 
			
		||||
\node at (-1,-2) {4};
 | 
			
		||||
\node at (0,-2) {6};
 | 
			
		||||
\node at (1,-2) {4};
 | 
			
		||||
\node at (2,-2) {1};
 | 
			
		||||
\node at (-2,-2.5) {$\ldots$};
 | 
			
		||||
\node at (-1,-2.5) {$\ldots$};
 | 
			
		||||
\node at (0,-2.5) {$\ldots$};
 | 
			
		||||
\node at (1,-2.5) {$\ldots$};
 | 
			
		||||
\node at (2,-2.5) {$\ldots$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laatikot ja pallot}
 | 
			
		||||
 | 
			
		||||
''Laatikot ja pallot'' on usein hyödyllinen malli,
 | 
			
		||||
jossa $n$ laatikkoon sijoitetaan $k$ palloa.
 | 
			
		||||
Tarkastellaan seuraavaksi kolmea tapausta:
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 1}: Kuhunkin laatikkoon saa sijoittaa
 | 
			
		||||
enintään yhden pallon.
 | 
			
		||||
Esimerkiksi kun $n=5$ ja $k=2$,
 | 
			
		||||
sijoitustapoja on 10:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\newcommand\lax[3]{
 | 
			
		||||
\path[draw,thick,-] (#1-0.5,#2+0.5) -- (#1-0.5,#2-0.5) --
 | 
			
		||||
                    (#1+0.5,#2-0.5) -- (#1+0.5,#2+0.5);
 | 
			
		||||
\ifthenelse{\equal{#3}{1}}{\draw[fill=black] (#1,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1-0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1+0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
}
 | 
			
		||||
\newcommand\laa[7]{
 | 
			
		||||
    \lax{#1}{#2}{#3}
 | 
			
		||||
    \lax{#1+1.2}{#2}{#4}
 | 
			
		||||
    \lax{#1+2.4}{#2}{#5}
 | 
			
		||||
    \lax{#1+3.6}{#2}{#6}
 | 
			
		||||
    \lax{#1+4.8}{#2}{#7}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
\laa{0}{0}{1}{1}{0}{0}{0}
 | 
			
		||||
\laa{0}{-2}{1}{0}{1}{0}{0}
 | 
			
		||||
\laa{0}{-4}{1}{0}{0}{1}{0}
 | 
			
		||||
\laa{0}{-6}{1}{0}{0}{0}{1}
 | 
			
		||||
\laa{8}{0}{0}{1}{1}{0}{0}
 | 
			
		||||
\laa{8}{-2}{0}{1}{0}{1}{0}
 | 
			
		||||
\laa{8}{-4}{0}{1}{0}{0}{1}
 | 
			
		||||
\laa{16}{0}{0}{0}{1}{1}{0}
 | 
			
		||||
\laa{16}{-2}{0}{0}{1}{0}{1}
 | 
			
		||||
\laa{16}{-4}{0}{0}{0}{1}{1}
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa vastauksen antaa suoraan binomikerroin ${n \choose k}$.
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 2}: Samaan laatikkoon saa sijoittaa
 | 
			
		||||
monta palloa.
 | 
			
		||||
Esimerkiksi kun $n=5$ ja $k=2$, sijoitustapoja on 15:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\newcommand\lax[3]{
 | 
			
		||||
\path[draw,thick,-] (#1-0.5,#2+0.5) -- (#1-0.5,#2-0.5) --
 | 
			
		||||
                    (#1+0.5,#2-0.5) -- (#1+0.5,#2+0.5);
 | 
			
		||||
\ifthenelse{\equal{#3}{1}}{\draw[fill=black] (#1,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1-0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1+0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
}
 | 
			
		||||
\newcommand\laa[7]{
 | 
			
		||||
    \lax{#1}{#2}{#3}
 | 
			
		||||
    \lax{#1+1.2}{#2}{#4}
 | 
			
		||||
    \lax{#1+2.4}{#2}{#5}
 | 
			
		||||
    \lax{#1+3.6}{#2}{#6}
 | 
			
		||||
    \lax{#1+4.8}{#2}{#7}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
\laa{0}{0}{2}{0}{0}{0}{0}
 | 
			
		||||
\laa{0}{-2}{1}{1}{0}{0}{0}
 | 
			
		||||
\laa{0}{-4}{1}{0}{1}{0}{0}
 | 
			
		||||
\laa{0}{-6}{1}{0}{0}{1}{0}
 | 
			
		||||
\laa{0}{-8}{1}{0}{0}{0}{1}
 | 
			
		||||
\laa{8}{0}{0}{2}{0}{0}{0}
 | 
			
		||||
\laa{8}{-2}{0}{1}{1}{0}{0}
 | 
			
		||||
\laa{8}{-4}{0}{1}{0}{1}{0}
 | 
			
		||||
\laa{8}{-6}{0}{1}{0}{0}{1}
 | 
			
		||||
\laa{8}{-8}{0}{0}{2}{0}{0}
 | 
			
		||||
\laa{16}{0}{0}{0}{1}{1}{0}
 | 
			
		||||
\laa{16}{-2}{0}{0}{1}{0}{1}
 | 
			
		||||
\laa{16}{-4}{0}{0}{0}{2}{0}
 | 
			
		||||
\laa{16}{-6}{0}{0}{0}{1}{1}
 | 
			
		||||
\laa{16}{-8}{0}{0}{0}{0}{2}
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Prosessin voi kuvata merkkijonona, joka muodostuu
 | 
			
		||||
merkeistä ''o'' ja ''$\rightarrow$''.
 | 
			
		||||
Pallojen sijoittaminen alkaa
 | 
			
		||||
vasemmanpuoleisimmasta laatikosta.
 | 
			
		||||
Merkki ''o'' tarkoittaa, että pallo sijoitetaan
 | 
			
		||||
nykyiseen laatikkoon, ja merkki
 | 
			
		||||
''$\rightarrow$'' tarkoittaa, että siirrytään
 | 
			
		||||
seuraavaan laatikkoon.
 | 
			
		||||
 | 
			
		||||
Nyt jokainen sijoitustapa on merkkijono, jossa
 | 
			
		||||
on $k$ kertaa merkki ''o'' ja $n-1$ kertaa
 | 
			
		||||
merkki ''$\rightarrow$''.
 | 
			
		||||
Esimerkiksi sijoitustapaa
 | 
			
		||||
ylhäällä oikealla
 | 
			
		||||
vastaa merkkijono ''$\rightarrow$ $\rightarrow$ o $\rightarrow$ o $\rightarrow$''.
 | 
			
		||||
Niinpä sijoitustapojen määrä on ${k+n-1 \choose k}$.
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 3}: Kuhunkin laatikkoon saa sijoittaa
 | 
			
		||||
enintään yhden pallon ja lisäksi missään kahdessa
 | 
			
		||||
vierekkäisessä laatikossa ei saa olla palloa.
 | 
			
		||||
Esimerkiksi kun $n=5$ ja $k=2$, sijoitustapoja on 6:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\newcommand\lax[3]{
 | 
			
		||||
\path[draw,thick,-] (#1-0.5,#2+0.5) -- (#1-0.5,#2-0.5) --
 | 
			
		||||
                    (#1+0.5,#2-0.5) -- (#1+0.5,#2+0.5);
 | 
			
		||||
\ifthenelse{\equal{#3}{1}}{\draw[fill=black] (#1,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1-0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
\ifthenelse{\equal{#3}{2}}{\draw[fill=black] (#1+0.2,#2-0.3) circle (0.15);}{}
 | 
			
		||||
}
 | 
			
		||||
\newcommand\laa[7]{
 | 
			
		||||
    \lax{#1}{#2}{#3}
 | 
			
		||||
    \lax{#1+1.2}{#2}{#4}
 | 
			
		||||
    \lax{#1+2.4}{#2}{#5}
 | 
			
		||||
    \lax{#1+3.6}{#2}{#6}
 | 
			
		||||
    \lax{#1+4.8}{#2}{#7}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
\laa{0}{0}{1}{0}{1}{0}{0}
 | 
			
		||||
\laa{0}{-2}{1}{0}{0}{1}{0}
 | 
			
		||||
\laa{8}{0}{1}{0}{0}{0}{1}
 | 
			
		||||
\laa{8}{-2}{0}{1}{0}{1}{0}
 | 
			
		||||
\laa{16}{0}{0}{1}{0}{0}{1}
 | 
			
		||||
\laa{16}{-2}{0}{0}{1}{0}{1}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa voi ajatella, että alussa $k$ palloa
 | 
			
		||||
ovat laatikoissaan ja joka välissä on yksi tyhjä laatikko.
 | 
			
		||||
Tämän jälkeen jää valittavaksi $n-k-(k-1)=n-2k+1$ tyhjän laatikon paikat.
 | 
			
		||||
Mahdollisia välejä on $k+1$, joten tapauksen 2 perusteella
 | 
			
		||||
sijoitustapoja on ${n-2k+1+k+1-1 \choose n-2k+1} = {n-k+1 \choose n-2k+1}$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Multinomikerroin}
 | 
			
		||||
 | 
			
		||||
\index{multinomikerroin@multinomikerroin}
 | 
			
		||||
 | 
			
		||||
Binomikertoimen yleistys on \key{multinomikerroin}
 | 
			
		||||
 | 
			
		||||
\[ {n \choose k_1,k_2,\ldots,k_m} = \frac{n!}{k_1! k_2! \cdots k_m!}, \]
 | 
			
		||||
 | 
			
		||||
missä $k_1+k_2+\cdots+k_m=n$.
 | 
			
		||||
Multinomikerroin ilmaisee, monellako tavalla $n$ alkiota voidaan jakaa osajoukkoihin,
 | 
			
		||||
joiden koot ovat $k_1,k_2,\ldots,k_m$.
 | 
			
		||||
Jos $m=2$, multinomikertoimen kaava vastaa binomikertoimen kaavaa.
 | 
			
		||||
 | 
			
		||||
\section{Catalanin luvut}
 | 
			
		||||
 | 
			
		||||
\index{Catalanin luku@Catalanin luku}
 | 
			
		||||
 | 
			
		||||
\key{Catalanin luku} $C_n$ ilmaisee,
 | 
			
		||||
montako tapaa on muodostaa kelvollinen sulkulauseke
 | 
			
		||||
$n$ alkusulusta ja $n$ loppusulusta.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi $C_3=5$, koska 3 alkusulusta
 | 
			
		||||
ja 3 loppusulusta on mahdollista muodostaa
 | 
			
		||||
seuraavat kelvolliset sulkulausekkeet:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}[noitemsep]
 | 
			
		||||
\item \texttt{()()()}
 | 
			
		||||
\item \texttt{(())()}
 | 
			
		||||
\item \texttt{()(())}
 | 
			
		||||
\item \texttt{((()))}
 | 
			
		||||
\item \texttt{(()())}
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Sulkulausekkeet}
 | 
			
		||||
 | 
			
		||||
\index{sulkulauseke@sulkulauseke}
 | 
			
		||||
 | 
			
		||||
Mikä sitten tarkkaan ottaen on
 | 
			
		||||
\textit{kelvollinen sulkulauseke}?
 | 
			
		||||
Seuraavat säännöt kuvailevat täsmällisesti
 | 
			
		||||
kaikki kelvolliset sulkulausekkeet:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item Sulkulauseke \texttt{()} on kelvollinen.
 | 
			
		||||
\item Jos sulkulauseke $A$ on kelvollinen,
 | 
			
		||||
niin myös sulkulauseke \texttt{(}$A$\texttt{)}
 | 
			
		||||
on kelvollinen.
 | 
			
		||||
\item Jos sulkulausekkeet $A$ ja $B$ ovat kelvollisia,
 | 
			
		||||
niin myös sulkulauseke $AB$ on kelvollinen.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Toinen tapa luonnehtia kelvollista sulkulauseketta on,
 | 
			
		||||
että jos valitaan mikä tahansa lausekkeen alkuosa,
 | 
			
		||||
niin alkusulkuja on ainakin yhtä monta
 | 
			
		||||
kuin loppusulkuja.
 | 
			
		||||
Lisäksi koko lausekkeessa
 | 
			
		||||
tulee olla tarkalleen yhtä monta
 | 
			
		||||
alkusulkua ja loppusulkua.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laskutapa 1}
 | 
			
		||||
 | 
			
		||||
Catalanin lukuja voi laskea rekursiivisesti kaavalla
 | 
			
		||||
\[ C_n = \sum_{i=0}^{n-1} C_{i} C_{n-i-1}.\]
 | 
			
		||||
 | 
			
		||||
Summa käy läpi tavat
 | 
			
		||||
jakaa sulkulauseke kahteen osaan niin,
 | 
			
		||||
että kumpikin osa on kelvollinen sulkulauseke
 | 
			
		||||
ja alkuosa on mahdollisimman lyhyt mutta ei tyhjä.
 | 
			
		||||
Kunkin vaihtoehdon kohdalla alkuosassa
 | 
			
		||||
on $i+1$ sulkuparia ja lausekkeiden määrä
 | 
			
		||||
saadaan kertomalla keskenään:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $C_{i}$: tavat muodostaa sulkulauseke
 | 
			
		||||
alkuosan sulkupareista ulointa sulkuparia lukuun ottamatta
 | 
			
		||||
\item $C_{n-i-1}$: tavat muodostaa sulkulauseke
 | 
			
		||||
loppuosan sulkupareista
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Lisäksi pohjatapauksena on $C_0=1$, koska 0
 | 
			
		||||
sulkuparista voi muodostaa
 | 
			
		||||
tyhjän sulkulausekkeen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Laskutapa 2}
 | 
			
		||||
 | 
			
		||||
Catalanin lukuja voi laskea myös binomikertoimen avulla:
 | 
			
		||||
\[ C_n = \frac{1}{n+1} {2n \choose n}\]
 | 
			
		||||
Kaavan voi perustella seuraavasti:
 | 
			
		||||
 | 
			
		||||
Kun käytössä on $n$ alkusulkua ja $n$ loppusulkua,
 | 
			
		||||
niistä voi muodostaa kaikkiaan ${2n \choose n}$
 | 
			
		||||
sulkulauseketta.
 | 
			
		||||
Lasketaan seuraavaksi, moniko tällainen
 | 
			
		||||
sulkulauseke \textit{ei} ole kelvollinen.
 | 
			
		||||
 | 
			
		||||
Jos sulkulauseke ei ole kelvollinen,
 | 
			
		||||
siinä on oltava alkuosa,
 | 
			
		||||
jossa loppusulkuja on alkusulkuja enemmän.
 | 
			
		||||
Muutetaan jokainen tällaisen alkuosan
 | 
			
		||||
sulkumerkki käänteiseksi.
 | 
			
		||||
Esimerkiksi lausekkeessa \texttt{())()(}
 | 
			
		||||
alkuosa on \texttt{())} ja kääntämisen
 | 
			
		||||
jälkeen lausekkeesta tulee \texttt{)((()(}.
 | 
			
		||||
 | 
			
		||||
Tuloksena olevassa lausekkeessa on $n+1$ alkusulkua
 | 
			
		||||
ja $n-1$ loppusulkua. Tällaisia lausekkeita on
 | 
			
		||||
kaikkiaan ${2n \choose n+1}$,
 | 
			
		||||
joka on sama kuin ei-kelvollisten
 | 
			
		||||
sulkulausekkeiden määrä.
 | 
			
		||||
Niinpä kelvollisten
 | 
			
		||||
sulkulausekkeiden määrä voidaan laskea kaavalla
 | 
			
		||||
\[{2n \choose n}-{2n \choose n+1} = {2n \choose n} - \frac{n}{n+1} {2n \choose n} = \frac{1}{n+1} {2n \choose n}.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Puiden laskeminen}
 | 
			
		||||
 | 
			
		||||
Catalanin luvut kertovat myös juurellisten
 | 
			
		||||
puiden lukumääriä:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item $n$ solmun binääripuiden määrä on $C_n$
 | 
			
		||||
\item $n$ solmun juurellisten puiden määrä on $C_{n-1}$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
\noindent
 | 
			
		||||
Esimerkiksi tapauksessa $C_3=5$ binääripuut ovat
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\path[draw,thick,-] (0,0) -- (-1,-1);
 | 
			
		||||
\path[draw,thick,-] (0,0) -- (1,-1);
 | 
			
		||||
\draw[fill=white] (0,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (-1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (1,-1) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (4,0) -- (4-0.75,-1) -- (4-1.5,-2);
 | 
			
		||||
\draw[fill=white] (4,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (4-0.75,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (4-1.5,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (6.5,0) -- (6.5-0.75,-1) -- (6.5-0,-2);
 | 
			
		||||
\draw[fill=white] (6.5,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (6.5-0.75,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (6.5-0,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (9,0) -- (9+0.75,-1) -- (9-0,-2);
 | 
			
		||||
\draw[fill=white] (9,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (9+0.75,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (9-0,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (11.5,0) -- (11.5+0.75,-1) -- (11.5+1.5,-2);
 | 
			
		||||
\draw[fill=white] (11.5,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (11.5+0.75,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (11.5+1.5,-2) circle (0.3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja yleiset puut ovat
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\path[draw,thick,-] (0,0) -- (-1,-1);
 | 
			
		||||
\path[draw,thick,-] (0,0) -- (0,-1);
 | 
			
		||||
\path[draw,thick,-] (0,0) -- (1,-1);
 | 
			
		||||
\draw[fill=white] (0,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (-1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (1,-1) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (3,0) -- (3,-1) -- (3,-2) -- (3,-3);
 | 
			
		||||
\draw[fill=white] (3,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (3,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (3,-2) circle (0.3);
 | 
			
		||||
\draw[fill=white] (3,-3) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (6+0,0) -- (6-1,-1);
 | 
			
		||||
\path[draw,thick,-] (6+0,0) -- (6+1,-1) -- (6+1,-2);
 | 
			
		||||
\draw[fill=white] (6+0,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (6-1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (6+1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (6+1,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (9+0,0) -- (9+1,-1);
 | 
			
		||||
\path[draw,thick,-] (9+0,0) -- (9-1,-1) -- (9-1,-2);
 | 
			
		||||
\draw[fill=white] (9+0,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (9+1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (9-1,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (9-1,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (12+0,0) -- (12+0,-1) -- (12-1,-2);
 | 
			
		||||
\path[draw,thick,-] (12+0,0) -- (12+0,-1) -- (12+1,-2);
 | 
			
		||||
\draw[fill=white] (12+0,0) circle (0.3);
 | 
			
		||||
\draw[fill=white] (12+0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=white] (12-1,-2) circle (0.3);
 | 
			
		||||
\draw[fill=white] (12+1,-2) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\section{Inkluusio-ekskluusio}
 | 
			
		||||
 | 
			
		||||
\index{inkluusio-ekskluusio}
 | 
			
		||||
 | 
			
		||||
\key{Inkluusio-ekskluusio}
 | 
			
		||||
on tekniikka, jonka avulla pystyy laskemaan
 | 
			
		||||
joukkojen yhdisteen koon leikkausten
 | 
			
		||||
kokojen perusteella ja päinvastoin.
 | 
			
		||||
Yksinkertainen esimerkki periaatteesta on kaava
 | 
			
		||||
\[ |A \cup B| = |A| + |B| - |A \cap B|,\]
 | 
			
		||||
jossa $A$ ja $B$ ovat joukkoja ja $|X|$
 | 
			
		||||
tarkoittaa joukon $X$ kokoa.
 | 
			
		||||
Seuraava kuva havainnollistaa kaavaa,
 | 
			
		||||
kun joukot ovat tason ympyröitä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.8]
 | 
			
		||||
 | 
			
		||||
\draw (0,0) circle (1.5);
 | 
			
		||||
\draw (1.5,0) circle (1.5);
 | 
			
		||||
 | 
			
		||||
\node at (-0.75,0) {\small $A$};
 | 
			
		||||
\node at (2.25,0) {\small $B$};
 | 
			
		||||
\node at (0.75,0) {\small $A \cap B$};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tavoitteena on laskea, kuinka suuri on yhdiste $A \cup B$
 | 
			
		||||
eli alue, joka on toisen tai kummankin ympyrän sisällä.
 | 
			
		||||
Kuvan mukaisesti yhdisteen $A \cup B$ koko
 | 
			
		||||
saadaan laskemalla ensin yhteen ympyröiden $A$ ja $B$ koot
 | 
			
		||||
ja vähentämällä siitä sitten leikkauksen $A \cap B$ koko.
 | 
			
		||||
 | 
			
		||||
Samaa ideaa voi soveltaa, kun joukkoja on enemmän.
 | 
			
		||||
Kolmen joukon tapauksessa kaavasta tulee
 | 
			
		||||
\[ |A \cup B \cup C| = |A| + |B| + |C| - |A \cap B|  - |A \cap C|  - |B \cap C| + |A \cap B \cap C| \]
 | 
			
		||||
ja vastaava kuva on
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.8]
 | 
			
		||||
 | 
			
		||||
\draw (0,0) circle (1.75);
 | 
			
		||||
\draw (2,0) circle (1.75);
 | 
			
		||||
\draw (1,1.5) circle (1.75);
 | 
			
		||||
 | 
			
		||||
\node at (-0.75,-0.25) {\small $A$};
 | 
			
		||||
\node at (2.75,-0.25) {\small $B$};
 | 
			
		||||
\node at (1,2.5) {\small $C$};
 | 
			
		||||
\node at (1,-0.5) {\small $A \cap B$};
 | 
			
		||||
\node at (0,1.25) {\small $A \cap C$};
 | 
			
		||||
\node at (2,1.25) {\small $B \cap C$};
 | 
			
		||||
\node at (1,0.5) {\scriptsize $A \cap B \cap C$};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Yleisessä tapauksessa yhdisteen $X_1 \cup X_2 \cup \cdots \cup X_n$
 | 
			
		||||
koon saa laskettua käymällä läpi kaikki tavat muodostaa
 | 
			
		||||
leikkaus joukoista $X_1,X_2,\ldots,X_n$.
 | 
			
		||||
Parittoman määrän joukkoja sisältävät leikkaukset
 | 
			
		||||
lasketaan mukaan positiivisina ja
 | 
			
		||||
parillisen määrän negatiivisina.
 | 
			
		||||
 | 
			
		||||
Huomaa, että vastaavat kaavat toimivat myös käänteisesti
 | 
			
		||||
leikkauksen koon laskemiseen yhdisteiden kokojen perusteella.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[ |A \cap B| = |A| + |B| - |A \cup B|\]
 | 
			
		||||
ja
 | 
			
		||||
\[ |A \cap B \cap C| = |A| + |B| + |C| - |A \cup B|  - |A \cup C|  - |B \cup C| + |A \cup B \cup C| .\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Epäjärjestykset}
 | 
			
		||||
 | 
			
		||||
\index{epxjxrjestys@epäjärjestys}
 | 
			
		||||
 | 
			
		||||
Lasketaan esimerkkinä,
 | 
			
		||||
montako tapaa on muodostaa luvuista
 | 
			
		||||
$(1,2,\ldots,n)$ \key{epäjärjestys}
 | 
			
		||||
eli permutaatio,
 | 
			
		||||
jossa mikään luku ei ole alkuperäisellä paikallaan.
 | 
			
		||||
Esimerkiksi jos $n=3$, niin epäjärjestyksiä on kaksi: $(2,3,1)$ ja $(3,1,2)$.
 | 
			
		||||
 | 
			
		||||
Yksi tapa lähestyä tehtävää on käyttää inkluusio-ekskluusiota.
 | 
			
		||||
Olkoon joukko $X_k$ niiden permutaatioiden joukko,
 | 
			
		||||
jossa kohdassa $k$ on luku $k$.
 | 
			
		||||
Esimerkiksi jos $n=3$, niin joukot ovat seuraavat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
X_1 & = & \{(1,2,3),(1,3,2)\} \\
 | 
			
		||||
X_2 & = & \{(1,2,3),(3,2,1)\} \\
 | 
			
		||||
X_3 & = & \{(1,2,3),(2,1,3)\} \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Näitä joukkoja käyttäen epäjärjestysten määrä on
 | 
			
		||||
\[ n! - |X_1 \cup X_2 \cup \cdots \cup X_n|, \]
 | 
			
		||||
eli
 | 
			
		||||
riittää laskea joukkojen yhdisteen koko.
 | 
			
		||||
Tämä palautuu inkluusio-eks\-kluu\-sion avulla
 | 
			
		||||
joukkojen leikkausten kokojen laskemiseen,
 | 
			
		||||
mikä onnistuu tehokkaasti.
 | 
			
		||||
Esimerkiksi kun $n=3$, joukon $|X_1 \cup X_2 \cup X_3|$ koko on
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
 & & |X_1| + |X_2| + |X_3| - |X_1 \cap X_2|  - |X_1 \cap X_3|  - |X_2 \cap X_3| + |X_1 \cap X_2 \cap X_3| \\
 | 
			
		||||
 & = & 2+2+2-1-1-1+1 \\
 | 
			
		||||
 & = & 4, \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
joten ratkaisujen määrä on $3!-4=2$.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tehtävän voi ratkaista myös toisella
 | 
			
		||||
tavalla käyttämättä inkluusio-ekskluusiota.
 | 
			
		||||
Merkitään $f(n)$:llä jonon $(1,2,\ldots,n)$ epäjärjestysten määrää,
 | 
			
		||||
jolloin seuraava rekursio pätee:
 | 
			
		||||
 | 
			
		||||
\begin{equation*}
 | 
			
		||||
    f(n) = \begin{cases}
 | 
			
		||||
               0               & n = 1\\
 | 
			
		||||
               1               & n = 2\\
 | 
			
		||||
               (n-1)(f(n-2) + f(n-1)) & n>2 \\
 | 
			
		||||
           \end{cases}
 | 
			
		||||
\end{equation*}
 | 
			
		||||
 | 
			
		||||
Kaavan voi perustella käymällä läpi tapaukset,
 | 
			
		||||
miten luku 1 muuttuu epäjärjestyksessä.
 | 
			
		||||
On $n-1$ tapaa valita jokin luku $x$ luvun 1 tilalle.
 | 
			
		||||
Jokaisessa tällaisessa valinnassa on kaksi vaihtoehtoa:
 | 
			
		||||
 | 
			
		||||
\textit{Vaihtoehto 1:} Luvun $x$ tilalle valitaan luku 1.
 | 
			
		||||
Tällöin jää $n-2$ lukua, joille tulee muodostaa epäjärjestys.
 | 
			
		||||
 | 
			
		||||
\textit{Vaihtoehto 2:} Luvun $x$ tilalle ei valita lukua 1.
 | 
			
		||||
Tällöin jää $n-1$ lukua, joille tulee muodostaa epäjärjestys,
 | 
			
		||||
koska luvun $x$ tilalle ei saa valita lukua 1
 | 
			
		||||
ja kaikki muut luvut tulee saattaa epäjärjestykseen.
 | 
			
		||||
 | 
			
		||||
\section{Burnsiden lemma}
 | 
			
		||||
 | 
			
		||||
\index{Burnsiden lemma@Burnsiden lemma}
 | 
			
		||||
 | 
			
		||||
\key{Burnsiden lemma} laskee yhdistelmien määrän niin,
 | 
			
		||||
että symmetrisistä yhdistelmistä lasketaan
 | 
			
		||||
mukaan vain yksi edustaja.
 | 
			
		||||
Burnsiden lemman mukaan yhdistelmien määrä on
 | 
			
		||||
\[\sum_{k=1}^n \frac{c(k)}{n},\]
 | 
			
		||||
missä yhdistelmän asentoa voi muuttaa $n$ tavalla
 | 
			
		||||
ja $c(k)$ on niiden yhdistelmien määrä,
 | 
			
		||||
jotka pysyvät ennallaan, kun asentoa
 | 
			
		||||
muutetaan tavalla $k$.
 | 
			
		||||
 | 
			
		||||
Lasketaan esimerkkinä, montako
 | 
			
		||||
erilaista tapaa on
 | 
			
		||||
muodostaa $n$ helmen helminauha,
 | 
			
		||||
kun kunkin helmen värin tulee olla
 | 
			
		||||
väliltä $1,2,\ldots,m$.
 | 
			
		||||
Kaksi helminauhaa ovat symmetriset,
 | 
			
		||||
jos ne voi saada näyttämään samalta pyörittämällä.
 | 
			
		||||
Esimerkiksi helminauhan
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw[fill=white] (0,0) circle (1);
 | 
			
		||||
\draw[fill=red] (0,1) circle (0.3);
 | 
			
		||||
\draw[fill=blue] (1,0) circle (0.3);
 | 
			
		||||
\draw[fill=red] (0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=green] (-1,0) circle (0.3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
kanssa symmetriset helminauhat ovat seuraavat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw[fill=white] (0,0) circle (1);
 | 
			
		||||
\draw[fill=red] (0,1) circle (0.3);
 | 
			
		||||
\draw[fill=blue] (1,0) circle (0.3);
 | 
			
		||||
\draw[fill=red] (0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=green] (-1,0) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\draw[fill=white] (4,0) circle (1);
 | 
			
		||||
\draw[fill=green] (4+0,1) circle (0.3);
 | 
			
		||||
\draw[fill=red] (4+1,0) circle (0.3);
 | 
			
		||||
\draw[fill=blue] (4+0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=red] (4+-1,0) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\draw[fill=white] (8,0) circle (1);
 | 
			
		||||
\draw[fill=red] (8+0,1) circle (0.3);
 | 
			
		||||
\draw[fill=green] (8+1,0) circle (0.3);
 | 
			
		||||
\draw[fill=red] (8+0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=blue] (8+-1,0) circle (0.3);
 | 
			
		||||
 | 
			
		||||
\draw[fill=white] (12,0) circle (1);
 | 
			
		||||
\draw[fill=blue] (12+0,1) circle (0.3);
 | 
			
		||||
\draw[fill=red] (12+1,0) circle (0.3);
 | 
			
		||||
\draw[fill=green] (12+0,-1) circle (0.3);
 | 
			
		||||
\draw[fill=red] (12+-1,0) circle (0.3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tapoja muuttaa asentoa on $n$,
 | 
			
		||||
koska helminauhaa voi pyörittää $0,1,\ldots,n-1$
 | 
			
		||||
askelta myötäpäivään.
 | 
			
		||||
Jos helminauhaa pyörittää 0 askelta,
 | 
			
		||||
kaikki $m^n$ väritystä säilyvät ennallaan.
 | 
			
		||||
Jos taas helminauhaa pyörittää 1 askeleen,
 | 
			
		||||
vain $m$ yksiväristä helminauhaa säilyy ennallaan.
 | 
			
		||||
 | 
			
		||||
Yleisemmin kun helminauhaa pyörittää $k$ askelta,
 | 
			
		||||
ennallaan säilyvien yhdistelmien määrä on
 | 
			
		||||
\[m^{\textrm{syt}(k,n)},\]
 | 
			
		||||
missä $\textrm{syt}(k,n)$ on lukujen $k$ ja $n$
 | 
			
		||||
suurin yhteinen tekijä.
 | 
			
		||||
Tämä johtuu siitä, että $\textrm{syt}(k,n)$-kokoiset
 | 
			
		||||
pätkät helmiä siirtyvät toistensa paikoille
 | 
			
		||||
$k$ askelta eteenpäin.
 | 
			
		||||
Niinpä helminauhojen määrä on
 | 
			
		||||
Burnsiden lemman mukaan
 | 
			
		||||
\[\sum_{i=0}^{n-1} \frac{m^{\textrm{syt}(i,n)}}{n}. \]
 | 
			
		||||
Esimerkiksi kun helminauhan pituus on 4
 | 
			
		||||
ja värejä on 3, helminauhoja on
 | 
			
		||||
\[\frac{3^4+3+3^2+3}{4} = 24. \]
 | 
			
		||||
 | 
			
		||||
\section{Cayleyn kaava}
 | 
			
		||||
 | 
			
		||||
\index{Cayleyn kaava@Cayleyn kaava}
 | 
			
		||||
 | 
			
		||||
\key{Cayleyn kaavan} mukaan $n$ solmusta voi
 | 
			
		||||
muodostaa $n^{n-2}$ numeroitua puuta.
 | 
			
		||||
Puun solmut on numeroitu $1,2,\ldots,n$,
 | 
			
		||||
ja kaksi puuta ovat erilaiset,
 | 
			
		||||
jos niiden rakenne on erilainen
 | 
			
		||||
tai niissä on eri numerointi.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
\noindent
 | 
			
		||||
Esimerkiksi kun $n=4$, numeroitujen puiden määrä on $4^{4-2}=16$:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.8]
 | 
			
		||||
\footnotesize
 | 
			
		||||
 | 
			
		||||
\newcommand\puua[6]{
 | 
			
		||||
\path[draw,thick,-] (#1,#2) -- (#1-1.25,#2-1.5);
 | 
			
		||||
\path[draw,thick,-] (#1,#2) -- (#1,#2-1.5);
 | 
			
		||||
\path[draw,thick,-] (#1,#2) -- (#1+1.25,#2-1.5);
 | 
			
		||||
\node[draw, circle, fill=white] at (#1,#2) {#3};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1-1.25,#2-1.5) {#4};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1,#2-1.5) {#5};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1+1.25,#2-1.5) {#6};
 | 
			
		||||
}
 | 
			
		||||
\newcommand\puub[6]{
 | 
			
		||||
\path[draw,thick,-] (#1,#2) -- (#1+1,#2);
 | 
			
		||||
\path[draw,thick,-] (#1+1,#2) -- (#1+2,#2);
 | 
			
		||||
\path[draw,thick,-] (#1+2,#2) -- (#1+3,#2);
 | 
			
		||||
\node[draw, circle, fill=white] at (#1,#2) {#3};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1+1,#2) {#4};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1+2,#2) {#5};
 | 
			
		||||
\node[draw, circle, fill=white] at (#1+3,#2) {#6};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
\puua{0}{0}{1}{2}{3}{4}
 | 
			
		||||
\puua{4}{0}{2}{1}{3}{4}
 | 
			
		||||
\puua{8}{0}{3}{1}{2}{4}
 | 
			
		||||
\puua{12}{0}{4}{1}{2}{3}
 | 
			
		||||
 | 
			
		||||
\puub{0}{-3}{1}{2}{3}{4}
 | 
			
		||||
\puub{4.5}{-3}{1}{2}{4}{3}
 | 
			
		||||
\puub{9}{-3}{1}{3}{2}{4}
 | 
			
		||||
\puub{0}{-4.5}{1}{3}{4}{2}
 | 
			
		||||
\puub{4.5}{-4.5}{1}{4}{2}{3}
 | 
			
		||||
\puub{9}{-4.5}{1}{4}{3}{2}
 | 
			
		||||
\puub{0}{-6}{2}{1}{3}{4}
 | 
			
		||||
\puub{4.5}{-6}{2}{1}{4}{3}
 | 
			
		||||
\puub{9}{-6}{2}{3}{1}{4}
 | 
			
		||||
\puub{0}{-7.5}{2}{4}{1}{3}
 | 
			
		||||
\puub{4.5}{-7.5}{3}{1}{2}{4}
 | 
			
		||||
\puub{9}{-7.5}{3}{2}{1}{4}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Seuraavaksi näemme, miten Cayleyn kaavan
 | 
			
		||||
voi perustella samastamalla numeroidut puut
 | 
			
		||||
Prüfer-koodeihin.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Prüfer-koodi}
 | 
			
		||||
 | 
			
		||||
\index{Prüfer-koodi}
 | 
			
		||||
 | 
			
		||||
\key{Prüfer-koodi} on $n-2$ luvun jono,
 | 
			
		||||
joka kuvaa numeroidun puun rakenteen.
 | 
			
		||||
Koodi muodostuu poistamalla puusta
 | 
			
		||||
joka askeleella lehden, jonka numero on pienin,
 | 
			
		||||
ja lisäämällä lehden vieressä olevan solmun
 | 
			
		||||
numeron koodiin.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi puun
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (2,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (2,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (5.5,2) {$5$};
 | 
			
		||||
 | 
			
		||||
%\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
%\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
%\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Prüfer-koodi on $[4,4,2]$,
 | 
			
		||||
koska puusta poistetaan ensin solmu 1,
 | 
			
		||||
sitten solmu 3 ja lopuksi solmu 5.
 | 
			
		||||
 | 
			
		||||
Jokaiselle puulle voidaan laskea
 | 
			
		||||
Prüfer-koodi, minkä lisäksi
 | 
			
		||||
Prüfer-koodista pystyy palauttamaan
 | 
			
		||||
yksikäsitteisesti alkuperäisen puun.
 | 
			
		||||
Niinpä numeroituja puita on yhtä monta
 | 
			
		||||
kuin Prüfer-koodeja eli $n^{n-2}$.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,858 @@
 | 
			
		|||
\chapter{Matrices}
 | 
			
		||||
 | 
			
		||||
\index{matriisi@matriisi}
 | 
			
		||||
 | 
			
		||||
\key{Matriisi} on kaksiulotteista taulukkoa
 | 
			
		||||
vastaava matemaattinen käsite,
 | 
			
		||||
jolle on määritelty laskutoimituksia.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
A = 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  6 & 13 & 7 & 4 \\
 | 
			
		||||
  7 & 0 & 8 & 2 \\
 | 
			
		||||
  9 & 5 & 4 & 18 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
on matriisi, jossa on 3 riviä ja 4 saraketta
 | 
			
		||||
eli se on kokoa $3 \times 4$.
 | 
			
		||||
Viittaamme matriisin alkioihin
 | 
			
		||||
merkinnällä $[i,j]$,
 | 
			
		||||
jossa $i$ on rivi ja $j$ on sarake.
 | 
			
		||||
Esimerkiksi yllä olevassa matriisissa
 | 
			
		||||
$A[2,3]=8$ ja $A[3,1]=9$.
 | 
			
		||||
 | 
			
		||||
\index{vektori@vektori}
 | 
			
		||||
 | 
			
		||||
Matriisin erikoistapaus on \key{vektori},
 | 
			
		||||
joka on kokoa $n \times 1$ oleva yksiulotteinen matriisi.
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
V =
 | 
			
		||||
\begin{bmatrix}
 | 
			
		||||
4 \\
 | 
			
		||||
7 \\
 | 
			
		||||
5 \\
 | 
			
		||||
\end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
on vektori, jossa on 3 alkiota.
 | 
			
		||||
 | 
			
		||||
\index{transpoosi@transpoosi}
 | 
			
		||||
 | 
			
		||||
Matriisin $A$ \key{transpoosi} $A^T$ syntyy,
 | 
			
		||||
kun matriisin rivit ja sarakkeet vaihdetaan
 | 
			
		||||
keskenään eli $A^T[i,j]=A[j,i]$:
 | 
			
		||||
\[
 | 
			
		||||
A^T = 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  6 & 7 & 9 \\
 | 
			
		||||
  13 & 0 & 5 \\
 | 
			
		||||
  7 & 8 & 4 \\
 | 
			
		||||
  4 & 2 & 18 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\index{nelizmatriisi@neliömatriisi}
 | 
			
		||||
 | 
			
		||||
Matriisi on \key{neliömatriisi}, jos sen
 | 
			
		||||
korkeus ja leveys ovat samat.
 | 
			
		||||
Esimerkiksi seuraava matriisi on neliömatriisi:
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
S = 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  3 & 12 & 4  \\
 | 
			
		||||
  5 & 9 & 15  \\
 | 
			
		||||
  0 & 2 & 4 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\section{Laskutoimitukset}
 | 
			
		||||
 | 
			
		||||
Matriisien $A$ ja $B$ summa $A+B$ on määritelty,
 | 
			
		||||
jos matriisit ovat yhtä suuret.
 | 
			
		||||
Tuloksena oleva matriisi on
 | 
			
		||||
samaa kokoa kuin
 | 
			
		||||
matriisit $A$ ja $B$ ja sen jokainen
 | 
			
		||||
alkio on vastaavissa kohdissa
 | 
			
		||||
olevien alkioiden summa.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  6 & 1 & 4 \\
 | 
			
		||||
  3 & 9 & 2 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
+
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  4 & 9 & 3 \\
 | 
			
		||||
  8 & 1 & 3 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  6+4 & 1+9 & 4+3 \\
 | 
			
		||||
  3+8 & 9+1 & 2+3 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  10 & 10 & 7 \\
 | 
			
		||||
  11 & 10 & 5 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Matriisin $A$ kertominen luvulla $x$ tarkoittaa,
 | 
			
		||||
että jokainen matriisin alkio kerrotaan luvulla $x$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 2 \cdot \begin{bmatrix}
 | 
			
		||||
  6 & 1 & 4 \\
 | 
			
		||||
  3 & 9 & 2 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 \cdot 6 & 2\cdot1 & 2\cdot4 \\
 | 
			
		||||
  2\cdot3 & 2\cdot9 & 2\cdot2 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  12 & 2 & 8 \\
 | 
			
		||||
  6 & 18 & 4 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Matriisitulo}
 | 
			
		||||
 | 
			
		||||
\index{matriisitulo@matriisitulo}
 | 
			
		||||
 | 
			
		||||
Matriisien $A$ ja $B$ tulo $AB$ on määritelty,
 | 
			
		||||
jos matriisi $A$ on kokoa $a \times n$
 | 
			
		||||
ja matriisi $B$ on kokoa $n \times b$
 | 
			
		||||
eli matriisin $A$ leveys on sama kuin matriisin
 | 
			
		||||
$B$ korkeus.
 | 
			
		||||
Tuloksena oleva matriisi
 | 
			
		||||
on kokoa $a \times b$
 | 
			
		||||
ja sen alkiot lasketaan kaavalla
 | 
			
		||||
\[
 | 
			
		||||
AB[i,j] = \sum_{k=1}^n A[i,k] \cdot B[k,j].
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Kaavan tulkintana on, että kukin $AB$:n alkio
 | 
			
		||||
saadaan summana, joka muodostuu $A$:n ja
 | 
			
		||||
$B$:n alkioparien tuloista seuraavan
 | 
			
		||||
kuvan mukaisesti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\draw (0,0) grid (4,3);
 | 
			
		||||
\draw (5,0) grid (10,3);
 | 
			
		||||
\draw (5,4) grid (10,8);
 | 
			
		||||
 | 
			
		||||
\node at (2,-1) {$A$};
 | 
			
		||||
\node at (7.5,-1) {$AB$};
 | 
			
		||||
\node at (11,6) {$B$};
 | 
			
		||||
 | 
			
		||||
\draw[thick,->,red,line width=2pt] (0,1.5) -- (4,1.5);
 | 
			
		||||
\draw[thick,->,red,line width=2pt] (6.5,8) -- (6.5,4);
 | 
			
		||||
\draw[thick,red,line width=2pt] (6.5,1.5) circle (0.4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
  3 & 9 \\
 | 
			
		||||
  8 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 6 \\
 | 
			
		||||
  2 & 9 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 \cdot 1 + 4 \cdot 2 & 1 \cdot 6 + 4 \cdot 9 \\
 | 
			
		||||
  3 \cdot 1 + 9 \cdot 2 & 3 \cdot 6 + 9 \cdot 9 \\
 | 
			
		||||
  8 \cdot 1 + 6 \cdot 2 & 8 \cdot 6 + 6 \cdot 9 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  9 & 42 \\
 | 
			
		||||
  21 & 99 \\
 | 
			
		||||
  20 & 102 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Matriisitulo ei ole vaihdannainen,
 | 
			
		||||
eli ei ole voimassa $A \cdot B = B \cdot A$.
 | 
			
		||||
Kuitenkin matriisitulo
 | 
			
		||||
on liitännäinen, eli on voimassa $A \cdot (B \cdot C)=(A \cdot B) \cdot C$.
 | 
			
		||||
 | 
			
		||||
\index{ykkzsmatriisi@ykkösmatriisi}
 | 
			
		||||
 | 
			
		||||
\key{Ykkösmatriisi} on neliömatriisi,
 | 
			
		||||
jonka lävistäjän jokainen alkio on 1
 | 
			
		||||
ja jokainen muu alkio on 0.
 | 
			
		||||
Esimerkiksi $3 \times 3$ -yksikkömatriisi on
 | 
			
		||||
seuraavanlainen:
 | 
			
		||||
\[
 | 
			
		||||
 I = \begin{bmatrix}
 | 
			
		||||
  1 & 0 & 0 \\
 | 
			
		||||
  0 & 1 & 0 \\
 | 
			
		||||
  0 & 0 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Ykkösmatriisilla kertominen säilyttää matriisin
 | 
			
		||||
ennallaan. Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 0 & 0 \\
 | 
			
		||||
  0 & 1 & 0 \\
 | 
			
		||||
  0 & 0 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
  3 & 9 \\
 | 
			
		||||
  8 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
  3 & 9 \\
 | 
			
		||||
  8 & 6 \\
 | 
			
		||||
 \end{bmatrix} \hspace{10px} \textrm{ja} \hspace{10px}
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
  3 & 9 \\
 | 
			
		||||
  8 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 0 \\
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
  3 & 9 \\
 | 
			
		||||
  8 & 6 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Kahden $n \times n$ kokoisen matriisin tulon
 | 
			
		||||
laskeminen vie aikaa $O(n^3)$
 | 
			
		||||
käyttäen suoraviivaista algoritmia.
 | 
			
		||||
Myös nopeampia algoritmeja on olemassa:
 | 
			
		||||
tällä hetkellä nopein tunnettu algoritmi
 | 
			
		||||
vie aikaa $O(n^{2{,}37})$.
 | 
			
		||||
Tällaiset algoritmit eivät kuitenkaan
 | 
			
		||||
ole tarpeen kisakoodauksessa.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Matriisipotenssi}
 | 
			
		||||
 | 
			
		||||
\index{matriisipotenssi@matriisipotenssi}
 | 
			
		||||
 | 
			
		||||
Matriisin $A$ potenssi $A^k$ on
 | 
			
		||||
määritelty, jos $A$ on neliömatriisi.
 | 
			
		||||
Määritelmä nojautuu kertolaskuun:
 | 
			
		||||
\[ A^k = \underbrace{A \cdot A \cdot A \cdots A}_{\textrm{$k$ kertaa}} \]
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix}^3 =
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix} \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix} \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix} =
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  48 & 165 \\
 | 
			
		||||
  33 & 114 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Lisäksi $A^0$ tuottaa ykkösmatriisin. Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix}^0 =
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 0 \\
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Matriisin $A^k$ voi laskea tehokkaasti ajassa
 | 
			
		||||
$O(n^3 \log k)$ soveltamalla luvun 21.2
 | 
			
		||||
tehokasta potenssilaskua. Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix}^8 =
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix}^4 \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 5 \\
 | 
			
		||||
  1 & 4 \\
 | 
			
		||||
 \end{bmatrix}^4.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\subsubsection{Determinantti}
 | 
			
		||||
 | 
			
		||||
\index{determinantti@determinantti}
 | 
			
		||||
 | 
			
		||||
Matriisin $A$ \key{determinantti} $\det(A)$
 | 
			
		||||
on määritelty, jos $A$ on neliömatriisi.
 | 
			
		||||
Jos $A$ on kokoa $1 \times 1$,
 | 
			
		||||
niin $\det(A)=A[1,1]$.
 | 
			
		||||
Suuremmalle matriisille determinaatti lasketaan rekursiivisesti
 | 
			
		||||
kaavalla \index{kofaktori@kofaktori}
 | 
			
		||||
\[\det(A)=\sum_{j=1}^n A[1,j] C[1,j],\]
 | 
			
		||||
missä $C[i,j]$ on matriisin $A$ \key{kofaktori}
 | 
			
		||||
kohdassa $[i,j]$.
 | 
			
		||||
Kofaktori lasketaan puolestaan kaavalla
 | 
			
		||||
\[C[i,j] = (-1)^{i+j} \det(M[i,j]),\]
 | 
			
		||||
missä $M[i,j]$ on matriisi $A$, josta on poistettu
 | 
			
		||||
rivi $i$ ja sarake $j$.
 | 
			
		||||
Kofaktorissa olevan kertoimen $(-1)^{i+j}$ ansiosta
 | 
			
		||||
joka toinen determinantti
 | 
			
		||||
lisätään summaan positiivisena
 | 
			
		||||
ja joka toinen negatiivisena.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
\det(
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  3 & 4 \\
 | 
			
		||||
  1 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
) = 3 \cdot 6 - 4 \cdot 1 = 14 
 | 
			
		||||
\]
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
ja
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
\det(
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 4 & 3 \\
 | 
			
		||||
  5 & 1 & 6 \\
 | 
			
		||||
  7 & 2 & 4 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
) = 
 | 
			
		||||
2 \cdot
 | 
			
		||||
\det(
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 6 \\
 | 
			
		||||
  2 & 4 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
)
 | 
			
		||||
-4 \cdot
 | 
			
		||||
\det(
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  5 & 6 \\
 | 
			
		||||
  7 & 4 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
)
 | 
			
		||||
+3 \cdot
 | 
			
		||||
\det(
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  5 & 1 \\
 | 
			
		||||
  7 & 2 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
) = 81.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\index{kxxnteismatriisi@käänteismatriisi}
 | 
			
		||||
 | 
			
		||||
Determinantti kertoo, onko matriisille
 | 
			
		||||
$A$ olemassa \key{käänteismatriisia}
 | 
			
		||||
$A^{-1}$, jolle pätee $A \cdot A^{-1} = I$,
 | 
			
		||||
missä $I$ on ykkösmatriisi.
 | 
			
		||||
Osoittautuu, että $A^{-1}$ on olemassa
 | 
			
		||||
tarkalleen silloin, kun $\det(A) \neq 0$,
 | 
			
		||||
ja sen voi laskea kaavalla
 | 
			
		||||
 | 
			
		||||
\[A^{-1}[i,j] = \frac{C[j,i]}{det(A)}.\]
 | 
			
		||||
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
\underbrace{
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 4 & 3\\
 | 
			
		||||
  5 & 1 & 6\\
 | 
			
		||||
  7 & 2 & 4\\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
}_{A}
 | 
			
		||||
\cdot
 | 
			
		||||
\underbrace{
 | 
			
		||||
 \frac{1}{81}
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
   -8 & -10 & 21 \\
 | 
			
		||||
   22 & -13 & 3 \\
 | 
			
		||||
   3 & 24 & -18 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
}_{A^{-1}}
 | 
			
		||||
=
 | 
			
		||||
\underbrace{
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 & 0 & 0 \\
 | 
			
		||||
  0 & 1 & 0 \\
 | 
			
		||||
  0 & 0 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
}_{I}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
\section{Lineaariset rekursioyhtälöt}
 | 
			
		||||
 | 
			
		||||
\index{rekursioyhtxlz@rekursioyhtälö}
 | 
			
		||||
\index{lineaarinen rekursioyhtxlz@lineaarinen rekursioyhtälö}
 | 
			
		||||
 | 
			
		||||
\key{Lineaarinen rekursioyhtälö}
 | 
			
		||||
voidaan esittää funktiona $f(n)$,
 | 
			
		||||
jolle on annettu alkuarvot
 | 
			
		||||
$f(0),f(1),\ldots,f(k-1)$
 | 
			
		||||
ja jonka suuremmat arvot
 | 
			
		||||
parametrista $k$ lähtien lasketaan
 | 
			
		||||
rekursiivisesti kaavalla
 | 
			
		||||
\[f(n) = c_1 f(n-1) + c_2 f(n-2) + \ldots + c_k f (n-k),\]
 | 
			
		||||
missä $c_1,c_2,\ldots,c_k$ ovat vakiokertoimia.
 | 
			
		||||
 | 
			
		||||
Funktion arvon $f(n)$ voi laskea dynaamisella
 | 
			
		||||
ohjelmoinnilla ajassa $O(kn)$
 | 
			
		||||
laskemalla kaikki arvot $f(0),f(1),\ldots,f(n)$ järjestyksessä.
 | 
			
		||||
Tätä ratkaisua voi kuitenkin tehostaa merkittävästi
 | 
			
		||||
matriisien avulla, kun $k$ on pieni.
 | 
			
		||||
Seuraavaksi näemme, miten arvon $f(n)$
 | 
			
		||||
voi laskea ajassa $O(k^3 \log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Fibonaccin luvut}
 | 
			
		||||
 | 
			
		||||
\index{Fibonaccin luku@Fibonaccin luku}
 | 
			
		||||
 | 
			
		||||
Yksinkertainen esimerkki lineaarisesta rekursioyhtälöstä
 | 
			
		||||
on Fibonaccin luvut määrittelevä funktio:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(0) & = & 0 \\
 | 
			
		||||
f(1) & = & 1 \\
 | 
			
		||||
f(n) & = & f(n-1)+f(n-2) \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Tässä tapauksessa $k=2$ ja $c_1=c_2=1$.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Ideana on esittää Fibonaccin lukujen laskukaava
 | 
			
		||||
$2 \times 2$ -kokoisena neliömatriisina
 | 
			
		||||
$X$, jolle pätee
 | 
			
		||||
\[ X \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(i) \\
 | 
			
		||||
  f(i+1) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(i+1) \\
 | 
			
		||||
  f(i+2) \\
 | 
			
		||||
 \end{bmatrix},
 | 
			
		||||
 \]
 | 
			
		||||
eli $X$:lle annetaan
 | 
			
		||||
''syötteenä'' arvot $f(i)$ ja $f(i+1)$,
 | 
			
		||||
ja $X$ muodostaa niistä
 | 
			
		||||
arvot $f(i+1)$ ja $f(i+2)$.
 | 
			
		||||
Osoittautuu, että tällainen matriisi on
 | 
			
		||||
 | 
			
		||||
\[ X = 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
  1 & 1 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
\end{samepage}
 | 
			
		||||
\noindent
 | 
			
		||||
Esimerkiksi
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
  1 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(5) \\
 | 
			
		||||
  f(6) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
  1 & 1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  5 \\
 | 
			
		||||
  8 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  8 \\
 | 
			
		||||
  13 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(6) \\
 | 
			
		||||
  f(7) \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Tämän ansiosta arvon $f(n)$ sisältävän matriisin saa laskettua
 | 
			
		||||
kaavalla
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(n) \\
 | 
			
		||||
  f(n+1) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
X^n \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(0) \\
 | 
			
		||||
  f(1) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1 \\
 | 
			
		||||
  1 & 1 \\
 | 
			
		||||
 \end{bmatrix}^n
 | 
			
		||||
\cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 \\
 | 
			
		||||
  1 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Potenssilasku $X^n$ on mahdollista laskea ajassa
 | 
			
		||||
$O(k^3 \log n)$,
 | 
			
		||||
joten myös funktion arvon $f(n)$
 | 
			
		||||
saa laskettua ajassa $O(k^3 \log n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yleinen tapaus}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten yleistä tapausta,
 | 
			
		||||
missä $f(n)$ on mikä tahansa lineaarinen
 | 
			
		||||
rekursioyhtälö. Nyt tavoitteena on etsiä
 | 
			
		||||
matriisi $X$, jolle pätee
 | 
			
		||||
 | 
			
		||||
\[ X \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(i) \\
 | 
			
		||||
  f(i+1) \\
 | 
			
		||||
  \vdots \\
 | 
			
		||||
  f(i+k-1) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(i+1) \\
 | 
			
		||||
  f(i+2) \\
 | 
			
		||||
  \vdots \\
 | 
			
		||||
  f(i+k) \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Tällainen matriisi on
 | 
			
		||||
\[
 | 
			
		||||
X =
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1 & 0 & 0 & \cdots & 0 \\
 | 
			
		||||
  0 & 0 & 1 & 0 & \cdots & 0 \\
 | 
			
		||||
  0 & 0 & 0 & 1 & \cdots & 0 \\
 | 
			
		||||
  \vdots & \vdots & \vdots & \vdots & \ddots & \vdots \\
 | 
			
		||||
  0 & 0 & 0 & 0 & \cdots & 1 \\
 | 
			
		||||
  c_k & c_{k-1} & c_{k-2} & c_{k-3} & \cdots & c_1 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Matriisin $k-1$ ensimmäisen rivin jokainen alkio on 0,
 | 
			
		||||
paitsi yksi alkio on 1.
 | 
			
		||||
Nämä rivit kopioivat
 | 
			
		||||
arvon $f(i+1)$ arvon $f(i)$ tilalle,
 | 
			
		||||
arvon $f(i+2)$ arvon $f(i+1)$ tilalle jne.
 | 
			
		||||
Viimeinen rivi sisältää rekursiokaavan kertoimet,
 | 
			
		||||
joiden avulla muodostuu uusi arvo $f(i+k)$.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Nyt arvon $f(n)$ pystyy laskemaan ajassa $O(k^3 \log n)$
 | 
			
		||||
kaavalla
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(n) \\
 | 
			
		||||
  f(n+1) \\
 | 
			
		||||
  \vdots \\
 | 
			
		||||
  f(n+k-1) \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
X^n \cdot
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  f(0) \\
 | 
			
		||||
  f(1) \\
 | 
			
		||||
  \vdots \\
 | 
			
		||||
  f(k-1) \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\section{Verkot ja matriisit}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Polkujen määrä}
 | 
			
		||||
 | 
			
		||||
Matriisipotenssilla
 | 
			
		||||
on mielenkiintoinen vaikutus
 | 
			
		||||
verkon vierusmatriisin sisältöön.
 | 
			
		||||
Kun $V$ on painottoman verkon vierusmatriisi,
 | 
			
		||||
niin $V^n$ kertoo,
 | 
			
		||||
montako $n$ kaaren pituista polkua
 | 
			
		||||
eri solmuista on toisiinsa.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi verkon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (6);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
vierusmatriisi on
 | 
			
		||||
\[
 | 
			
		||||
V= \begin{bmatrix}
 | 
			
		||||
  0 & 0 & 0 & 1 & 0 & 0 \\
 | 
			
		||||
  1 & 0 & 0 & 0 & 1 & 1 \\
 | 
			
		||||
  0 & 1 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 1 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 0 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 0 & 1 & 0 & 1 & 0 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Nyt esimerkiksi matriisi
 | 
			
		||||
\[
 | 
			
		||||
V^4= \begin{bmatrix}
 | 
			
		||||
  0 & 0 & 1 & 1 & 1 & 0 \\
 | 
			
		||||
  2 & 0 & 0 & 0 & 2 & 2 \\
 | 
			
		||||
  0 & 2 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 2 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 0 & 0 & 0 & 0 & 0 \\
 | 
			
		||||
  0 & 0 & 1 & 1 & 1 & 0 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
kertoo, montako 4 kaaren pituista polkua
 | 
			
		||||
solmuista on toisiinsa.
 | 
			
		||||
Esimerkiksi $V^4[2,5]=2$,
 | 
			
		||||
koska solmusta 2 solmuun 5 on olemassa
 | 
			
		||||
4 kaaren pituiset polut
 | 
			
		||||
$2 \rightarrow 1 \rightarrow 4 \rightarrow 2 \rightarrow 5$
 | 
			
		||||
ja 
 | 
			
		||||
$2 \rightarrow 6 \rightarrow 3 \rightarrow 2 \rightarrow 5$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Lyhimmät polut}
 | 
			
		||||
 | 
			
		||||
Samantapaisella idealla voi laskea painotetussa verkossa
 | 
			
		||||
kullekin solmuparille,
 | 
			
		||||
kuinka pitkä on lyhin $n$ kaaren pituinen polku solmujen välillä.
 | 
			
		||||
Tämä vaatii matriisitulon määritelmän muuttamista
 | 
			
		||||
niin, että siinä ei lasketa polkujen yhteismäärää
 | 
			
		||||
vaan minimoidaan polun pituutta.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Tarkastellaan esimerkkinä seuraavaa verkkoa:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (1,1) {$4$};
 | 
			
		||||
\node[draw, circle] (3) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (4) at (5,3) {$3$};
 | 
			
		||||
\node[draw, circle] (5) at (3,1) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (5,1) {$6$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (1) -- node[font=\small,label=left:4] {} (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- node[font=\small,label=left:1] {} (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=north:2] {} (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- node[font=\small,label=north:4] {} (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=left:1] {} (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- node[font=\small,label=left:2] {} (6);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- node[font=\small,label=right:3] {} (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- node[font=\small,label=below:2] {} (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Muodostetaan verkosta vierusmatriisi, jossa arvo
 | 
			
		||||
$\infty$ tarkoittaa, että kaarta ei ole,
 | 
			
		||||
ja muut arvot ovat kaarten pituuksia.
 | 
			
		||||
Matriisista tulee
 | 
			
		||||
\[
 | 
			
		||||
V= \begin{bmatrix}
 | 
			
		||||
  \infty & \infty & \infty & 4 & \infty & \infty \\
 | 
			
		||||
  2 & \infty & \infty & \infty & 1 & 2 \\
 | 
			
		||||
  \infty & 4 & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & 1 & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & \infty & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & \infty & 3 & \infty & 2 & \infty \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Nyt voimme laskea matriisitulon kaavan
 | 
			
		||||
\[
 | 
			
		||||
AB[i,j] = \sum_{k=1}^n A[i,k] \cdot B[k,j]
 | 
			
		||||
\]
 | 
			
		||||
sijasta kaavalla
 | 
			
		||||
\[
 | 
			
		||||
AB[i,j] = \min_{k=1}^n A[i,k] + B[k,j],
 | 
			
		||||
\]
 | 
			
		||||
eli summa muuttuu minimiksi ja tulo summaksi.
 | 
			
		||||
Tämän seurauksena matriisipotenssi
 | 
			
		||||
selvittää lyhimmät polkujen pituudet solmujen
 | 
			
		||||
välillä. Esimerkiksi
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
V^4= \begin{bmatrix}
 | 
			
		||||
  \infty & \infty & 10 & 11 & 9 & \infty \\
 | 
			
		||||
  9 & \infty & \infty & \infty & 8 & 9 \\
 | 
			
		||||
  \infty & 11 & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & 8 & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & \infty & \infty & \infty & \infty & \infty \\
 | 
			
		||||
  \infty & \infty & 12 & 13 & 11 & \infty \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\]
 | 
			
		||||
eli esimerkiksi lyhin 4 kaaren pituinen polku
 | 
			
		||||
solmusta 2 solmuun 5 on pituudeltaan 8.
 | 
			
		||||
Tämä polku on $2 \rightarrow 1 \rightarrow 4 \rightarrow 2 \rightarrow 5$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kirchhoffin lause}
 | 
			
		||||
 | 
			
		||||
\index{Kirchhoffin lause@Kirchhoffin lause}
 | 
			
		||||
\index{virittxvx puu@virittävä puu}
 | 
			
		||||
 | 
			
		||||
\key{Kirchhoffin lause} laskee
 | 
			
		||||
verkon virittävän puiden määrän
 | 
			
		||||
verkosta muodostetun matriisin determinantin avulla.
 | 
			
		||||
Esimerkiksi verkolla
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
on kolme virittävää puuta:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1a) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2a) at (3,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3a) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4a) at (3,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1a) -- (2a);
 | 
			
		||||
%\path[draw,thick,-] (1a) -- (3a);
 | 
			
		||||
\path[draw,thick,-] (3a) -- (4a);
 | 
			
		||||
\path[draw,thick,-] (1a) -- (4a);
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (1b) at (1+4,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2b) at (3+4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3b) at (1+4,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4b) at (3+4,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1b) -- (2b);
 | 
			
		||||
\path[draw,thick,-] (1b) -- (3b);
 | 
			
		||||
%\path[draw,thick,-] (3b) -- (4b);
 | 
			
		||||
\path[draw,thick,-] (1b) -- (4b);
 | 
			
		||||
 | 
			
		||||
\node[draw, circle] (1c) at (1+8,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2c) at (3+8,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3c) at (1+8,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4c) at (3+8,1) {$4$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1c) -- (2c);
 | 
			
		||||
\path[draw,thick,-] (1c) -- (3c);
 | 
			
		||||
\path[draw,thick,-] (3c) -- (4c);
 | 
			
		||||
%\path[draw,thick,-] (1c) -- (4c);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\index{Laplacen matriisi@Laplacen matriisi}
 | 
			
		||||
Muodostetaan verkosta \key{Laplacen matriisi} $L$,
 | 
			
		||||
jossa $L[i,i]$ on solmun $i$ aste ja
 | 
			
		||||
$L[i,j]=-1$, jos solmujen $i$ ja $j$ välillä on kaari,
 | 
			
		||||
ja muuten $L[i,j]=0$.
 | 
			
		||||
Tässä tapauksessa matriisista tulee
 | 
			
		||||
 | 
			
		||||
\[
 | 
			
		||||
L= \begin{bmatrix}
 | 
			
		||||
  3 & -1 & -1 & -1 \\
 | 
			
		||||
  -1 & 1 & 0 & 0 \\
 | 
			
		||||
  -1 & 0 & 2 & -1 \\
 | 
			
		||||
  -1 & 0 & -1 & 2 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Nyt virittävien puiden määrä on determinantti
 | 
			
		||||
matriisista, joka saadaan poistamasta matriisista $L$
 | 
			
		||||
jokin rivi ja jokin sarake.
 | 
			
		||||
Esimerkiksi jos poistamme ylimmän rivin ja
 | 
			
		||||
vasemman sarakkeen, tuloksena on
 | 
			
		||||
 | 
			
		||||
\[ \det(
 | 
			
		||||
\begin{bmatrix}
 | 
			
		||||
  1 & 0 & 0 \\
 | 
			
		||||
  0 & 2 & -1 \\
 | 
			
		||||
  0 & -1 & 2 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
) =3.\]
 | 
			
		||||
Determinantista tulee aina sama riippumatta siitä,
 | 
			
		||||
mikä rivi ja sarake matriisista $L$ poistetaan.
 | 
			
		||||
 | 
			
		||||
Huomaa, että Kirchhoffin lauseen erikoistapauksena on
 | 
			
		||||
luvun 22.5 Cayleyn kaava, koska
 | 
			
		||||
täydellisessä $n$ solmun verkossa
 | 
			
		||||
 | 
			
		||||
\[ \det(
 | 
			
		||||
\begin{bmatrix}
 | 
			
		||||
  n-1 & -1 & \cdots & -1 \\
 | 
			
		||||
  -1 & n-1 & \cdots & -1 \\
 | 
			
		||||
  \vdots & \vdots & \ddots & \vdots \\
 | 
			
		||||
  -1 & -1 & \cdots & n-1 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
) =n^{n-2}.\]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,686 @@
 | 
			
		|||
\chapter{Probability}
 | 
			
		||||
 | 
			
		||||
\index{todennxkzisyys@todennäköisyys}
 | 
			
		||||
 | 
			
		||||
\key{Todennäköisyys} on luku väliltä $0 \ldots 1$,
 | 
			
		||||
joka kuvaa sitä, miten todennäköinen jokin
 | 
			
		||||
tapahtuma on.
 | 
			
		||||
Varman tapahtuman todennäköisyys on 1,
 | 
			
		||||
ja mahdottoman tapahtuman todennäköisyys on 0.
 | 
			
		||||
 | 
			
		||||
Tyypillinen esimerkki todennäköisyydestä
 | 
			
		||||
on nopan heitto, jossa tuloksena
 | 
			
		||||
on silmäluku väliltä $1,2,\ldots,6$.
 | 
			
		||||
Yleensä oletetaan, että kunkin silmäluvun
 | 
			
		||||
todennäköisyys on $1/6$
 | 
			
		||||
eli kaikki tulokset ovat yhtä todennäköisiä.
 | 
			
		||||
 | 
			
		||||
Tapahtuman todennäköisyyttä merkitään $P(\cdots)$,
 | 
			
		||||
jossa kolmen pisteen tilalla on tapahtuman kuvaus.
 | 
			
		||||
Esimerkiksi nopan heitossa
 | 
			
		||||
$P(\textrm{''silmäluku on 4''})=1/6$,
 | 
			
		||||
$P(\textrm{''silmäluku ei ole 6''})=5/6$
 | 
			
		||||
ja $P(\textrm{''silmäluku on parillinen''})=1/2$.
 | 
			
		||||
 | 
			
		||||
\section{Laskutavat}
 | 
			
		||||
 | 
			
		||||
Todennäköisyyden laskemiseen on kaksi
 | 
			
		||||
tavallista laskutapaa:
 | 
			
		||||
kombinatorinen laskeminen ja prosessin simulointi.
 | 
			
		||||
Lasketaan esimerkkinä, mikä on todennäköisyys sille,
 | 
			
		||||
että kun sekoitetusta korttipakasta nostetaan
 | 
			
		||||
kolme ylintä korttia, jokaisen kortin arvo on sama
 | 
			
		||||
(esimerkiksi ristikasi, herttakasi ja patakasi).
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Laskutapa 1}
 | 
			
		||||
 | 
			
		||||
Kombinatorisessa laskutavassa
 | 
			
		||||
todennäköisyyden kaava on
 | 
			
		||||
 | 
			
		||||
\[\frac{\textrm{halutut tapaukset}}{\textrm{kaikki tapaukset}}.\]
 | 
			
		||||
 | 
			
		||||
Tässä tehtävässä halutut tapaukset ovat niitä,
 | 
			
		||||
joissa jokaisen kolmen kortin arvo on sama.
 | 
			
		||||
Tällaisia tapauksia on $13 {4 \choose 3}$,
 | 
			
		||||
koska on 13 vaihtoehtoa, mikä on kortin arvo,
 | 
			
		||||
ja ${4 \choose 3}$ tapaa valita 3 maata 4 mahdollisesta.
 | 
			
		||||
 | 
			
		||||
Kaikkien tapausten määrä on ${52 \choose 3}$,
 | 
			
		||||
koska 52 kortista valitaan 3 korttia.
 | 
			
		||||
Niinpä tapahtuman todennäköisyys on
 | 
			
		||||
 | 
			
		||||
\[\frac{13 {4 \choose 3}}{{52 \choose 3}} = \frac{1}{425}.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Laskutapa 2}
 | 
			
		||||
 | 
			
		||||
Toinen tapa laskea todennäköisyys on simuloida prosessia,
 | 
			
		||||
jossa tapahtuma syntyy.
 | 
			
		||||
Tässä tapauksessa pakasta nostetaan kolme korttia,
 | 
			
		||||
joten prosessissa on kolme vaihetta.
 | 
			
		||||
Vaatimuksena on, että prosessin jokainen vaihe onnistuu.
 | 
			
		||||
 | 
			
		||||
Ensimmäisen kortin nosto onnistuu varmasti,
 | 
			
		||||
koska mikä tahansa kortti kelpaa.
 | 
			
		||||
Tämän jälkeen kahden seuraavan kortin
 | 
			
		||||
arvon tulee olla sama.
 | 
			
		||||
Toisen kortin nostossa kortteja on jäljellä 51
 | 
			
		||||
ja niistä 3 kelpaa, joten todennäköisyys on $3/51$.
 | 
			
		||||
Vastaavasti kolmannen kortin nostossa
 | 
			
		||||
todennäköisyys on $2/50$.
 | 
			
		||||
 | 
			
		||||
Todennäköisyys koko prosessin onnistumiselle on
 | 
			
		||||
 | 
			
		||||
\[1 \cdot \frac{3}{51} \cdot \frac{2}{50} = \frac{1}{425}.\]
 | 
			
		||||
 | 
			
		||||
\section{Tapahtumat}
 | 
			
		||||
 | 
			
		||||
Todennäköisyyden tapahtuma
 | 
			
		||||
voidaan esittää joukkona
 | 
			
		||||
\[A \subset X,\]
 | 
			
		||||
missä $X$ sisältää kaikki mahdolliset alkeistapaukset
 | 
			
		||||
ja $A$ on jokin alkeistapausten osajoukko.
 | 
			
		||||
Esimerkiksi nopanheitossa alkeistapaukset ovat
 | 
			
		||||
\[X = \{x_1,x_2,x_3,x_4,x_5,x_6\},\]
 | 
			
		||||
missä $x_k$ tarkoittaa silmälukua $k$.
 | 
			
		||||
Nyt esimerkiksi tapahtumaa ''silmäluku on parillinen''
 | 
			
		||||
vastaa joukko
 | 
			
		||||
\[A = \{x_2,x_4,x_6\}.\]
 | 
			
		||||
 | 
			
		||||
Jokaista alkeistapausta $x$
 | 
			
		||||
vastaa todennäköisyys $p(x)$.
 | 
			
		||||
Tämän ansiosta joukkoa $A$ vastaavan tapahtuman
 | 
			
		||||
todennäköisyys $P(A)$ voidaan
 | 
			
		||||
laskea alkeistapausten todennäköisyyksien
 | 
			
		||||
summana kaavalla
 | 
			
		||||
\[P(A) = \sum_{x \in A} p(x).\]
 | 
			
		||||
Esimerkiksi nopanheitossa $p(x)=1/6$
 | 
			
		||||
jokaiselle alkeistapaukselle $x$, joten
 | 
			
		||||
tapahtuman ''silmäluku on parillinen''
 | 
			
		||||
todennäköisyys on
 | 
			
		||||
\[p(x_2)+p(x_4)+p(x_6)=1/2.\]
 | 
			
		||||
 | 
			
		||||
Alkeistapahtumat tulee aina valita niin,
 | 
			
		||||
että kaikkien alkeistapausten
 | 
			
		||||
todennäköisyyksien summa on 1 eli $P(X)=1$.
 | 
			
		||||
 | 
			
		||||
Koska todennäköisyyden tapahtumat ovat joukkoja,
 | 
			
		||||
niihin voi soveltaa jouk\-ko-opin operaatioita:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item \key{Komplementti} $\bar A$ tarkoittaa
 | 
			
		||||
tapahtumaa ''$A$ ei tapahdu''. 
 | 
			
		||||
Esimerkiksi nopanheitossa tapahtuman
 | 
			
		||||
$A=\{x_2,x_4,x_6\}$ komplementti on
 | 
			
		||||
$\bar A = \{x_1,x_3,x_5\}$.
 | 
			
		||||
\item \key{Yhdiste} $A \cup B$ tarkoittaa
 | 
			
		||||
tapahtumaa ''$A$ tai $B$ tapahtuu''.
 | 
			
		||||
Esimerkiksi tapahtumien $A=\{x_2,x_5\}$
 | 
			
		||||
ja $B=\{x_4,x_5,x_6\}$ yhdiste on
 | 
			
		||||
$A \cup B = \{x_2,x_4,x_5,x_6\}$.
 | 
			
		||||
\item \key{Leikkaus} $A \cap B$ tarkoittaa
 | 
			
		||||
tapahtumaa ''$A$ ja $B$ tapahtuvat''.
 | 
			
		||||
Esimerkiksi tapahtumien $A=\{x_2,x_5\}$
 | 
			
		||||
ja $B=\{x_4,x_5,x_6\}$ leikkaus on
 | 
			
		||||
$A \cap B = \{x_5\}$.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Komplementti}
 | 
			
		||||
 | 
			
		||||
Komplementin $\bar A$
 | 
			
		||||
todennäköisyys lasketaan kaavalla
 | 
			
		||||
\[P(\bar A)=1-P(A).\]
 | 
			
		||||
 | 
			
		||||
Joskus tehtävän ratkaisu on kätevää
 | 
			
		||||
laskea komplementin kautta
 | 
			
		||||
miettimällä tilannetta käänteisesti.
 | 
			
		||||
Esimerkiksi todennäköisyys saada
 | 
			
		||||
silmäluku 6 ainakin kerran,
 | 
			
		||||
kun noppaa heitetään kymmenen kertaa, on
 | 
			
		||||
\[1-(5/6)^{10}.\]
 | 
			
		||||
 | 
			
		||||
Tässä $5/6$ on todennäköisyys,
 | 
			
		||||
että yksittäisen heiton silmäluku ei ole 6,
 | 
			
		||||
ja $(5/6)^{10}$ on todennäköisyys, että yksikään
 | 
			
		||||
silmäluku ei ole 6 kymmenessä heitossa.
 | 
			
		||||
Tämän komplementti tuottaa halutun tuloksen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Yhdiste}
 | 
			
		||||
 | 
			
		||||
Yhdisteen $A \cup B$ todennäköisyys lasketaan kaavalla 
 | 
			
		||||
\[P(A \cup B)=P(A)+P(B)-P(A \cap B).\]
 | 
			
		||||
Esimerkiksi nopanheitossa tapahtumien
 | 
			
		||||
\[A=\textrm{''silmäluku on parillinen''}\]
 | 
			
		||||
ja
 | 
			
		||||
\[B=\textrm{''silmäluku on alle 4''}\]
 | 
			
		||||
yhdisteen
 | 
			
		||||
\[A \cup B=\textrm{''silmäluku on parillinen tai alle 4''}\]
 | 
			
		||||
todennäköisyys on
 | 
			
		||||
\[P(A \cup B) = P(A)+P(B)-P(A \cap B)=1/2+1/2-1/6=5/6.\]
 | 
			
		||||
 | 
			
		||||
Jos tapahtumat $A$ ja $B$ ovat \key{erilliset} eli $A \cap B$ on tyhjä,
 | 
			
		||||
yhdisteen $A \cup B$ todennäköisyys on yksinkertaisesti
 | 
			
		||||
 | 
			
		||||
\[P(A \cup B)=P(A)+P(B).\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Ehdollinen todennäköisyys}
 | 
			
		||||
 | 
			
		||||
\index{ehdollinen todennxkzisyys@ehdollinen todennäköisyys}
 | 
			
		||||
 | 
			
		||||
\key{Ehdollinen todennäköisyys}
 | 
			
		||||
\[P(A | B) = \frac{P(A \cap B)}{P(B)}\]
 | 
			
		||||
on tapahtuman $A$ todennäköisyys
 | 
			
		||||
olettaen, että tapahtuma $B$ tapahtuu.
 | 
			
		||||
Tällöin todennäköisyyden laskennassa otetaan
 | 
			
		||||
huomioon vain ne alkeistapaukset,
 | 
			
		||||
jotka kuuluvat joukkoon $B$.
 | 
			
		||||
 | 
			
		||||
Äskeisen esimerkin joukkoja käyttäen
 | 
			
		||||
\[P(A | B)= 1/3,\]
 | 
			
		||||
koska joukon $B$ alkeistapaukset ovat
 | 
			
		||||
$\{x_1,x_2,x_3\}$ ja niistä yhdessä
 | 
			
		||||
silmäluku on parillinen.
 | 
			
		||||
Tämä on todennäköisyys saada parillinen silmäluku,
 | 
			
		||||
jos tiedetään, että silmäluku on välillä 1--3.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Leikkaus}
 | 
			
		||||
 | 
			
		||||
\index{riippumattomuus@riippumattomuus}
 | 
			
		||||
 | 
			
		||||
Ehdollisen todennäköisyyden avulla
 | 
			
		||||
leikkauksen $A \cap B$ todennäköisyys
 | 
			
		||||
voidaan laskea kaavalla
 | 
			
		||||
\[P(A \cap B)=P(A)P(B|A).\]
 | 
			
		||||
Tapahtumat $A$ ja $B$ ovat \key{riippumattomat}, jos
 | 
			
		||||
\[P(A|B)=P(A) \hspace{10px}\textrm{ja}\hspace{10px} P(B|A)=P(B),\]
 | 
			
		||||
jolloin $B$:n tapahtuminen ei vaikuta $A$:n
 | 
			
		||||
todennäköisyyteen ja päinvastoin.
 | 
			
		||||
Tässä tapauksessa leikkauksen
 | 
			
		||||
todennäköisyys on
 | 
			
		||||
\[P(A \cap B)=P(A)P(B).\]
 | 
			
		||||
Esimerkiksi pelikortin nostamisessa
 | 
			
		||||
tapahtumat
 | 
			
		||||
\[A = \textrm{''kortin maa on risti''}\]
 | 
			
		||||
ja
 | 
			
		||||
\[B = \textrm{''kortin arvo on 4''}\]
 | 
			
		||||
ovat riippumattomat.
 | 
			
		||||
Niinpä tapahtuman
 | 
			
		||||
\[A \cap B = \textrm{''kortti on ristinelonen''}\]
 | 
			
		||||
todennäköisyys on
 | 
			
		||||
\[P(A \cap B)=P(A)P(B)=1/4 \cdot 1/13 = 1/52.\]
 | 
			
		||||
 | 
			
		||||
\section{Satunnaismuuttuja}
 | 
			
		||||
 | 
			
		||||
\index{satunnaismuuttuja@satunnaismuuttuja}
 | 
			
		||||
 | 
			
		||||
\key{Satunnaismuuttuja} on arvo, joka syntyy satunnaisen
 | 
			
		||||
prosessin tuloksena.
 | 
			
		||||
Satunnaismuuttujaa merkitään yleensä
 | 
			
		||||
suurella kirjaimella.
 | 
			
		||||
Esimerkiksi kahden nopan heitossa yksi mahdollinen
 | 
			
		||||
satunnaismuuttuja on
 | 
			
		||||
\[X=\textrm{''silmälukujen summa''}.\]
 | 
			
		||||
Esimerkiksi jos heitot ovat $(4,6)$,
 | 
			
		||||
niin $X$ saa arvon 10.
 | 
			
		||||
 | 
			
		||||
Merkintä $P(X=x)$ tarkoittaa todennäköisyyttä,
 | 
			
		||||
että satunnaismuuttujan $X$ arvo on $x$.
 | 
			
		||||
Edellisessä esimerkissä $P(X=10)=3/36$,
 | 
			
		||||
koska erilaisia heittotapoja on 36
 | 
			
		||||
ja niistä summan 10 tuottavat heitot
 | 
			
		||||
$(4,6)$, $(5,5)$ ja $(6,4)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Odotusarvo}
 | 
			
		||||
 | 
			
		||||
\index{odotusarvo@odotusarvo}
 | 
			
		||||
 | 
			
		||||
\key{Odotusarvo} $E[X]$ kertoo, mikä satunnaismuuttujan $X$
 | 
			
		||||
arvo on keskimääräisessä tilanteessa.
 | 
			
		||||
Odotusarvo lasketaan summana
 | 
			
		||||
\[\sum_x P(X=x)x,\]
 | 
			
		||||
missä $x$ saa kaikki mahdolliset satunnaismuuttujan arvot.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi nopan heitossa silmäluvun odotusarvo on
 | 
			
		||||
 | 
			
		||||
\[1/6 \cdot 1 + 1/6 \cdot 2 + 1/6 \cdot 3 + 1/6 \cdot 4 + 1/6 \cdot 5 + 1/6 \cdot 6 = 7/2.\]
 | 
			
		||||
 | 
			
		||||
Usein hyödyllinen odotusarvon ominaisuus on \key{lineaarisuus}.
 | 
			
		||||
Sen ansiosta summa $E[X_1+X_2+\cdots+X_n]$ voidaan laskea $E[X_1]+E[X_2]+\cdots+E[X_n]$.
 | 
			
		||||
Kaava pätee myös silloin, kun satunnaismuuttujat riippuvat toisistaan.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi kahden nopan heitossa silmälukujen summan odotusarvo on
 | 
			
		||||
\[E[X_1+X_2]=E[X_1]+E[X_2]=7/2+7/2=7.\]
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten tehtävää,
 | 
			
		||||
jossa $n$ laatikkoon sijoitetaan
 | 
			
		||||
satunnaisesti $n$ palloa
 | 
			
		||||
ja laskettavana on odotusarvo,
 | 
			
		||||
montako laatikkoa jää tyhjäksi.
 | 
			
		||||
Kullakin pallolla on yhtä suuri todennäköisyys
 | 
			
		||||
päätyä mihin tahansa laatikkoon.
 | 
			
		||||
Esimerkiksi jos $n=2$, niin
 | 
			
		||||
vaihtoehdot ovat seuraavat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\draw (0,0) rectangle (1,1);
 | 
			
		||||
\draw (1.2,0) rectangle (2.2,1);
 | 
			
		||||
\draw (3,0) rectangle (4,1);
 | 
			
		||||
\draw (4.2,0) rectangle (5.2,1);
 | 
			
		||||
\draw (6,0) rectangle (7,1);
 | 
			
		||||
\draw (7.2,0) rectangle (8.2,1);
 | 
			
		||||
\draw (9,0) rectangle (10,1);
 | 
			
		||||
\draw (10.2,0) rectangle (11.2,1);
 | 
			
		||||
 | 
			
		||||
\draw[fill=blue] (0.5,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=red] (1.7,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=red] (3.5,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=blue] (4.7,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=blue] (6.25,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=red] (6.75,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=blue] (10.45,0.2) circle (0.1);
 | 
			
		||||
\draw[fill=red] (10.95,0.2) circle (0.1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tässä tapauksessa odotusarvo
 | 
			
		||||
tyhjien laatikoiden määrälle on
 | 
			
		||||
\[\frac{0+0+1+1}{4} = \frac{1}{2}.\]
 | 
			
		||||
Yleisessä tapauksessa
 | 
			
		||||
todennäköisyys, että yksittäinen hattu on tyhjä,
 | 
			
		||||
on
 | 
			
		||||
\[\Big(\frac{n-1}{n}\Big)^n,\]
 | 
			
		||||
koska mikään pallo ei saa mennä sinne.
 | 
			
		||||
Niinpä odotusarvon lineaarisuuden ansiosta tyhjien hattujen
 | 
			
		||||
määrän odotusarvo on
 | 
			
		||||
\[n \cdot \Big(\frac{n-1}{n}\Big)^n.\]
 | 
			
		||||
 | 
			
		||||
\subsubsection{Jakaumat}
 | 
			
		||||
 | 
			
		||||
\index{jakauma@jakauma}
 | 
			
		||||
 | 
			
		||||
Satunnaismuuttujan \key{jakauma} kertoo,
 | 
			
		||||
millä todennäköisyydellä satunnaismuuttuja
 | 
			
		||||
saa minkäkin arvon.
 | 
			
		||||
Jakauma muodostuu arvoista $P(X=x)$.
 | 
			
		||||
Esimerkiksi kahden nopan heitossa
 | 
			
		||||
silmälukujen summan jakauma on:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\small {
 | 
			
		||||
\begin{tabular}{r|rrrrrrrrrrrrr}
 | 
			
		||||
$x$ & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 & 11 & 12 \\
 | 
			
		||||
$P(X=x)$ & $1/36$ & $2/36$ & $3/36$ & $4/36$ & $5/36$ & $6/36$ & $5/36$ & $4/36$ & $3/36$ & $2/36$ & $1/36$ \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tutustumme seuraavaksi muutamaan usein esiintyvään jakaumaan.
 | 
			
		||||
\index{tasajakauma@tasajakauma}
 | 
			
		||||
~\\\\
 | 
			
		||||
\key{Tasajakauman} satunnaismuuttuja
 | 
			
		||||
saa arvoja väliltä $a \ldots b$
 | 
			
		||||
ja jokaisen arvon todennäköisyys on sama.
 | 
			
		||||
Esimerkiksi yhden nopan heitto tuottaa tasajakauman,
 | 
			
		||||
jossa $P(X=x)=1/6$, kun $x=1,2,\ldots,6$.
 | 
			
		||||
 | 
			
		||||
Tasajakaumassa $X$:n odotusarvo on
 | 
			
		||||
\[E[X] = \frac{a+b}{2}.\]
 | 
			
		||||
\index{binomijakauma@binomijakauma}
 | 
			
		||||
~\\
 | 
			
		||||
\key{Binomijakauma} kuvaa tilannetta, jossa tehdään $n$
 | 
			
		||||
yritystä ja joka yrityksessä onnistumisen
 | 
			
		||||
todennäköisyys on $p$. Satunnaismuuttuja $X$
 | 
			
		||||
on onnistuneiden yritysten määrä,
 | 
			
		||||
ja arvon $x$ todennäköisyys on
 | 
			
		||||
\[P(X=x)=p^x (1-p)^{n-x} {n \choose x},\]
 | 
			
		||||
missä $p^x$ kuvaa onnistuneita yrityksiä,
 | 
			
		||||
$(1-p)^{n-x}$ kuvaa epäonnistuneita yrityksiä
 | 
			
		||||
ja ${n \choose x}$ antaa erilaiset tavat,
 | 
			
		||||
miten yritykset sijoittuvat toisiinsa nähden.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos heitetään 10 kertaa noppaa,
 | 
			
		||||
todennäköisyys saada tarkalleen 3 kertaa silmäluku 6
 | 
			
		||||
on $(1/6)^3 (5/6)^7 {10 \choose 3}$.
 | 
			
		||||
 | 
			
		||||
Binomijakaumassa $X$:n odotusarvo on
 | 
			
		||||
\[E[X] = pn.\]
 | 
			
		||||
\index{geometrinen jakauma@geometrinen jakauma}
 | 
			
		||||
~\\
 | 
			
		||||
\key{Geometrinen jakauma} kuvaa tilannetta,
 | 
			
		||||
jossa onnistumisen todennäköisyys on $p$
 | 
			
		||||
ja yrityksiä tehdään, kunnes tulee ensimmäinen
 | 
			
		||||
onnistuminen. Satunnaismuuttuja $X$ on
 | 
			
		||||
tarvittavien heittojen määrä,
 | 
			
		||||
ja arvon $x$ todennäköisyys on
 | 
			
		||||
\[P(X=x)=(1-p)^{x-1} p,\]
 | 
			
		||||
missä $(1-p)^{x-1}$ kuvaa epäonnistuneita yrityksiä ja
 | 
			
		||||
$p$ on ensimmäinen onnistunut yritys.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos heitetään noppaa,
 | 
			
		||||
kunnes tulee silmäluku 6, todennäköisyys
 | 
			
		||||
heittää tarkalleen 4 kertaa on $(5/6)^3 1/6$.
 | 
			
		||||
 | 
			
		||||
Geometrisessa jakaumassa $X$:n odotusarvo on
 | 
			
		||||
\[E[X]=\frac{1}{p}.\]
 | 
			
		||||
 | 
			
		||||
\section{Markovin ketju}
 | 
			
		||||
 | 
			
		||||
\index{Markovin ketju@Markovin ketju}
 | 
			
		||||
 | 
			
		||||
\key{Markovin ketju} on satunnaisprosessi,
 | 
			
		||||
joka muodostuu tiloista ja niiden välisistä siirtymistä.
 | 
			
		||||
Jokaisesta tilasta tiedetään, millä todennäköisyydellä
 | 
			
		||||
siitä siirrytään toisiin tiloihin.
 | 
			
		||||
Markovin ketju voidaan esittää verkkona,
 | 
			
		||||
jonka solmut ovat tiloja
 | 
			
		||||
ja kaaret niiden välisiä siirtymiä.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää,
 | 
			
		||||
jossa olet alussa $n$-kerroksisen
 | 
			
		||||
rakennuksen kerroksessa 1.
 | 
			
		||||
Joka askeleella liikut satunnaisesti
 | 
			
		||||
kerroksen ylöspäin tai alaspäin,
 | 
			
		||||
paitsi kerroksesta 1 liikut aina ylöspäin
 | 
			
		||||
ja kerroksesta $n$ aina alaspäin.
 | 
			
		||||
Mikä on todennäköisyys, että olet $m$
 | 
			
		||||
askeleen jälkeen kerroksessa $k$?
 | 
			
		||||
 | 
			
		||||
Tehtävässä kukin rakennuksen kerros
 | 
			
		||||
on yksi tiloista, ja kerrosten välillä liikutaan
 | 
			
		||||
satunnaisesti.
 | 
			
		||||
Esimerkiksi jos $n=5$, verkosta tulee:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (4,0) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (6,0) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (8,0) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (1) edge [bend left=40] node[font=\small,label=$1$] {} (2);
 | 
			
		||||
\path[draw,thick,->] (2) edge [bend left=40] node[font=\small,label=$1/2$] {} (3);
 | 
			
		||||
\path[draw,thick,->] (3) edge [bend left=40] node[font=\small,label=$1/2$] {} (4);
 | 
			
		||||
\path[draw,thick,->] (4) edge [bend left=40] node[font=\small,label=$1/2$] {} (5);
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->] (5) edge [bend left=40] node[font=\small,label=below:$1$] {} (4);
 | 
			
		||||
\path[draw,thick,->] (4) edge [bend left=40] node[font=\small,label=below:$1/2$] {} (3);
 | 
			
		||||
\path[draw,thick,->] (3) edge [bend left=40] node[font=\small,label=below:$1/2$] {} (2);
 | 
			
		||||
\path[draw,thick,->] (2) edge [bend left=40] node[font=\small,label=below:$1/2$] {} (1);
 | 
			
		||||
 | 
			
		||||
%\path[draw,thick,->] (1) edge [bend left=40] node[font=\small,label=below:$1$] {} (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Markovin ketjun tilajakauma on vektori
 | 
			
		||||
$[p_1,p_2,\ldots,p_n]$, missä $p_k$ tarkoittaa
 | 
			
		||||
todennäköisyyttä olla tällä hetkellä tilassa $k$.
 | 
			
		||||
Todennäköisyyksille pätee aina $p_1+p_2+\cdots+p_n=1$.
 | 
			
		||||
 | 
			
		||||
Esimerkissä jakauma on ensin $[1,0,0,0,0]$,
 | 
			
		||||
koska on varmaa, että kulku alkaa kerroksesta 1.
 | 
			
		||||
Seuraava jakauma on $[0,1,0,0,0]$,
 | 
			
		||||
koska kerroksesta 1 pääsee vain kerrokseen 2.
 | 
			
		||||
Tämän jälkeen on mahdollisuus mennä joko ylöspäin
 | 
			
		||||
tai alaspäin, joten seuraava jakauma on $[1/2,0,1/2,0,0]$, jne.
 | 
			
		||||
 | 
			
		||||
Tehokas tapa simuloida kulkua Markovin ketjussa
 | 
			
		||||
on käyttää dynaamista ohjelmointia.
 | 
			
		||||
Ideana on pitää yllä tilajakaumaa
 | 
			
		||||
ja käydä joka vuorolla läpi kaikki tilat
 | 
			
		||||
ja jokaisesta tilasta kaikki mahdollisuudet jatkaa eteenpäin.
 | 
			
		||||
Tätä menetelmää käyttäen $m$ askeleen simulointi
 | 
			
		||||
vie aikaa $O(n^2 m)$.
 | 
			
		||||
 | 
			
		||||
Markovin ketjun tilasiirtymät voi esittää myös matriisina,
 | 
			
		||||
jonka avulla voi päivittää tilajakaumaa askeleen eteenpäin.
 | 
			
		||||
Tässä tapauksessa matriisi on
 | 
			
		||||
 | 
			
		||||
\[ 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1/2 & 0 & 0 & 0 \\
 | 
			
		||||
  1 & 0 & 1/2 & 0 & 0 \\
 | 
			
		||||
  0 & 1/2 & 0 & 1/2 & 0 \\
 | 
			
		||||
  0 & 0 & 1/2 & 0 & 1 \\
 | 
			
		||||
  0 & 0 & 0 & 1/2 & 0 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Tällaisella matriisilla voi kertoa tilajakaumaa esittävän
 | 
			
		||||
vektorin, jolloin saadaan tilajakauma yhtä askelta myöhemmin.
 | 
			
		||||
Esimerkiksi jakaumasta $[1,0,0,0,0]$ pääsee jakaumaan
 | 
			
		||||
$[0,1,0,0,0]$ seuraavasti:
 | 
			
		||||
 | 
			
		||||
\[ 
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 1/2 & 0 & 0 & 0 \\
 | 
			
		||||
  1 & 0 & 1/2 & 0 & 0 \\
 | 
			
		||||
  0 & 1/2 & 0 & 1/2 & 0 \\
 | 
			
		||||
  0 & 0 & 1/2 & 0 & 1 \\
 | 
			
		||||
  0 & 0 & 0 & 1/2 & 0 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 \\
 | 
			
		||||
  1 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
  0 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
 | 
			
		||||
Matriisiin voi soveltaa edelleen tehokasta
 | 
			
		||||
matriisipotenssia, jonka avulla voi laskea
 | 
			
		||||
ajassa $O(n^3 \log m)$,
 | 
			
		||||
mikä on jakauma $m$ askeleen jälkeen.
 | 
			
		||||
 | 
			
		||||
\section{Satunnaisalgoritmit}
 | 
			
		||||
 | 
			
		||||
\index{satunnaisalgoritmi@satunnaisalgoritmi}
 | 
			
		||||
 | 
			
		||||
Joskus tehtävässä voi hyödyntää satunnaisuutta,
 | 
			
		||||
vaikka tehtävä ei itsessään liittyisi todennäköisyyteen.
 | 
			
		||||
\key{Satunnaisalgoritmi} on algoritmi, jonka toiminta
 | 
			
		||||
perustuu satunnaisuuteen.
 | 
			
		||||
 | 
			
		||||
\index{Monte Carlo -algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Monte Carlo -algoritmi} on satunnaisalgoritmi,
 | 
			
		||||
joka saattaa tuottaa joskus väärän tuloksen.
 | 
			
		||||
Jotta algoritmi olisi käyttökelpoinen,
 | 
			
		||||
väärän vastauksen todennäköisyyden tulee olla pieni.
 | 
			
		||||
 | 
			
		||||
\index{Las Vegas -algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Las Vegas -algoritmi} on satunnaisalgoritmi,
 | 
			
		||||
joka tuottaa aina oikean tuloksen mutta jonka
 | 
			
		||||
suoritusaika vaihtelee satunnaisesti.
 | 
			
		||||
Tavoitteena on, että algoritmi toimisi nopeasti
 | 
			
		||||
suurella todennäköisyydellä.
 | 
			
		||||
 | 
			
		||||
Tutustumme seuraavaksi kolmeen esimerkkitehtävään,
 | 
			
		||||
jotka voi ratkaista satunnaisuuden avulla.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Järjestystunnusluku}
 | 
			
		||||
 | 
			
		||||
\index{järjestystunnusluku}
 | 
			
		||||
 | 
			
		||||
Taulukon $k$. \key{järjestystunnusluku}
 | 
			
		||||
on kohdassa $k$ oleva alkio,
 | 
			
		||||
kun alkiot järjestetään
 | 
			
		||||
pienimmästä suurimpaan.
 | 
			
		||||
On helppoa laskea mikä tahansa
 | 
			
		||||
järjestystunnusluku ajassa $O(n \log n)$
 | 
			
		||||
järjestämällä taulukko,
 | 
			
		||||
mutta onko oikeastaan tarpeen järjestää koko taulukkoa?
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että järjestystunnusluvun
 | 
			
		||||
voi etsiä satunnaisalgoritmilla ilman taulukon
 | 
			
		||||
järjestämistä.
 | 
			
		||||
Algoritmi on Las Vegas -tyyppinen:
 | 
			
		||||
sen aikavaativuus on yleensä $O(n)$,
 | 
			
		||||
mutta pahimmassa tapauksessa $O(n^2)$.
 | 
			
		||||
 | 
			
		||||
Algoritmi valitsee taulukosta satunnaisen alkion $x$
 | 
			
		||||
ja siirtää $x$:ää pienemmät alkiot
 | 
			
		||||
taulukon vasempaan osaan ja loput alkiot
 | 
			
		||||
taulukon oikeaan osaan.
 | 
			
		||||
Tämä vie aikaa $O(n)$, kun taulukossa on $n$ alkiota.
 | 
			
		||||
Oletetaan, että vasemmassa osassa on $a$
 | 
			
		||||
alkiota ja oikeassa osassa on $b$ alkiota.
 | 
			
		||||
Nyt jos $a=k-1$, alkio $x$ on haluttu alkio.
 | 
			
		||||
Jos $a>k-1$, etsitään rekursiivisesti
 | 
			
		||||
vasemmasta osasta, mikä on kohdassa $k$ oleva alkio.
 | 
			
		||||
Jos taas $a<k-1$, etsitään rekursiivisesti
 | 
			
		||||
oikeasta osasta, mikä on kohdassa $k-a-1$ oleva alkio.
 | 
			
		||||
Haku jatkuu vastaavalla tavalla rekursiivisesti,
 | 
			
		||||
kunnes haluttu alkio on löytynyt.
 | 
			
		||||
 | 
			
		||||
Kun alkiot $x$ valitaan satunnaisesti,
 | 
			
		||||
taulukon koko suunnilleen puolittuu
 | 
			
		||||
joka vaiheessa, joten kohdassa $k$ olevan
 | 
			
		||||
alkion etsiminen vie aikaa
 | 
			
		||||
\[n+n/2+n/4+n/8+\cdots=O(n).\]
 | 
			
		||||
 | 
			
		||||
Algoritmin pahin tapaus on silti $O(n^2)$,
 | 
			
		||||
koska on mahdollista,
 | 
			
		||||
että $x$ valitaan sattumalta aina niin,
 | 
			
		||||
että se on taulukon pienin alkio.
 | 
			
		||||
Silloin taulukko pienenee joka vaiheessa
 | 
			
		||||
vain yhden alkion verran.
 | 
			
		||||
Tämän todennäköisyys on kuitenkin erittäin pieni,
 | 
			
		||||
eikä näin tapahdu käytännössä.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Matriisitulon tarkastaminen}
 | 
			
		||||
 | 
			
		||||
\index{matriisitulo@matriisitulo}
 | 
			
		||||
 | 
			
		||||
Seuraava tehtävämme on \emph{tarkastaa},
 | 
			
		||||
päteekö matriisitulo $AB=C$, kun $A$, $B$ ja $C$
 | 
			
		||||
ovat $n \times n$ -kokoisia matriiseja.
 | 
			
		||||
Tehtävän voi ratkaista laskemalla matriisitulon
 | 
			
		||||
$AB$ (perusalgoritmilla ajassa $O(n^3)$), mutta voisi toivoa,
 | 
			
		||||
että ratkaisun tarkastaminen olisi helpompaa
 | 
			
		||||
kuin sen laskeminen alusta alkaen uudestaan.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tehtävän voi ratkaista
 | 
			
		||||
Monte Carlo -algoritmilla,
 | 
			
		||||
jonka aikavaativuus on vain $O(n^2)$.
 | 
			
		||||
Idea on yksinkertainen: valitaan satunnainen
 | 
			
		||||
$n \times 1$ -matriisi $X$ ja lasketaan
 | 
			
		||||
matriisit $ABX$ ja $CX$.
 | 
			
		||||
Jos $ABX=CX$, ilmoitetaan, että $AB=C$,
 | 
			
		||||
ja muuten ilmoitetaan, että $AB \neq C$.
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on $O(n^2)$,
 | 
			
		||||
koska matriisien $ABX$ ja $CX$ laskeminen
 | 
			
		||||
vie aikaa $O(n^2)$.
 | 
			
		||||
Matriisin $ABX$ tapauksessa laskennan
 | 
			
		||||
voi suorittaa osissa $A(BX)$, jolloin riittää
 | 
			
		||||
kertoa kahdesti $n \times n$- ja $n \times 1$-kokoiset
 | 
			
		||||
matriisit.
 | 
			
		||||
 | 
			
		||||
Algoritmin heikkoutena on, että on pieni mahdollisuus,
 | 
			
		||||
että algoritmi erehtyy, kun se ilmoittaa, että $AB=C$.
 | 
			
		||||
Esimerkiksi 
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 4 \\
 | 
			
		||||
  1 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
\neq
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 5 \\
 | 
			
		||||
  7 & 4 \\
 | 
			
		||||
 \end{bmatrix},
 | 
			
		||||
\]
 | 
			
		||||
mutta
 | 
			
		||||
\[
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  2 & 4 \\
 | 
			
		||||
  1 & 6 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 \\
 | 
			
		||||
  3 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
=
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  0 & 5 \\
 | 
			
		||||
  7 & 4 \\
 | 
			
		||||
 \end{bmatrix}
 | 
			
		||||
 \begin{bmatrix}
 | 
			
		||||
  1 \\
 | 
			
		||||
  3 \\
 | 
			
		||||
 \end{bmatrix}.
 | 
			
		||||
\]
 | 
			
		||||
Käytännössä erehtymisen todennäköisyys on kuitenkin
 | 
			
		||||
pieni ja todennäköisyyttä voi pienentää lisää
 | 
			
		||||
tekemällä tarkastuksen usealla
 | 
			
		||||
satunnaisella matriisilla $X$ ennen vastauksen
 | 
			
		||||
$AB=C$ ilmoittamista.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Verkon värittäminen}
 | 
			
		||||
 | 
			
		||||
\index{vxritys@väritys}
 | 
			
		||||
 | 
			
		||||
Annettuna on verkko, jossa on $n$ solmua ja $m$ kaarta.
 | 
			
		||||
Tehtävänä on etsiä tapa värittää verkon solmut kahdella värillä
 | 
			
		||||
niin, että ainakin $m/2$ kaaressa
 | 
			
		||||
päätesolmut ovat eri väriset.
 | 
			
		||||
Esimerkiksi verkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
yksi kelvollinen väritys on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle, fill=blue!40] (1) at (1,3) {$1$};
 | 
			
		||||
\node[draw, circle, fill=red!40] (2) at (4,3) {$2$};
 | 
			
		||||
\node[draw, circle, fill=red!40] (3) at (1,1) {$3$};
 | 
			
		||||
\node[draw, circle, fill=blue!40] (4) at (4,1) {$4$};
 | 
			
		||||
\node[draw, circle, fill=blue!40] (5) at (6,2) {$5$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,-] (1) -- (2);
 | 
			
		||||
\path[draw,thick,-] (1) -- (3);
 | 
			
		||||
\path[draw,thick,-] (1) -- (4);
 | 
			
		||||
\path[draw,thick,-] (3) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (4);
 | 
			
		||||
\path[draw,thick,-] (2) -- (5);
 | 
			
		||||
\path[draw,thick,-] (4) -- (5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Yllä olevassa verkossa on 7 kaarta ja niistä 5:ssä
 | 
			
		||||
päätesolmut ovat eri väriset,
 | 
			
		||||
joten väritys on kelvollinen.
 | 
			
		||||
 | 
			
		||||
Tehtävä on mahdollista ratkaista Las Vegas -algoritmilla
 | 
			
		||||
muodostamalla satunnaisia värityksiä niin kauan,
 | 
			
		||||
kunnes syntyy kelvollinen väritys.
 | 
			
		||||
Satunnaisessa värityksessä jokaisen solmun väri on
 | 
			
		||||
valittu toisistaan riippumatta niin,
 | 
			
		||||
että kummankin värin todennäköisyys on $1/2$.
 | 
			
		||||
 | 
			
		||||
Satunnaisessa värityksessä todennäköisyys, että yksittäisen kaaren päätesolmut
 | 
			
		||||
ovat eri väriset on $1/2$. Niinpä odotusarvo, monessako kaaressa
 | 
			
		||||
päätesolmut ovat eri väriset, on $1/2 \cdot m = m/2$.
 | 
			
		||||
Koska satunnainen väritys on odotusarvoisesti kelvollinen,
 | 
			
		||||
jokin kelvollinen väritys löytyy käytännössä nopeasti.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,786 @@
 | 
			
		|||
\chapter{Game theory}
 | 
			
		||||
 | 
			
		||||
Tässä luvussa keskitymme kahden pelaajan peleihin,
 | 
			
		||||
joissa molemmat pelaajat tekevät
 | 
			
		||||
samanlaisia siirtoja eikä pelissä ole satunnaisuutta.
 | 
			
		||||
Tavoitteemme on etsiä strategia, jota käyttäen
 | 
			
		||||
pelaaja pystyy voittamaan pelin toisen pelaajan
 | 
			
		||||
toimista riippumatta, jos tämä on mahdollista.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että kaikki tällaiset pelit ovat
 | 
			
		||||
pohjimmiltaan samanlaisia ja niiden analyysi on
 | 
			
		||||
mahdollista \key{nim-teorian} avulla.
 | 
			
		||||
Perehdymme aluksi yksinkertaisiin tikkupeleihin,
 | 
			
		||||
joissa pelaajat poistavat tikkuja kasoista,
 | 
			
		||||
ja yleistämme sitten näiden pelien teorian kaikkiin peleihin.
 | 
			
		||||
 | 
			
		||||
\section{Pelin tilat}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan peliä, jossa kasassa on $n$ tikkua.
 | 
			
		||||
Pelaajat $A$ ja $B$ siirtävät vuorotellen ja
 | 
			
		||||
pelaaja $A$ aloittaa.
 | 
			
		||||
Jokaisella siirrolla pelaajan tulee poistaa
 | 
			
		||||
1, 2 tai 3 tikkua kasasta.
 | 
			
		||||
Pelin voittaa se pelaaja, joka poistaa viimeisen tikun.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi jos $n=10$, peli saattaa edetä seuraavasti:
 | 
			
		||||
\begin{enumerate}[noitemsep]
 | 
			
		||||
\item Pelaaja $A$ poistaa 2 tikkua (jäljellä 8 tikkua).
 | 
			
		||||
\item Pelaaja $B$ poistaa 3 tikkua (jäljellä 5 tikkua).
 | 
			
		||||
\item Pelaaja $A$ poistaa 1 tikun (jäljellä 4 tikkua).
 | 
			
		||||
\item Pelaaja $B$ poistaa 2 tikkua (jäljellä 2 tikkua).
 | 
			
		||||
\item Pelaaja $A$ poistaa 2 tikkua ja voittaa.
 | 
			
		||||
\end{enumerate}
 | 
			
		||||
Tämä peli muodostuu tiloista $0,1,2,\ldots,n$,
 | 
			
		||||
missä tilan numero vastaa sitä, montako tikkua
 | 
			
		||||
kasassa on jäljellä.
 | 
			
		||||
Tietyssä tilassa olevan pelaajan valittavana on,
 | 
			
		||||
montako tikkua hän poistaa kasasta.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Voittotila ja häviötila}
 | 
			
		||||
 | 
			
		||||
\index{voittotila@voittotila}
 | 
			
		||||
\index{hxviztila@häviötila}
 | 
			
		||||
 | 
			
		||||
\key{Voittotila} on tila, jossa oleva pelaaja voittaa
 | 
			
		||||
pelin varmasti, jos hän pelaa optimaalisesti.
 | 
			
		||||
Vastaavasti \key{häviötila} on tila,
 | 
			
		||||
jossa oleva pelaaja häviää varmasti, jos vastustaja
 | 
			
		||||
pelaa optimaalisesti.
 | 
			
		||||
Osoittautuu, että pelin tilat on mahdollista luokitella
 | 
			
		||||
niin, että jokainen tila on joko voittotila tai häviötila.
 | 
			
		||||
 | 
			
		||||
Yllä olevassa pelissä tila 0 on selkeästi häviötila,
 | 
			
		||||
koska siinä oleva pelaaja häviää pelin suoraan.
 | 
			
		||||
Tilat 1, 2 ja 3 taas ovat voittotiloja,
 | 
			
		||||
koska niissä oleva pelaaja voi poistaa
 | 
			
		||||
1, 2 tai 3 tikkua ja voittaa pelin.
 | 
			
		||||
Vastaavasti tila 4 on häviötila, koska mikä tahansa
 | 
			
		||||
siirto johtaa toisen pelaajan voittoon.
 | 
			
		||||
 | 
			
		||||
Yleisemmin voidaan havaita, että jos tilasta on
 | 
			
		||||
jokin häviötilaan johtava siirto, niin tila on voittotila,
 | 
			
		||||
ja muussa tapauksessa tila on häviötila.
 | 
			
		||||
Tämän ansiosta voidaan luokitella kaikki pelin tilat
 | 
			
		||||
alkaen varmoista häviötiloista, joista ei ole siirtoja
 | 
			
		||||
mihinkään muihin tiloihin.
 | 
			
		||||
 | 
			
		||||
Seuraavassa on pelin tilojen $0 \ldots 15$ luokittelu
 | 
			
		||||
($V$ tarkoittaa voittotilaa ja $H$ tarkoittaa häviötilaa):
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (16,1);
 | 
			
		||||
 | 
			
		||||
\node at (0.5,0.5) {$H$};
 | 
			
		||||
\node at (1.5,0.5) {$V$};
 | 
			
		||||
\node at (2.5,0.5) {$V$};
 | 
			
		||||
\node at (3.5,0.5) {$V$};
 | 
			
		||||
\node at (4.5,0.5) {$H$};
 | 
			
		||||
\node at (5.5,0.5) {$V$};
 | 
			
		||||
\node at (6.5,0.5) {$V$};
 | 
			
		||||
\node at (7.5,0.5) {$V$};
 | 
			
		||||
\node at (8.5,0.5) {$H$};
 | 
			
		||||
\node at (9.5,0.5) {$V$};
 | 
			
		||||
\node at (10.5,0.5) {$V$};
 | 
			
		||||
\node at (11.5,0.5) {$V$};
 | 
			
		||||
\node at (12.5,0.5) {$H$};
 | 
			
		||||
\node at (13.5,0.5) {$V$};
 | 
			
		||||
\node at (14.5,0.5) {$V$};
 | 
			
		||||
\node at (15.5,0.5) {$V$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (0.5,1.4) {$0$};
 | 
			
		||||
\node at (1.5,1.4) {$1$};
 | 
			
		||||
\node at (2.5,1.4) {$2$};
 | 
			
		||||
\node at (3.5,1.4) {$3$};
 | 
			
		||||
\node at (4.5,1.4) {$4$};
 | 
			
		||||
\node at (5.5,1.4) {$5$};
 | 
			
		||||
\node at (6.5,1.4) {$6$};
 | 
			
		||||
\node at (7.5,1.4) {$7$};
 | 
			
		||||
\node at (8.5,1.4) {$8$};
 | 
			
		||||
\node at (9.5,1.4) {$9$};
 | 
			
		||||
\node at (10.5,1.4) {$10$};
 | 
			
		||||
\node at (11.5,1.4) {$11$};
 | 
			
		||||
\node at (12.5,1.4) {$12$};
 | 
			
		||||
\node at (13.5,1.4) {$13$};
 | 
			
		||||
\node at (14.5,1.4) {$14$};
 | 
			
		||||
\node at (15.5,1.4) {$15$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän pelin analyysi on yksinkertainen:
 | 
			
		||||
tila $k$ on häviötila, jos $k$ on jaollinen 4:llä,
 | 
			
		||||
ja muuten tila $k$ on voittotila.
 | 
			
		||||
Optimaalinen tapa pelata peliä on
 | 
			
		||||
valita aina sellainen siirto, että vastustajalle
 | 
			
		||||
jää 4:llä jaollinen määrä tikkuja,
 | 
			
		||||
kunnes lopulta tikut loppuvat ja vastustaja on hävinnyt.
 | 
			
		||||
 | 
			
		||||
Tämä pelitapa edellyttää luonnollisesti sitä,
 | 
			
		||||
että tikkujen määrä omalla siirrolla \emph{ei} ole
 | 
			
		||||
4:llä jaollinen. Jos näin kuitenkin on, mitään ei ole
 | 
			
		||||
tehtävissä vaan vastustaja voittaa
 | 
			
		||||
pelin varmasti, jos hän pelaa optimaalisesti.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Tilaverkko}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten toisenlaista tikkupeliä,
 | 
			
		||||
jossa tilassa $k$ saa poistaa minkä tahansa
 | 
			
		||||
määrän tikkuja $x$, kunhan $k$ on jaollinen $x$:llä
 | 
			
		||||
ja $x$ on pienempi kuin $k$.
 | 
			
		||||
Esimerkiksi tilassa 8 on sallittua poistaa
 | 
			
		||||
1, 2 tai 4 tikkua, mutta tilassa 7
 | 
			
		||||
ainoa mahdollinen siirto on poistaa 1 tikku.
 | 
			
		||||
 | 
			
		||||
Seuraava kuva esittää pelin tilat $1 \ldots 9$ \key{tilaverkkona}, jossa solmut ovat pelin tiloja
 | 
			
		||||
ja kaaret kuvaavat mahdollisia siirtoja tilojen välillä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {$1$};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {$2$};
 | 
			
		||||
\node[draw, circle] (3) at (3.5,-1) {$3$};
 | 
			
		||||
\node[draw, circle] (4) at (1.5,-2) {$4$};
 | 
			
		||||
\node[draw, circle] (5) at (3,-2.75) {$5$};
 | 
			
		||||
\node[draw, circle] (6) at (2.5,-4.5) {$6$};
 | 
			
		||||
\node[draw, circle] (7) at (0.5,-3.25) {$7$};
 | 
			
		||||
\node[draw, circle] (8) at (-1,-4) {$8$};
 | 
			
		||||
\node[draw, circle] (9) at (1,-5.5) {$9$};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) edge [bend right=20] (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) edge [bend left=20] (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) edge [bend left=20] (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) edge [bend right=20] (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) edge [bend left=20] (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) edge [bend left=20] (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) edge [bend right=40] (3);
 | 
			
		||||
\path[draw,thick,->,>=latex] (7) edge [bend right=20] (6);
 | 
			
		||||
\path[draw,thick,->,>=latex] (8) edge [bend right=20] (7);
 | 
			
		||||
\path[draw,thick,->,>=latex] (8) edge [bend right=20] (6);
 | 
			
		||||
\path[draw,thick,->,>=latex] (8) edge [bend left=20] (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (9) edge [bend left=20] (8);
 | 
			
		||||
\path[draw,thick,->,>=latex] (9) edge [bend right=20] (6);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämä peli päättyy aina tilaan 1, joka on häviötila,
 | 
			
		||||
koska siinä ei voi tehdä mitään siirtoja.
 | 
			
		||||
Pelin tilojen $1 \ldots 9$ luokittelu on seuraava:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (1,0) grid (10,1);
 | 
			
		||||
 | 
			
		||||
\node at (1.5,0.5) {$H$};
 | 
			
		||||
\node at (2.5,0.5) {$V$};
 | 
			
		||||
\node at (3.5,0.5) {$H$};
 | 
			
		||||
\node at (4.5,0.5) {$V$};
 | 
			
		||||
\node at (5.5,0.5) {$H$};
 | 
			
		||||
\node at (6.5,0.5) {$V$};
 | 
			
		||||
\node at (7.5,0.5) {$H$};
 | 
			
		||||
\node at (8.5,0.5) {$V$};
 | 
			
		||||
\node at (9.5,0.5) {$H$};
 | 
			
		||||
 | 
			
		||||
\footnotesize
 | 
			
		||||
\node at (1.5,1.4) {$1$};
 | 
			
		||||
\node at (2.5,1.4) {$2$};
 | 
			
		||||
\node at (3.5,1.4) {$3$};
 | 
			
		||||
\node at (4.5,1.4) {$4$};
 | 
			
		||||
\node at (5.5,1.4) {$5$};
 | 
			
		||||
\node at (6.5,1.4) {$6$};
 | 
			
		||||
\node at (7.5,1.4) {$7$};
 | 
			
		||||
\node at (8.5,1.4) {$8$};
 | 
			
		||||
\node at (9.5,1.4) {$9$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Yllättävää kyllä, tässä pelissä kaikki
 | 
			
		||||
parilliset tilat ovat voittotiloja ja
 | 
			
		||||
kaikki parittomat tilat ovat häviötiloja.
 | 
			
		||||
 | 
			
		||||
\section{Nim-peli}
 | 
			
		||||
 | 
			
		||||
\index{nim-peli}
 | 
			
		||||
 | 
			
		||||
\key{Nim-peli} on yksinkertainen peli,
 | 
			
		||||
joka on tärkeässä asemassa peliteoriassa,
 | 
			
		||||
koska monia pelejä voi pelata samalla
 | 
			
		||||
strategialla kuin nim-peliä.
 | 
			
		||||
Tutustumme aluksi nim-peliin ja yleistämme
 | 
			
		||||
strategian sitten muihin peleihin.
 | 
			
		||||
 | 
			
		||||
Nim-pelissä on $n$ kasaa tikkuja,
 | 
			
		||||
joista kussakin on tietty määrä tikkuja.
 | 
			
		||||
Pelaajat poistavat kasoista tikkuja vuorotellen.
 | 
			
		||||
Joka vuorolla pelaaja valitsee yhden kasan,
 | 
			
		||||
jossa on vielä tikkuja,
 | 
			
		||||
ja poistaa siitä minkä tahansa määrän tikkuja.
 | 
			
		||||
Pelin voittaa se, joka poistaa viimeisen tikun.
 | 
			
		||||
 | 
			
		||||
Nim-pelin tila on muotoa $[x_1,x_2,\ldots,x_n]$,
 | 
			
		||||
jossa $x_k$ on tikkujen määrä kasassa $k$.
 | 
			
		||||
Esimerkiksi $[10,12,5]$ tarkoittaa peliä,
 | 
			
		||||
jossa on kolme kasaa ja tikkujen määrät ovat 10, 12 ja 5.
 | 
			
		||||
Tila $[0,0,\ldots,0]$ on häviötila,
 | 
			
		||||
koska siitä ei voi poistaa mitään tikkua,
 | 
			
		||||
ja peli päättyy aina tähän tilaan.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Analyysi}
 | 
			
		||||
\index{nim-summa}
 | 
			
		||||
Osoittautuu, että nim-pelin tilan luonteen
 | 
			
		||||
kertoo \key{nim-summa} $x_1 \oplus x_2 \oplus \cdots \oplus x_n$,
 | 
			
		||||
missä $\oplus$ tarkoittaa xor-operaatiota.
 | 
			
		||||
Jos nim-summa on 0, tila on häviötila,
 | 
			
		||||
ja muussa tapauksessa tila on voittotila.
 | 
			
		||||
Esimerkiksi tilan $[10,12,5]$ nim-summa on
 | 
			
		||||
$10 \oplus 12 \oplus 5 = 3$, joten tila on voittotila.
 | 
			
		||||
 | 
			
		||||
Mutta miten nim-summa liittyy nim-peliin?
 | 
			
		||||
Tämä selviää tutkimalla, miten nim-summa muuttuu,
 | 
			
		||||
kun nim-pelin tila muuttuu.
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
\noindent
 | 
			
		||||
\textit{Häviötilat:}
 | 
			
		||||
Pelin päätöstila $[0,0,\ldots,0]$ on häviötila,
 | 
			
		||||
ja sen nim-summa on 0, kuten kuuluukin.
 | 
			
		||||
Muissa häviötiloissa mikä tahansa siirto johtaa
 | 
			
		||||
voittotilaan, koska yksi luvuista $x_k$ muuttuu
 | 
			
		||||
ja samalla pelin nim-summa muuttuu
 | 
			
		||||
eli siirron jälkeen nim-summasta tulee jokin muu kuin 0.
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
\noindent
 | 
			
		||||
\textit{Voittotilat:}
 | 
			
		||||
Voittotilasta pääsee häviötilaan muuttamalla
 | 
			
		||||
jonkin kasan $k$ tikkujen määräksi $x_k \oplus s$,
 | 
			
		||||
missä $s$ on pelin nim-summa.
 | 
			
		||||
Vaatimuksena on, että $x_k \oplus s < x_k$,
 | 
			
		||||
koska kasasta voi vain poistaa tikkuja.
 | 
			
		||||
Sopiva kasa $x_k$ on sellainen,
 | 
			
		||||
jossa on ykkösbitti samassa kohdassa kuin
 | 
			
		||||
$s$:n vasemmanpuoleisin ykkösbitti.
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
\noindent
 | 
			
		||||
Tarkastellaan esimerkkinä tilaa $[10,2,5]$.
 | 
			
		||||
Tämä tila on voittotila,
 | 
			
		||||
koska sen nim-summa on 3.
 | 
			
		||||
Täytyy siis olla olemassa siirto,
 | 
			
		||||
jolla tilasta pääsee häviötilaan.
 | 
			
		||||
Selvitetään se seuraavaksi.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Pelin nim-summa muodostuu seuraavasti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|r}
 | 
			
		||||
10 & \texttt{1010} \\
 | 
			
		||||
12 & \texttt{1100} \\
 | 
			
		||||
5 & \texttt{0101} \\
 | 
			
		||||
\hline
 | 
			
		||||
3 & \texttt{0011} \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa
 | 
			
		||||
10 tikun kasa on ainoa,
 | 
			
		||||
jonka bittiesityksessä on ykkösbitti
 | 
			
		||||
samassa kohdassa kuin 
 | 
			
		||||
nim-summan vasemmanpuoleisin ykkösbitti:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|r}
 | 
			
		||||
10 & \texttt{10\textcircled{1}0} \\
 | 
			
		||||
12 & \texttt{1100} \\
 | 
			
		||||
5 & \texttt{0101} \\
 | 
			
		||||
\hline
 | 
			
		||||
3 & \texttt{00\textcircled{1}1} \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kasan uudeksi sisällöksi täytyy saada
 | 
			
		||||
$10 \oplus 3 = 9$ tikkua,
 | 
			
		||||
mikä onnistuu poistamalla 1 tikku
 | 
			
		||||
10 tikun kasasta.
 | 
			
		||||
Tämän seurauksena tilaksi tulee $[9,12,5]$,
 | 
			
		||||
joka on häviötila, kuten pitääkin:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{r|r}
 | 
			
		||||
9 & \texttt{1001} \\
 | 
			
		||||
12 & \texttt{1100} \\
 | 
			
		||||
5 & \texttt{0101} \\
 | 
			
		||||
\hline
 | 
			
		||||
0 & \texttt{0000} \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Misääripeli}
 | 
			
		||||
 | 
			
		||||
\index{misxxripeli@misääripeli}
 | 
			
		||||
 | 
			
		||||
\key{Misääripelissä} nim-pelin tavoite on käänteinen,
 | 
			
		||||
eli pelin häviää se, joka poistaa viimeisen tikun.
 | 
			
		||||
Osoittautuu, että misääripeliä pystyy pelaamaan lähes samalla
 | 
			
		||||
strategialla kuin tavallista nim-peliä.
 | 
			
		||||
 | 
			
		||||
Ideana on pelata misääripeliä aluksi kuin tavallista
 | 
			
		||||
nim-peliä, mutta muuttaa strategiaa pelin
 | 
			
		||||
lopussa. Käänne tapahtuu silloin, kun seuraavan
 | 
			
		||||
siirron seurauksena kaikissa pelin kasoissa olisi 0 tai 1 tikkua.
 | 
			
		||||
 | 
			
		||||
Tavallisessa nim-pelissä tulisi nyt tehdä siirto,
 | 
			
		||||
jonka jälkeen 1-tikkuisia kasoja on parillinen määrä.
 | 
			
		||||
Misääripelissä tulee kuitenkin tehdä siirto,
 | 
			
		||||
jonka jälkeen 1-tikkuisia kasoja on pariton määrä.
 | 
			
		||||
 | 
			
		||||
Tämä strategia toimii, koska käännekohta tulee aina
 | 
			
		||||
vastaan jossakin vaiheessa peliä,
 | 
			
		||||
ja kyseinen tila on voittotila,
 | 
			
		||||
koska siinä on tarkalleen yksi kasa,
 | 
			
		||||
jossa on yli 1 tikkua,
 | 
			
		||||
joten nim-summa ei ole 0.
 | 
			
		||||
 | 
			
		||||
\section{Sprague–Grundyn lause}
 | 
			
		||||
 | 
			
		||||
\index{Sprague–Grundyn lause}
 | 
			
		||||
 | 
			
		||||
\key{Sprague–Grundyn lause} yleistää nim-pelin strategian
 | 
			
		||||
kaikkiin peleihin, jotka täyttävät
 | 
			
		||||
seuraavat vaatimukset:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}[noitemsep]
 | 
			
		||||
\item Pelissä on kaksi pelaajaa, jotka tekevät vuorotellen siirtoja.
 | 
			
		||||
\item Peli muodostuu tiloista ja mahdolliset siirrot tilasta
 | 
			
		||||
eivät riipu siitä, kumpi pelaaja on vuorossa.
 | 
			
		||||
\item Peli päättyy, kun toinen pelaaja ei voi tehdä siirtoa.
 | 
			
		||||
\item Peli päättyy varmasti ennemmin tai myöhemmin.
 | 
			
		||||
\item Pelaajien saatavilla on kaikki tieto tiloista
 | 
			
		||||
ja siirroista, eikä pelissä ole satunnaisuutta.
 | 
			
		||||
\end{itemize}
 | 
			
		||||
Ideana on laskea kullekin pelin tilalle Grundy-luku,
 | 
			
		||||
joka vastaa tikkujen määrää nim-pelin kasassa.
 | 
			
		||||
Kun kaikkien tilojen Grundy-luvut ovat tiedossa,
 | 
			
		||||
peliä voi pelata aivan kuin se olisi nim-peli.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Grundy-luku}
 | 
			
		||||
 | 
			
		||||
\index{Grundy-luku}
 | 
			
		||||
\index{mex-funktio}
 | 
			
		||||
 | 
			
		||||
Pelin tilan \key{Grundy-luku} määritellään rekursiivisesti
 | 
			
		||||
kaavalla
 | 
			
		||||
\[\textrm{mex}(\{g_1,g_2,\ldots,g_n\}),\]
 | 
			
		||||
jossa $g_1,g_2,\ldots,g_n$ ovat niiden tilojen
 | 
			
		||||
Grundy-luvut, joihin tilasta pääsee yhdellä siirrolla,
 | 
			
		||||
ja funktio mex antaa pienimmän ei-negatiivisen
 | 
			
		||||
luvun, jota ei esiinny joukossa.
 | 
			
		||||
Esimerkiksi $\textrm{mex}(\{0,1,3\})=2$.
 | 
			
		||||
Jos tilasta ei voi tehdä mitään siirtoa,
 | 
			
		||||
sen Grundy-luku on 0, koska $\textrm{mex}(\emptyset)=0$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi tilaverkossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {\phantom{0}};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {\phantom{0}};
 | 
			
		||||
\node[draw, circle] (3) at (4,0) {\phantom{0}};
 | 
			
		||||
\node[draw, circle] (4) at (1,-2) {\phantom{0}};
 | 
			
		||||
\node[draw, circle] (5) at (3,-2) {\phantom{0}};
 | 
			
		||||
\node[draw, circle] (6) at (5,-2) {\phantom{0}};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Grundy-luvut ovat seuraavat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\node[draw, circle] (1) at (0,0) {0};
 | 
			
		||||
\node[draw, circle] (2) at (2,0) {1};
 | 
			
		||||
\node[draw, circle] (3) at (4,0) {0};
 | 
			
		||||
\node[draw, circle] (4) at (1,-2) {2};
 | 
			
		||||
\node[draw, circle] (5) at (3,-2) {0};
 | 
			
		||||
\node[draw, circle] (6) at (5,-2) {2};
 | 
			
		||||
 | 
			
		||||
\path[draw,thick,->,>=latex] (2) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (3) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (4);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (5);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (1);
 | 
			
		||||
\path[draw,thick,->,>=latex] (4) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (5) -- (2);
 | 
			
		||||
\path[draw,thick,->,>=latex] (6) -- (2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Jos tila on häviötila, sen Grundy-luku on 0.
 | 
			
		||||
Jos taas tila on voittotila, sen Grundy-luku
 | 
			
		||||
on jokin positiivinen luku.
 | 
			
		||||
 | 
			
		||||
Grundy-luvun hyötynä on,
 | 
			
		||||
että se vastaa tikkujen määrää nim-kasassa.
 | 
			
		||||
Jos Grundy-luku on 0, niin tilasta pääsee vain tiloihin,
 | 
			
		||||
joiden Grundy-luku ei ole 0.
 | 
			
		||||
Jos taas Grundy-luku on $x>0$, niin tilasta pääsee tiloihin,
 | 
			
		||||
joiden Grundy-luvut kattavat välin $0,1,\ldots,x-1$.
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
\noindent
 | 
			
		||||
Tarkastellaan esimerkkinä peliä,
 | 
			
		||||
jossa pelaajat siirtävät vuorotellen
 | 
			
		||||
pelihahmoa sokkelossa.
 | 
			
		||||
Jokainen sokkelon ruutu on lattiaa tai seinää.
 | 
			
		||||
Kullakin siirrolla hahmon tulee liikkua jokin
 | 
			
		||||
määrä askeleita vasemmalle tai jokin
 | 
			
		||||
määrä askeleita ylöspäin.
 | 
			
		||||
Pelin voittaja on se, joka tekee viimeisen siirron.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi seuraavassa on pelin mahdollinen aloitustilanne,
 | 
			
		||||
jossa @ on pelihahmo ja * merkitsee ruutua, johon hahmo voi siirtyä.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (0, 1) rectangle (1, 2);
 | 
			
		||||
    \fill [color=black] (0, 3) rectangle (1, 4);
 | 
			
		||||
    \fill [color=black] (2, 2) rectangle (3, 3);
 | 
			
		||||
    \fill [color=black] (2, 4) rectangle (3, 5);
 | 
			
		||||
    \fill [color=black] (4, 3) rectangle (5, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
    
 | 
			
		||||
    \node at (4.5,0.5) {@};
 | 
			
		||||
    \node at (3.5,0.5) {*};
 | 
			
		||||
    \node at (2.5,0.5) {*};
 | 
			
		||||
    \node at (1.5,0.5) {*};
 | 
			
		||||
    \node at (0.5,0.5) {*};
 | 
			
		||||
    \node at (4.5,1.5) {*};
 | 
			
		||||
    \node at (4.5,2.5) {*};
 | 
			
		||||
    
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Sokkelopelin tiloja ovat kaikki sokkelon
 | 
			
		||||
lattiaruudut. Tässä tapauksessa
 | 
			
		||||
tilojen Grundy-luvut ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=.65]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (0, 1) rectangle (1, 2);
 | 
			
		||||
    \fill [color=black] (0, 3) rectangle (1, 4);
 | 
			
		||||
    \fill [color=black] (2, 2) rectangle (3, 3);
 | 
			
		||||
    \fill [color=black] (2, 4) rectangle (3, 5);
 | 
			
		||||
    \fill [color=black] (4, 3) rectangle (5, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
    
 | 
			
		||||
    \node at (0.5,4.5) {0};
 | 
			
		||||
    \node at (1.5,4.5) {1};
 | 
			
		||||
    \node at (2.5,4.5) {};
 | 
			
		||||
    \node at (3.5,4.5) {0};
 | 
			
		||||
    \node at (4.5,4.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,3.5) {};
 | 
			
		||||
    \node at (1.5,3.5) {0};
 | 
			
		||||
    \node at (2.5,3.5) {1};
 | 
			
		||||
    \node at (3.5,3.5) {2};
 | 
			
		||||
    \node at (4.5,3.5) {};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,2.5) {0};
 | 
			
		||||
    \node at (1.5,2.5) {2};
 | 
			
		||||
    \node at (2.5,2.5) {};
 | 
			
		||||
    \node at (3.5,2.5) {1};
 | 
			
		||||
    \node at (4.5,2.5) {0};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,1.5) {};
 | 
			
		||||
    \node at (1.5,1.5) {3};
 | 
			
		||||
    \node at (2.5,1.5) {0};
 | 
			
		||||
    \node at (3.5,1.5) {4};
 | 
			
		||||
    \node at (4.5,1.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,0.5) {0};
 | 
			
		||||
    \node at (1.5,0.5) {4};
 | 
			
		||||
    \node at (2.5,0.5) {1};
 | 
			
		||||
    \node at (3.5,0.5) {3};
 | 
			
		||||
    \node at (4.5,0.5) {2};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämän seurauksena sokkelopelin
 | 
			
		||||
tila käyttäytyy
 | 
			
		||||
samalla tavalla kuin nim-pelin kasa.
 | 
			
		||||
Esimerkiksi oikean alakulman ruudun
 | 
			
		||||
Grundy-luku on 2,
 | 
			
		||||
joten kyseessä on voittotila.
 | 
			
		||||
Voittoon johtava siirto on joko liikkua neljä
 | 
			
		||||
askelta vasemmalle tai kaksi askelta ylöspäin.
 | 
			
		||||
 | 
			
		||||
Huomaa, että toisin kuin alkuperäisessä nim-pelissä,
 | 
			
		||||
tilasta saattaa päästä toiseen tilaan,
 | 
			
		||||
jonka Grundy-luku on suurempi.
 | 
			
		||||
Vastustaja voi kuitenkin aina peruuttaa
 | 
			
		||||
tällaisen siirron niin,
 | 
			
		||||
että Grundy-luku palautuu samaksi.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Alipelit}
 | 
			
		||||
 | 
			
		||||
Oletetaan seuraavaksi, että peli muodostuu
 | 
			
		||||
alipeleistä ja jokaisella vuorolla
 | 
			
		||||
pelaaja valitsee jonkin alipeleistä ja
 | 
			
		||||
tekee siirron siinä.
 | 
			
		||||
Peli päättyy, kun missään alipelissä ei
 | 
			
		||||
pysty tekemään siirtoa.
 | 
			
		||||
 | 
			
		||||
Nyt pelin tilan Grundy-luku on alipelien
 | 
			
		||||
Grundy-lukujen nim-summa.
 | 
			
		||||
Peliä pystyy pelaamaan nim-pelin
 | 
			
		||||
tapaan selvittämällä kaikkien alipelien Grundy-luvut
 | 
			
		||||
ja laskemalla niiden nim-summa.
 | 
			
		||||
 | 
			
		||||
~\\
 | 
			
		||||
\noindent
 | 
			
		||||
Tarkastellaan esimerkkinä kolmen sokkelon peliä.
 | 
			
		||||
Tässä pelissä pelaaja valitsee joka siirrolla
 | 
			
		||||
yhden sokkeloista ja siirtää siinä olevaa hahmoa.
 | 
			
		||||
Pelin aloitustilanne voi olla seuraavanlainen:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{ccc}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (0, 1) rectangle (1, 2);
 | 
			
		||||
    \fill [color=black] (0, 3) rectangle (1, 4);
 | 
			
		||||
    \fill [color=black] (2, 2) rectangle (3, 3);
 | 
			
		||||
    \fill [color=black] (2, 4) rectangle (3, 5);
 | 
			
		||||
    \fill [color=black] (4, 3) rectangle (5, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
 | 
			
		||||
    \node at (4.5,0.5) {@};
 | 
			
		||||
 | 
			
		||||
    \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
&
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (1, 1) rectangle (2, 3);
 | 
			
		||||
    \fill [color=black] (2, 3) rectangle (3, 4);
 | 
			
		||||
    \fill [color=black] (4, 4) rectangle (5, 5);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
    
 | 
			
		||||
    \node at (4.5,0.5) {@};
 | 
			
		||||
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
&
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (1, 1) rectangle (4, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
    
 | 
			
		||||
    \node at (4.5,0.5) {@};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Sokkeloiden ruutujen Grundy-luvut ovat:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{ccc}
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (0, 1) rectangle (1, 2);
 | 
			
		||||
    \fill [color=black] (0, 3) rectangle (1, 4);
 | 
			
		||||
    \fill [color=black] (2, 2) rectangle (3, 3);
 | 
			
		||||
    \fill [color=black] (2, 4) rectangle (3, 5);
 | 
			
		||||
    \fill [color=black] (4, 3) rectangle (5, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,4.5) {0};
 | 
			
		||||
    \node at (1.5,4.5) {1};
 | 
			
		||||
    \node at (2.5,4.5) {};
 | 
			
		||||
    \node at (3.5,4.5) {0};
 | 
			
		||||
    \node at (4.5,4.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,3.5) {};
 | 
			
		||||
    \node at (1.5,3.5) {0};
 | 
			
		||||
    \node at (2.5,3.5) {1};
 | 
			
		||||
    \node at (3.5,3.5) {2};
 | 
			
		||||
    \node at (4.5,3.5) {};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,2.5) {0};
 | 
			
		||||
    \node at (1.5,2.5) {2};
 | 
			
		||||
    \node at (2.5,2.5) {};
 | 
			
		||||
    \node at (3.5,2.5) {1};
 | 
			
		||||
    \node at (4.5,2.5) {0};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,1.5) {};
 | 
			
		||||
    \node at (1.5,1.5) {3};
 | 
			
		||||
    \node at (2.5,1.5) {0};
 | 
			
		||||
    \node at (3.5,1.5) {4};
 | 
			
		||||
    \node at (4.5,1.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,0.5) {0};
 | 
			
		||||
    \node at (1.5,0.5) {4};
 | 
			
		||||
    \node at (2.5,0.5) {1};
 | 
			
		||||
    \node at (3.5,0.5) {3};
 | 
			
		||||
    \node at (4.5,0.5) {2};
 | 
			
		||||
    \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
&
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (1, 1) rectangle (2, 3);
 | 
			
		||||
    \fill [color=black] (2, 3) rectangle (3, 4);
 | 
			
		||||
    \fill [color=black] (4, 4) rectangle (5, 5);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,4.5) {0};
 | 
			
		||||
    \node at (1.5,4.5) {1};
 | 
			
		||||
    \node at (2.5,4.5) {2};
 | 
			
		||||
    \node at (3.5,4.5) {3};
 | 
			
		||||
    \node at (4.5,4.5) {};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,3.5) {1};
 | 
			
		||||
    \node at (1.5,3.5) {0};
 | 
			
		||||
    \node at (2.5,3.5) {};
 | 
			
		||||
    \node at (3.5,3.5) {0};
 | 
			
		||||
    \node at (4.5,3.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,2.5) {2};
 | 
			
		||||
    \node at (1.5,2.5) {};
 | 
			
		||||
    \node at (2.5,2.5) {0};
 | 
			
		||||
    \node at (3.5,2.5) {1};
 | 
			
		||||
    \node at (4.5,2.5) {2};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,1.5) {3};
 | 
			
		||||
    \node at (1.5,1.5) {};
 | 
			
		||||
    \node at (2.5,1.5) {1};
 | 
			
		||||
    \node at (3.5,1.5) {2};
 | 
			
		||||
    \node at (4.5,1.5) {0};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,0.5) {4};
 | 
			
		||||
    \node at (1.5,0.5) {0};
 | 
			
		||||
    \node at (2.5,0.5) {2};
 | 
			
		||||
    \node at (3.5,0.5) {5};
 | 
			
		||||
    \node at (4.5,0.5) {3};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
&
 | 
			
		||||
\begin{tikzpicture}[scale=.55]
 | 
			
		||||
  \begin{scope}
 | 
			
		||||
    \fill [color=black] (1, 1) rectangle (4, 4);
 | 
			
		||||
 | 
			
		||||
    \draw (0, 0) grid (5, 5);
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,4.5) {0};
 | 
			
		||||
    \node at (1.5,4.5) {1};
 | 
			
		||||
    \node at (2.5,4.5) {2};
 | 
			
		||||
    \node at (3.5,4.5) {3};
 | 
			
		||||
    \node at (4.5,4.5) {4};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,3.5) {1};
 | 
			
		||||
    \node at (1.5,3.5) {};
 | 
			
		||||
    \node at (2.5,3.5) {};
 | 
			
		||||
    \node at (3.5,3.5) {};
 | 
			
		||||
    \node at (4.5,3.5) {0};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,2.5) {2};
 | 
			
		||||
    \node at (1.5,2.5) {};
 | 
			
		||||
    \node at (2.5,2.5) {};
 | 
			
		||||
    \node at (3.5,2.5) {};
 | 
			
		||||
    \node at (4.5,2.5) {1};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,1.5) {3};
 | 
			
		||||
    \node at (1.5,1.5) {};
 | 
			
		||||
    \node at (2.5,1.5) {};
 | 
			
		||||
    \node at (3.5,1.5) {};
 | 
			
		||||
    \node at (4.5,1.5) {2};
 | 
			
		||||
 | 
			
		||||
    \node at (0.5,0.5) {4};
 | 
			
		||||
    \node at (1.5,0.5) {0};
 | 
			
		||||
    \node at (2.5,0.5) {1};
 | 
			
		||||
    \node at (3.5,0.5) {2};
 | 
			
		||||
    \node at (4.5,0.5) {3};
 | 
			
		||||
  \end{scope}
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Aloitustilanteessa Grundy-lukujen nim-summa on
 | 
			
		||||
$2 \oplus 3 \oplus 3 = 2$, joten
 | 
			
		||||
aloittaja pystyy voittamaan pelin.
 | 
			
		||||
Voittoon johtava siirto on liikkua vasemmassa sokkelossa
 | 
			
		||||
2 askelta ylöspäin, jolloin nim-summaksi
 | 
			
		||||
tulee $0 \oplus 3 \oplus 3 = 0$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Jakautuminen}
 | 
			
		||||
 | 
			
		||||
Joskus siirto pelissä jakaa pelin alipeleihin,
 | 
			
		||||
jotka ovat toisistaan riippumattomia.
 | 
			
		||||
Tällöin pelin Grundy-luku on
 | 
			
		||||
 | 
			
		||||
\[\textrm{mex}(\{g_1, g_2, \ldots, g_n \}),\]
 | 
			
		||||
missä $n$ on siirtojen määrä ja
 | 
			
		||||
\[g_k = a_{k,1} \oplus a_{k,2} \oplus \ldots \oplus a_{k,m},\]
 | 
			
		||||
missä siirron $k$ tuottamien alipelien
 | 
			
		||||
Grundy-luvut ovat $a_{k,1},a_{k,2},\ldots,a_{k,m}$.
 | 
			
		||||
 | 
			
		||||
\index{Grundyn peli@Grundyn peli}
 | 
			
		||||
 | 
			
		||||
Esimerkki tällaisesta pelistä on \key{Grundyn peli}.
 | 
			
		||||
Pelin alkutilanteessa on yksittäinen kasa, jossa on $n$ tikkua.
 | 
			
		||||
Joka vuorolla pelaaja valitsee jonkin kasan
 | 
			
		||||
ja jakaa sen kahdeksi epätyhjäksi kasaksi
 | 
			
		||||
niin, että kasoissa on eri määrä tikkuja.
 | 
			
		||||
Pelin voittaja on se, joka tekee viimeisen jaon.
 | 
			
		||||
 | 
			
		||||
Merkitään $f(n)$ Grundy-lukua kasalle,
 | 
			
		||||
jossa on $n$ tikkua.
 | 
			
		||||
Grundy-luku muodostuu käymällä läpi tavat
 | 
			
		||||
jakaa kasa kahdeksi kasaksi.
 | 
			
		||||
Esimerkiksi tapauksessa $n=8$ mahdolliset jakotavat
 | 
			
		||||
ovat $1+7$, $2+6$ ja $3+5$, joten
 | 
			
		||||
\[f(8)=\textrm{mex}(\{f(1) \oplus f(7), f(2) \oplus f(6), f(3) \oplus f(5)\}).\]
 | 
			
		||||
 | 
			
		||||
Tässä pelissä luvun $f(n)$ laskeminen vaatii lukujen
 | 
			
		||||
$f(1),\ldots,f(n-1)$ laskemista.
 | 
			
		||||
Pohjatapauksina $f(1)=f(2)=0$, koska 1 ja 2 tikun
 | 
			
		||||
kasaa ei ole mahdollista jakaa mitenkään.
 | 
			
		||||
Ensimmäiset Grundy-luvut ovat:
 | 
			
		||||
\[
 | 
			
		||||
\begin{array}{lcl}
 | 
			
		||||
f(1) & = & 0 \\
 | 
			
		||||
f(2) & = & 0 \\
 | 
			
		||||
f(3) & = & 1 \\
 | 
			
		||||
f(4) & = & 0 \\
 | 
			
		||||
f(5) & = & 2 \\
 | 
			
		||||
f(6) & = & 1 \\
 | 
			
		||||
f(7) & = & 0 \\
 | 
			
		||||
f(8) & = & 2 \\
 | 
			
		||||
\end{array}
 | 
			
		||||
\]
 | 
			
		||||
Tapauksen $n=8$ Grundy-luku on 2, joten peli on mahdollista
 | 
			
		||||
voittaa.
 | 
			
		||||
Voittosiirto on muodostaa kasat $1+7$,
 | 
			
		||||
koska $f(1) \oplus f(7) = 0$.
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,405 @@
 | 
			
		|||
\chapter{Square root algorithms}
 | 
			
		||||
 | 
			
		||||
\index{nelizjuurialgoritmi@neliöjuurialgoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Neliöjuurialgoritmi} on algoritmi,
 | 
			
		||||
jonka aikavaativuudessa esiintyy neliöjuuri.
 | 
			
		||||
Neliöjuurta voi ajatella ''köyhän miehen logaritmina'':
 | 
			
		||||
aikavaativuus $O(\sqrt n)$ on parempi kuin $O(n)$
 | 
			
		||||
mutta huonompi kuin $O(\log n)$.
 | 
			
		||||
Toisaalta neliöjuurialgoritmit toimivat
 | 
			
		||||
käytännössä hyvin ja niiden vakiokertoimet ovat pieniä.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tuttua ongelmaa,
 | 
			
		||||
jossa toteutettavana on summakysely taulukkoon.
 | 
			
		||||
Halutut operaatiot ovat:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item muuta kohdassa $x$ olevaa lukua
 | 
			
		||||
\item laske välin $[a,b]$ lukujen summa
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Olemme aiemmin ratkaisseet tehtävän
 | 
			
		||||
binääri-indeksipuun ja segmenttipuun avulla,
 | 
			
		||||
jolloin kummankin operaation aikavaativuus on $O(\log n)$.
 | 
			
		||||
Nyt ratkaisemme tehtävän toisella
 | 
			
		||||
tavalla neliöjuurirakennetta käyttäen,
 | 
			
		||||
jolloin summan laskenta vie aikaa $O(\sqrt n)$
 | 
			
		||||
ja luvun muuttaminen vie aikaa $O(1)$.
 | 
			
		||||
 | 
			
		||||
Ideana on jakaa taulukko $\sqrt n$-kokoisiin
 | 
			
		||||
väleihin niin, että jokaiseen väliin
 | 
			
		||||
tallennetaan lukujen summa välillä.
 | 
			
		||||
Seuraavassa on esimerkki taulukosta ja
 | 
			
		||||
sitä vastaavista $\sqrt n$-väleistä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) grid (16,1);
 | 
			
		||||
 | 
			
		||||
\draw (0,1) rectangle (4,2);
 | 
			
		||||
\draw (4,1) rectangle (8,2);
 | 
			
		||||
\draw (8,1) rectangle (12,2);
 | 
			
		||||
\draw (12,1) rectangle (16,2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5, 0.5) {5};
 | 
			
		||||
\node at (1.5, 0.5) {8};
 | 
			
		||||
\node at (2.5, 0.5) {6};
 | 
			
		||||
\node at (3.5, 0.5) {3};
 | 
			
		||||
\node at (4.5, 0.5) {2};
 | 
			
		||||
\node at (5.5, 0.5) {7};
 | 
			
		||||
\node at (6.5, 0.5) {2};
 | 
			
		||||
\node at (7.5, 0.5) {6};
 | 
			
		||||
\node at (8.5, 0.5) {7};
 | 
			
		||||
\node at (9.5, 0.5) {1};
 | 
			
		||||
\node at (10.5, 0.5) {7};
 | 
			
		||||
\node at (11.5, 0.5) {5};
 | 
			
		||||
\node at (12.5, 0.5) {6};
 | 
			
		||||
\node at (13.5, 0.5) {2};
 | 
			
		||||
\node at (14.5, 0.5) {3};
 | 
			
		||||
\node at (15.5, 0.5) {2};
 | 
			
		||||
 | 
			
		||||
\node at (2, 1.5) {21};
 | 
			
		||||
\node at (6, 1.5) {17};
 | 
			
		||||
\node at (10, 1.5) {20};
 | 
			
		||||
\node at (14, 1.5) {13};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kun taulukon luku muuttuu,
 | 
			
		||||
tämän yhteydessä täytyy laskea uusi summa
 | 
			
		||||
vastaavalle $\sqrt n$-välille:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (5,0) rectangle (6,1);
 | 
			
		||||
\draw (0,0) grid (16,1);
 | 
			
		||||
 | 
			
		||||
\fill[color=lightgray] (4,1) rectangle (8,2);
 | 
			
		||||
\draw (0,1) rectangle (4,2);
 | 
			
		||||
\draw (4,1) rectangle (8,2);
 | 
			
		||||
\draw (8,1) rectangle (12,2);
 | 
			
		||||
\draw (12,1) rectangle (16,2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5, 0.5) {5};
 | 
			
		||||
\node at (1.5, 0.5) {8};
 | 
			
		||||
\node at (2.5, 0.5) {6};
 | 
			
		||||
\node at (3.5, 0.5) {3};
 | 
			
		||||
\node at (4.5, 0.5) {2};
 | 
			
		||||
\node at (5.5, 0.5) {5};
 | 
			
		||||
\node at (6.5, 0.5) {2};
 | 
			
		||||
\node at (7.5, 0.5) {6};
 | 
			
		||||
\node at (8.5, 0.5) {7};
 | 
			
		||||
\node at (9.5, 0.5) {1};
 | 
			
		||||
\node at (10.5, 0.5) {7};
 | 
			
		||||
\node at (11.5, 0.5) {5};
 | 
			
		||||
\node at (12.5, 0.5) {6};
 | 
			
		||||
\node at (13.5, 0.5) {2};
 | 
			
		||||
\node at (14.5, 0.5) {3};
 | 
			
		||||
\node at (15.5, 0.5) {2};
 | 
			
		||||
 | 
			
		||||
\node at (2, 1.5) {21};
 | 
			
		||||
\node at (6, 1.5) {15};
 | 
			
		||||
\node at (10, 1.5) {20};
 | 
			
		||||
\node at (14, 1.5) {13};
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Välin summan laskeminen taas tapahtuu muodostamalla
 | 
			
		||||
summa reunoissa olevista yksittäisistä luvuista
 | 
			
		||||
sekä keskellä olevista $\sqrt n$-väleistä:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (3,0) rectangle (4,1);
 | 
			
		||||
\fill[color=lightgray] (12,0) rectangle (13,1);
 | 
			
		||||
\fill[color=lightgray] (13,0) rectangle (14,1);
 | 
			
		||||
\draw (0,0) grid (16,1);
 | 
			
		||||
 | 
			
		||||
\fill[color=lightgray] (4,1) rectangle (8,2);
 | 
			
		||||
\fill[color=lightgray] (8,1) rectangle (12,2);
 | 
			
		||||
\draw (0,1) rectangle (4,2);
 | 
			
		||||
\draw (4,1) rectangle (8,2);
 | 
			
		||||
\draw (8,1) rectangle (12,2);
 | 
			
		||||
\draw (12,1) rectangle (16,2);
 | 
			
		||||
 | 
			
		||||
\node at (0.5, 0.5) {5};
 | 
			
		||||
\node at (1.5, 0.5) {8};
 | 
			
		||||
\node at (2.5, 0.5) {6};
 | 
			
		||||
\node at (3.5, 0.5) {3};
 | 
			
		||||
\node at (4.5, 0.5) {2};
 | 
			
		||||
\node at (5.5, 0.5) {5};
 | 
			
		||||
\node at (6.5, 0.5) {2};
 | 
			
		||||
\node at (7.5, 0.5) {6};
 | 
			
		||||
\node at (8.5, 0.5) {7};
 | 
			
		||||
\node at (9.5, 0.5) {1};
 | 
			
		||||
\node at (10.5, 0.5) {7};
 | 
			
		||||
\node at (11.5, 0.5) {5};
 | 
			
		||||
\node at (12.5, 0.5) {6};
 | 
			
		||||
\node at (13.5, 0.5) {2};
 | 
			
		||||
\node at (14.5, 0.5) {3};
 | 
			
		||||
\node at (15.5, 0.5) {2};
 | 
			
		||||
 | 
			
		||||
\node at (2, 1.5) {21};
 | 
			
		||||
\node at (6, 1.5) {15};
 | 
			
		||||
\node at (10, 1.5) {20};
 | 
			
		||||
\node at (14, 1.5) {13};
 | 
			
		||||
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.5mm] (14,-0.25) -- (3,-0.25);
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Luvun muuttamisen aikavaativuus on
 | 
			
		||||
$O(1)$, koska riittää muuttaa yhden $\sqrt n$-välin summaa.
 | 
			
		||||
Välin summa taas lasketaan kolmessa osassa:
 | 
			
		||||
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item vasemmassa reunassa on $O(\sqrt n)$ yksittäistä lukua
 | 
			
		||||
\item keskellä on $O(\sqrt n)$ peräkkäistä $\sqrt n$-väliä
 | 
			
		||||
\item oikeassa reunassa on $O(\sqrt n)$ yksittäistä lukua
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Jokaisen osan summan laskeminen vie aikaa $O(\sqrt n)$,
 | 
			
		||||
joten summan laskemisen aikavaativuus on yhteensä $O(\sqrt n)$.
 | 
			
		||||
 | 
			
		||||
Neliöjuurialgoritmeissa parametri $\sqrt n$
 | 
			
		||||
johtuu siitä, että se saattaa kaksi asiaa tasapainoon:
 | 
			
		||||
esimerkiksi $n$ alkion taulukko jakautuu
 | 
			
		||||
$\sqrt n$ osaan, joista jokaisessa on $\sqrt n$ alkiota.
 | 
			
		||||
Käytännössä algoritmeissa
 | 
			
		||||
ei ole kuitenkaan pakko käyttää
 | 
			
		||||
tarkalleen parametria $\sqrt n$,
 | 
			
		||||
vaan voi olla parempi valita toiseksi
 | 
			
		||||
parametriksi $k$ ja toiseksi $n/k$,
 | 
			
		||||
missä $k$ on pienempi tai suurempi kuin $\sqrt n$.
 | 
			
		||||
 | 
			
		||||
Paras parametri selviää usein kokeilemalla
 | 
			
		||||
ja riippuu tehtävästä ja syötteestä.
 | 
			
		||||
Esimerkiksi jos taulukkoa käsittelevä algoritmi
 | 
			
		||||
käy usein läpi välit mutta harvoin välin sisällä
 | 
			
		||||
olevia alkioita, taulukko voi olla järkevää
 | 
			
		||||
jakaa $k < \sqrt n$ väliin,
 | 
			
		||||
joista jokaisella on $n/k > \sqrt n$ alkiota.
 | 
			
		||||
 | 
			
		||||
\section{Eräkäsittely}
 | 
			
		||||
 | 
			
		||||
\index{erxkxsittely@eräkäsittely}
 | 
			
		||||
 | 
			
		||||
\key{Eräkäsittelyssä} algoritmin suorittamat
 | 
			
		||||
operaatiot jaetaan eriin,
 | 
			
		||||
jotka käsitellään omina kokonaisuuksina.
 | 
			
		||||
Erien välissä tehdään yksittäinen työläs toimenpide,
 | 
			
		||||
joka auttaa tulevien operaatioiden käsittelyä.
 | 
			
		||||
 | 
			
		||||
Neliöjuurialgoritmi syntyy, kun $n$ operaatiota
 | 
			
		||||
jaetaan $O(\sqrt n)$-kokoisiin eriin,
 | 
			
		||||
jolloin sekä eriä että operaatioita kunkin erän
 | 
			
		||||
sisällä on $O(\sqrt n)$.
 | 
			
		||||
Tämä tasapainottaa sitä, miten usein erien välinen
 | 
			
		||||
työläs toimenpide tapahtuu sekä miten paljon työtä
 | 
			
		||||
erän sisällä täytyy tehdä.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää, jossa
 | 
			
		||||
ruudukossa on $k \times k$ ruutua,
 | 
			
		||||
jotka ovat aluksi valkoisia.
 | 
			
		||||
Tehtävänä on suorittaa ruudukkoon
 | 
			
		||||
$n$ operaatiota,
 | 
			
		||||
joista jokainen on jompikumpi seuraavista:
 | 
			
		||||
\begin{itemize}
 | 
			
		||||
\item
 | 
			
		||||
väritä ruutu $(y,x)$ mustaksi
 | 
			
		||||
\item
 | 
			
		||||
etsi ruudusta $(y,x)$ lähin
 | 
			
		||||
musta ruutu, kun
 | 
			
		||||
ruutujen $(y_1,x_1)$ ja $(y_2,x_2)$
 | 
			
		||||
etäisyys on $|y_1-y_2|+|x_1-x_2|$
 | 
			
		||||
\end{itemize}
 | 
			
		||||
 | 
			
		||||
Ratkaisuna on jakaa operaatiot $O(\sqrt n)$ erään,
 | 
			
		||||
joista jokaisessa on $O(\sqrt n)$ operaatiota.
 | 
			
		||||
Kunkin erän alussa jokaiseen ruudukon ruutuun
 | 
			
		||||
lasketaan pienin etäisyys mustaan ruutuun.
 | 
			
		||||
Tämä onnistuu ajassa $O(k^2)$ leveyshaun avulla.
 | 
			
		||||
 | 
			
		||||
Kunkin erän käsittelyssä pidetään yllä listaa ruuduista,
 | 
			
		||||
jotka on muutettu mustaksi tässä erässä.
 | 
			
		||||
Nyt etäisyys ruudusta lähimpään mustaan ruutuun
 | 
			
		||||
on joko erän alussa laskettu etäisyys tai sitten
 | 
			
		||||
etäisyys johonkin listassa olevaan tämän erän aikana mustaksi
 | 
			
		||||
muutettuun ruutuun.
 | 
			
		||||
 | 
			
		||||
Algoritmi vie aikaa $O((k^2+n) \sqrt n)$,
 | 
			
		||||
koska erien välissä tehdään $O(\sqrt n)$ kertaa
 | 
			
		||||
$O(k^2)$-aikainen läpikäynti, ja
 | 
			
		||||
erissä käsitellään yhteensä $O(n)$ solmua,
 | 
			
		||||
joista jokaisen kohdalla käydään läpi
 | 
			
		||||
$O(\sqrt n)$ solmua listasta.
 | 
			
		||||
 | 
			
		||||
Jos algoritmi tekisi leveyshaun jokaiselle operaatiolle,
 | 
			
		||||
aikavaativuus olisi $O(k^2 n)$.
 | 
			
		||||
Jos taas algoritmi kävisi kaikki muutetut ruudut läpi
 | 
			
		||||
jokaisen operaation kohdalla,
 | 
			
		||||
aikavaativuus olisi $O(n^2)$.
 | 
			
		||||
Neliöjuurialgoritmi yhdistää nämä aikavaativuudet
 | 
			
		||||
ja muuttaa kertoimen $n$ kertoimeksi $\sqrt n$.
 | 
			
		||||
 | 
			
		||||
\section{Tapauskäsittely}
 | 
			
		||||
 | 
			
		||||
\index{tapauskxsittely@tapauskäsittely}
 | 
			
		||||
 | 
			
		||||
\key{Tapauskäsittelyssä} algoritmissa on useita
 | 
			
		||||
toimintatapoja, jotka aktivoituvat syötteen
 | 
			
		||||
ominaisuuksista riippuen.
 | 
			
		||||
Tyypillisesti yksi algoritmin osa on tehokas
 | 
			
		||||
pienellä parametrilla
 | 
			
		||||
ja toinen osa on tehokas suurella parametrilla,
 | 
			
		||||
ja sopiva jakokohta kulkee suunnilleen arvon $\sqrt n$ kohdalla.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää, jossa
 | 
			
		||||
puussa on $n$ solmua, joista jokaisella on tietty väri.
 | 
			
		||||
Tavoitteena on etsiä puusta kaksi solmua,
 | 
			
		||||
jotka ovat samanvärisiä ja mahdollisimman
 | 
			
		||||
kaukana toisistaan.
 | 
			
		||||
 | 
			
		||||
Tehtävän voi ratkaista
 | 
			
		||||
käymällä läpi värit yksi kerrallaan ja
 | 
			
		||||
etsimällä kullekin värille kaksi solmua, jotka ovat
 | 
			
		||||
mahdollisimman kaukana toisistaan.
 | 
			
		||||
Tietyllä värillä algoritmin toiminta riippuu siitä,
 | 
			
		||||
montako kyseisen väristä solmua puussa on.
 | 
			
		||||
Oletetaan nyt, että käsittelyssä on väri $x$
 | 
			
		||||
ja puussa on $c$ solmua, joiden väri on $x$.
 | 
			
		||||
Tapaukset ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Tapaus 1: $c \le \sqrt n$}
 | 
			
		||||
 | 
			
		||||
Jos $x$-värisiä solmuja on vähän,
 | 
			
		||||
käydään läpi kaikki $x$-väristen solmujen parit
 | 
			
		||||
ja valitaan pari, jonka etäisyys on suurin.
 | 
			
		||||
Jokaisesta solmusta täytyy
 | 
			
		||||
laskea etäisyys $O(\sqrt n)$ muuhun solmuun (ks. luku 18.3),
 | 
			
		||||
joten kaikkien tapaukseen 1 osuvien solmujen
 | 
			
		||||
käsittely vie aikaa yhteensä $O(n \sqrt n)$.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Tapaus 2: $c > \sqrt n$}
 | 
			
		||||
 | 
			
		||||
Jos $x$-värisiä solmuja on paljon,
 | 
			
		||||
käydään koko puu läpi ja
 | 
			
		||||
lasketaan suurin etäisyys kahden
 | 
			
		||||
$x$-värisen solmun välillä.
 | 
			
		||||
Läpikäynnin aikavaativuus on $O(n)$,
 | 
			
		||||
ja tapaus 2 aktivoituu korkeintaan $O(\sqrt n)$
 | 
			
		||||
värille, joten tapauksen 2 solmut 
 | 
			
		||||
tuottavat aikavaativuuden $O(n \sqrt n)$.\\\\
 | 
			
		||||
\noindent
 | 
			
		||||
Algoritmin kokonaisaikavaativuus on $O(n \sqrt n)$,
 | 
			
		||||
koska sekä tapaus 1 että tapaus 2 vievät aikaa
 | 
			
		||||
yhteensä $O(n \sqrt n)$.
 | 
			
		||||
 | 
			
		||||
\section{Mo'n algoritmi}
 | 
			
		||||
 | 
			
		||||
\index{Mo'n algoritmi}
 | 
			
		||||
 | 
			
		||||
\key{Mo'n algoritmi} soveltuu tehtäviin,
 | 
			
		||||
joissa taulukkoon tehdään välikyselyitä ja
 | 
			
		||||
taulukon sisältö kaikissa kyselyissä on sama.
 | 
			
		||||
Algoritmi järjestää
 | 
			
		||||
kyselyt uudestaan niin,
 | 
			
		||||
että niiden käsittely on tehokasta.
 | 
			
		||||
 | 
			
		||||
Algoritmi pitää yllä taulukon väliä,
 | 
			
		||||
jolle on laskettu kyselyn vastaus.
 | 
			
		||||
Kyselystä toiseen siirryttäessä algoritmi
 | 
			
		||||
muuttaa väliä askel kerrallaan niin,
 | 
			
		||||
että vastaus uuteen kyselyyn saadaan laskettua.
 | 
			
		||||
Algoritmin aikavaativuus on $O(n \sqrt n f(n))$,
 | 
			
		||||
kun kyselyitä on $n$ ja 
 | 
			
		||||
yksi välin muutosaskel vie aikaa $f(n)$.
 | 
			
		||||
 | 
			
		||||
Algoritmin toiminta perustuu järjestykseen,
 | 
			
		||||
jossa kyselyt käsitellään.
 | 
			
		||||
Kun kyselyjen välit ovat muotoa $[a,b]$,
 | 
			
		||||
algoritmi järjestää ne ensisijaisesti arvon
 | 
			
		||||
$\lfloor a/\sqrt n \rfloor$ mukaan ja toissijaisesti arvon $b$ mukaan.
 | 
			
		||||
Algoritmi suorittaa siis peräkkäin kaikki kyselyt,
 | 
			
		||||
joiden alkukohta on tietyllä $\sqrt n$-välillä.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tämän järjestyksen ansiosta
 | 
			
		||||
algoritmi tekee yhteensä vain $O(n \sqrt n)$ muutosaskelta.
 | 
			
		||||
Tämä johtuu siitä, että välin vasen reuna liikkuu
 | 
			
		||||
$n$ kertaa $O(\sqrt n)$ askelta,
 | 
			
		||||
kun taas välin oikea reuna liikkuu $\sqrt n$
 | 
			
		||||
kertaa $O(n)$ askelta. Molemmat reunat liikkuvat
 | 
			
		||||
siis yhteensä $O(n \sqrt n)$ askelta.
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Esimerkki}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää,
 | 
			
		||||
jossa annettuna on joukko välejä taulukossa
 | 
			
		||||
ja  tehtävänä on selvittää kullekin välille,
 | 
			
		||||
montako eri lukua taulukossa on kyseisellä välillä.
 | 
			
		||||
 | 
			
		||||
Mo'n algoritmissa kyselyt järjestetään aina samalla
 | 
			
		||||
tavalla, ja tehtävästä riippuva osa on,
 | 
			
		||||
miten kyselyn vastausta pidetään yllä.
 | 
			
		||||
Tässä tehtävässä luonteva tapa on
 | 
			
		||||
pitää muistissa kyselyn vastausta sekä
 | 
			
		||||
taulukkoa \texttt{c}, jossa $\texttt{c}[x]$
 | 
			
		||||
on alkion $x$ lukumäärä aktiivisella välillä.
 | 
			
		||||
 | 
			
		||||
Kyselystä toiseen siirryttäessä taulukon aktiivinen
 | 
			
		||||
väli muuttuu. Esimerkiksi jos nykyinen kysely koskee väliä
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (1,0) rectangle (5,1);
 | 
			
		||||
\draw (0,0) grid (9,1);
 | 
			
		||||
\node at (0.5, 0.5) {4};
 | 
			
		||||
\node at (1.5, 0.5) {2};
 | 
			
		||||
\node at (2.5, 0.5) {5};
 | 
			
		||||
\node at (3.5, 0.5) {4};
 | 
			
		||||
\node at (4.5, 0.5) {2};
 | 
			
		||||
\node at (5.5, 0.5) {4};
 | 
			
		||||
\node at (6.5, 0.5) {3};
 | 
			
		||||
\node at (7.5, 0.5) {3};
 | 
			
		||||
\node at (8.5, 0.5) {4};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
ja seuraava kysely koskee väliä
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\fill[color=lightgray] (2,0) rectangle (7,1);
 | 
			
		||||
\draw (0,0) grid (9,1);
 | 
			
		||||
\node at (0.5, 0.5) {4};
 | 
			
		||||
\node at (1.5, 0.5) {2};
 | 
			
		||||
\node at (2.5, 0.5) {5};
 | 
			
		||||
\node at (3.5, 0.5) {4};
 | 
			
		||||
\node at (4.5, 0.5) {2};
 | 
			
		||||
\node at (5.5, 0.5) {4};
 | 
			
		||||
\node at (6.5, 0.5) {3};
 | 
			
		||||
\node at (7.5, 0.5) {3};
 | 
			
		||||
\node at (8.5, 0.5) {4};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
niin tapahtuu kolme muutosaskelta:
 | 
			
		||||
välin vasen reuna siirtyy askeleen oikealle
 | 
			
		||||
ja välin oikea reuna siirtyy kaksi askelta oikealle.
 | 
			
		||||
 | 
			
		||||
Jokaisen muutosaskeleen jälkeen täytyy
 | 
			
		||||
päivittää taulukkoa \texttt{c}.
 | 
			
		||||
Jos väliin tulee alkio $x$,
 | 
			
		||||
arvo $\texttt{c}[x]$ kasvaa 1:llä,
 | 
			
		||||
ja jos välistä poistuu alkio $x$,
 | 
			
		||||
arvo $\texttt{c}[x]$ vähenee 1:llä.
 | 
			
		||||
Jos lisäyksen jälkeen $\texttt{c}[x]=1$,
 | 
			
		||||
kyselyn vastaus kasvaa 1:llä,
 | 
			
		||||
ja jos poiston jälkeen $\texttt{c}[x]=0$,
 | 
			
		||||
kyselyn vastaus vähenee 1:llä.
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa muutosaskeleen aikavaativuus on $O(1)$,
 | 
			
		||||
joten algoritmin kokonaisaikavaativuus on $O(n \sqrt n)$.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,738 @@
 | 
			
		|||
\chapter{Geometry}
 | 
			
		||||
 | 
			
		||||
\index{geometria@geometria}
 | 
			
		||||
 | 
			
		||||
Geometrian tehtävissä on usein haasteena keksiä,
 | 
			
		||||
mistä suunnasta ongelmaa kannattaa lähestyä,
 | 
			
		||||
jotta ratkaisun saa koodattua mukavasti ja
 | 
			
		||||
erikoistapauksia tulee mahdollisimman vähän.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää,
 | 
			
		||||
jossa annettuna on nelikulmion kulmapisteet
 | 
			
		||||
ja tehtävänä on laskea sen pinta-ala.
 | 
			
		||||
Esimerkiksi syötteenä voi olla
 | 
			
		||||
seuraavanlainen nelikulmio:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[fill] (6,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (5,6) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (2,5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (1,1) circle [radius=0.1];
 | 
			
		||||
\draw[thick] (6,2) -- (5,6) -- (2,5) -- (1,1) -- (6,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Yksi tapa lähestyä tehtävää on jakaa nelikulmio
 | 
			
		||||
kahdeksi kolmioksi vetämällä jakoviiva kahden
 | 
			
		||||
vastakkaisen kulmapisteen välille:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[fill] (6,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (5,6) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (2,5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (1,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw[thick] (6,2) -- (5,6) -- (2,5) -- (1,1) -- (6,2);
 | 
			
		||||
\draw[dashed,thick] (2,5) -- (6,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tämän jälkeen riittää laskea yhteen kolmioiden
 | 
			
		||||
pinta-alat. Kolmion pinta-alan voi laskea
 | 
			
		||||
esimerkiksi \key{Heronin kaavalla}
 | 
			
		||||
\[ \sqrt{s (s-a) (s-b) (s-c)},\]
 | 
			
		||||
kun kolmion sivujen pituudet ovat
 | 
			
		||||
$a$, $b$ ja $c$ ja $s=(a+b+c)/2$.
 | 
			
		||||
\index{Heronin kaava@Heronin kaava}
 | 
			
		||||
 | 
			
		||||
Tämä on mahdollinen tapa ratkaista tehtävä,
 | 
			
		||||
mutta siinä on ongelma:
 | 
			
		||||
miten löytää kelvollinen tapa vetää jakoviiva?
 | 
			
		||||
Osoittautuu, että
 | 
			
		||||
mitkä tahansa vastakkaiset pisteet eivät kelpaa.
 | 
			
		||||
Esimerkiksi seuraavassa nelikulmiossa
 | 
			
		||||
jakoviiva menee nelikulmion ulkopuolelle:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[fill] (6,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (3,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (2,5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (1,1) circle [radius=0.1];
 | 
			
		||||
\draw[thick] (6,2) -- (3,2) -- (2,5) -- (1,1) -- (6,2);
 | 
			
		||||
 | 
			
		||||
\draw[dashed,thick] (2,5) -- (6,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Toinen tapa vetää jakoviiva on kuitenkin toimiva:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[fill] (6,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (3,2) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (2,5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (1,1) circle [radius=0.1];
 | 
			
		||||
\draw[thick] (6,2) -- (3,2) -- (2,5) -- (1,1) -- (6,2);
 | 
			
		||||
 | 
			
		||||
\draw[dashed,thick] (3,2) -- (1,1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Ihmiselle on selvää, kumpi jakoviiva jakaa nelikulmion
 | 
			
		||||
kahdeksi kolmioksi, mutta tietokoneen kannalta
 | 
			
		||||
tilanne on hankala.
 | 
			
		||||
 | 
			
		||||
Osoittautuu, että tehtävän ratkaisuun on olemassa
 | 
			
		||||
paljon helpommin toteutettava tapa,
 | 
			
		||||
jossa ei tarvitse miettiä erikoistapauksia.
 | 
			
		||||
Nelikulmion pinta-alan laskemiseen
 | 
			
		||||
on nimittäin yleinen kaava
 | 
			
		||||
\[x_1y_2-x_2y_1+x_2y_3-x_3y_2+x_3y_4-x_4y_3+x_4y_1-x_1y_4,\]
 | 
			
		||||
kun kulmapisteet ovat
 | 
			
		||||
$(x_1,y_1)$,
 | 
			
		||||
$(x_2,y_2)$,
 | 
			
		||||
$(x_3,y_3)$ ja
 | 
			
		||||
$(x_4,y_4)$.
 | 
			
		||||
Tämä kaava on helppo laskea, siinä ei ole erikoistapauksia
 | 
			
		||||
ja osoittautuu, että kaava on mahdollista yleistää
 | 
			
		||||
\textit{kaikille} monikulmioille.
 | 
			
		||||
 | 
			
		||||
\section{Kompleksiluvut}
 | 
			
		||||
 | 
			
		||||
\index{kompleksiluku@kompleksiluku}
 | 
			
		||||
\index{piste@piste}
 | 
			
		||||
\index{vektori@vektori}
 | 
			
		||||
 | 
			
		||||
\key{Kompleksiluku} on luku muotoa $x+y i$, missä $i = \sqrt{-1}$
 | 
			
		||||
on \key{imaginääriyksikkö}.
 | 
			
		||||
Kompleksiluvun luonteva geometrinen tulkinta on,
 | 
			
		||||
että se esittää kaksiulotteisen tason pistettä $(x,y)$
 | 
			
		||||
tai vektoria origosta pisteeseen $(x,y)$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi luku $4+2i$ tarkoittaa seuraavaa
 | 
			
		||||
pistettä ja vektoria:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[->,thick] (-5,0)--(5,0);
 | 
			
		||||
\draw[->,thick] (0,-5)--(0,5);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (4,2) circle [radius=0.1];
 | 
			
		||||
\draw[->,thick] (0,0)--(4-0.1,2-0.1);
 | 
			
		||||
 | 
			
		||||
\node at (4,2.8) {$(4,2)$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{complex@\texttt{complex}}
 | 
			
		||||
 | 
			
		||||
C++:ssa on kompleksilukujen käsittelyyn luokka \texttt{complex},
 | 
			
		||||
josta on hyötyä geometriassa.
 | 
			
		||||
Luokan avulla voi esittää pisteen tai vektorin
 | 
			
		||||
kompleksilukuna, ja luokassa on valmiita
 | 
			
		||||
geometriaan soveltuvia työkaluja.
 | 
			
		||||
 | 
			
		||||
Seuraavassa koodissa \texttt{C} on koordinaatin tyyppi
 | 
			
		||||
ja \texttt{P} on pisteen tai vektorin tyyppi.
 | 
			
		||||
Lisäksi koodi määrittelee
 | 
			
		||||
lyhennysmerkinnät \texttt{X} ja \texttt{Y},
 | 
			
		||||
joiden avulla pystyy viittaamaan x- ja y-koordinaatteihin.
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
typedef long long C;
 | 
			
		||||
typedef complex<C> P;
 | 
			
		||||
#define X real()
 | 
			
		||||
#define Y imag()
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraava koodi määrittelee pisteen $p=(4,2)$
 | 
			
		||||
ja ilmoittaa sen x- ja y-koordinaatin:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
P p = {4,2};
 | 
			
		||||
cout << p.X << " " << p.Y << "\n"; // 4 2
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Seuraava koodi määrittelee vektorit $v=(3,1)$
 | 
			
		||||
ja $u=(2,2)$ ja laskee sitten niiden summan $s=v+u$:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
P v = {3,1};
 | 
			
		||||
P u = {2,2};
 | 
			
		||||
P s = v+u;
 | 
			
		||||
cout << s.X << " " << s.Y << "\n"; // 5 3
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Sopiva koordinaatin tyyppi \texttt{C} on tilanteesta
 | 
			
		||||
riippuen \texttt{long long} (kokonaisluku)
 | 
			
		||||
tai \texttt{long double} (liukuluku).
 | 
			
		||||
Kokonaislukuja kannattaa käyttää aina kun mahdollista,
 | 
			
		||||
koska silloin laskenta on tarkkaa.
 | 
			
		||||
 | 
			
		||||
Jos koordinaatit ovat liukulukuja,
 | 
			
		||||
niiden vertailussa täytyy ottaa huomioon epätarkkuus.
 | 
			
		||||
Turvallinen tapa tarkistaa,
 | 
			
		||||
ovatko liukuluvut $a$ ja $b$ samat
 | 
			
		||||
on käyttää vertailua $|a-b|<\epsilon$, jossa $\epsilon$
 | 
			
		||||
on pieni luku (esimerkiksi $\epsilon=10^{-9}$).
 | 
			
		||||
 | 
			
		||||
\subsubsection*{Funktioita}
 | 
			
		||||
 | 
			
		||||
Seuraavissa esimerkeissä koordinaatin tyyppinä on
 | 
			
		||||
\texttt{long double}.
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{abs(v)} laskee vektorin $v=(x,y)$
 | 
			
		||||
pituuden $|v|$ kaavalla $\sqrt{x^2+y^2}$.
 | 
			
		||||
Sillä voi laskea myös pisteiden $(x_1,y_1)$
 | 
			
		||||
ja $(x_2,y_2)$ etäisyyden,
 | 
			
		||||
koska pisteiden etäisyys
 | 
			
		||||
on sama kuin vektorin $(x_2-x_1,y_2-y_1)$ pituus.
 | 
			
		||||
Joskus hyödyllinen on myös funktio \texttt{norm(v)},
 | 
			
		||||
joka laskee vektorin $v=(x,y)$ pituuden neliön $|v|^2$.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi laskee
 | 
			
		||||
pisteiden $(4,2)$ ja $(3,-1)$ etäisyyden:
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
P a = {4,2};
 | 
			
		||||
P b = {3,-1};
 | 
			
		||||
cout << abs(b-a) << "\n"; // 3.60555
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{arg(v)} laskee vektorin $v=(x,y)$
 | 
			
		||||
kulman radiaaneina suhteessa x-akseliin.
 | 
			
		||||
Radiaaneina ilmoitettu kulma $r$ vastaa asteina
 | 
			
		||||
kulmaa $180  r/\pi$ astetta.
 | 
			
		||||
Jos vektori osoittaa suoraan oikealle,
 | 
			
		||||
sen kulma on 0.
 | 
			
		||||
Kulma kasvaa vastapäivään ja vähenee myötäpäivään
 | 
			
		||||
liikuttaessa.
 | 
			
		||||
 | 
			
		||||
Funktio \texttt{polar(s,a)} muodostaa vektorin,
 | 
			
		||||
jonka pituus on $s$ ja joka osoittaa kulmaan $a$.
 | 
			
		||||
Lisäksi vektoria pystyy kääntämään kulman $a$
 | 
			
		||||
verran kertomalla se vektorilla,
 | 
			
		||||
jonka pituus on 1 ja kulma on $a$.
 | 
			
		||||
 | 
			
		||||
Seuraava koodi laskee vektorin $(4,2)$ kulman,
 | 
			
		||||
kääntää sitä sitten $1/2$ radiaania vastapäivään
 | 
			
		||||
ja laskee uuden kulman:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
P v = {4,2};
 | 
			
		||||
cout << arg(v) << "\n"; // 0.463648
 | 
			
		||||
v *= polar(1.0,0.5);
 | 
			
		||||
cout << arg(v) << "\n"; // 0.963648
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
\section{Pisteet ja suorat}
 | 
			
		||||
 | 
			
		||||
\index{ristitulo@ristitulo}
 | 
			
		||||
Vektorien
 | 
			
		||||
$a=(x_1,y_1)$ ja $b=(x_2,y_2)$ \key{ristitulo} $a \times b$
 | 
			
		||||
lasketaan kaavalla $x_1 y_2 - x_2 y_1$.
 | 
			
		||||
Ristitulo ilmaisee, mihin suuntaan vektori $b$
 | 
			
		||||
kääntyy, jos se laitetaan vektorin $a$ perään.
 | 
			
		||||
Positiivinen ristitulo tarkoittaa käännöstä vasemmalle,
 | 
			
		||||
negatiivinen käännöstä oikealle, ja nolla tarkoittaa,
 | 
			
		||||
että vektorit ovat samalla suoralla.
 | 
			
		||||
 | 
			
		||||
Seuraava kuva näyttää kolme esimerkkiä ristitulosta:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
 | 
			
		||||
\draw[->,thick] (0,0)--(4,2);
 | 
			
		||||
\draw[->,thick] (4,2)--(4+1,2+2);
 | 
			
		||||
 | 
			
		||||
\node at (2.5,0.5) {$a$};
 | 
			
		||||
\node at (5,2.5) {$b$};
 | 
			
		||||
 | 
			
		||||
\node at (3,-2) {$a \times b = 6$};
 | 
			
		||||
 | 
			
		||||
\draw[->,thick] (8+0,0)--(8+4,2);
 | 
			
		||||
\draw[->,thick] (8+4,2)--(8+4+2,2+1);
 | 
			
		||||
 | 
			
		||||
\node at (8+2.5,0.5) {$a$};
 | 
			
		||||
\node at (8+5,1.5) {$b$};
 | 
			
		||||
 | 
			
		||||
\node at (8+3,-2) {$a \times b = 0$};
 | 
			
		||||
 | 
			
		||||
\draw[->,thick] (16+0,0)--(16+4,2);
 | 
			
		||||
\draw[->,thick] (16+4,2)--(16+4+2,2-1);
 | 
			
		||||
 | 
			
		||||
\node at (16+2.5,0.5) {$a$};
 | 
			
		||||
\node at (16+5,2.5) {$b$};
 | 
			
		||||
 | 
			
		||||
\node at (16+3,-2) {$a \times b = -8$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\noindent
 | 
			
		||||
Esimerkiksi vasemmassa kuvassa
 | 
			
		||||
$a=(4,2)$ ja $b=(1,2)$.
 | 
			
		||||
Seuraava koodi laskee vastaavan ristitulon
 | 
			
		||||
luokkaa \texttt{complex} käyttäen:
 | 
			
		||||
 | 
			
		||||
\begin{lstlisting}
 | 
			
		||||
P a = {4,2};
 | 
			
		||||
P b = {1,2};
 | 
			
		||||
C r = (conj(a)*b).Y; // 6
 | 
			
		||||
\end{lstlisting}
 | 
			
		||||
 | 
			
		||||
Tämä perustuu siihen, että funktio \texttt{conj}
 | 
			
		||||
muuttaa vektorin y-koordinaatin käänteiseksi
 | 
			
		||||
ja kompleksilukujen kertolaskun seurauksena
 | 
			
		||||
vektorien $(x_1,-y_1)$ ja $(x_2,y_2)$
 | 
			
		||||
kertolaskun y-koordinaatti on $x_1 y_2 - x_2 y_1$.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pisteen sijainti suoraan nähden}
 | 
			
		||||
 | 
			
		||||
Ristitulon avulla voi selvittää,
 | 
			
		||||
kummalla puolella suoraa tutkittava piste sijaitsee.
 | 
			
		||||
Oletetaan, että suora kulkee pisteiden
 | 
			
		||||
$s_1$ ja $s_2$ kautta, katsontasuunta on
 | 
			
		||||
pisteestä $s_1$ pisteeseen $s_2$ ja
 | 
			
		||||
tutkittava piste on $p$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa piste $p$
 | 
			
		||||
on suoran vasemmalla puolella:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.45]
 | 
			
		||||
\draw[dashed,thick,->] (0,-3)--(12,6);
 | 
			
		||||
\draw[fill] (4,0) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (8,3) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (5,3) circle [radius=0.1];
 | 
			
		||||
\node at (4,-1) {$s_1$};
 | 
			
		||||
\node at (8,2) {$s_2$};
 | 
			
		||||
\node at (5,4) {$p$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Nyt ristitulo $(p-s_1) \times (p-s_2)$
 | 
			
		||||
kertoo, kummalla puolella suoraa piste $p$ sijaitsee.
 | 
			
		||||
Jos ristitulo on positiivinen,
 | 
			
		||||
piste $p$ on suoran vasemmalla puolella,
 | 
			
		||||
ja jos ristitulo on negatiivinen,
 | 
			
		||||
piste $p$ on suoran oikealla puolella.
 | 
			
		||||
Jos taas ristitulo on nolla,
 | 
			
		||||
piste $p$ on pisteiden $s_1$ ja $s_2$
 | 
			
		||||
kanssa suoralla.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Janojen leikkaaminen}
 | 
			
		||||
 | 
			
		||||
\index{leikkauspiste@leikkauspiste}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan tilannetta, jossa tasossa on kaksi
 | 
			
		||||
janaa $ab$ ja $cd$ ja tehtävänä on selvittää,
 | 
			
		||||
leikkaavatko janat. Mahdolliset tapaukset ovat seuraavat:
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 1:}
 | 
			
		||||
Janat ovat samalla suoralla ja ne sivuavat toisiaan.
 | 
			
		||||
Tällöin janoilla on ääretön määrä leikkauspisteitä.
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa janat leikkaavat
 | 
			
		||||
pisteestä $c$ pisteeseen $b$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\draw (1.5,1.5)--(6,3);
 | 
			
		||||
\draw (0,1)--(4.5,2.5);
 | 
			
		||||
\draw[fill] (0,1) circle [radius=0.05];
 | 
			
		||||
\node at (0,0.5) {$a$};
 | 
			
		||||
\draw[fill] (1.5,1.5) circle [radius=0.05];
 | 
			
		||||
\node at (6,2.5) {$d$};
 | 
			
		||||
\draw[fill] (4.5,2.5) circle [radius=0.05];
 | 
			
		||||
\node at (1.5,1) {$c$};
 | 
			
		||||
\draw[fill] (6,3) circle [radius=0.05];
 | 
			
		||||
\node at (4.5,2) {$b$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa ristitulon avulla voi tarkastaa,
 | 
			
		||||
ovatko kaikki pisteet samalla suoralla.
 | 
			
		||||
Tämän jälkeen riittää järjestää pisteet ja
 | 
			
		||||
tarkastaa, menevätkö janat toistensa päälle.
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 2:}
 | 
			
		||||
Janoilla on yhteinen päätepiste, joka on
 | 
			
		||||
ainoa leikkauspiste.
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa
 | 
			
		||||
janat leikkaavat pisteessä $b=c$:
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\draw (0,0)--(4,2);
 | 
			
		||||
\draw (4,2)--(6,1);
 | 
			
		||||
\draw[fill] (0,0) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (4,2) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (6,1) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (0,0.5) {$a$};
 | 
			
		||||
\node at (4,2.5) {$b=c$};
 | 
			
		||||
\node at (6,1.5) {$d$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tämä tapaus on helppoa tarkastaa,
 | 
			
		||||
koska mahdolliset vaihtoehdot
 | 
			
		||||
yhteiselle päätepisteelle ovat
 | 
			
		||||
$a=c$, $a=d$, $b=c$ ja $b=d$.
 | 
			
		||||
 | 
			
		||||
\textit{Tapaus 3:}
 | 
			
		||||
Janoilla on yksi leikkauspiste,
 | 
			
		||||
joka ei ole mikään janojen päätepisteistä.
 | 
			
		||||
Seuraavassa kuvassa leikkauspiste on $p$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.9]
 | 
			
		||||
\draw (0,1)--(6,3);
 | 
			
		||||
\draw (2,4)--(4,0);
 | 
			
		||||
\draw[fill] (0,1) circle [radius=0.05];
 | 
			
		||||
\node at (0,0.5) {$c$};
 | 
			
		||||
\draw[fill] (6,3) circle [radius=0.05];
 | 
			
		||||
\node at (6,2.5) {$d$};
 | 
			
		||||
\draw[fill] (2,4) circle [radius=0.05];
 | 
			
		||||
\node at (1.5,3.5) {$a$};
 | 
			
		||||
\draw[fill] (4,0) circle [radius=0.05];
 | 
			
		||||
\node at (4,-0.4) {$b$};
 | 
			
		||||
\draw[fill] (3,2) circle [radius=0.05];
 | 
			
		||||
\node at (3,1.5) {$p$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Tässä tapauksessa janat leikkaavat
 | 
			
		||||
tarkalleen silloin, kun samaan aikaan
 | 
			
		||||
pisteet $c$ ja $d$ ovat eri puolilla
 | 
			
		||||
$a$:sta $b$:hen kulkevaa suoraa
 | 
			
		||||
ja pisteet $a$ ja $b$
 | 
			
		||||
ovat eri puolilla 
 | 
			
		||||
$c$:stä $d$:hen kulkevaa suoraa.
 | 
			
		||||
Niinpä janojen leikkaamisen voi tarkastaa
 | 
			
		||||
ristitulon avulla.
 | 
			
		||||
 | 
			
		||||
% Janojen leikkauspiste $p$ selviää etsimällä
 | 
			
		||||
% parametrit $t$ ja $u$ niin, että
 | 
			
		||||
% 
 | 
			
		||||
% \[ p = a+t(b-a) = c+u(d-c). \]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pisteen etäisyys suorasta}
 | 
			
		||||
 | 
			
		||||
Kolmion pinta-ala voidaan lausua
 | 
			
		||||
ristitulon avulla
 | 
			
		||||
\[\frac{| (a-c) \times (b-c) |}{2},\]
 | 
			
		||||
missä $a$, $b$ ja $c$ ovat kolmion kärkipisteet.
 | 
			
		||||
 | 
			
		||||
Tämän kaavan avulla on mahdollista laskea,
 | 
			
		||||
kuinka kaukana annettu piste on suorasta.
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa $d$
 | 
			
		||||
on lyhin etäisyys pisteestä $p$ suoralle,
 | 
			
		||||
jonka määrittävät pisteet $s_1$ ja $s_2$:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.75]
 | 
			
		||||
\draw (-2,-1)--(6,3);
 | 
			
		||||
\draw[dashed] (1,4)--(2.40,1.2);
 | 
			
		||||
\node at (0,-0.5) {$s_1$};
 | 
			
		||||
\node at (4,1.5) {$s_2$};
 | 
			
		||||
\node at (0.5,4) {$p$};
 | 
			
		||||
\node at (2,2.7) {$d$};
 | 
			
		||||
\draw[fill] (0,0) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (4,2) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (1,4) circle [radius=0.05];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Pisteiden $s_1$, $s_2$ ja $p$ muodostaman kolmion
 | 
			
		||||
pinta-ala on toisaalta $\frac{1}{2} |s_2-s_1| d$ ja toisaalta
 | 
			
		||||
$\frac{1}{2} ((s_1-p) \times (s_2-p))$.
 | 
			
		||||
Niinpä haluttu etäisyys on
 | 
			
		||||
 | 
			
		||||
\[ d = \frac{(s_1-p) \times (s_2-p)}{|s_2-s_1|} .\]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
\subsubsection{Piste monikulmiossa}
 | 
			
		||||
 | 
			
		||||
Tarkastellaan sitten tehtävää, jossa
 | 
			
		||||
tulee selvittää, onko annettu piste
 | 
			
		||||
monikulmion sisäpuolella vai ulkopuolella.
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa piste $a$ on
 | 
			
		||||
sisäpuolella ja piste $b$ on 
 | 
			
		||||
ulkopuolella.
 | 
			
		||||
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.75]
 | 
			
		||||
%\draw (0,0)--(2,-2)--(3,1)--(5,1)--(2,3)--(1,2)--(-1,2)--(1,4)--(-2,4)--(-2,1)--(-3,3)--(-4,0)--(0,0);
 | 
			
		||||
\draw (0,0)--(2,2)--(5,1)--(2,3)--(1,2)--(-1,2)--(1,4)--(-2,4)--(-2,1)--(-3,3)--(-4,0)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (-3,1) circle [radius=0.05];
 | 
			
		||||
\node at (-3,0.5) {$a$};
 | 
			
		||||
\draw[fill] (1,3) circle [radius=0.05];
 | 
			
		||||
\node at (1,2.5) {$b$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Kätevä ratkaisu tehtävään
 | 
			
		||||
on lähettää pisteestä säde
 | 
			
		||||
satunnaiseen suuntaan ja laskea,
 | 
			
		||||
montako kertaa se osuu monikulmion reunaan.
 | 
			
		||||
Jos kertoja on pariton määrä,
 | 
			
		||||
niin piste on sisäpuolella,
 | 
			
		||||
ja jos taas kertoja on parillinen määrä,
 | 
			
		||||
niin piste on ulkopuolella.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Äskeisessä tilanteessa säteitä
 | 
			
		||||
voisi lähteä seuraavasti:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.75]
 | 
			
		||||
\draw (0,0)--(2,2)--(5,1)--(2,3)--(1,2)--(-1,2)--(1,4)--(-2,4)--(-2,1)--(-3,3)--(-4,0)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (-3,1) circle [radius=0.05];
 | 
			
		||||
\node at (-3,0.5) {$a$};
 | 
			
		||||
\draw[fill] (1,3) circle [radius=0.05];
 | 
			
		||||
\node at (1,2.5) {$b$};
 | 
			
		||||
 | 
			
		||||
\draw[dashed,->] (-3,1)--(-6,0);
 | 
			
		||||
\draw[dashed,->] (-3,1)--(0,5);
 | 
			
		||||
 | 
			
		||||
\draw[dashed,->] (1,3)--(3.5,0);
 | 
			
		||||
\draw[dashed,->] (1,3)--(3,4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Pisteestä $a$ lähtevät säteet osuvat 1 ja 3
 | 
			
		||||
kertaa monikulmion reunaan,
 | 
			
		||||
joten piste on sisäpuolella.
 | 
			
		||||
Vastaavasti pisteestä $b$ lähtevät
 | 
			
		||||
säteet osuvat 0 ja 2 kertaa monikulmion reunaan,
 | 
			
		||||
joten piste on ulkopuolella.
 | 
			
		||||
 | 
			
		||||
\section{Monikulmion pinta-ala}
 | 
			
		||||
 | 
			
		||||
Yleinen kaava monikulmion pinta-alan laskemiseen on
 | 
			
		||||
\[\frac{1}{2} |\sum_{i=1}^{n-1} (p_i \times p_{i+1})| =
 | 
			
		||||
\frac{1}{2} |\sum_{i=1}^{n-1} (x_i y_{i+1} - x_{i+1} y_i)|, \]
 | 
			
		||||
kun kärkipisteet ovat
 | 
			
		||||
$p_1=(x_1,y_1)$, $p_2=(x_2,y_2)$, $\ldots$, $p_n=(x_n,y_n)$
 | 
			
		||||
järjestettynä niin,
 | 
			
		||||
että $p_i$ ja $p_{i+1}$ ovat vierekkäiset kärkipisteet
 | 
			
		||||
monikulmion reunalla
 | 
			
		||||
ja ensimmäinen ja viimeinen kärkipiste ovat samat eli $p_1=p_n$.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi monikulmion
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\filldraw (4,1.4) circle (2pt);
 | 
			
		||||
\filldraw (7,3.4) circle (2pt);
 | 
			
		||||
\filldraw (5,5.4) circle (2pt);
 | 
			
		||||
\filldraw (2,4.4) circle (2pt);
 | 
			
		||||
\filldraw (4,3.4) circle (2pt);
 | 
			
		||||
\node (1) at (4,1) {(4,1)};
 | 
			
		||||
\node (2) at (7.2,3) {(7,3)};
 | 
			
		||||
\node (3) at (5,5.8) {(5,5)};
 | 
			
		||||
\node (4) at (2,4) {(2,4)};
 | 
			
		||||
\node (5) at (3.5,3) {(4,3)};
 | 
			
		||||
\path[draw] (4,1.4) -- (7,3.4) -- (5,5.4) -- (2,4.4) -- (4,3.4) -- (4,1.4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
pinta-ala on
 | 
			
		||||
\[\frac{|(2\cdot5-4\cdot5)+(5\cdot3-5\cdot7)+(7\cdot1-3\cdot4)+(4\cdot3-1\cdot4)+(4\cdot4-3\cdot2)|}{2} = 17/2.\]
 | 
			
		||||
Kaavassa on ideana käydä läpi puolisuunnikkaita,
 | 
			
		||||
joiden yläreuna on yksi monikulmion sivuista ja
 | 
			
		||||
alareuna on vaakataso. Esimerkiksi:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\path[draw,fill=lightgray] (5,5.4) -- (7,3.4) -- (7,0) -- (5,0) -- (5,5.4);
 | 
			
		||||
\filldraw (4,1.4) circle (2pt);
 | 
			
		||||
\filldraw (7,3.4) circle (2pt);
 | 
			
		||||
\filldraw (5,5.4) circle (2pt);
 | 
			
		||||
\filldraw (2,4.4) circle (2pt);
 | 
			
		||||
\filldraw (4,3.4) circle (2pt);
 | 
			
		||||
\node (1) at (4,1) {(4,1)};
 | 
			
		||||
\node (2) at (7.2,3) {(7,3)};
 | 
			
		||||
\node (3) at (5,5.8) {(5,5)};
 | 
			
		||||
\node (4) at (2,4) {(2,4)};
 | 
			
		||||
\node (5) at (3.5,3) {(4,3)};
 | 
			
		||||
\path[draw] (4,1.4) -- (7,3.4) -- (5,5.4) -- (2,4.4) -- (4,3.4) -- (4,1.4);
 | 
			
		||||
\draw (0,0) -- (10,0);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Tällaisen puolisuunnikkaan pinta-ala on
 | 
			
		||||
\[(x_{i+1}-x_{i}) \frac{y_i+y_{i+1}}{2},\]
 | 
			
		||||
kun kärkipisteet ovat $p_i$ ja $p_{i+1}$.
 | 
			
		||||
Jos $x_{i+1}>x_{i}$, niin pinta-ala on positiivinen,
 | 
			
		||||
ja jos $x_{i+1}<x_{i}$, niin pinta-ala on negatiivinen.
 | 
			
		||||
 | 
			
		||||
Monikulmion pinta-ala saadaan laskemalla yhteen kaikkien
 | 
			
		||||
tällaisten puolisuunnikkaiden pinta-alat, mistä tulee:
 | 
			
		||||
 | 
			
		||||
\[|\sum_{i=1}^{n-1} (x_{i+1}-x_{i}) \frac{y_i+y_{i+1}}{2}| =
 | 
			
		||||
\frac{1}{2} |\sum_{i=1}^{n-1} (x_i y_{i+1} - x_{i+1} y_i)|.\]
 | 
			
		||||
 | 
			
		||||
Huomaa, että pinta-alan kaavassa on itseisarvo,
 | 
			
		||||
koska monikulmion kiertosuunnasta (myötä- tai vastapäivään)
 | 
			
		||||
riippuen tuloksena oleva pinta-ala on joko
 | 
			
		||||
positiivinen tai negatiivinen.
 | 
			
		||||
 | 
			
		||||
\subsubsection{Pickin lause}
 | 
			
		||||
 | 
			
		||||
\index{Pickin lause@Pickin lause}
 | 
			
		||||
 | 
			
		||||
\key{Pickin lause} on vaihtoehtoinen tapa laskea
 | 
			
		||||
monikulmion pinta-ala,
 | 
			
		||||
kun kaikki monikulmion kärkipisteet
 | 
			
		||||
ovat kokonaislukupisteissä.
 | 
			
		||||
Pickin lauseen mukaan monikulmion pinta-ala on
 | 
			
		||||
\[ a + b/2 -1,\]
 | 
			
		||||
missä $a$ on kokonaislukupisteiden määrä monikulmion sisällä
 | 
			
		||||
ja $b$ on kokonaislukupisteiden määrä monikulmion reunalla.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi monikulmion
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\filldraw (4,1.4) circle (2pt);
 | 
			
		||||
\filldraw (7,3.4) circle (2pt);
 | 
			
		||||
\filldraw (5,5.4) circle (2pt);
 | 
			
		||||
\filldraw (2,4.4) circle (2pt);
 | 
			
		||||
\filldraw (4,3.4) circle (2pt);
 | 
			
		||||
\node (1) at (4,1) {(4,1)};
 | 
			
		||||
\node (2) at (7.2,3) {(7,3)};
 | 
			
		||||
\node (3) at (5,5.8) {(5,5)};
 | 
			
		||||
\node (4) at (2,4) {(2,4)};
 | 
			
		||||
\node (5) at (3.5,3) {(4,3)};
 | 
			
		||||
\path[draw] (4,1.4) -- (7,3.4) -- (5,5.4) -- (2,4.4) -- (4,3.4) -- (4,1.4);
 | 
			
		||||
 | 
			
		||||
\filldraw (2,4.4) circle (2pt);
 | 
			
		||||
\filldraw (3,4.4) circle (2pt);
 | 
			
		||||
\filldraw (4,4.4) circle (2pt);
 | 
			
		||||
\filldraw (5,4.4) circle (2pt);
 | 
			
		||||
\filldraw (6,4.4) circle (2pt);
 | 
			
		||||
 | 
			
		||||
\filldraw (4,3.4) circle (2pt);
 | 
			
		||||
\filldraw (5,3.4) circle (2pt);
 | 
			
		||||
\filldraw (6,3.4) circle (2pt);
 | 
			
		||||
\filldraw (7,3.4) circle (2pt);
 | 
			
		||||
 | 
			
		||||
\filldraw (4,2.4) circle (2pt);
 | 
			
		||||
\filldraw (5,2.4) circle (2pt);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
pinta-ala on $6+7/2-1=17/2$.
 | 
			
		||||
 | 
			
		||||
\section{Etäisyysmitat}
 | 
			
		||||
 | 
			
		||||
\index{etxisyysmitta@etäisyysmitta}
 | 
			
		||||
\index{Euklidinen etxisyys@Euklidinen etäisyys}
 | 
			
		||||
\index{Manhattan-etäisyys}
 | 
			
		||||
 | 
			
		||||
\key{Etäisyysmitta} määrittää tavan laskea kahden pisteen etäisyys.
 | 
			
		||||
Tavallisimmin geometriassa käytetty etäisyysmitta on
 | 
			
		||||
\key{euklidinen etäisyys}, jolloin pisteiden
 | 
			
		||||
$(x_1,y_1)$ ja $(x_2,y_2)$ etäisyys on
 | 
			
		||||
\[\sqrt{(x_2-x_1)^2+(y_2-y_1)^2}.\]
 | 
			
		||||
Vaihtoehtoinen etäisyysmitta on \key{Manhattan-etäisyys},
 | 
			
		||||
jota käyttäen pisteiden
 | 
			
		||||
$(x_1,y_1)$ ja $(x_2,y_2)$ etäisyys on
 | 
			
		||||
\[|x_1-x_2|+|y_1-y_2|.\]
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi kuvaparissa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
 | 
			
		||||
\draw[fill] (2,1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (5,2) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (2,0.5) {$(2,1)$};
 | 
			
		||||
\node at (5,1.5) {$(5,2)$};
 | 
			
		||||
 | 
			
		||||
\draw[dashed] (2,1) -- (5,2);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (5+2,1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (5+5,2) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (5+2,0.5) {$(2,1)$};
 | 
			
		||||
\node at (5+5,1.5) {$(5,2)$};
 | 
			
		||||
 | 
			
		||||
\draw[dashed] (5+2,1) -- (5+2,2);
 | 
			
		||||
\draw[dashed] (5+2,2) -- (5+5,2);
 | 
			
		||||
 | 
			
		||||
\node at (3.5,-0.5) {euklidinen etäisyys};
 | 
			
		||||
\node at (5+3.5,-0.5) {Manhattan-etäisyys};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
pisteiden euklidinen etäisyys on
 | 
			
		||||
\[\sqrt{(5-2)^2+(2-1)^2}=\sqrt{10}\]
 | 
			
		||||
ja pisteiden Manhattan-etäisyys on
 | 
			
		||||
\[|5-2|+|2-1|=4.\]
 | 
			
		||||
Seuraava kuvapari näyttää alueen, joka on pisteestä etäisyyden 1
 | 
			
		||||
sisällä käyttäen euklidista ja Manhattan-etäisyyttä:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
 | 
			
		||||
\draw[fill=gray!20] (0,0) circle [radius=1];
 | 
			
		||||
\draw[fill] (0,0) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (0,-1.5) {euklidinen etäisyys};
 | 
			
		||||
 | 
			
		||||
\draw[fill=gray!20] (5+0,1) -- (5-1,0) -- (5+0,-1) -- (5+1,0) -- (5+0,1);
 | 
			
		||||
\draw[fill] (5,0) circle [radius=0.05];
 | 
			
		||||
\node at (5,-1.5) {Manhattan-etäisyys};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\subsubsection{Kaukaisimmat pisteet}
 | 
			
		||||
 | 
			
		||||
Joidenkin ongelmien ratkaiseminen on helpompaa, jos käytössä on
 | 
			
		||||
Manhattan-etäisyys euklidisen etäisyyden sijasta.
 | 
			
		||||
Tarkastellaan esimerkkinä tehtävää, jossa annettuna
 | 
			
		||||
on $n$ pistettä $(x_1,y_1),(x_2,y_2),\ldots,(x_n,y_n)$
 | 
			
		||||
ja tehtävänä on laskea, mikä on suurin mahdollinen etäisyys
 | 
			
		||||
kahden pisteen välillä.
 | 
			
		||||
 | 
			
		||||
Tämän tehtävän ratkaiseminen tehokkaasti on vaikeaa,
 | 
			
		||||
jos laskettavana on euklidinen etäisyys.
 | 
			
		||||
Sen sijaan suurin Manhattan-etäisyys on helppoa selvittää,
 | 
			
		||||
koska se on suurempi etäisyyksistä
 | 
			
		||||
\[\max A - \min A \hspace{20px} \textrm{ja} \hspace{20px} \max B - \min B,\]
 | 
			
		||||
missä
 | 
			
		||||
\[A = \{x_i+y_i : i = 1,2,\ldots,n\}\]
 | 
			
		||||
ja
 | 
			
		||||
\[B = \{x_i-y_i : i = 1,2,\ldots,n\}.\]
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Tämä johtuu siitä, että Manhattan-etäisyys
 | 
			
		||||
\[|x_a-x_b|+|y_a-y_b]\]
 | 
			
		||||
voidaan ilmaista muodossa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{cl}
 | 
			
		||||
& $\max(x_a-x_b+y_a-y_b,\,x_a-x_b-y_a+y_b)$ \\
 | 
			
		||||
$=$ & $\max(x_a+y_a-(x_b+y_b),\,x_a-y_a-(x_b-y_b))$
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
olettaen, että $x_a \ge x_b$.
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
\subsubsection{Koordinaatiston kääntäminen}
 | 
			
		||||
 | 
			
		||||
Kätevä tekniikka Manhattan-etäisyyden yhteydessä on myös
 | 
			
		||||
kääntää koordinaatistoa 45 astetta
 | 
			
		||||
niin, että pisteestä $(x,y)$ tulee piste $(a(x+y),a(y-x))$,
 | 
			
		||||
missä $a=1/\sqrt{2}$.
 | 
			
		||||
Kerroin $a$ on valittu niin, että
 | 
			
		||||
pisteiden etäisyydet säilyvät samana käännöksen jälkeen.
 | 
			
		||||
 | 
			
		||||
Tämän seurauksena pisteestä etäisyydellä $d$ oleva alue
 | 
			
		||||
on neliö, jonka sivut ovat vaaka- ja pystysuuntaisia,
 | 
			
		||||
mikä helpottaa alueen käsittelyä.
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}
 | 
			
		||||
\draw[fill=gray!20] (0,1) -- (-1,0) -- (0,-1) -- (1,0) -- (0,1);
 | 
			
		||||
\draw[fill] (0,0) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (2.5,0) {$\Rightarrow$};
 | 
			
		||||
 | 
			
		||||
\draw[fill=gray!20] (5-0.71,0.71) -- (5-0.71,-0.71) -- (5+0.71,-0.71) -- (5+0.71,0.71) -- (5-0.71,0.71);
 | 
			
		||||
\draw[fill] (5,0) circle [radius=0.05];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,856 @@
 | 
			
		|||
\chapter{Sweep line}
 | 
			
		||||
 | 
			
		||||
\index{pyyhkxisyviiva@pyyhkäisyviiva}
 | 
			
		||||
 | 
			
		||||
\key{Pyyhkäisyviiva} on tason halki kulkeva viiva,
 | 
			
		||||
jonka avulla voi ratkaista useita geometrisia tehtäviä.
 | 
			
		||||
Ideana on esittää tehtävä joukkona tapahtumia,
 | 
			
		||||
jotka vastaavat tason pisteitä.
 | 
			
		||||
Kun pyyhkäisyviiva törmää pisteeseen,
 | 
			
		||||
tapahtuma käsitellään ja tehtävän ratkaisu edistyy.
 | 
			
		||||
 | 
			
		||||
Tarkastellaan esimerkkinä tekniikan
 | 
			
		||||
käyttämisestä tehtävää,
 | 
			
		||||
jossa yrityksessä on töissä $n$ henkilöä
 | 
			
		||||
ja jokaisesta henkilöstä tiedetään,
 | 
			
		||||
milloin hän tuli töihin ja lähti töistä
 | 
			
		||||
tiettynä päivänä.
 | 
			
		||||
Tehtävänä on laskea,
 | 
			
		||||
mikä on suurin määrä henkilöitä,
 | 
			
		||||
jotka olivat samaan aikaan töissä.
 | 
			
		||||
 | 
			
		||||
Tehtävän voi ratkaista mallintamalla tilanteen
 | 
			
		||||
niin, että jokaista henkilöä vastaa kaksi tapahtumaa:
 | 
			
		||||
tuloaika töihin ja lähtöaika töistä.
 | 
			
		||||
Pyyhkäisyviiva käy läpi tapahtumat aikajärjestyksessä
 | 
			
		||||
ja pitää kirjaa, montako henkilöä oli töissä milloinkin.
 | 
			
		||||
Esimerkiksi tilannetta
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tabular}{ccc}
 | 
			
		||||
henkilö & tuloaika & lähtöaika \\
 | 
			
		||||
\hline
 | 
			
		||||
Uolevi & 10 & 15 \\
 | 
			
		||||
Maija & 6 & 12 \\
 | 
			
		||||
Kaaleppi & 14 & 16 \\
 | 
			
		||||
Liisa & 5 & 13 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\end{center}
 | 
			
		||||
vastaavat seuraavat tapahtumat:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.6]
 | 
			
		||||
\draw (0,0) rectangle (17,-6.5);
 | 
			
		||||
\path[draw,thick,-] (10,-1) -- (15,-1);
 | 
			
		||||
\path[draw,thick,-] (6,-2.5) -- (12,-2.5);
 | 
			
		||||
\path[draw,thick,-] (14,-4) -- (16,-4);
 | 
			
		||||
\path[draw,thick,-] (5,-5.5) -- (13,-5.5);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (10,-1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (15,-1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (6,-2.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (12,-2.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (14,-4) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (16,-4) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (5,-5.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (13,-5.5) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (2,-1) {Uolevi};
 | 
			
		||||
\node at (2,-2.5) {Maija};
 | 
			
		||||
\node at (2,-4) {Kaaleppi};
 | 
			
		||||
\node at (2,-5.5) {Liisa};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Pyyhkäisyviiva käy läpi tapahtumat vasemmalta oikealle
 | 
			
		||||
ja pitää yllä laskuria.
 | 
			
		||||
Aina kun henkilö tulee töihin, laskurin arvo
 | 
			
		||||
kasvaa yhdellä, ja kun henkilö lähtee töistä,
 | 
			
		||||
laskurin arvo vähenee yhdellä.
 | 
			
		||||
Tehtävän ratkaisu on suurin laskuri arvo
 | 
			
		||||
pyyhkäisyviivan kulun aikana.
 | 
			
		||||
 | 
			
		||||
Pyyhkäisyviiva kulkee seuraavasti tason halki:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.6]
 | 
			
		||||
\path[draw,thick,->] (0.5,0.5) -- (16.5,0.5);
 | 
			
		||||
\draw (0,0) rectangle (17,-6.5);
 | 
			
		||||
\path[draw,thick,-] (10,-1) -- (15,-1);
 | 
			
		||||
\path[draw,thick,-] (6,-2.5) -- (12,-2.5);
 | 
			
		||||
\path[draw,thick,-] (14,-4) -- (16,-4);
 | 
			
		||||
\path[draw,thick,-] (5,-5.5) -- (13,-5.5);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (10,-1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (15,-1) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (6,-2.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (12,-2.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (14,-4) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (16,-4) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (5,-5.5) circle [radius=0.05];
 | 
			
		||||
\draw[fill] (13,-5.5) circle [radius=0.05];
 | 
			
		||||
 | 
			
		||||
\node at (2,-1) {Uolevi};
 | 
			
		||||
\node at (2,-2.5) {Maija};
 | 
			
		||||
\node at (2,-4) {Kaaleppi};
 | 
			
		||||
\node at (2,-5.5) {Liisa};
 | 
			
		||||
 | 
			
		||||
\path[draw,dashed] (10,0)--(10,-6.5);
 | 
			
		||||
\path[draw,dashed] (15,0)--(15,-6.5);
 | 
			
		||||
\path[draw,dashed] (6,0)--(6,-6.5);
 | 
			
		||||
\path[draw,dashed] (12,0)--(12,-6.5);
 | 
			
		||||
\path[draw,dashed] (14,0)--(14,-6.5);
 | 
			
		||||
\path[draw,dashed] (16,0)--(16,-6.5);
 | 
			
		||||
\path[draw,dashed] (5,0)--(5,-6.5);
 | 
			
		||||
\path[draw,dashed] (13,0)--(13,-6.5);
 | 
			
		||||
 | 
			
		||||
\node at (10,-7) {$+$};
 | 
			
		||||
\node at (15,-7) {$-$};
 | 
			
		||||
\node at (6,-7) {$+$};
 | 
			
		||||
\node at (12,-7) {$-$};
 | 
			
		||||
\node at (14,-7) {$+$};
 | 
			
		||||
\node at (16,-7) {$-$};
 | 
			
		||||
\node at (5,-7) {$+$};
 | 
			
		||||
\node at (13,-7) {$-$};
 | 
			
		||||
 | 
			
		||||
\node at (10,-8) {$3$};
 | 
			
		||||
\node at (15,-8) {$1$};
 | 
			
		||||
\node at (6,-8) {$2$};
 | 
			
		||||
\node at (12,-8) {$2$};
 | 
			
		||||
\node at (14,-8) {$2$};
 | 
			
		||||
\node at (16,-8) {$0$};
 | 
			
		||||
\node at (5,-8) {$1$};
 | 
			
		||||
\node at (13,-8) {$1$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
Kuvan alareunan merkinnät $+$ ja $-$
 | 
			
		||||
tarkoittavat, että laskurin arvo kasvaa
 | 
			
		||||
ja vähenee yhdellä.
 | 
			
		||||
Niiden alapuolella on laskurin uusi arvo.
 | 
			
		||||
Laskurin suurin arvo 3 on voimassa 
 | 
			
		||||
Uolevi tulohetken ja Maijan lähtöhetken välillä.
 | 
			
		||||
 | 
			
		||||
Ratkaisun aikavaativuus on $O(n \log n)$,
 | 
			
		||||
koska tapahtumien järjestäminen vie aikaa $O(n \log n)$
 | 
			
		||||
ja pyyhkäisyviivan läpikäynti vie aikaa $O(n)$.
 | 
			
		||||
 | 
			
		||||
\section{Janojen leikkauspisteet}
 | 
			
		||||
 | 
			
		||||
\index{leikkauspiste@leikkauspiste}
 | 
			
		||||
 | 
			
		||||
Annettuna on $n$ janaa, joista jokainen
 | 
			
		||||
on vaaka- tai pystysuuntainen.
 | 
			
		||||
Tehtävänä on laskea tehokkaasti, monessako pisteessä
 | 
			
		||||
kaksi janaa leikkaavat toisensa.
 | 
			
		||||
Esimerkiksi tilanteessa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\path[draw,thick,-] (0,2) -- (5,2);
 | 
			
		||||
\path[draw,thick,-] (1,4) -- (6,4);
 | 
			
		||||
\path[draw,thick,-] (6,3) -- (10,3);
 | 
			
		||||
\path[draw,thick,-] (2,1) -- (2,6);
 | 
			
		||||
\path[draw,thick,-] (8,2) -- (8,5);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
leikkauspisteitä on kolme:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.5]
 | 
			
		||||
\path[draw,thick,-] (0,2) -- (5,2);
 | 
			
		||||
\path[draw,thick,-] (1,4) -- (6,4);
 | 
			
		||||
\path[draw,thick,-] (6,3) -- (10,3);
 | 
			
		||||
\path[draw,thick,-] (2,1) -- (2,6);
 | 
			
		||||
\path[draw,thick,-] (8,2) -- (8,5);
 | 
			
		||||
 | 
			
		||||
\draw[fill] (2,2) circle [radius=0.15];
 | 
			
		||||
\draw[fill] (2,4) circle [radius=0.15];
 | 
			
		||||
\draw[fill] (8,3) circle [radius=0.15];
 | 
			
		||||
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Tehtävä on helppoa ratkaista ajassa $O(n^2)$,
 | 
			
		||||
koska riittää käydä läpi kaikki mahdolliset janaparit
 | 
			
		||||
ja tarkistaa, moniko leikkaa toisiaan.
 | 
			
		||||
Seuraavaksi ratkaisemme tehtävän
 | 
			
		||||
ajassa $O(n \log n)$ pyyhkäisyviivan avulla.
 | 
			
		||||
 | 
			
		||||
Ideana on luoda janoista kolmenlaisia tapahtumia:
 | 
			
		||||
\begin{enumerate}[noitemsep]
 | 
			
		||||
\item[(1)] vaakajana alkaa
 | 
			
		||||
\item[(2)] vaakajana päättyy
 | 
			
		||||
\item[(3)] pystyjana
 | 
			
		||||
\end{enumerate}
 | 
			
		||||
 | 
			
		||||
Esimerkkitilannetta vastaava pistejoukko on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.6]
 | 
			
		||||
\path[draw,dashed] (0,2) -- (5,2);
 | 
			
		||||
\path[draw,dashed] (1,4) -- (6,4);
 | 
			
		||||
\path[draw,dashed] (6,3) -- (10,3);
 | 
			
		||||
\path[draw,dashed] (2,1) -- (2,6);
 | 
			
		||||
\path[draw,dashed] (8,2) -- (8,5);
 | 
			
		||||
 | 
			
		||||
\node at (0,2) {$1$};
 | 
			
		||||
\node at (5,2) {$2$};
 | 
			
		||||
\node at (1,4) {$1$};
 | 
			
		||||
\node at (6,4) {$2$};
 | 
			
		||||
\node at (6,3) {$1$};
 | 
			
		||||
\node at (10,3) {$2$};
 | 
			
		||||
 | 
			
		||||
\node at (2,3.5) {$3$};
 | 
			
		||||
\node at (8,3.5) {$3$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmi käy läpi pisteet vasemmalta oikealle
 | 
			
		||||
ja pitää yllä tietorakennetta y-koordinaateista,
 | 
			
		||||
joissa on tällä hetkellä aktiivinen vaakajana.
 | 
			
		||||
Tapahtuman 1 kohdalla vaakajanan y-koordinaatti
 | 
			
		||||
lisätään joukkoon ja tapahtuman 2 kohdalla
 | 
			
		||||
vaakajanan y-koordinaatti poistetaan joukosta.
 | 
			
		||||
 | 
			
		||||
Algoritmi laskee janojen leikkauspisteet
 | 
			
		||||
tapahtumien 3 kohdalla.
 | 
			
		||||
Kun pystyjana kulkee y-koordinaattien
 | 
			
		||||
$y_1 \ldots y_2$ välillä,
 | 
			
		||||
algoritmi laskee tietorakenteesta,
 | 
			
		||||
monessako vaakajanassa on y-koordinaatti
 | 
			
		||||
välillä $y_1 \ldots y_2$ ja kasvattaa
 | 
			
		||||
leikkauspisteiden määrää tällä arvolla.
 | 
			
		||||
 | 
			
		||||
Sopiva tietorakenne vaakajanojen y-koordinaattien
 | 
			
		||||
tallentamiseen on bi\-nää\-ri-indeksipuu tai segmenttipuu,
 | 
			
		||||
johon on tarvittaessa yhdistetty indeksien pakkaus.
 | 
			
		||||
Tällöin jokaisen pisteen käsittely
 | 
			
		||||
vie aikaa $O(\log n)$, joten algoritmin
 | 
			
		||||
kokonaisaikavaativuus on $O(n \log n)$.
 | 
			
		||||
 | 
			
		||||
\section{Lähin pistepari}
 | 
			
		||||
 | 
			
		||||
\index{lzhin pistepari@lähin pistepari}
 | 
			
		||||
 | 
			
		||||
Seuraava tehtävämme on etsiä $n$ pisteen
 | 
			
		||||
joukosta kaksi pistettä, jotka ovat
 | 
			
		||||
mahdollisimman lähellä toisiaan.
 | 
			
		||||
Esimerkiksi tilanteessa
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0)--(12,0)--(12,4)--(0,4)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw (1,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,1) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (6,2.5) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (9,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (10,2) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,3.5) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,1) circle [radius=0.1];
 | 
			
		||||
\draw (2.5,3) circle [radius=0.1];
 | 
			
		||||
\draw (4.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (5.25,0.5) circle [radius=0.1];
 | 
			
		||||
\draw (6.5,2) circle [radius=0.1];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
lähin pistepari on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0)--(12,0)--(12,4)--(0,4)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw (1,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,1) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (6,2.5) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (9,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (10,2) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,3.5) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,1) circle [radius=0.1];
 | 
			
		||||
\draw (2.5,3) circle [radius=0.1];
 | 
			
		||||
\draw (4.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (5.25,0.5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (6.5,2) circle [radius=0.1];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
 | 
			
		||||
Tämäkin tehtävä ratkeaa
 | 
			
		||||
$O(n \log n)$-ajassa pyyhkäisyviivan avulla.
 | 
			
		||||
Algoritmi käy pisteet läpi vasemmalta oikealle
 | 
			
		||||
ja pitää yllä arvoa $d$,
 | 
			
		||||
joka on pienin kahden
 | 
			
		||||
pisteen etäisyys.
 | 
			
		||||
Kunkin pisteen kohdalla algoritmi
 | 
			
		||||
etsii lähimmän toisen pisteen vasemmalta.
 | 
			
		||||
Jos etäisyys tähän pisteeseen on alle $d$,
 | 
			
		||||
tämä on uusi pienin kahden pisteen etäisyys
 | 
			
		||||
ja algoritmi päivittää $d$:n arvon.
 | 
			
		||||
 | 
			
		||||
Jos käsiteltävä piste on $(x,y)$
 | 
			
		||||
ja jokin vasemmalla oleva piste on
 | 
			
		||||
alle $d$:n etäisyydellä,
 | 
			
		||||
sen x-koordinaatin
 | 
			
		||||
tulee olla välillä $[x-d,x]$
 | 
			
		||||
ja y-koordinaatin tulee olla välillä $[y-d,y+d]$.
 | 
			
		||||
Algoritmin riittää siis tarkistaa
 | 
			
		||||
ainoastaan pisteet, jotka osuvat tälle välille,
 | 
			
		||||
mikä tehostaa hakua merkittävästi.
 | 
			
		||||
 | 
			
		||||
Esimerkiksi seuraavassa kuvassa
 | 
			
		||||
katkoviiva-alue sisältää pisteet,
 | 
			
		||||
jotka voivat olla alle $d$:n etäisyydellä
 | 
			
		||||
tummennetusta pisteestä.
 | 
			
		||||
\\
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0)--(12,0)--(12,4)--(0,4)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw (1,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,1) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (6,2.5) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (9,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (10,2) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,3.5) circle [radius=0.1];
 | 
			
		||||
\draw (1.5,1) circle [radius=0.1];
 | 
			
		||||
\draw (2.5,3) circle [radius=0.1];
 | 
			
		||||
\draw (4.5,1.5) circle [radius=0.1];
 | 
			
		||||
\draw (5.25,0.5) circle [radius=0.1];
 | 
			
		||||
\draw[fill] (6.5,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw[dashed] (6.5,0.75)--(6.5,3.25);
 | 
			
		||||
\draw[dashed] (5.25,0.75)--(5.25,3.25);
 | 
			
		||||
\draw[dashed] (5.25,0.75)--(6.5,0.75);
 | 
			
		||||
\draw[dashed] (5.25,3.25)--(6.5,3.25);
 | 
			
		||||
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (5.25,3.5) -- (6.5,3.5);
 | 
			
		||||
\node at (5.875,4) {$d$};
 | 
			
		||||
\draw [decoration={brace}, decorate, line width=0.3mm] (6.75,3.25) -- (6.75,2);
 | 
			
		||||
\node at (7.25,2.625) {$d$};
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
Algoritmin tehokkuus perustuu siihen,
 | 
			
		||||
että $d$:n rajoittamalla alueella
 | 
			
		||||
on aina vain $O(1)$ pistettä.
 | 
			
		||||
Nämä pisteet pystyy käymään läpi
 | 
			
		||||
$O(\log n)$-aikaisesti
 | 
			
		||||
pitämällä algoritmin aikana yllä joukkoa pisteistä,
 | 
			
		||||
joiden x-koordinaatti on välillä $[x-d,x]$
 | 
			
		||||
ja jotka on järjestetty y-koordinaatin mukaan.
 | 
			
		||||
 | 
			
		||||
Algoritmin aikavaativuus on $O(n \log n)$,
 | 
			
		||||
koska se käy läpi $n$ pistettä
 | 
			
		||||
ja etsii jokaiselle lähimmän
 | 
			
		||||
edeltävän pisteen ajassa $O(\log n)$.
 | 
			
		||||
 | 
			
		||||
\section{Konveksi peite}
 | 
			
		||||
 | 
			
		||||
\key{Konveksi peite}
 | 
			
		||||
on pienin konveksi monikulmio,
 | 
			
		||||
joka ympäröi kaikki pistejoukon pisteet.
 | 
			
		||||
Konveksius tarkoittaa,
 | 
			
		||||
että minkä tahansa kahden kärkipisteen välinen jana
 | 
			
		||||
kulkee monikulmion sisällä.
 | 
			
		||||
Hyvä mielikuva asiasta on,
 | 
			
		||||
että pistejoukko ympäröidään tiukasti
 | 
			
		||||
viritetyllä narulla.
 | 
			
		||||
 | 
			
		||||
\begin{samepage}
 | 
			
		||||
Esimerkiksi pistejoukon
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
\end{samepage}
 | 
			
		||||
konveksi peite on seuraava:
 | 
			
		||||
\begin{center}
 | 
			
		||||
\begin{tikzpicture}[scale=0.7]
 | 
			
		||||
\draw (0,0)--(4,-1)--(7,1)--(6,3)--(2,4)--(0,2)--(0,0);
 | 
			
		||||
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\end{center}
 | 
			
		||||
 | 
			
		||||
\index{Andrew'n algoritmi}
 | 
			
		||||
 | 
			
		||||
Tehokas ja helposti toteutettava menetelmä
 | 
			
		||||
konveksin peitteen muodostamiseen
 | 
			
		||||
on \key{Andrew'n algoritmi},
 | 
			
		||||
jonka aikavaativuus on $O(n \log n)$.
 | 
			
		||||
Algoritmi muodostaa konveksin peitteen kahdessa
 | 
			
		||||
osassa: ensin peitteen yläosan ja sitten peitteen alaosan.
 | 
			
		||||
Kummankin osan muodostaminen tapahtuu samalla tavalla,
 | 
			
		||||
minkä vuoksi voimme keskittyä yläosan muodostamiseen.
 | 
			
		||||
 | 
			
		||||
Algoritmi järjestää ensin pisteet ensisijaisesti x-koordinaatin
 | 
			
		||||
ja toissijaisesti y-koordinaatin mukaan.
 | 
			
		||||
Tämän jälkeen se käy pisteet läpi järjestyksessä
 | 
			
		||||
ja lisää aina uuden pisteen osaksi peitettä.
 | 
			
		||||
Aina pisteen lisäämisen jälkeen algoritmi tarkastaa
 | 
			
		||||
ristitulon avulla,
 | 
			
		||||
muodostavatko kolme viimeistä pistettä peitteessä
 | 
			
		||||
vasemmalle kääntyvän osan.
 | 
			
		||||
Jos näin on,
 | 
			
		||||
algoritmi poistaa näistä keskimmäisen pisteen.
 | 
			
		||||
Tämän jälkeen algoritmi tarkastaa uudestaan
 | 
			
		||||
kolme viimeistä pistettä ja poistaa taas tarvittaessa
 | 
			
		||||
keskimmäisen pisteen.
 | 
			
		||||
Sama jatkuu, kunnes kolme viimeistä pistettä
 | 
			
		||||
eivät muodosta vasemmalle kääntyvää osaa.
 | 
			
		||||
 | 
			
		||||
Seuraava kuvasarja esittää Andrew'n algoritmin toimintaa:
 | 
			
		||||
\\
 | 
			
		||||
\begin{tabular}{ccccccc}
 | 
			
		||||
\\
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(1,1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(1,1)--(2,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\\
 | 
			
		||||
1 & & 2 & & 3 & & 4 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\\
 | 
			
		||||
\begin{tabular}{ccccccc}
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,2)--(2,4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2)--(4,-1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\\
 | 
			
		||||
5 & & 6 & & 7 & & 8 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\\
 | 
			
		||||
\begin{tabular}{ccccccc}
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2)--(4,-1)--(4,0);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2)--(4,0);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2)--(4,0)--(4,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(3,2)--(4,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\\
 | 
			
		||||
9 & & 10 & & 11 & & 12 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\\
 | 
			
		||||
\begin{tabular}{ccccccc}
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3)--(5,2);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3)--(5,2)--(6,1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3)--(5,2)--(6,1)--(6,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\\
 | 
			
		||||
13 & & 14 & & 15 & & 16 \\
 | 
			
		||||
\end{tabular}
 | 
			
		||||
\\
 | 
			
		||||
\begin{tabular}{ccccccc}
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3)--(5,2)--(6,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(4,3)--(6,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(6,3);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
& \hspace{0.1cm} &
 | 
			
		||||
\begin{tikzpicture}[scale=0.3]
 | 
			
		||||
\draw (-1,-2)--(8,-2)--(8,5)--(-1,5)--(-1,-2);
 | 
			
		||||
\draw (0,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,-1) circle [radius=0.1];
 | 
			
		||||
\draw (7,1) circle [radius=0.1];
 | 
			
		||||
\draw (6,3) circle [radius=0.1];
 | 
			
		||||
\draw (2,4) circle [radius=0.1];
 | 
			
		||||
\draw (0,2) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (1,1) circle [radius=0.1];
 | 
			
		||||
\draw (2,2) circle [radius=0.1];
 | 
			
		||||
\draw (3,2) circle [radius=0.1];
 | 
			
		||||
\draw (4,0) circle [radius=0.1];
 | 
			
		||||
\draw (4,3) circle [radius=0.1];
 | 
			
		||||
\draw (5,2) circle [radius=0.1];
 | 
			
		||||
\draw (6,1) circle [radius=0.1];
 | 
			
		||||
 | 
			
		||||
\draw (0,0)--(0,2)--(2,4)--(6,3)--(7,1);
 | 
			
		||||
\end{tikzpicture}
 | 
			
		||||
\\
 | 
			
		||||
17 & & 18 & & 19 & & 20
 | 
			
		||||
\end{tabular}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue