mirror of
https://github.com/janishutz/eth-summaries.git
synced 2025-11-25 02:24:23 +00:00
Add A&D summary. Still WIP for fixing errors
This commit is contained in:
BIN
algorithms-and-datastructures/ad.pdf
Normal file
BIN
algorithms-and-datastructures/ad.pdf
Normal file
Binary file not shown.
89
algorithms-and-datastructures/ad.tex
Executable file
89
algorithms-and-datastructures/ad.tex
Executable file
@@ -0,0 +1,89 @@
|
||||
\documentclass{article}
|
||||
|
||||
\newcommand{\dir}{~/projects/latex}
|
||||
\input{\dir/include.tex}
|
||||
\load{recommended}
|
||||
|
||||
\setup{Algorithms and Data structures}
|
||||
|
||||
\begin{document}
|
||||
\startDocument
|
||||
\usetcolorboxes
|
||||
|
||||
|
||||
% ── Title page ──────────────────────────────────────────────────────
|
||||
\vspace{3cm}
|
||||
|
||||
\begin{Huge}
|
||||
\[
|
||||
\Omega(n) \leq \Theta(n \cdot \log(n)) \leq O(n^2)
|
||||
\]
|
||||
\end{Huge}
|
||||
|
||||
\vspace{6cm}
|
||||
\begin{center}
|
||||
\begin{Large}
|
||||
``\textit{Jetzt bleibt natürlich nur noch die Frage: Geht es besser??}''
|
||||
\end{Large}
|
||||
|
||||
\hspace{4cm} - David Steurer, 2024
|
||||
\end{center}
|
||||
|
||||
\vspace{2cm}
|
||||
\begin{center}
|
||||
HS2024, ETHZ\\[0.2cm]
|
||||
\begin{Large}
|
||||
\textsc{Summary of all algorithms discussed}
|
||||
\end{Large}\\[0.2cm]
|
||||
\end{center}
|
||||
|
||||
|
||||
% ── Table of Contents ───────────────────────────────────────────────
|
||||
\newpage
|
||||
|
||||
\printtoc{Aquamarine}
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
% ╭────────────────────────────────────────────────╮
|
||||
% │ Import parts │
|
||||
% ╰────────────────────────────────────────────────╯
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\input{parts/intro}
|
||||
|
||||
% Search and Sort
|
||||
\input{parts/search/search.tex}
|
||||
\input{parts/search/comparison-based-sort.tex}
|
||||
\input{parts/search/quick-sort.tex}
|
||||
\input{parts/search/heap-sort.tex}
|
||||
|
||||
% Datatypes
|
||||
\input{parts/datatypes/lists-dictionaries.tex}
|
||||
\input{parts/datatypes/binary-tree.tex}
|
||||
\input{parts/datatypes/avl-tree.tex}
|
||||
|
||||
% Dynamic Programming
|
||||
\input{parts/dp.tex}
|
||||
|
||||
% Graph theory
|
||||
\input{parts/graphs/intro.tex}
|
||||
\input{parts/graphs/paths-walks-cycles.tex}
|
||||
\input{parts/graphs/connected-components.tex}
|
||||
\input{parts/graphs/bfs-dfs.tex}
|
||||
\input{parts/graphs/shortest-path.tex}
|
||||
\input{parts/graphs/mst/prim.tex}
|
||||
\input{parts/graphs/mst/kruskal.tex}
|
||||
\input{parts/graphs/mst/boruvka.tex}
|
||||
\input{parts/graphs/all-pairs-sp.tex}
|
||||
\input{parts/graphs/matrix.tex}
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
% ── Authors ─────────────────────────────────────────────────────────
|
||||
\newpage
|
||||
\section{Authors}
|
||||
Created by Janis Hutz (\url{https://janishutz.com}).
|
||||
|
||||
Some examples were taken from the script, others were AI-Generated (because it's a bit of a pain to draw trees by hand using Tikz)
|
||||
|
||||
\end{document}
|
||||
225
algorithms-and-datastructures/parts/datatypes/avl-tree.tex
Normal file
225
algorithms-and-datastructures/parts/datatypes/avl-tree.tex
Normal file
@@ -0,0 +1,225 @@
|
||||
\newpage
|
||||
\subsection{AVL Tree}
|
||||
\begin{definition}[]{AVL Trees}
|
||||
An AVL Tree is a self-balancing binary search tree in which the difference in heights of the left and right subtrees (called the balance factor) of any node is at most 1. Named after its inventors, Adelson-Velsky and Landis, it ensures logarithmic height for efficient operations. The operations in core work the same as in Binary Trees, but might require rebalancing of the tree after a BST operation is performed on the tree
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics}
|
||||
\begin{itemize}
|
||||
\item \textbf{Balance Factor:} For any node, $\text{Balance Factor} = \text{Height of Left Subtree} - \text{Height of Right Subtree}$. The balance factor is always $-1, 0, \text{or } 1$.
|
||||
\item \textbf{Rotations:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Single Rotation:} Left or right rotation to restore balance.
|
||||
\item \textbf{Double Rotation:} A combination of left-right or right-left rotations for more complex imbalance cases.
|
||||
\end{itemize}
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Search: \tct{\log n}.
|
||||
\item Insert: \tct{\log n}.
|
||||
\item Delete: \tct{\log n}.
|
||||
\end{itemize}
|
||||
\item \textbf{Height:} The height of an AVL Tree with $n$ nodes is \tct{\log n}.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{AVL-Tree}
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[20
|
||||
[10
|
||||
[5]
|
||||
[15]
|
||||
]
|
||||
[30
|
||||
[25]
|
||||
[35]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
|
||||
\subsubsection{In more detail}
|
||||
|
||||
\fhlc{Aquamarine}{Balance Factor}
|
||||
|
||||
The key to maintaining balance in an AVL tree is the concept of the \textbf{balance factor}. For each node, the balance factor is calculated as the height of its left subtree minus the height of its right subtree:
|
||||
|
||||
\begin{equation*}
|
||||
\text{Balance Factor} = \text{height}(\text{left subtree}) - \text{height}(\text{right subtree})
|
||||
\end{equation*}
|
||||
|
||||
A node with a balance factor of 0, 1, or -1 is considered balanced. If the balance factor becomes 2 or -2, the tree is unbalanced and requires rebalancing.
|
||||
|
||||
\fhlc{Aquamarine}{Rotations}
|
||||
\begin{definition}[]{Rotations in AVL Trees}
|
||||
Rotations in AVL Trees are operations used to restore balance when the balance factor of a node exceeds $1$ or goes below $-1$. They are classified into single and double rotations based on the imbalance type.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Types of Rotations and Their Characteristics}
|
||||
\begin{itemize}
|
||||
\item \textbf{Single Rotations:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Right Rotation (RR):} Used to fix a left-heavy subtree.
|
||||
\item \textbf{Left Rotation (LL):} Used to fix a right-heavy subtree.
|
||||
\end{itemize}
|
||||
\item \textbf{Double Rotations:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Left-Right Rotation (LR):} A combination of a left rotation followed by a right rotation.
|
||||
\item \textbf{Right-Left Rotation (RL):} A combination of a right rotation followed by a left rotation.
|
||||
\end{itemize}
|
||||
\item \textbf{Restoring Balance:} After a rotation, the balance factors of affected nodes are recalculated to ensure the AVL property is restored.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{Right Rotation (RR)}
|
||||
The following illustrates a right rotation around node 30:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[30
|
||||
[20
|
||||
[10]
|
||||
]
|
||||
[40]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Right Rotate}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[20
|
||||
[10]
|
||||
[30
|
||||
[]
|
||||
[40]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\begin{example}[]{Left Rotation (LL)}
|
||||
The following illustrates a left rotation around node 10:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[10
|
||||
[]
|
||||
[20
|
||||
[]
|
||||
[30]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Left Rotate}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[20
|
||||
[10]
|
||||
[30]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\newpage
|
||||
\begin{example}[]{Left-Right Rotation (LR)}
|
||||
The following illustrates a left-right rotation:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[30
|
||||
[10
|
||||
[]
|
||||
[20]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Left Rotate on 10}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[30
|
||||
[20
|
||||
[10]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Right Rotate on 30}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[20
|
||||
[10]
|
||||
[30]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\begin{example}[]{Right-Left Rotation (RL)}
|
||||
The following illustrates a right-left rotation:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[10
|
||||
[]
|
||||
[30
|
||||
[20]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Right Rotate on 30}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[10
|
||||
[]
|
||||
[20
|
||||
[]
|
||||
[30]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
$\xrightarrow{\text{Left Rotate on 10}}$
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[20
|
||||
[10]
|
||||
[30]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
264
algorithms-and-datastructures/parts/datatypes/binary-tree.tex
Normal file
264
algorithms-and-datastructures/parts/datatypes/binary-tree.tex
Normal file
@@ -0,0 +1,264 @@
|
||||
\newpage
|
||||
\subsection{Binary tree}
|
||||
\begin{definition}[]{Binary Trees}
|
||||
A Binary Tree is a hierarchical data structure in which each node has at most two children, referred to as the left child and the right child.
|
||||
It is widely used in applications such as searching, sorting, and hierarchical data representation.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics}
|
||||
\begin{itemize}
|
||||
\item \textbf{Nodes:} Each node contains a value, and at most two child pointers (left and right).
|
||||
\item \textbf{Tree Types:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Full Binary Tree:} Each node has 0 or 2 children.
|
||||
\item \textbf{Complete Binary Tree:} All levels except possibly the last are fully filled, and the last level is filled from left to right.
|
||||
\item \textbf{Perfect Binary Tree:} All internal nodes have 2 children, and all leaves are at the same level.
|
||||
\end{itemize}
|
||||
\item \textbf{Time Complexity (basic operations):}
|
||||
\begin{itemize}
|
||||
\item Search: \tct{h}, where $h$ is the height of the tree.
|
||||
\item Insert: \tct{h}.
|
||||
\item Delete: \tct{h}.
|
||||
\end{itemize}
|
||||
\item \textbf{Height:} The height of a binary tree is the longest path from the root to a leaf node.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{Binary Tree}
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[10
|
||||
[5
|
||||
[3]
|
||||
[7]
|
||||
]
|
||||
[15
|
||||
[12]
|
||||
[18]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\subsubsection{Operations}
|
||||
\begin{properties}[]{Basic Operations on Binary Trees}
|
||||
\begin{itemize}
|
||||
\item \textbf{Traversal:} Visit nodes in a specific order:
|
||||
\begin{itemize}
|
||||
\item \textbf{Inorder (LNR):} Left, Node, Right.
|
||||
\item \textbf{Preorder (NLR):} Node, Left, Right.
|
||||
\item \textbf{Postorder (LRN):} Left, Right, Node.
|
||||
\item \textbf{Level-order:} Breadth-first traversal using a queue.
|
||||
\end{itemize}
|
||||
\item \textbf{Insertion:} Add a new node to the tree, ensuring binary tree properties are preserved.
|
||||
\item \textbf{Deletion:} Remove a node and reorganize the tree to maintain structure:
|
||||
\begin{itemize}
|
||||
\item Replace with the deepest node (binary tree property).
|
||||
\item If deleting the root in a binary search tree, replace it with the smallest node in the right subtree.
|
||||
\end{itemize}
|
||||
\item \textbf{Searching:} Locate a node with a specific value. For binary search trees, use the property that left child $<$ parent $<$ right child.
|
||||
\item \textbf{Height Calculation:} Compute the maximum depth from the root to any leaf node.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\subsubsection{Construction / Adding}
|
||||
\begin{guides}[]{Manual Construction of a Binary Tree}{Tutorial}
|
||||
A binary tree can be created by manually following these steps:
|
||||
\begin{enumerate}
|
||||
\item Start with an empty tree.
|
||||
\item Insert the root node.
|
||||
\item For each subsequent value:
|
||||
\begin{itemize}
|
||||
\item Compare it to the current node.
|
||||
\item Place it as the left child if it is smaller or as the right child if it is larger.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
This method results in a binary search tree if values are inserted in order.
|
||||
\end{guides}
|
||||
|
||||
\begin{example}[]{Manual Construction of a Binary Tree}
|
||||
We construct a binary tree from the values: $50, 30, 70, 20, 40, 60, 80$.
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt,
|
||||
s sep=15mm, l sep=15mm
|
||||
}
|
||||
[50
|
||||
[30
|
||||
[20]
|
||||
[40]
|
||||
]
|
||||
[70
|
||||
[60]
|
||||
[80]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
|
||||
\begin{remarks}[]{Steps for Construction (Visualized Above)}
|
||||
\begin{itemize}
|
||||
\item Insert $50$ as the root.
|
||||
\item Insert $30$: Smaller than $50$, placed as the left child.
|
||||
\item Insert $70$: Larger than $50$, placed as the right child.
|
||||
\item Insert $20$: Smaller than $50$ and $30$, placed as the left child of $30$.
|
||||
\item Insert $40$: Smaller than $50$, larger than $30$, placed as the right child of $30$.
|
||||
\item Insert $60$: Smaller than $70$, larger than $50$, placed as the left child of $70$.
|
||||
\item Insert $80$: Larger than $70$, placed as the right child of $70$.
|
||||
\end{itemize}
|
||||
\end{remarks}
|
||||
|
||||
\begin{properties}[]{Key Notes}
|
||||
\begin{itemize}
|
||||
\item The structure depends on the order of insertion.
|
||||
\item Duplicate values are either not allowed or placed in a consistent direction (e.g., always left).
|
||||
\item Balancing may be required for large datasets to avoid degeneration into a linked list. (AVL-Trees)
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\newpage
|
||||
\subsubsection{Deletion}
|
||||
\begin{definition}[]{Deletion in Binary Trees}
|
||||
Deleting a node from a binary tree involves removing the node and reorganizing the tree to maintain the binary tree properties. The process depends on the number of children the node has.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Cases for Deletion in Binary Trees}
|
||||
\begin{itemize}
|
||||
\item \textbf{Leaf Node} (No children): Simply remove the node.
|
||||
\item \textbf{One Child:} Replace the node with its child.
|
||||
\item \textbf{Two Children:} Replace the node with the smallest node in its right subtree (inorder successor).
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{Deletion of a Node in a Binary Tree}
|
||||
We consider the binary search tree below and perform the following operations:
|
||||
\begin{itemize}
|
||||
\item Delete $20$ (leaf node).
|
||||
\item Delete $30$ (one child).
|
||||
\item Delete $50$ (two children).
|
||||
\end{itemize}
|
||||
|
||||
\fhl{Initial Tree:}
|
||||
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
grow=east,
|
||||
edge={-stealth},
|
||||
l sep=15mm,
|
||||
s sep=8mm
|
||||
}
|
||||
[50
|
||||
[30
|
||||
[20]
|
||||
[40]
|
||||
]
|
||||
[70
|
||||
[60]
|
||||
[80]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\begin{remarks}[]{Steps for Deletion}
|
||||
\begin{itemize}
|
||||
\item \textbf{Delete $20$:}
|
||||
\begin{itemize}
|
||||
\item $20$ is a leaf node. Remove it directly.
|
||||
\end{itemize}
|
||||
\item \textbf{Delete $30$:}
|
||||
\begin{itemize}
|
||||
\item $30$ has one child ($40$). Replace $30$ with $40$.
|
||||
\end{itemize}
|
||||
\item \textbf{Delete $50$:}
|
||||
\begin{itemize}
|
||||
\item $50$ has two children. Find the inorder successor (smallest element in right subtree) ($60$).
|
||||
\item Replace $50$ with $60$ and recursively delete $60$.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{remarks}
|
||||
|
||||
|
||||
\newpage
|
||||
\fhlc{Aquamarine}{Intermediate Steps and Final Tree:}
|
||||
\begin{guides}[]{Delete elements}{Usage}
|
||||
\begin{enumerate}
|
||||
\item After deleting $20$:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
grow=east,
|
||||
edge={-stealth},
|
||||
l sep=15mm,
|
||||
s sep=8mm
|
||||
}
|
||||
[50
|
||||
[30
|
||||
[]
|
||||
[40]
|
||||
]
|
||||
[70
|
||||
[60]
|
||||
[80]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
|
||||
\item After deleting $30$:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
grow=east,
|
||||
edge={-stealth},
|
||||
l sep=15mm,
|
||||
s sep=8mm
|
||||
}
|
||||
[50
|
||||
[40]
|
||||
[70
|
||||
[60]
|
||||
[80]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
|
||||
\item After deleting $50$:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
grow=east,
|
||||
edge={-stealth},
|
||||
l sep=15mm,
|
||||
s sep=8mm
|
||||
}
|
||||
[60
|
||||
[40]
|
||||
[70
|
||||
[]
|
||||
[80]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{enumerate}
|
||||
\end{guides}
|
||||
|
||||
\begin{properties}[]{Key Points to Remember}
|
||||
\begin{itemize}
|
||||
\item For leaf nodes, deletion is straightforward.
|
||||
\item For nodes with one child, bypass the node by connecting its parent to its child.
|
||||
\item For nodes with two children, replacing with the inorder successor ensures the binary search tree property is preserved.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
@@ -0,0 +1,186 @@
|
||||
\newsection
|
||||
\section{Datatypes}
|
||||
Abstract datatypes help with generalizing how data is stored across programming languages.
|
||||
|
||||
\subsection{Lists}
|
||||
\subsubsection{Arrays}
|
||||
An array is a data structure that stores a collection of elements of the same type in subsequent memory locations. Each element can be accessed directly using its index.
|
||||
|
||||
\fhlc{ForestGreen}{Advantages}
|
||||
\begin{itemize}
|
||||
\item \textbf{Fixed Size}: Arrays have a fixed size, which makes memory allocation easy.
|
||||
\item \textbf{Fast Access}: Elements can be accessed in constant time \(O(1)\), due to simple \verb|base address| + \verb|index| $\cdot$ \verb|stride| calculation.
|
||||
\item \textbf{Cache Performance}: Neighbouring memory locations lead to better cache performance (spatial locality, see DDCA).
|
||||
\end{itemize}
|
||||
|
||||
\fhlc{BrickRed}{Disadvantages}
|
||||
\begin{itemize}
|
||||
\item \textbf{Fixed Size}: The size of an array must be specified at the time of creation and cannot be changed dynamically.
|
||||
\item \textbf{Insertion/Deletion Overhead}: Inserting or deleting elements can be costly (if we don't allow gaps), as it requires shifting elements, resulting in a time complexity of \(O(n)\).
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection{Single Linked List}
|
||||
A linked list is a data structure consisting of nodes where each node contains data and a reference (or link) to the next node.
|
||||
In a single linked list, each node contains data and a pointer to the next node in the list. The last node points to \texttt{null}.
|
||||
|
||||
\fhlc{ForestGreen}{Advantages}
|
||||
\begin{itemize}
|
||||
\item \textbf{Dynamic Size}: Linked lists can grow or shrink in size dynamically.
|
||||
\item \textbf{Efficient Insertions/Deletions}: Inserting or deleting elements at any point in the list is efficient if we already have a pointer to the desired location, with a time complexity of \(O(1)\).
|
||||
\end{itemize}
|
||||
|
||||
\fhlc{BrickRed}{Disadvantages}
|
||||
\begin{itemize}
|
||||
\item \textbf{Slow Access}: Elements cannot be accessed directly. We must traverse from the head node to find the desired element, thus time complexity \(O(n)\).
|
||||
\item \textbf{Memory Overhead}: Each node requires additional memory for the pointer.
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection{Double Linked List}
|
||||
In a double linked list, each node contains data, a pointer to the next node, and a pointer to the previous node. Java's \texttt{LinkedList} is a double linked list.
|
||||
|
||||
\fhlc{ForestGreen}{Advantages}
|
||||
\begin{itemize}
|
||||
\item \textbf{As above}: Same apply as above
|
||||
\item \textbf{Bidirectional Traversal}: Allows traversal in both forward and backward directions.
|
||||
\end{itemize}
|
||||
|
||||
\fhlc{BrickRed}{Disadvantages}
|
||||
The same disadvantages as for single linked lists apply here as well
|
||||
|
||||
\newpage
|
||||
\subsubsection{Time Complexity Comparison}
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\begin{tabular}{lccc}
|
||||
\toprule
|
||||
\textbf{Operation} & \textbf{Array} & \textbf{Single Linked List} & \textbf{Double Linked List} \\
|
||||
\midrule
|
||||
$\textsc{insert}(k, L)$ & \tco{1} & \tco{1} & \tco{1} \\
|
||||
$\textsc{get}(i, L)$ & \tco{1} & \tco{l} & \tco{l} \\
|
||||
$\textsc{insertAfter}(k, k', L)$ & \tco{l} & \tco{1} & \tco{1} \\
|
||||
$\textsc{delete}(k, L)$ & \tco{l} & \tco{l} & \tco{1} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\smallskip
|
||||
\begin{flushleft}
|
||||
\footnotesize We assume that for the \textsc{delete} and \textsc{inserAfter} operation we get the memory address of $k$. We also assume that for the linked lists, we have stored a pointer to the last element. $l$ is the current length of the list.
|
||||
\end{flushleft}
|
||||
|
||||
\caption{Time Complexity Comparison of Arrays and Linked Lists}
|
||||
\end{table}
|
||||
The operation $\textsc{insert}(k, L)$ appends an element at the end of the list $L$, $\textsc{get}(i, L)$ returns the element at index $i$ in list $L$, $\textsc{insertAfter}(k, k', L)$ inserts element $k'$ after element $k$ into the list $L$, while $\textsc{delete}(k, L)$ removes the element $k$ from the list ($k$ being either an index (in the case of array) or memory address (in the case of linked lists)).
|
||||
|
||||
\subsubsection{Space Complexity Comparison}
|
||||
\begin{table}[h]
|
||||
\centering
|
||||
\begin{tabular}{lccc}
|
||||
\toprule
|
||||
\textbf{Data Structure} & \textbf{Array} & \textbf{Single Linked List} & \textbf{Double Linked List} \\
|
||||
\midrule
|
||||
Fixed Size & Yes & No & No \\
|
||||
Memory Overhead & None & 1 pointer per node & 2 pointers per node \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Space Complexity Comparison of Arrays and Linked Lists}
|
||||
\end{table}
|
||||
|
||||
|
||||
\subsection{Stack}
|
||||
We stack elements in it, so it is a first in - last out data structure. The time complexity of each operation depends on the underlying data structure used.
|
||||
This data structure is commonly an array or linked list, where a new item is inserted at the start of said linked list.
|
||||
|
||||
\shade{Cyan}{Operations:} $\textsc{push}(k, S)$ pushes object $k$ to stack $S$. $\textsc{pop}(S)$ removes and returns the top most (most recent) element of the stack. $\textsc{top}(S)$ returns the top most (most recent) element of the stack.
|
||||
|
||||
\subsection{Queue}
|
||||
A queue has similar properties like the stack, but is first in - first out. The time complexity of each operation depends again on the underlying data structure used. Commonly an array or a linked list.
|
||||
\shade{Cyan}{Operations:} $\textsc{enqueue}(k, Q)$ adds the element $k$ to the queue $Q$, $\textsc{dequeue}(Q)$ returns the first element of the queue (the least recent element) from the queue $Q$.
|
||||
|
||||
\subsection{Priority Queue}
|
||||
A priority queue is an extension of the queue, where we can also specify a priority for an element. This can for example be used for a notification system, which sends out urgent messages first \textit{(although that would be quite inefficient and better be solved by using a normal queue for each priority level (if there are not that many) and then for each iteration process the notifications from the highest priority queue, then, if empty, decrease priority)}.
|
||||
|
||||
\shade{Cyan}{Operations:} Priority queues commonly have additional operations, like $\textsc{remove}(k, Q)$, which removes the element from the queue and $\textsc{increaseKey}(k, p, Q)$, which increases the priority of $k$ to $p$ if $p$ is greater than the current priority.
|
||||
|
||||
The basic operations are $\textsc{insert}(k, p, Q)$, which inserts the element $k$ with priority $p$ into $Q$ and $\textsc{extractMax}(Q)$, which returns the element with the highest priority.
|
||||
If two elements have the same priority, the order of insertion does \textit{not} matter and a \textit{tie-braking} function is used to determine the priority of the two.
|
||||
Commonly, elements with high priority have low numbers and we then have $\textsc{extractMin}(Q)$ to find the highest priority element.
|
||||
They are most commonly implemented using some kind of heap, whilst a fibonacci heap is the fastest in terms of time complexity.
|
||||
|
||||
\newpage
|
||||
\subsection{Dictionaries}
|
||||
A \textbf{dictionary} stores a collection of key-value pairs.
|
||||
|
||||
\subsubsection{Operations:}
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Insertion (\texttt{insert(key,\ value)})}:
|
||||
\begin{itemize}
|
||||
\item Adds a new key-value pair to the dictionary. If the key already exists, it may update the existing value.
|
||||
\item Time Complexity: Average case \tct{1} (with hashing), worst case \tcl{n} (when all keys hash to the same bucket).
|
||||
\end{itemize}
|
||||
\item \textbf{Deletion (\texttt{remove(key)})}:
|
||||
\begin{itemize}
|
||||
\item Removes a key-value pair from the dictionary by key.
|
||||
\item Time Complexity: Average case \tct{1}, worst case \tco{n}.
|
||||
\end{itemize}
|
||||
\item \textbf{Search/Access (\texttt{get(key)} or \texttt{find(key)})}:
|
||||
\begin{itemize}
|
||||
\item Retrieves the value associated with a given key.
|
||||
\item Time Complexity: Average case \tct{1}, worst case \tco{n}.
|
||||
\end{itemize}
|
||||
\item \textbf{Contains (\texttt{containsKey(key)})}:
|
||||
\begin{itemize}
|
||||
\item Checks if a key is present in the dictionary.
|
||||
\item Time Complexity: Average case \tct{1}, worst case \tco{n}.
|
||||
\end{itemize}
|
||||
\item \textbf{Size/Length}:
|
||||
\begin{itemize}
|
||||
\item Returns the number of key-value pairs in the dictionary.
|
||||
\item Time Complexity: \tco{1} (stored separately).
|
||||
\end{itemize}
|
||||
\item \textbf{Clear}:
|
||||
\begin{itemize}
|
||||
\item Removes all key-value pairs from the dictionary.
|
||||
\item Time Complexity: \tco{n} (depends on implementation, some implementations might be faster).
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
|
||||
\subsubsection{Space Complexity:}
|
||||
|
||||
The space complexity is dependent on the underlying data structure used
|
||||
to implement the dictionary.
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Hash Table:}
|
||||
\begin{itemize}
|
||||
\item Average case: \tct{n}, where $n$ is the number of key-value pairs.
|
||||
\item Additional space can be used for maintaining hash table buckets, which may lead to higher constant factors but not asymptotic growth in complexity.
|
||||
\end{itemize}
|
||||
\item \textbf{Balanced Binary Search Tree (e.g., AVL tree or Red-Black Tree):}
|
||||
\begin{itemize}
|
||||
\item Space Complexity: \tco{n}.
|
||||
\item This structure uses more space compared to a hash table due to the need for storing balance information at each node.
|
||||
\end{itemize}
|
||||
\item \textbf{Dynamic Array:}
|
||||
\begin{itemize}
|
||||
\item This is not a common implementation for dictionaries due to inefficiencies in search and insertion operations compared to hash tables or balanced trees.
|
||||
\item Space Complexity: \tco{n}.
|
||||
\item Time complexity for insertion, deletion, and access can degrade significantly to \tco{n} without additional optimizations.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
|
||||
|
||||
\newpage
|
||||
\subsubsection{Operation time complexity}
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\begin{tabular}{l c c c c c}
|
||||
\toprule
|
||||
\textbf{Operation} & \textbf{Unsorted Arrays} & \textbf{Sorted Arrays} & \textbf{Doubly Linked Lists} & \textbf{Binary Trees} & \textbf{AVL Trees} \\
|
||||
\midrule
|
||||
Insertion & \tco{1} & \tco{n} & \tco{1} & \tco{h} & \tco{\log n} \\
|
||||
Deletion & \tco{n} & \tco{(n)} & \tco{n} & \tco{h} & \tco{\log n} \\
|
||||
Search & \tco{n} & \tco{\log n} & \tco{n} & \tco{h} & \tco{\log n} \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Time Complexities of Dictionary Operations for Various Data Structures}
|
||||
\end{table}
|
||||
159
algorithms-and-datastructures/parts/dp.tex
Normal file
159
algorithms-and-datastructures/parts/dp.tex
Normal file
@@ -0,0 +1,159 @@
|
||||
\newsection
|
||||
\section{Dynamic Programming}
|
||||
\subsection{Algorithm design}
|
||||
We focus on these six crucial steps when creating an algorithm in DP (for the exams at least):
|
||||
|
||||
\begin{usage}[]{Dynamic Programming Algorithm design}
|
||||
\begin{enumerate}[label=\Roman*]
|
||||
\item \textit{Dimension of the DP table}: What is the sizing of the DP table and how many dimensions does it have?
|
||||
\item \textit{Subproblems}: What is the meaning of each entry in the DP table? (Usually the hardest part)
|
||||
\item \textit{Recursion / Recurrence relation}: How to calculate an entry of the DP table from previously computed entries? Also justify why it is correct, specifying base cases.
|
||||
\item \textit{Calculation order}: In which order do the entries of the table have to calculated to ensure that all entries needed to compute an entry have been computed. Usually to be specified as either top-down or bottom up, but in tricky cases also specify edge cases and elements that need to be specifically computed in advance and ones that can be ignored.
|
||||
\item \textit{Extracting the solution}: How do we find the solution in the table after the algorithm has finished processing? Often it's the last element, but sometimes can be more complex than that
|
||||
\item \textit{Time \& space complexity}: Analyze how much storage and time is required for this algorithm (often though only time needed) and note that down in big-$O$ notation
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
% ┌ ┐
|
||||
% │ EXAMPLES │
|
||||
% └ ┘
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\subsection{Examples}
|
||||
\subsubsection{Maximum Subarray Sum}
|
||||
The maximum subarray sum is the problem where we need to find the maximum sum of a subarray of length $k$
|
||||
|
||||
Here, the concept is to either choose an element or not to do so, i.e. the recurrence relation is $R_j = \max \{A[j], R_{j - 1} + A[j]\}$, where the base case is simply $R_1 = A[1]$. Then, using simple bottom up calculation, we get the algorithm.
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Maximum Subarray Sum}
|
||||
\begin{algorithmic}[1]
|
||||
\State $R[1\ldots n] \gets$ new array
|
||||
\State $R[1] \gets A[1]$
|
||||
\For{$j \gets 2, 3, \ldots, n$}
|
||||
\State $R[j] \gets \max\{A[j], R[j - 1] + A[j]\}$
|
||||
\EndFor
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
The same algorithm can also be adapted for minimum subarray sum, or other problems using the same idea.
|
||||
|
||||
\timecomplexity \tct{n} (Polynomial)
|
||||
|
||||
|
||||
\subsubsection{Jump Game}
|
||||
We want to return the minimum number of jumps to get to a position $n$. Each field at an index $i$ has a number $A[i]$ that tells us how far we can jump at most.
|
||||
|
||||
A somewhat efficient way to solve this problem is the recurrence relation $M[k] = \max\{i + A[i] | 1 \leq i \leq M[k - 1]\}$, but an even more efficient one is based on $M[k] = \max \{i + A[i] | M[k - 2] < i \leq M[k - 1]\}$, which essentially uses the fact that we only need to look at all $i$ that can be reached with \textit{exactly} $l - 1$ jumps, since $i \leq M[k - 2]$ can already be reached with $k - 2$ jumps. While the first one has time complexity \tco{n^2}, the new one has \tco{n}
|
||||
|
||||
|
||||
\newpage
|
||||
\subsubsection{Longest common subsequence}
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Longest common subsequence}
|
||||
\begin{algorithmic}[1]
|
||||
\State $L[0..n, 0..m] \gets$ new table
|
||||
\For{$i \gets 0, 1, \ldots, n$}
|
||||
$L[i, 0] \gets 0$
|
||||
\EndFor
|
||||
\For{$j \gets 0, 1, \ldots, m$}
|
||||
$L[0, j] \gets 0$
|
||||
\EndFor
|
||||
\For{$i \gets 1, 2, \ldots, n$}
|
||||
\For{$j \gets 1, 2, \ldots, m$}
|
||||
\If{$a_i = b_j$}
|
||||
$L[i, j] \gets 1 + L[i - 1, j - 1]$
|
||||
\Else
|
||||
\hspace{2mm} $L[i, j] = \max\{L[i, j - 1], L[i - 1, j]\}$
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndFor
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
To find the actual solution (in the sense of which letters are in the longest common subsequence), we need to use backtracking, i.e. finding which letters we picked.
|
||||
|
||||
\timecomplexity \tct{n \cdot m} (Polynomial)
|
||||
|
||||
|
||||
\subsubsection{Editing distance}
|
||||
This problem is based on the LCS problem, where we want to insert, modify or delete characters to change a sequence $A$ into a sequence $B$. (Application: Spell checker).
|
||||
|
||||
The recurrence relation is $ED(i, j) = \min \begin{cases}
|
||||
ED(i - 1, j) + 1\\
|
||||
ED(i, j - 1) + 1\\
|
||||
ED(i - 1, j - 1) + \begin{cases}
|
||||
1 & \text{if } a_i \neq b_j\\
|
||||
0 & \text{if } a_i = b_j
|
||||
\end{cases}
|
||||
\end{cases}$
|
||||
|
||||
\timecomplexity \tct{n \cdot m} (Polynomial)
|
||||
|
||||
\subsubsection{Subset sum}
|
||||
We want to find a subset of a set $A[1], \ldots, A[n]$ such that the sum of them equals a number $b$. Its recurrence relation is $T(i, s) = T(i - 1, s) \lor T(i - 1, s - A[i])$, where $i$ is the $i$-th entry in the array and $s$ the current sum. Base cases are $T(0, s) = false$ and $T(0, 0) = true$. In our DP-Table, we store if the subset sum can be constructed up to this element. Therefore, the DP table is a boolean table and the value $T(n, b)$ only tells us if we have a solution or not. To find the solution, we need to backtrack again.
|
||||
|
||||
\timecomplexity \tct{n \cdot b} (Pseudopolynomial)
|
||||
|
||||
|
||||
\subsubsection{Knapsack problem}
|
||||
We have the element $i$ with weight $W[i]$ and profit $P[i]$.
|
||||
|
||||
The recurrence relation is $DP(i, w) = \begin{cases}
|
||||
DP(i - 1, w) & \text{if } w < W[i]\\
|
||||
\max\{DP(i - 1, w), P[i] + DP(i - 1, w - W[i])\} & \text{else}
|
||||
\end{cases}$. The solution can be found in $P(n, W)$, where $W$ is the weight limit.
|
||||
|
||||
\timecomplexity \tct{n \cdot W} (Pseudopolynomial)
|
||||
|
||||
|
||||
\newpage
|
||||
\subsection{Polynomial vs non-polynomial}
|
||||
An interesting theorem from theoretical computer science is this: If the subset sum problem is solveable in polynomial time, then all non-polynomial problems are solveable in polynomial time. The same goes for the Knapsack problem and many many more.
|
||||
|
||||
\fhlc{Aquamarine}{Pseudopolynomial}: The efficiency of the algorithm is dependent on the input given.
|
||||
|
||||
|
||||
\subsection{Knapsack with approximation}
|
||||
We can use approximation to solve the Knapsack problem in polynomial time. For that, we round the profits of the items and define $\displaystyle \overline{p_i} := K \cdot \floor{\frac{p_i}{K}}$, meaning we round down to the next multiple of $K$. As such, we reduce the time and space complexity by a factor of $K$, whilst also reducing the accuracy of the output, but only slightly lower (which is good, because overshooting would be less than ideal in most circumstances)
|
||||
|
||||
|
||||
\subsection{Longest ascending subsequence}
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Longest ascending subsequence}
|
||||
\begin{algorithmic}[1]
|
||||
\State $T[1..n] \gets$ new table
|
||||
\State $T[1] \gets A[1]$
|
||||
\For{$l \gets 2, 3, \ldots, n$}
|
||||
$T[l] \gets \infty$
|
||||
\EndFor
|
||||
\For{$i \gets 2, 3, \ldots, n$}
|
||||
\State $l \gets$ smallest index with $A[i + 1] \leq T[l]$
|
||||
\State $T[l] \gets A[i + 1]$
|
||||
\EndFor
|
||||
\State \Return $\max\{l : T[l] \leq \infty\}$
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
\timecomplexity \tco{n \cdot \log(n)}
|
||||
|
||||
|
||||
\subsection{Matrix chain multiplication}
|
||||
Multiplying matrices can be more efficient if done in a certain order (because associativity exists). The problem to solve is finding the optimal parenthesis placement for the minimal number of operations.
|
||||
|
||||
The recurrence relation for this problem is $M(i, j) = \begin{cases}
|
||||
0 & \text{falls } i = j\\
|
||||
\min_{i \leq s < j}\{M(i, s) + M(s + 1, j) + k_{i - 1}\cdot k_s \cdot k_j\} & \text{else}
|
||||
\end{cases}$
|
||||
|
||||
|
||||
\timecomplexity \tco{n^3}
|
||||
148
algorithms-and-datastructures/parts/graphs/all-pairs-sp.tex
Normal file
148
algorithms-and-datastructures/parts/graphs/all-pairs-sp.tex
Normal file
@@ -0,0 +1,148 @@
|
||||
\newpage
|
||||
\subsection{All-Pair Shortest Paths}
|
||||
We can also use $n$-times dijkstra or any other shortest path algorithm, or any of the dedicated ones
|
||||
|
||||
\subsubsection{Floyd-Warshall Algorithm}
|
||||
\begin{definition}[]{Floyd-Warshall Algorithm}
|
||||
The \textbf{Floyd-Warshall Algorithm} is a dynamic programming algorithm used to compute the shortest paths between all pairs of vertices in a weighted graph.
|
||||
\end{definition}
|
||||
|
||||
|
||||
\begin{algo}{FloydWarshall(G)}
|
||||
\Procedure{Floyd-Warshall}{$G = (V, E)$}
|
||||
\State Initialize distance matrix $d[i][j]$: $d[i][j] =
|
||||
\begin{cases}
|
||||
0 & \text{if } i = j \\
|
||||
w(i, j) & \text{if } (i, j) \in E \\
|
||||
\infty & \text{otherwise}
|
||||
\end{cases}$
|
||||
\For{each intermediate vertex $k \in V$}
|
||||
\For{each pair of vertices $i, j \in V$}
|
||||
\State $d[i][j] \gets \min(d[i][j], d[i][k] + d[k][j])$
|
||||
\EndFor
|
||||
\EndFor
|
||||
\State \textbf{Return} $d$
|
||||
\EndProcedure
|
||||
\end{algo}
|
||||
|
||||
\begin{properties}[]{Characteristics of Floyd-Warshall Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Time Complexity:} \tco{|V|^3}.
|
||||
\item Works for graphs with negative edge weights but no negative weight cycles.
|
||||
\item Computes shortest paths for all pairs in one execution.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{usage}[]{Floyd-Warshall Algorithm}
|
||||
The Floyd-Warshall algorithm computes shortest paths between all pairs of vertices in a weighted graph (handles negative weights but no negative cycles).
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialize:}
|
||||
\begin{itemize}
|
||||
\item Create a distance matrix \(D\), where \(D[i][j]\) is the weight of the edge from vertex \(i\) to vertex \(j\), or infinity if no edge exists. Set \(D[i][i] = 0\).
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Iterate Over Intermediate Vertices:}
|
||||
\begin{itemize}
|
||||
\item For each vertex \(k\) (acting as an intermediate vertex):
|
||||
\begin{itemize}
|
||||
\item Update \(D[i][j]\) for all pairs of vertices \(i, j\) using:
|
||||
\[
|
||||
D[i][j] = \min(D[i][j], D[i][k] + D[k][j])
|
||||
\]
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Repeat:}
|
||||
\begin{itemize}
|
||||
\item Repeat for all vertices as intermediate vertices (\(k = 1, 2, \ldots, n\)).
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The final distance matrix \(D\) contains the shortest path distances between all pairs of vertices.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
\newpage
|
||||
\subsubsection{Johnson's Algorithm}
|
||||
\begin{definition}[]{Johnson's Algorithm}
|
||||
The \textbf{Johnson’s Algorithm} computes shortest paths between all pairs of vertices in a sparse graph. It uses the Bellman-Ford algorithm as a preprocessing step to reweight edges and ensures all weights are non-negative.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics of Johnson's Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Steps:}
|
||||
\begin{enumerate}
|
||||
\item Add a new vertex $s$ with edges of weight $0$ to all vertices.
|
||||
\item Run Bellman-Ford from $s$ to detect negative cycles and compute vertex potentials.
|
||||
\item Reweight edges to remove negative weights.
|
||||
\item Use Dijkstra's algorithm for each vertex to find shortest paths.
|
||||
\end{enumerate}
|
||||
\item \textbf{Time Complexity:} \tco{|V| \cdot (|E| + |V| \log |V|)}.
|
||||
\item Efficient for sparse graphs compared to Floyd-Warshall.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{usage}[]{Johnson's Algorithm}
|
||||
Johnson's algorithm computes shortest paths between all pairs of vertices in a weighted graph (handles negative weights but no negative cycles).
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Add a New Vertex:}
|
||||
\begin{itemize}
|
||||
\item Add a new vertex \(s\) to the graph and connect it to all vertices with zero-weight edges.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Run Bellman-Ford:}
|
||||
\begin{itemize}
|
||||
\item Use the Bellman-Ford algorithm starting from \(s\) to compute the shortest distance \(h[v]\) from \(s\) to each vertex \(v\).
|
||||
\item If a negative-weight cycle is detected, stop.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Reweight Edges:}
|
||||
\begin{itemize}
|
||||
\item For each edge \(u \to v\) with weight \(w(u, v)\), reweight it as:
|
||||
\[
|
||||
w'(u, v) = w(u, v) + h[u] - h[v]
|
||||
\]
|
||||
\item This ensures all edge weights are non-negative.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Run Dijkstra's Algorithm:}
|
||||
\begin{itemize}
|
||||
\item For each vertex \(v\), use Dijkstra's algorithm to compute the shortest paths to all other vertices.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Adjust Back:}
|
||||
\begin{itemize}
|
||||
\item Convert the distances back to the original weights using:
|
||||
\[
|
||||
d'(u, v) = d'(u, v) - h[u] + h[v]
|
||||
\]
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The resulting shortest path distances between all pairs of vertices are valid.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
\subsubsection{Comparison}
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\begin{tabular}{lccc}
|
||||
\toprule
|
||||
\textbf{Algorithm} & \textbf{Primary Use} & \textbf{Time Complexity} & \textbf{Remarks} \\
|
||||
\midrule
|
||||
Floyd-Warshall & AP-SP & \tco{|V|^3} & Handles negative weights \\
|
||||
Johnson’s Algorithm & AP-SP (sparse graphs) & \tco{|V|(|E| + |V| \log |V|)} & Requires reweighting \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Comparison of the All-Pair Shortest path (AP-SP) algorithms discussed in the lecture}
|
||||
\end{table}
|
||||
113
algorithms-and-datastructures/parts/graphs/bfs-dfs.tex
Normal file
113
algorithms-and-datastructures/parts/graphs/bfs-dfs.tex
Normal file
@@ -0,0 +1,113 @@
|
||||
\newpage
|
||||
\subsection{Topological Sorting / Ordering}
|
||||
\begin{definition}[]{Topological Ordering}
|
||||
A \textbf{topological ordering} of a directed acyclic graph (DAG) $G = (V, E)$ is a linear ordering of its vertices such that for every directed edge $(u, v) \in E$, vertex $u$ comes before vertex $v$ in the ordering.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Topological Ordering}
|
||||
\begin{itemize}
|
||||
\item A graph has a topological ordering if and only if it is a DAG.
|
||||
\item The ordering is not unique if the graph contains multiple valid sequences of vertices.
|
||||
\item Common algorithms to compute topological ordering:
|
||||
\begin{itemize}
|
||||
\item \textbf{DFS-based Approach:} Perform a depth-first search and record vertices in reverse postorder.
|
||||
\item \textbf{Kahn’s Algorithm:} Iteratively remove vertices with no incoming edges while maintaining order.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
|
||||
|
||||
|
||||
\newpage
|
||||
\subsection{Graph search}
|
||||
\subsubsection{DFS}
|
||||
\begin{definition}[]{Depth-First Search (DFS)}
|
||||
\textbf{Depth-First Search} is an algorithm for traversing or searching a graph by exploring as far as possible along each branch before backtracking.
|
||||
\end{definition}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Depth-First Search (Recursive)}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{DFS}{$v$}
|
||||
\State \textbf{Mark} $v$ as visited
|
||||
\For{each neighbor $u$ of $v$}
|
||||
\If{$u$ is not visited}
|
||||
\State \Call{DFS}{$u$}
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{properties}[]{Depth-First Search}
|
||||
\begin{itemize}
|
||||
\item Can be implemented recursively or iteratively (using a stack).
|
||||
\item Time complexity: \tco{|V| + |E|}, where $|V|$ is the number of vertices and $|E|$ is the number of edges.
|
||||
\item Used for:
|
||||
\begin{itemize}
|
||||
\item Detecting cycles in directed and undirected graphs.
|
||||
\item Finding connected components in undirected graphs.
|
||||
\item Computing topological ordering in a DAG.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
|
||||
\subsubsection{BFS}
|
||||
\begin{definition}[]{Breadth-First Search (BFS)}
|
||||
\textbf{Breadth-First Search} is an algorithm for traversing or searching a graph by exploring all neighbors of a vertex before moving to the next level of neighbors.
|
||||
\end{definition}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Breadth-First Search (Iterative)}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{BFS}{$start$}
|
||||
\State \textbf{Initialize} queue $Q$ and mark $start$ as visited
|
||||
\State \textbf{Enqueue} $start$ into $Q$
|
||||
\While{$Q$ is not empty}
|
||||
\State $v \gets \textbf{Dequeue}(Q)$
|
||||
\For{each neighbor $u$ of $v$}
|
||||
\If{$u$ is not visited}
|
||||
\State \textbf{Mark} $u$ as visited
|
||||
\State \textbf{Enqueue} $u$ into $Q$
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndWhile
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{properties}[]{Breadth-First Search}
|
||||
\begin{itemize}
|
||||
\item Implements a queue-based approach for level-order traversal.
|
||||
\item Time complexity: \tco{|V| + |E|}.
|
||||
\item Used for:
|
||||
\begin{itemize}
|
||||
\item Finding shortest paths in unweighted graphs.
|
||||
\item Checking bipartiteness.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{DFS and BFS Traversal}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.5cm, main/.style={circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt}]
|
||||
% Graph vertices
|
||||
\node[main] (1) {1};
|
||||
\node[main] (2) [above right of=1] {2};
|
||||
\node[main] (3) [below right of=1] {3};
|
||||
\node[main] (4) [right of=2] {4};
|
||||
\node[main] (5) [right of=3] {5};
|
||||
|
||||
% Edges
|
||||
\draw (1) -- (2);
|
||||
\draw (1) -- (3);
|
||||
\draw (2) -- (4);
|
||||
\draw (3) -- (5);
|
||||
\draw (4) -- (5);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\textbf{DFS Traversal:} Starting at $1$, a possible traversal is $1 \to 2 \to 4 \to 5 \to 3$.\\
|
||||
\textbf{BFS Traversal:} Starting at $1$, a possible traversal is $1 \to 2 \to 3 \to 4 \to 5$.
|
||||
\end{example}
|
||||
@@ -0,0 +1,54 @@
|
||||
\newpage
|
||||
\subsection{Connected Components}
|
||||
\begin{definition}[]{Connected Component}
|
||||
A \textbf{connected component} of a graph $G = (V, E)$ is a maximal subset of vertices $C \subseteq V$ such that:
|
||||
\begin{itemize}
|
||||
\item For every pair of vertices $u, v \in C$, there exists a path connecting $u$ and $v$.
|
||||
\item Adding any vertex $w \notin C$ to $C$ would violate the connectedness condition.
|
||||
\end{itemize}
|
||||
\end{definition}
|
||||
|
||||
\begin{remarks}[]{Key Points About Connected Components}
|
||||
\begin{itemize}
|
||||
\item \textbf{Undirected Graphs:} A connected component is a subgraph where any two vertices are connected by a path, and which is connected to no additional vertices in the graph.
|
||||
\item \textbf{Directed Graphs:} There are two types of connected components:
|
||||
\begin{itemize}
|
||||
\item \textbf{Strongly Connected Component:} A maximal subset of vertices where every vertex is reachable from every other vertex (considering edge direction).
|
||||
\item \textbf{Weakly Connected Component:} A maximal subset of vertices where connectivity is considered by ignoring edge directions.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{remarks}
|
||||
|
||||
\begin{example}[]{Connected Components in a Graph}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.5cm, main/.style={circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt}]
|
||||
% Connected component 1
|
||||
\node[main] (1) {1};
|
||||
\node[main] (2) [right of=1] {2};
|
||||
\node[main] (3) [below of=1] {3};
|
||||
\draw (1) -- (2);
|
||||
\draw (1) -- (3);
|
||||
\draw (2) -- (3);
|
||||
|
||||
% Connected component 2
|
||||
\node[main] (4) [right of=2, xshift=2cm] {4};
|
||||
\node[main] (5) [right of=4] {5};
|
||||
\draw (4) -- (5);
|
||||
|
||||
% Isolated vertex (another component)
|
||||
\node[main] (6) [below of=5, yshift=-1cm] {6};
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\begin{remarks}[]{Understanding the Example}
|
||||
\begin{itemize}
|
||||
\item In the given undirected graph:
|
||||
\begin{itemize}
|
||||
\item \textbf{Component 1:} $\{1, 2, 3\}$ (fully connected subgraph).
|
||||
\item \textbf{Component 2:} $\{4, 5\}$ (connected by a single edge).
|
||||
\item \textbf{Component 3:} $\{6\}$ (an isolated vertex).
|
||||
\end{itemize}
|
||||
\item These subsets are disjoint and collectively partition the graph's vertex set $V$.
|
||||
\end{itemize}
|
||||
\end{remarks}
|
||||
73
algorithms-and-datastructures/parts/graphs/intro.tex
Normal file
73
algorithms-and-datastructures/parts/graphs/intro.tex
Normal file
@@ -0,0 +1,73 @@
|
||||
\newsection
|
||||
\section{Graph theory}
|
||||
\begin{definition}[]{Graphs: Directed and Undirected}
|
||||
A \textbf{graph} $G = (V, E)$ consists of:
|
||||
\begin{itemize}
|
||||
\item A set of \textbf{vertices} (or nodes) $V$, and
|
||||
\item A set of \textbf{edges} $E$, representing connections between pairs of vertices.
|
||||
\end{itemize}
|
||||
Graphs can be classified into:
|
||||
\begin{itemize}
|
||||
\item \textbf{Undirected Graphs:} Edges have no direction, represented as unordered pairs $\{u, v\}$.
|
||||
\item \textbf{Directed Graphs (Digraphs):} Edges have a direction, represented as ordered pairs $(u, v)$.
|
||||
\end{itemize}
|
||||
\end{definition}
|
||||
|
||||
\begin{terms}[]{Graph Theory}
|
||||
\begin{itemize}
|
||||
\item \textbf{Adjacent (Neighbors):} Two vertices $u$ and $v$ are adjacent if there is an edge between them.
|
||||
\item \textbf{Degree:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Undirected Graph:} The degree of a vertex $v$ is the number of edges incident to it.
|
||||
\item \textbf{Directed Graph:}
|
||||
\begin{itemize}
|
||||
\item \textbf{In-Degree:} Number of incoming edges to $v$.
|
||||
\item \textbf{Out-Degree:} Number of outgoing edges from $v$.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\item \textbf{Path:} A sequence of vertices where each adjacent pair is connected by an edge.
|
||||
\begin{itemize}
|
||||
\item \textbf{Simple Path:} A path with no repeated vertices.
|
||||
\item \textbf{Cycle:} A path that starts and ends at the same vertex.
|
||||
\end{itemize}
|
||||
\item \textbf{Connected Graph:} A graph where there is a path between any two vertices.
|
||||
\begin{itemize}
|
||||
\item \textbf{Strongly Connected:} In a directed graph, every vertex is reachable from every other vertex.
|
||||
\item \textbf{Weakly Connected:} A directed graph becomes connected if the direction of edges is ignored.
|
||||
\end{itemize}
|
||||
\item \textbf{Subgraph:} A graph formed from a subset of vertices and edges of the original graph.
|
||||
\item \textbf{Complete Graph:} A graph in which every pair of vertices is connected by an edge.
|
||||
\item \textbf{Weighted Graph:} A graph where each edge has an associated weight or cost.
|
||||
\item \textbf{Multigraph:} A graph that may have multiple edges (parallel edges) between the same pair of vertices.
|
||||
\item \textbf{Self-Loop:} An edge that connects a vertex to itself.
|
||||
\item \textbf{Bipartite Graph:} A graph whose vertices can be divided into two disjoint sets such that every edge connects a vertex in one set to a vertex in the other set.
|
||||
\item \textbf{Tree:} A graph is a tree if it is connected and has no cycles (sufficient condition), a tree has $n - 1$ edges for $n$ vertices (necessary condition).
|
||||
\item \textbf{Reachability:} A vertex $u$ is called \textit{reachable} from $v$, if there exists a walk or path with endpoints $u$ and $v$.
|
||||
\end{itemize}
|
||||
\end{terms}
|
||||
|
||||
\begin{example}[]{Directed and Undirected Graphs}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.5cm, main/.style={circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt}]
|
||||
% Undirected graph
|
||||
\node[main] (1) {1};
|
||||
\node[main] (2) [right of=1] {2};
|
||||
\node[main] (3) [below of=1] {3};
|
||||
\node[main] (4) [below of=2] {4};
|
||||
\draw (1) -- (2);
|
||||
\draw (1) -- (3);
|
||||
\draw (3) -- (4);
|
||||
\draw (2) -- (4);
|
||||
|
||||
% Directed graph
|
||||
\node[main] (5) [right of=2, xshift=2cm] {A};
|
||||
\node[main] (6) [right of=5] {B};
|
||||
\node[main] (7) [below of=5] {C};
|
||||
\node[main] (8) [below of=6] {D};
|
||||
\draw[->] (5) -- (6);
|
||||
\draw[->] (5) -- (7);
|
||||
\draw[->] (7) -- (8);
|
||||
\draw[->] (6) -- (8);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{example}
|
||||
67
algorithms-and-datastructures/parts/graphs/matrix.tex
Normal file
67
algorithms-and-datastructures/parts/graphs/matrix.tex
Normal file
@@ -0,0 +1,67 @@
|
||||
\newpage
|
||||
\subsection{Matrix Multiplication}
|
||||
\subsubsection{Strassen's Algorithm}
|
||||
\begin{definition}[]{Strassen’s Algorithm}
|
||||
The \textbf{Strassen’s Algorithm} is an efficient algorithm for matrix multiplication, reducing the asymptotic complexity compared to the standard method.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics of Strassen’s Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Standard Multiplication:} Requires \tco{n^3} time for two $n \times n$ matrices.
|
||||
\item \textbf{Strassen’s Approach:} Reduces the complexity to \tco{n^{\log_2 7}} (approximately \tco{n^{2.81}}).
|
||||
\item \textbf{Idea:} Uses divide-and-conquer to reduce the number of scalar multiplications from $8$ to $7$ in each recursive step.
|
||||
\item Useful for applications involving large matrix multiplications.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{usage}[]{Strassen's Algorithm}
|
||||
Strassen's algorithm is used for matrix multiplication, reducing the computational complexity from \(O(n^3)\) to approximately \(O(n^{2.81})\).
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Divide Matrices:}
|
||||
\begin{itemize}
|
||||
\item Split the input matrices \(A\) and \(B\) into four submatrices each:
|
||||
\[
|
||||
A = \begin{bmatrix} A_{11} & A_{12} \\ A_{21} & A_{22} \end{bmatrix}, \quad
|
||||
B = \begin{bmatrix} B_{11} & B_{12} \\ B_{21} & B_{22} \end{bmatrix}
|
||||
\]
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Compute 7 Products:}
|
||||
\begin{itemize}
|
||||
\item Calculate seven intermediate products using combinations of the submatrices:
|
||||
\begin{align*}
|
||||
M_1 & = (A_{11} + A_{22})(B_{11} + B_{22}) \\
|
||||
M_2 & = (A_{21} + A_{22})B_{11} \\
|
||||
M_3 & = A_{11}(B_{12} - B_{22}) \\
|
||||
M_4 & = A_{22}(B_{21} - B_{11}) \\
|
||||
M_5 & = (A_{11} + A_{12})B_{22} \\
|
||||
M_6 & = (A_{21} - A_{11})(B_{11} + B_{12}) \\
|
||||
M_7 & = (A_{12} - A_{22})(B_{21} + B_{22})
|
||||
\end{align*}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Combine Results:}
|
||||
\begin{itemize}
|
||||
\item Use the intermediate products to compute the submatrices of the result \(C\):
|
||||
\[
|
||||
C_{11} = M_1 + M_4 - M_5 + M_7, \quad
|
||||
C_{12} = M_3 + M_5
|
||||
\]
|
||||
\[
|
||||
C_{21} = M_2 + M_4, \quad
|
||||
C_{22} = M_1 - M_2 + M_3 + M_6
|
||||
\]
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Repeat Recursively:}
|
||||
\begin{itemize}
|
||||
\item If the submatrices are larger than a certain threshold, repeat the process recursively.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The resulting matrix \(C\) is the product of \(A\) and \(B\).
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
113
algorithms-and-datastructures/parts/graphs/mst/boruvka.tex
Normal file
113
algorithms-and-datastructures/parts/graphs/mst/boruvka.tex
Normal file
@@ -0,0 +1,113 @@
|
||||
\newpage
|
||||
\subsubsection{Boruvka's algorithm}
|
||||
\begin{definition}[]{Definition of Borůvka's Algorithm}
|
||||
Borůvka's Algorithm is a greedy algorithm for finding the Minimum Spanning Tree (MST) of a connected, weighted graph. It repeatedly selects the smallest edge from each connected component and adds it to the MST, merging the components until only one component remains.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance of Borůvka's Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Graph Type:} Works on undirected, weighted graphs.
|
||||
\item \textbf{Approach:} Greedy, component-centric.
|
||||
\item \textbf{Time Complexity:} \tct{(|V| + |E|) \log(|V|)}.
|
||||
\item \textbf{Space Complexity:} Depends on the graph representation, typically \tct{E + V}.
|
||||
\item \textbf{Limitations:} Efficient for parallel implementations but less commonly used in practice compared to Kruskal's and Prim's.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Borůvka's Algorithm}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{Boruvka}{$G = (V, E)$}
|
||||
\State Initialize a forest $F$ with each vertex as a separate component.
|
||||
\State Initialize an empty MST $T$.
|
||||
\While{the number of components in $F > 1$}
|
||||
\State Initialize an empty set $minEdges$.
|
||||
\For{each component $C$ in $F$}
|
||||
\State Find the smallest edge $(u, v)$ such that $u \in C$ and $v \notin C$.
|
||||
\State Add $(u, v)$ to $minEdges$.
|
||||
\EndFor
|
||||
\For{each edge $(u, v)$ in $minEdges$}
|
||||
\If{$u$ and $v$ are in different components in $F$}
|
||||
\State Add $(u, v)$ to $T$.
|
||||
\State Merge the components containing $u$ and $v$ in $F$.
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndWhile
|
||||
\State \Return $T$.
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{usage}[]{Borůvka's Algorithm}
|
||||
Borůvka's algorithm finds the Minimum Spanning Tree (MST) by repeatedly finding the smallest outgoing edge from each connected component.
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialize:}
|
||||
\begin{itemize}
|
||||
\item Assign each vertex to its own component.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Find Smallest Edges:}
|
||||
\begin{itemize}
|
||||
\item For each component, find the smallest outgoing edge. After combination, the other vertex will only be evaluated if it has an even lower weight edge outgoing from it.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Merge Components:}
|
||||
\begin{itemize}
|
||||
\item Add the selected edges to the MST.
|
||||
\item Merge the connected components of the graph.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Repeat:}
|
||||
\begin{itemize}
|
||||
\item Repeat steps 2-3 until only one component remains (all vertices are connected).
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The MST is complete, and all selected edges form a connected acyclic graph.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
% \newpage
|
||||
% \subsection{Example: Electrification of Möhrens}
|
||||
% \begin{figure*}[ht]
|
||||
% \centering
|
||||
% \begin{tikzpicture}
|
||||
% \tikzset{enclosed/.style={draw, circle, inner sep=0pt, minimum size =.5cm, circle}};
|
||||
% \node[enclosed, label={center: $\lightning$}] (power) at (0, 0) {};
|
||||
% \node[enclosed, label={center: A}] (A) at (-1, 1) {};
|
||||
% \node[enclosed, label={center: B}] (B) at (-1, 0) {};
|
||||
% \node[enclosed, label={center: C}] (C) at (-1, -1) {};
|
||||
% \node[enclosed, label={center: D}] (D) at (1, 1) {};
|
||||
% \node[enclosed, label={center: E}] (E) at (1, 0) {};
|
||||
% \node[enclosed, label={center: F}] (F) at (1, -1) {};
|
||||
%
|
||||
% \draw (A) -- node[left] {\scriptsize 3} ++ (B);
|
||||
% \draw (B) -- node[left] {\scriptsize 7} ++ (C);
|
||||
%
|
||||
% \draw (D) -- node[right] {\scriptsize 30} ++ (E);
|
||||
% \draw (E) -- node[right] {\scriptsize 25} ++ (F);
|
||||
% \draw plot[smooth] coordinates {(C) (1.5, -1.5) (1.5, 0.8) (D)};
|
||||
% \node[label={\scriptsize 20}] at (1.8, -1.8) {};
|
||||
%
|
||||
%
|
||||
% \draw (A) -- node[right] {\scriptsize 10} ++ (power);
|
||||
% \draw (B) -- node[above,yshift=-0.1cm] {\scriptsize 5} ++ (power);
|
||||
% \draw (C) -- node[right] {\scriptsize 15} ++ (power);
|
||||
% \draw (D) -- node[left] {\scriptsize 40} ++ (power);
|
||||
% \draw (E) -- node[above,yshift=-0.1cm] {\scriptsize 0} ++ (power);
|
||||
% \draw (F) -- node[left] {\scriptsize 35} ++ (power);
|
||||
% \end{tikzpicture}
|
||||
% \end{figure*}
|
||||
%
|
||||
% \fhlc{Aquamarine}{Goal:} Find sub-graph for which the weights are minimal.
|
||||
%
|
||||
% $G = (V, E)$ is non-directed, connected.
|
||||
%
|
||||
% \fhlc{lightgray}{Weights of edges:} $\{w(e)\}\rvert_{e \in E}$ (non-negative)\\[0.2cm]
|
||||
% \fhlc{lightgray}{Spanning edges $A\subseteq E$} Graph $(V, A)$ is connected\\[0.2cm]
|
||||
% \fhlc{lightgray}{Weight:} $w(A) := \sum_{e \in A} w(e)$\\[0.2cm]
|
||||
134
algorithms-and-datastructures/parts/graphs/mst/kruskal.tex
Normal file
134
algorithms-and-datastructures/parts/graphs/mst/kruskal.tex
Normal file
@@ -0,0 +1,134 @@
|
||||
\newpage
|
||||
\subsubsection{Kruskal's algorithm}
|
||||
\begin{definition}[]{Kruskal's Algorithm}
|
||||
Kruskal's Algorithm is a greedy algorithm for finding the Minimum Spanning Tree (MST) of a connected, weighted graph. It sorts all the edges by weight and adds them to the MST in order of increasing weight, provided they do not form a cycle with the edges already included.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance}
|
||||
\begin{itemize}
|
||||
\item \textbf{Graph Type:} Works on undirected, weighted graphs.
|
||||
\item \textbf{Approach:} Greedy, edge-centric.
|
||||
\item \textbf{Time Complexity:} \tco{|E| \log (|E|)} (for sort), \tco{|V| \log(|V|)} (for union find data structure).\\
|
||||
\timecomplexity \tco{|E| \log(|E|) + |V| \log(|V|)}
|
||||
\item \textbf{Space Complexity:} Depends on the graph representation, typically \tct{E + V}.
|
||||
\item \textbf{Limitations:} Requires sorting of edges, which can dominate runtime.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Kruskal's Algorithm}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{Kruskal}{$G = (V, E)$}
|
||||
\State Sort all edges $E$ in non-decreasing order of weight.
|
||||
\State Initialize an empty MST $T$.
|
||||
\State Initialize a disjoint-set data structure for $V$.
|
||||
\For{each edge $(u, v)$ in $E$ (in sorted order)}
|
||||
\If{$u$ and $v$ belong to different components in the disjoint set}
|
||||
\State Add $(u, v)$ to $T$.
|
||||
\State Union the sets containing $u$ and $v$.
|
||||
\EndIf
|
||||
\EndFor
|
||||
\State \Return $T$.
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{usage}[]{Kruskal's Algorithm}
|
||||
Kruskal's algorithm finds the Minimum Spanning Tree (MST) by sorting edges and adding them to the MST, provided they don't form a cycle.
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Sort Edges:}
|
||||
\begin{itemize}
|
||||
\item Sort all edges in ascending order by their weights.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Initialize Disjoint Sets (union find):}
|
||||
\begin{itemize}
|
||||
\item Assign each vertex to its own disjoint set to track connected components.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Iterate Over Edges:}
|
||||
\begin{itemize}
|
||||
\item For each edge in the sorted list:
|
||||
\begin{itemize}
|
||||
\item Check if the edge connects vertices from different sets.
|
||||
\item If it does, add the edge to the MST and merge the sets.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Stop When Done:}
|
||||
\begin{itemize}
|
||||
\item Stop when the MST contains \(n-1\) edges (for a graph with \(n\) vertices).
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The MST is complete, and all selected edges form a connected acyclic graph.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
\newpage
|
||||
\fhlc{Aquamarine}{Union-Find}
|
||||
\begin{usage}[]{Union-Find Data Structure - Step-by-Step Execution}
|
||||
The Union-Find data structure efficiently supports two primary operations for disjoint sets:
|
||||
\begin{itemize}
|
||||
\item \textbf{Union:} Merge two sets into one.
|
||||
\item \textbf{Find:} Identify the representative (or root) of the set containing a given element.
|
||||
\end{itemize}
|
||||
|
||||
\textbf{Steps for Using the Union-Find Data Structure:}
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialization:}
|
||||
\begin{itemize}
|
||||
\item Create an array \(parent\), where \(parent[i] = i\), indicating that each element is its own parent (a singleton set).
|
||||
\item Optionally, maintain a \(rank\) array to track the rank (or size) of each set for optimization.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Find Operation:}
|
||||
\begin{itemize}
|
||||
\item To find the representative (or root) of a set containing element \(x\):
|
||||
\begin{enumerate}
|
||||
\item Follow the \(parent\) array recursively until \(parent[x] = x\).
|
||||
\item Apply \textbf{path compression} by updating \(parent[x]\) directly to the root to flatten the tree structure:
|
||||
\[
|
||||
parent[x] = \text{Find}(parent[x])
|
||||
\]
|
||||
\end{enumerate}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Union Operation:}
|
||||
\begin{itemize}
|
||||
\item To merge the sets containing \(x\) and \(y\):
|
||||
\begin{enumerate}
|
||||
\item Find the roots of \(x\) and \(y\) using the Find operation.
|
||||
\item Compare their ranks:
|
||||
\begin{itemize}
|
||||
\item Attach the smaller tree under the larger tree to keep the structure shallow.
|
||||
\item If ranks are equal, arbitrarily choose one as the root and increment its rank.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Optimization Techniques:}
|
||||
\begin{itemize}
|
||||
\item \textbf{Path Compression:} Flattens the tree during Find operations, reducing the time complexity.
|
||||
\item \textbf{Union by Rank/Size:} Ensures smaller trees are always attached under larger trees, maintaining a logarithmic depth.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item After performing Find and Union operations, the Union-Find structure can determine connectivity between elements or group them into distinct sets efficiently.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
\begin{properties}[]{Performance}
|
||||
\begin{itemize}
|
||||
\item \textsc{make}$(V)$: Initialize data structure \tco{n}
|
||||
\item \textsc{same}$(u, v)$: Check if two components belong to the same set \tco{1} or \tco{n}, depending on if the representant is stored in an array or not
|
||||
\item \textsc{union}$(u, v)$: Combine two sets, \tco{\log(n)}, in Kruskal we call this \tco{n} times, so total number (amortised) is \tco{n \log(n)}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
83
algorithms-and-datastructures/parts/graphs/mst/prim.tex
Normal file
83
algorithms-and-datastructures/parts/graphs/mst/prim.tex
Normal file
@@ -0,0 +1,83 @@
|
||||
\newpage
|
||||
\subsection{MST}
|
||||
\subsubsection{Prim's algorithm}
|
||||
\begin{definition}[]{Definition of Prim's Algorithm}
|
||||
Prim's Algorithm is a greedy algorithm for finding the Minimum Spanning Tree (MST) of a connected, weighted graph.
|
||||
It starts with an arbitrary node and iteratively adds the smallest edge connecting a vertex in the MST to a vertex outside the MST until all vertices are included.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance of Prim's Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Graph Type:} Works on undirected, weighted graphs.
|
||||
\item \textbf{Approach:} Greedy, vertex-centric.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item With an adjacency matrix: \tct{V^2}.
|
||||
\item With a priority queue and adjacency list: \tct{(|V| + |E|) \log(|V|)}.
|
||||
\end{itemize}
|
||||
\item \textbf{Space Complexity:} Depends on the graph representation, typically \tct{E + V}.
|
||||
\item \textbf{Limitations:} Less efficient than Kruskal's for sparse graphs using adjacency matrices.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Prim's Algorithm}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{Prim}{$G = (V, E)$, $start$}
|
||||
\State Initialize a priority queue $Q$.
|
||||
\State Initialize $key[v] \gets \infty$ for all $v \in V$, except $key[start] \gets 0$.
|
||||
\State Initialize an empty MST $T$.
|
||||
\State Add all vertices to $Q$ with their key values.
|
||||
\While{$Q$ is not empty}
|
||||
\State $u \gets$ ExtractMin($Q$).
|
||||
\State Add $u$ to $T$.
|
||||
\For{each $(u, v)$ in $E$}
|
||||
\If{$v$ is in $Q$ and weight($u, v$) $< key[v]$}
|
||||
\State $key[v] \gets$ weight($u, v$).
|
||||
\State Update $Q$ with $key[v]$.
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndWhile
|
||||
\State \Return $T$.
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{usage}[]{Prim's Algorithm}
|
||||
Prim's algorithm is used to find the Minimum Spanning Tree (MST) of a graph. It starts with a single node and grows the MST by adding the smallest edge that connects a vertex in the MST to a vertex outside it.
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialize:}
|
||||
\begin{itemize}
|
||||
\item Pick any starting vertex as part of the MST.
|
||||
\item Mark the vertex as visited and add it to the MST.
|
||||
\item Initialize a priority queue to store edges by their weight.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Explore Edges:}
|
||||
\begin{itemize}
|
||||
\item Add all edges from the visited vertex to the priority queue.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Pick the Smallest Edge:}
|
||||
\begin{itemize}
|
||||
\item Choose the smallest-weight edge from the priority queue that connects a visited vertex to an unvisited vertex.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Add the New Vertex:}
|
||||
\begin{itemize}
|
||||
\item Mark the vertex connected by the chosen edge as visited.
|
||||
\item Add the edge and the vertex to the MST.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Repeat:}
|
||||
\begin{itemize}
|
||||
\item Repeat steps 2-4 until all vertices are part of the MST or no more edges are available.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The MST is complete when all vertices are visited, and the selected edges form a connected acyclic graph.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
@@ -0,0 +1,66 @@
|
||||
\newpage
|
||||
\subsection{Paths, Walks, Cycles}
|
||||
\begin{definition}[]{Paths, Walks, and Cycles in Graphs}
|
||||
\begin{itemize}
|
||||
\item \textbf{Walk:} A sequence of vertices and edges in a graph, where each edge connects consecutive vertices in the sequence. Vertices and edges may repeat.
|
||||
\item \textbf{Path:} A walk with no repeated vertices. In a directed graph, the edges must respect the direction.
|
||||
\item \textbf{Cycle:} A path that starts and ends at the same vertex. For a simple cycle, all vertices (except the start/end vertex) and edges are distinct.
|
||||
\item \textbf{Eulerian Walk:} A walk that traverses every edge of a graph exactly once.
|
||||
\item \textbf{Closed Eulerian Walk (Eulerian Cycle):} An Eulerian walk that starts and ends at the same vertex.
|
||||
\item \textbf{Hamiltonian Path:} A path that visits each vertex of a graph exactly once. Edges may or may not repeat.
|
||||
\item \textbf{Hamiltonian Cycle:} A Hamiltonian path that starts and ends at the same vertex.
|
||||
\end{itemize}
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Eulerian Graphs}
|
||||
\begin{itemize}
|
||||
\item \textbf{Undirected Graph:}
|
||||
\begin{itemize}
|
||||
\item A graph has an Eulerian walk if it has exactly two vertices of odd degree (necessary condition).
|
||||
\item A graph has a closed Eulerian walk if all vertices have even degree (necessary condition).
|
||||
\end{itemize}
|
||||
\item \textbf{Directed Graph:}
|
||||
\begin{itemize}
|
||||
\item A graph has an Eulerian walk if at most one vertex has \textit{in-degree} one greater than its \textit{out-degree}, and at most one vertex has \textit{out-degree} one greater than its \textit{in-degree}. All other vertices must have equal in-degree and out-degree.
|
||||
\item A graph has a closed Eulerian walk if every vertex has equal in-degree and out-degree.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{properties}[]{Hamiltonian Graphs}
|
||||
\begin{itemize}
|
||||
\item Unlike Eulerian walks, there is no simple necessary or sufficient condition for the existence of Hamiltonian paths or cycles.
|
||||
\item A graph with $n$ vertices is Hamiltonian if every vertex has a degree of at least $\lceil n / 2 \rceil$ (Dirac's Theorem, sufficient condition).
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{Eulerian and Hamiltonian}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=1.5cm, main/.style={circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt}] % Eulerian graph example
|
||||
\node[main] (1) {1};
|
||||
\node[main] (2) [above right of=1] {2};
|
||||
\node[main] (3) [below right of=1] {3};
|
||||
\node[main] (4) [right of=3] {4};
|
||||
\node[main] (5) [above right of=4] {5};
|
||||
\draw (1) -- (2) -- (5) -- (4) -- (3) -- (1) -- (4) -- (2);
|
||||
\draw (2) -- (3);
|
||||
|
||||
% Hamiltonian graph example
|
||||
\node[main] (6) [right of=5, xshift=3cm] {A};
|
||||
\node[main] (7) [above right of=6] {B};
|
||||
\node[main] (8) [below right of=6] {C};
|
||||
\node[main] (9) [right of=7] {D};
|
||||
\node[main] (10) [right of=8] {E};
|
||||
\draw (6) -- (7) -- (9) -- (10) -- (8) -- (6);
|
||||
\draw (6) -- (10);
|
||||
\draw (7) -- (8);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\end{example}
|
||||
|
||||
\begin{remarks}[]{Key Differences Between Eulerian and Hamiltonian Concepts}
|
||||
\begin{itemize}
|
||||
\item Eulerian paths are concerned with traversing every \textbf{edge} exactly once, while Hamiltonian paths are about visiting every \textbf{vertex} exactly once.
|
||||
\item Eulerian properties depend on the degree of vertices, whereas Hamiltonian properties depend on overall vertex connectivity.
|
||||
\end{itemize}
|
||||
\end{remarks}
|
||||
169
algorithms-and-datastructures/parts/graphs/shortest-path.tex
Normal file
169
algorithms-and-datastructures/parts/graphs/shortest-path.tex
Normal file
@@ -0,0 +1,169 @@
|
||||
\newpage
|
||||
\subsection{Shortest path}
|
||||
\subsubsection{Dijkstra's Algorithm}
|
||||
\begin{definition}[]{Dijkstra's Algorithm}
|
||||
\textbf{Dijkstra's Algorithm} is a graph search algorithm that finds the shortest paths from a source vertex to all other vertices in a graph with non-negative edge weights.
|
||||
\end{definition}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Dijkstra's Algorithm}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{Dijkstra}{$G = (V, E), s$} \Comment{$s$ is the source vertex}
|
||||
\State Initialize distances: $d[v] \gets \infty \; \forall v \in V, d[s] \gets 0$
|
||||
\State Initialize priority queue $Q$ with all vertices, priority set to $\infty$
|
||||
\While{$Q$ is not empty}
|
||||
\State $u \gets \textbf{Extract-Min}(Q)$
|
||||
\For{each neighbor $v$ of $u$}
|
||||
\If{$d[u] + w(u, v) < d[v]$} \Comment{If weight through current vertex is lower, update}
|
||||
\State $d[v] \gets d[u] + w(u, v)$
|
||||
\State Update $Q$ with new distance of $v$
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndWhile
|
||||
\State \textbf{Return} distances $d$
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{properties}[]{Characteristics of Dijkstra's Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item \tco{|V|^2} for a simple implementation.
|
||||
\item \tco{(|V| + |E|) \log |V|} using a priority queue.
|
||||
\end{itemize}
|
||||
\item Only works with non-negative edge weights.
|
||||
\item Greedy algorithm that processes vertices in increasing order of distance.
|
||||
\item Common applications include navigation systems and network routing.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{usage}[]{Dijkstra's Algorithm}
|
||||
Dijkstra's algorithm finds the shortest path from a source vertex to all other vertices in a weighted graph (non-negative weights).
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialize:}
|
||||
\begin{itemize}
|
||||
\item Set the distance to the source vertex as 0 and to all other vertices as infinity.
|
||||
\item Mark all vertices as unvisited.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Start from Source:}
|
||||
\begin{itemize}
|
||||
\item Select the unvisited vertex with the smallest tentative distance.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Update Distances:}
|
||||
\begin{itemize}
|
||||
\item For each unvisited neighbor of the current vertex:
|
||||
\begin{itemize}
|
||||
\item Calculate the tentative distance through the current vertex.
|
||||
\item If the calculated distance is smaller than the current distance, update it.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Mark as Visited:}
|
||||
\begin{itemize}
|
||||
\item Mark the current vertex as visited. Once visited, it will not be revisited.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Repeat:}
|
||||
\begin{itemize}
|
||||
\item Repeat steps 2-4 until all vertices are visited or the shortest path to all vertices is determined.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item The algorithm completes when all vertices are visited or when the shortest paths to all reachable vertices are found.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
|
||||
\newpage
|
||||
\subsubsection{Bellman-Ford Algorithm}
|
||||
\begin{definition}[]{Bellman-Ford Algorithm}
|
||||
The \textbf{Bellman-Ford Algorithm} computes shortest paths from a source vertex to all other vertices, allowing for graphs with negative edge weights.
|
||||
\end{definition}
|
||||
|
||||
\begin{algorithm}
|
||||
\caption{Bellman-Ford Algorithm}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{Bellman-Ford}{$G = (V, E), s$} \Comment{$s$ is the source vertex}
|
||||
\State Initialize distances: $d[v] \gets \infty \; \forall v \in V, d[s] \gets 0$
|
||||
\For{$i \gets 1$ to $|V| - 1$} \Comment{Relax all edges $|V| - 1$ times}
|
||||
\For{each edge $(u, v, w(u, v)) \in E$}
|
||||
\If{$d[u] + w(u, v) < d[v]$}
|
||||
\State $d[v] \gets d[u] + w(u, v)$
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndFor
|
||||
\For{each edge $(u, v, w(u, v)) \in E$} \Comment{Check for negative-weight cycles}
|
||||
\If{$d[u] + w(u, v) < d[v]$}
|
||||
\State \textbf{Report Negative Cycle}
|
||||
\EndIf
|
||||
\EndFor
|
||||
\State \Return distances $d$
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{properties}[]{Characteristics of Bellman-Ford Algorithm}
|
||||
\begin{itemize}
|
||||
\item \textbf{Time Complexity:} \tco{|V| \cdot |E|}.
|
||||
\item Can handle graphs with negative edge weights but not graphs with negative weight cycles.
|
||||
\item Used for:
|
||||
\begin{itemize}
|
||||
\item Detecting negative weight cycles.
|
||||
\item Computing shortest paths in graphs where Dijkstra’s algorithm is not applicable.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{usage}[]{Bellman-Ford Algorithm}
|
||||
The Bellman-Ford algorithm finds the shortest path from a source vertex to all other vertices in a weighted graph (handles negative weights).
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Initialize:}
|
||||
\begin{itemize}
|
||||
\item Set the distance to the source vertex as 0 and to all other vertices as infinity.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Relax Edges:}
|
||||
\begin{itemize}
|
||||
\item Repeat for \(V-1\) iterations (where \(V\) is the number of vertices):
|
||||
\begin{itemize}
|
||||
\item For each edge, update the distance to its destination vertex if the distance through the edge is smaller than the current distance.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{Check for Negative Cycles:}
|
||||
\begin{itemize}
|
||||
\item Check all edges to see if a shorter path can still be found. If so, the graph contains a negative-weight cycle.
|
||||
\end{itemize}
|
||||
|
||||
\item \textbf{End:}
|
||||
\begin{itemize}
|
||||
\item If no negative-weight cycle is found, the algorithm outputs the shortest paths.
|
||||
\end{itemize}
|
||||
\end{enumerate}
|
||||
\end{usage}
|
||||
|
||||
|
||||
|
||||
\begin{properties}[]{Comparison of Dijkstra and Bellman-Ford}
|
||||
\begin{center}
|
||||
\begin{tabular}{lcc}
|
||||
\toprule
|
||||
\textbf{Feature} & \textbf{Dijkstra's Algorithm} & \textbf{Bellman-Ford Algorithm} \\
|
||||
\midrule
|
||||
Handles Negative Weights & No & Yes \\
|
||||
Detects Negative Cycles & No & Yes \\
|
||||
Time Complexity & \tco{(|V| + |E|) \log |V|} & \tco{|V| \cdot |E|} \\
|
||||
Algorithm Type & Greedy & Dynamic Programming \\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\end{center}
|
||||
\end{properties}
|
||||
93
algorithms-and-datastructures/parts/intro.tex
Normal file
93
algorithms-and-datastructures/parts/intro.tex
Normal file
@@ -0,0 +1,93 @@
|
||||
\newsection
|
||||
\section{Introduction}
|
||||
|
||||
\subsection{Sufficiency \& Necessity}
|
||||
\begin{definition}[]{Sufficiency}
|
||||
A condition $P$ is called \textit{sufficient} for $Q$ if knowing $P$ is true is enough evidence to conclude that $Q$ is true.
|
||||
|
||||
This is equivalent to saying $Q \Rightarrow P$.
|
||||
\end{definition}
|
||||
|
||||
\begin{definition}[]{Necessity}
|
||||
A condition $P$ is called \textit{necessary} for $Q$ if $Q$ cannot occur unless $P$ is true, but doesn't imply that $Q$ is true, only that it is false if $P$ is false.
|
||||
|
||||
This is equivalent to saying $P \Rightarrow Q$
|
||||
\end{definition}
|
||||
|
||||
\subsection{Asymptotic Growth}
|
||||
$f$ grows asymptotically slower than $g$ if $\displaystyle\lim_{m \rightarrow \infty} \frac{f(m)}{g(m)} = 0$.
|
||||
We can remark that $f$ is upper-bounded by $g$, thus $f \leq$\tco{g} and we can say $g$ is lower bounded by $f$, thus $g \geq$ \tcl{f}.
|
||||
If two functions grow equally fast asymptotically, \tct{f} $= g$
|
||||
|
||||
|
||||
\subsection{Runtime evaluation}
|
||||
Identify the basic operations (usually given by the task), then count how often they are called and express that as a function in $n$.
|
||||
It is easier to note that in sum notation, then simplify that sum notation into a formula not containing any summation symbols.
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\subsection{Tips for Converting Summation Notation into Summation-Free Notation}
|
||||
|
||||
\subsubsection{Identify the Pattern:}
|
||||
\begin{itemize}
|
||||
\item Examine the summand.
|
||||
\item Look for patterns related to the index variable (usually $i$, $j$, etc.). Is it a linear function, a power of $i$, a combination?
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\subsubsection{Arithmetic Series Formula}
|
||||
If the summand is a simple arithmetic progression (e.g., $a + bi$ where $a$ and $b$ are constants), use the formula:
|
||||
\[
|
||||
\sum_{i=m}^{n} (a + bi) = (n - m + 1)\left(a + b\frac{m + n}{2}\right)
|
||||
\]
|
||||
|
||||
|
||||
\subsubsection{Power Rule for Sums}
|
||||
\begin{itemize}
|
||||
\item For sums involving powers of $i$, you can use the following pattern:
|
||||
\[
|
||||
\sum_{i=1}^{n} i^k = \frac{n^{k+1}}{k+1}
|
||||
\]
|
||||
\item Remember that this rule only applies when the index starts at 1.
|
||||
\end{itemize}
|
||||
|
||||
|
||||
\subsubsection{Telescoping Series}
|
||||
Look for terms in consecutive elements of the summand that cancel out, leaving a simpler expression after expanding. This is particularly helpful for fractions and ratios.
|
||||
|
||||
|
||||
\subsubsection{Geometric Series Formula}
|
||||
For sums involving constant ratios (e.g., $a \cdot r^i$ where $r$ is the common ratio), use:
|
||||
\[
|
||||
\sum_{i=0}^{n} a \cdot r^i = a \frac{1 - r^{n+1}}{1-r}
|
||||
\]
|
||||
|
||||
|
||||
\subsubsection{Gaussian Formula}
|
||||
If $S$ is an arithmetic series with $n$ terms, then $S = \frac{n}{2} * (a + 1)$
|
||||
|
||||
|
||||
\subsubsection{Examples}
|
||||
The only other way (other than learning these tips) in which you are going to get better at this is by parctising.
|
||||
Work through examples, starting with simpler ones and moving towards more complex expressions.
|
||||
|
||||
|
||||
\fhlc{Aquamarine}{Example:}
|
||||
|
||||
Let's convert the summation: $\sum_{i=1}^{5} i$
|
||||
|
||||
\begin{enumerate}
|
||||
\item \textbf{Pattern:} The summand is simply $i$, which represents a linear arithmetic progression.
|
||||
|
||||
\item \textbf{Arithmetic Series Formula:} Applying the formula with $a = 1$, $b = 1$, $m = 1$, and $n = 5$:
|
||||
\[
|
||||
\sum_{i=1}^{5} i = (5 - 1 + 1)\left(1 + 1 \cdot \frac{1 + 5}{2}\right) = 5 \cdot 3 = 15
|
||||
\]
|
||||
\end{enumerate}
|
||||
|
||||
Therefore, the summation evaluates to $15$.
|
||||
|
||||
\subsection{Specific examples}
|
||||
\begin{align*}
|
||||
\frac{n}{\log(n)} \geq \Omega(\sqrt{n}) \Leftrightarrow \sqrt{n} \leq \text{\tco{\frac{n}{\log(n)}}}
|
||||
\end{align*}
|
||||
@@ -0,0 +1,175 @@
|
||||
\newpage
|
||||
\subsection{Sort}
|
||||
Sorted data proved to be much quicker to search through, but how do we sort efficiently?
|
||||
|
||||
First, how to check if an array is sorted. This can be done in linear time:
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{\textsc{sorted(A)}}
|
||||
\begin{algorithmic}[1]
|
||||
\For{$i \gets 1, 2, \ldots, n - 1$}
|
||||
\If{$A[i] > A[i + 1]$} \Return false \Comment{Item is unsorted}
|
||||
\EndIf
|
||||
\EndFor
|
||||
\State \Return true
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
\tc{n}
|
||||
|
||||
|
||||
\subsubsection{Bubble Sort}
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{\textsc{bubbleSort(A)}}
|
||||
\begin{algorithmic}[1]
|
||||
\For{$i \gets 1, 2, \ldots, n$}
|
||||
\For{$j \gets 1, 2, \ldots, n$}
|
||||
\If{$A[j] > A[j + 1]$}
|
||||
\State exchange $A[j]$ and $A[j + 1]$ \Comment{Causes the element to ``bubble up''}
|
||||
\EndIf
|
||||
\EndFor
|
||||
\EndFor
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
\tc{n^2}
|
||||
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\subsubsection{Selection Sort}
|
||||
The concept for this algorithm is selecting an element (that being the largest one for each iteration, where the iteration variable determines the upper bound for the indices of the array) and swapping it with the last item (thus moving the largest elements up, whilst moving the smallest items down). This algorithm uses a similar concept to bubble sort, but saves some runtime compared to it, by reducing the number of comparisons having to be made.
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{\textsc{selectionSort(A)}}
|
||||
\begin{algorithmic}[1]
|
||||
\For{$i \gets n, n - 1, \ldots, 1$}
|
||||
\State $k \gets$ Index of maximum element in $A[1, \ldots, i]$ \Comment{Runtime: $O(n)$}
|
||||
\State exchange $A[k]$ and $A[i]$
|
||||
\EndFor
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
\tc{n^2} because we have runtime \tco{n} for the search of the maximal entry and run through the loop \tco{n} times, but we have saved some runtime elsewhere, which is not visible in the asymptotic time complexity compared to bubble sort.
|
||||
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\newpage
|
||||
\subsubsection{Insertion Sort}
|
||||
\begin{definition}[]{Insertion Sort}
|
||||
Insertion Sort is a simple sorting algorithm that builds the final sorted array (or list) one element at a time. It iteratively takes one element from the input, finds its correct position in the sorted portion, and inserts it there. The algorithm starts with the first element, assuming it is sorted. It then picks the next element and inserts it into its correct position relative to the sorted portion. This process is repeated for all elements until the entire list is sorted. At each iteration step, the algorithm moves all elements that are larger than the currently picked element to the right by one, i.e. an element $A[i]$ to $A[i + 1]$
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance}
|
||||
\begin{itemize}
|
||||
\item \textbf{Efficiency:} Works well for small datasets or nearly sorted arrays.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Best case (already sorted): \tcl{n\log(n)}
|
||||
\item Worst case (reversed order): \tco{n^2}
|
||||
\item Average case: \tct{n^2}
|
||||
\end{itemize}
|
||||
\item \textbf{Limitations:} Inefficient on large datasets due to its \tct{n^2} time complexity and requires additional effort for linked list implementations.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{\textsc{insertionSort(A)}}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{InsertionSort}{$A$}
|
||||
\For{$i \gets 2$ to $n$} \Comment{Iterate over the array}
|
||||
\State $key \gets A[i]$ \Comment{Element to be inserted}
|
||||
\State $j \gets i - 1$
|
||||
\While{$j > 0$ and $A[j] > key$}
|
||||
\State $A[j+1] \gets A[j]$ \Comment{Shift elements}
|
||||
\State $j \gets j - 1$
|
||||
\EndWhile
|
||||
\State $A[j+1] \gets key$ \Comment{Insert element}
|
||||
\EndFor
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
|
||||
|
||||
|
||||
% ────────────────────────────────────────────────────────────────────
|
||||
\newpage
|
||||
\subsubsection{Merge Sort}
|
||||
\begin{definition}[]{Definition of Merge Sort}
|
||||
Merge Sort is a divide-and-conquer algorithm that splits the input array into two halves, recursively sorts each half, and then merges the two sorted halves into a single sorted array. This process continues until the base case of a single element or an empty array is reached, as these are inherently sorted.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance of Merge Sort}
|
||||
\begin{itemize}
|
||||
\item \textbf{Efficiency:} Suitable for large datasets due to its predictable time complexity.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Best case: \tcl{n \log n}
|
||||
\item Worst case: \tco{n \log n}
|
||||
\item Average case: \tct{n \log n}
|
||||
\end{itemize}
|
||||
\item \textbf{Space Complexity:} Requires additional memory for temporary arrays, typically \tct{n}.
|
||||
\item \textbf{Limitations:} Not in-place, and memory overhead can be significant for large datasets.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Merge Sort}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{MergeSort}{$A[1..n], l, r$}
|
||||
\If{$l \geq r$}
|
||||
\State \Return $A$ \Comment{Base case: already sorted}
|
||||
\EndIf
|
||||
\State $m \gets \floor{(l + r)/2}$
|
||||
\State $\Call{MergeSort}{A, l, m}$ \Comment{Recursive sort on left half}
|
||||
\State $\Call{MergeSort}{A, m + 1, r}$ \Comment{Recursive sort on right half}
|
||||
\State \Call{Merge}{$A, l, m, r$}
|
||||
\EndProcedure
|
||||
|
||||
\Procedure{Merge}{$A[1..n], l, m, r$} \Comment{Runtime: \tco{n}}
|
||||
\State $result \gets$ new array of size $r - l + 1$
|
||||
\State $i \gets l$
|
||||
\State $j \gets m + 1$
|
||||
\State $k \gets 1$
|
||||
\While{$i \leq m$ and $j \leq r$}
|
||||
\If{$A[i] \leq A[j]$}
|
||||
\State $result[k] \gets A[i]$
|
||||
\State $i \gets i + 1$
|
||||
\Else
|
||||
\State $result[k] \gets A[j]$
|
||||
\State $j \gets j + 1$
|
||||
\EndIf
|
||||
\State $k \gets k + 1$
|
||||
\EndWhile
|
||||
\State Append remaining elements of left / right site to $result$
|
||||
\State Copy $result$ to $A[l, \ldots, r]$
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
\begin{table}[h!]
|
||||
\centering
|
||||
\begin{tabular}{lccccc}
|
||||
\toprule
|
||||
\textbf{Algorithm} & \textbf{Comparisons} & \textbf{Operations} & \textbf{Space Complexity} & \textbf{Locality} & \textbf{Time complexity}\\
|
||||
\midrule
|
||||
\textit{Bubble-Sort} & \tco{n^2} & \tco{n^2} & \tco{1} & good & \tco{n^2}\\
|
||||
\textit{Selection-Sort} & \tco{n^2} & \tco{n} & \tco{1} & good & \tco{n^2}\\
|
||||
\textit{Insertion-Sort} & \tco{n \cdot \log(n)} & \tco{n^2} & \tco{1} & good & \tco{n^2}\\
|
||||
\textit{Merge-Sort} & \tco{n\cdot \log(n)} & \tco{n \cdot \log(n)} & \tco{n} & good & \tco{n \cdot \log(n)}\\
|
||||
\bottomrule
|
||||
\end{tabular}
|
||||
\caption{Comparison of four comparison-based sorting algorithms discussed in the lecture. Operations designates the number of write operations in RAM}
|
||||
\end{table}
|
||||
|
||||
|
||||
130
algorithms-and-datastructures/parts/search/heap-sort.tex
Normal file
130
algorithms-and-datastructures/parts/search/heap-sort.tex
Normal file
@@ -0,0 +1,130 @@
|
||||
\newpage
|
||||
\subsubsection{Heap Sort}
|
||||
\begin{definition}[]{Heap Sort}
|
||||
Heap Sort is a comparison-based sorting algorithm that uses a binary heap data structure. It builds a max-heap (or min-heap) from the input array and repeatedly extracts the largest (or smallest) element to place it in the correct position in the sorted array.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance}
|
||||
\begin{itemize}
|
||||
\item \textbf{Efficiency:} Excellent for in-place sorting with predictable performance.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Best case: \tcl{n \log n}
|
||||
\item Worst case: \tco{n \log n}
|
||||
\item Average case: \tct{n \log n}
|
||||
\end{itemize}
|
||||
\item \textbf{Space Complexity:} In-place sorting requires \tct{1} additional space.
|
||||
\item \textbf{Limitations:} Inefficient compared to Quick Sort for most practical datasets.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Heap Sort}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{HeapSort}{$A$}
|
||||
\State $H \gets \Call{Heapify}{A}$
|
||||
\For{$i \gets \text{length}(A)$ to $2$}
|
||||
\State $A[i] \gets \Call{ExtractMax}{A}$
|
||||
\EndFor
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
The lecture does not cover the implementation of a heap tree. See the specific section \ref{sec:heap-trees} on Heap-Trees
|
||||
|
||||
|
||||
\newpage
|
||||
\subsubsection{Bucket Sort}
|
||||
\begin{definition}[]{Bucket Sort}
|
||||
Bucket Sort is a distribution-based sorting algorithm that divides the input into a fixed number of buckets, sorts the elements within each bucket (using another sorting algorithm, typically Insertion Sort), and then concatenates the buckets to produce the sorted array.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance}
|
||||
\begin{itemize}
|
||||
\item \textbf{Efficiency:} Performs well for uniformly distributed datasets.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Best case: \tcl{n + k} (for uniform distribution and $k$ buckets)
|
||||
\item Worst case: \tco{n^2} (when all elements fall into a single bucket)
|
||||
\item Average case: \tct{n + k}
|
||||
\end{itemize}
|
||||
\item \textbf{Space Complexity:} Requires \tct{n + k} additional space.
|
||||
\item \textbf{Limitations:} Performance depends on the choice of bucket size and distribution of input elements.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Bucket Sort}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{BucketSort}{$A, k$}
|
||||
\State $B[1..n] \gets [0, 0, \ldots, 0]$
|
||||
\For{$j \gets 1, 2, \ldots, n$}
|
||||
\State $B[A[j]] \gets B[A[j]] + 1$ \Comment{Count in $B[i]$ how many times $i$ occurs}
|
||||
\EndFor
|
||||
\State $k \gets 1$
|
||||
\For{$i \gets 1, 2, \ldots, n$}
|
||||
\State $A[k, \ldots, k + B[i] - 1] \gets [i, i, \ldots, i]$ \Comment {Write $B[i]$ times the value $i$ into $A$}
|
||||
\State $k \gets k + i$ \Comment{$A$ is filled until position $k - 1$}
|
||||
\EndFor
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
|
||||
\newpage
|
||||
\subsection{Heap trees}
|
||||
\label{sec:heap-trees}
|
||||
\subsubsection{Min/Max-Heap}
|
||||
\begin{definition}[]{Min-/Max-Heap}
|
||||
A Min-Heap is a complete binary tree where the value of each node is less than or equal to the values of its children.
|
||||
Conversely, a Max-Heap is a complete binary tree where the value of each node is greater than or equal to the values of its children.
|
||||
In the characteristics below, $A$ is an array storing the value of a element
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics}
|
||||
\begin{itemize}
|
||||
\item \textbf{Heap Property:}
|
||||
\begin{itemize}
|
||||
\item Min-Heap: $A[parent] \leq A[child]$ for all nodes.
|
||||
\item Max-Heap: $A[parent] \geq A[child]$ for all nodes.
|
||||
\end{itemize}
|
||||
\item \textbf{Operations:} Both Min-Heaps and Max-Heaps support:
|
||||
\begin{itemize}
|
||||
\item \textbf{Insert:} Add an element to the heap and adjust to maintain the heap property.
|
||||
\item \textbf{Extract Min/Max:} Remove the root element (minimum or maximum), replace it with the last element (bottom right most element), and adjust the heap.
|
||||
\end{itemize}
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Insert: \tct{\log n}.
|
||||
\item Extract Min/Max: \tct{\log n}.
|
||||
\item Build Heap: \tct{n}.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{example}[]{Min-Heap}
|
||||
The following illustrates a Min-Heap with seven elements:
|
||||
\begin{center}
|
||||
\begin{forest}
|
||||
for tree={
|
||||
circle, draw, fill=blue!20, minimum size=10mm, inner sep=0pt, % Node style
|
||||
s sep=15mm, % Sibling separation
|
||||
l sep=15mm % Level separation
|
||||
}
|
||||
[2
|
||||
[4
|
||||
[8]
|
||||
[10]
|
||||
]
|
||||
[6
|
||||
[14]
|
||||
[18]
|
||||
]
|
||||
]
|
||||
\end{forest}
|
||||
\end{center}
|
||||
\end{example}
|
||||
59
algorithms-and-datastructures/parts/search/quick-sort.tex
Normal file
59
algorithms-and-datastructures/parts/search/quick-sort.tex
Normal file
@@ -0,0 +1,59 @@
|
||||
\newpage
|
||||
\subsubsection{Quick Sort}
|
||||
\begin{definition}[]{Quick Sort}
|
||||
Quick Sort is a divide-and-conquer algorithm that selects a pivot element from the array, partitions the other elements into two subarrays according to whether they are less than or greater than the pivot, and then recursively sorts the subarrays. The process continues until the base case of an empty or single-element array is reached.
|
||||
\end{definition}
|
||||
|
||||
\begin{properties}[]{Characteristics and Performance}
|
||||
\begin{itemize}
|
||||
\item \textbf{Efficiency:} Performs well on average and for in-place sorting but can degrade on specific inputs.
|
||||
\item \textbf{Time Complexity:}
|
||||
\begin{itemize}
|
||||
\item Best case: \tcl{n \log n}
|
||||
\item Worst case: \tco{n^2} (when the pivot is poorly chosen)
|
||||
\item Average case: \tct{n \log n}
|
||||
\end{itemize}
|
||||
\item \textbf{Space Complexity:} In-place sorting typically requires \tct{\log n} additional space for recursion.
|
||||
\item \textbf{Limitations:} Performance depends heavily on pivot selection.
|
||||
\end{itemize}
|
||||
\end{properties}
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{Quick Sort}
|
||||
\begin{algorithmic}[1]
|
||||
\Procedure{QuickSort}{$A, l, r$}
|
||||
\If{$l < r$}
|
||||
\State $k \gets \Call{Partition}{A, l, r}$
|
||||
\State \Call{QuickSort}{$A, l, k - 1$} \Comment{Sort left group}
|
||||
\State \Call{QuickSort}{$A, k + 1, r$} \Comment{Sort right group}
|
||||
\EndIf
|
||||
\EndProcedure
|
||||
|
||||
\Procedure{Partition}{$A, l, r$}
|
||||
\State $i \gets l$
|
||||
\State $j \gets r - 1$
|
||||
\State $p \gets A[r]$
|
||||
\While{$i > j$} \Comment{Loop ends when $i$ and $j$ meet}
|
||||
\While{$i < r$ and $A[i] \leq p$}
|
||||
\State $i \gets i + 1$ \Comment{Search next element for left group}
|
||||
\EndWhile
|
||||
|
||||
\While{$i > l$ and $A[j] > p$}
|
||||
\State $j \gets j - 1$ \Comment{Search next element for right group}
|
||||
\EndWhile
|
||||
|
||||
\If{$i \leq j$}
|
||||
\State Exchange $A[i]$ and $A[j]$
|
||||
\EndIf
|
||||
\EndWhile
|
||||
\State Swap $A[i]$ and $A[r]$ \Comment{Move pivot element to correct position}
|
||||
\State \Return $i$
|
||||
\EndProcedure
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
|
||||
The tests $i < r$ and $j > l$ in the while loops in \textsc{Partition} catch the cases where there are no elements that can be added to the left or right group.
|
||||
|
||||
The correct position for the pivot element is $k = j + 1 = i$, since all elements on the left hand side are smaller and all on the right hand side larger than $p$.
|
||||
35
algorithms-and-datastructures/parts/search/search.tex
Normal file
35
algorithms-and-datastructures/parts/search/search.tex
Normal file
@@ -0,0 +1,35 @@
|
||||
\newsection
|
||||
\section{Search \& Sort}
|
||||
\subsection{Search}
|
||||
\subsubsection{Linear search}
|
||||
Linear search, as the name implies, searches through the entire array and has linear runtime, i.e. $\Theta(n)$.
|
||||
|
||||
It works by simply iterating over an iterable object (usually array) and returns the first element (it can also be modified to return \textit{all} elements that match the search pattern) where the search pattern is matched.
|
||||
|
||||
\tc{n}
|
||||
|
||||
\subsubsection{Binary search}
|
||||
If we want to search in a sorted array, however, we can use what is known as binary array, improving our runtime to logarithmic, i.e. $\Theta(\log(n))$.
|
||||
It works using divide and conquer, hence it picks a pivot in the middle of the array (at $\floor{\frac{n}{2}}$) and there checks if the value is bigger than our search query $b$, i.e. if $A[m] < b$. This is repeated, until we have homed in on $b$. Pseudo-Code:
|
||||
|
||||
\begin{algorithm}
|
||||
\begin{spacing}{1.2}
|
||||
\caption{\textsc{binarySearch(b)}}
|
||||
\begin{algorithmic}[1]
|
||||
\State $l \gets 1$, $r \gets n$ \Comment{\textit{Left and right bound}}
|
||||
\While{$l \leq r$}
|
||||
\State $m \gets \floor{\frac{l + r}{2}}$
|
||||
\If{$A[m] = b$} \Return m \Comment{\textit{Element found}}
|
||||
\ElsIf{$A[m] > b$} $r \gets m - 1$ \Comment{\textit{Search to the left}}
|
||||
\Else \hspace{0.2em} $l \gets m + 1$ \Comment{\textit{Search to the right}}
|
||||
\EndIf
|
||||
\EndWhile
|
||||
\State \Return "Not found"
|
||||
\end{algorithmic}
|
||||
\end{spacing}
|
||||
\end{algorithm}
|
||||
\tc{\log(n)}
|
||||
|
||||
Proving runtime lower bounds (worst case runtime) for this kind of algorithm is done using a decision tree. It in fact is $\Omega(\log(n))$
|
||||
|
||||
% INFO: If =0, then there is an issue with math environment in the algorithm
|
||||
Reference in New Issue
Block a user