Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
D
ds2-notes
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Model registry
Analyze
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
datovky
ds2-notes
Commits
e62e5242
Commit
e62e5242
authored
3 years ago
by
Václav Končický
Browse files
Options
Downloads
Patches
Plain Diff
Dynamization: Addition of a picture and rewrite
parent
3a9cc9de
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
vk-dynamic/Makefile
+1
-0
1 addition, 0 deletions
vk-dynamic/Makefile
vk-dynamic/dynamic.tex
+60
-31
60 additions, 31 deletions
vk-dynamic/dynamic.tex
vk-dynamic/semidynamic-insert.asy
+48
-0
48 additions, 0 deletions
vk-dynamic/semidynamic-insert.asy
with
109 additions
and
31 deletions
vk-dynamic/Makefile
+
1
−
0
View file @
e62e5242
TOP
=
..
PICS
=
semidynamic-insert
include
../Makerules
This diff is collapsed.
Click to expand it.
vk-dynamic/dynamic.tex
+
60
−
31
View file @
e62e5242
...
...
@@ -7,46 +7,78 @@
A data structure can be, depending on what operations are supported:
\list
{
o
}
\
tight
list
{
o
}
\:
{
\I
static
}
if all operations after building the structure do not alter the
data,
\:
{
\I
semidynamic
}
if data insertion is possible as an operation,
\:
{
\I
fully dynamic
}
if deletion of inserted data is allowed along with insertion.
\endlist
Static data structures are useful if we know the structure beforehand. In many
cases, static data structures are simpler and faster than their dynamic
alternatives.
A sorted array is a typical example of a static data structure to store an
ordered set of
$
n
$
elements. Its supported operations are
$
\alg
{
Index
}
(
i
)
$
which simply returns
$
i
$
-th smallest element in constant time, and
$
\alg
{
Find
}
(
x
)
$
which finds
$
x
$
and its index
$
i
$
in the array using binary
search in time
$
\O
(
\log
n
)
$
.
However, if we wish to insert a new element to already existing sorted array,
this operation will take
$
\Omega
(
n
)
$
-- we must shift the elements to keep
the sorted order. In order to have a fast insertion, we may decide to use a
different dynamic data structure, such as a binary search tree. But then the
operation
\alg
{
Index
}
slows down to logarithmic time.
In this chapter we will look at techniques of
{
\I
dynamization
}
--
transformation of a static data structure into a (semi)dynamic data structure.
As we have seen with a sorted array, the simple and straight-forward attempts
often lead to slow operations. Therefore, we want to dynamize data structures
in such way that the operations stay reasonably fast.
\section
{
Structure rebuilding
}
Consider a data structure with
$
n
$
elements such that modifying it may cause
severe problems that are too hard to fix easily. Therefore, we give up on
fixing it and rebuild it completely anew. If we do this after
$
\Theta
(
n
)
$
operations, we can amortize the cost of rebuild into those operations. Let us
look at such cases.
severe problems that are too hard to fix easily. In such case, we give up on
fixing it and rebuild it completely anew.
If building such structure takes time
$
\O
(
f
(
n
))
$
and we perform the rebuild
after
$
\Theta
(
n
)
$
modifying operations, we can amortize the cost of rebuild
into the operations. This adds an amortized factor
$
\O
(
f
(
n
)/
n
)
$
to
their time complexity, given that
$
n
$
does not change asymptotically between
the rebuilds.
\examples
\list
{
o
}
\:
An array is a structure with limited capacity
$
c
$
. While it is dynamic (we can
insert or remove elements
from
the end), we cannot insert new elements
insert or remove elements
at
the end), we cannot insert new elements
indefinitely. Once we run out of space, we build a new structure with capacity
$
2
c
$
and elements from the old structure.
Since we insert at least
$
\Theta
(
n
)
$
elements to reach the limit from a freshly
rebuilt structure, this amortizes to
$
\O
(
1
)
$
amortized time per an insertion,
as we can rebuild an array in time
$
\O
(
n
)
$
.
Another example of such structure is an
$
y
$
-fast tree. It is parametrized by
\:
Another example of such structure is an
$
y
$
-fast trie. It is parametrized by
block size required to be
$
\Theta
(
\log
n
)
$
for good time complexity. If we let
$
n
$
change enough such that
$
\log
n
$
changes asymptotically, everything breaks.
We can save this by rebuilding the tree before this happens
$
n
$
changes enough,
which happens after
$
\Omega
(
n
)
$
operations.
$
n
$
change enough such that
$
\log
n
$
changes asymptotically, the proven time
complexity no longer holds.
We can save this by rebuilding the trie once
$
n
$
changes by a constant factor (then
$
\log
n
$
changes by a constant additively).
This happens no sooner than after
$
\Theta
(
n
)
$
insertions or deletions.
\:
Consider a data structure where instead of proper deletion of elements we just
replace them with ``tombstones''. When we run a query, we ignore them. After
enough deletions, most of the structure becomes filled with tombstones, leaving
too little space for proper elements and slowing down the queries. Once again,
too little space for proper elements and slowing down the queries.
Once again,
the fix is simple -- once at least
$
n
/
2
$
of elements are tombstones, we rebuild
the structure. To reach
$
n
/
2
$
tombstones we need to delete
$
\Theta
(
n
)
$
elements. If a rebuild takes
$
\Theta
(
n
)
$
time, this again amortizes.
elements.
\endlist
\subsection
{
Local rebuilding
}
...
...
@@ -201,7 +233,7 @@ decomposable search problem $f$ and the resulting dynamic data structure $D$:}
\tightlist
{
o
}
\:
$
B
_
S
(
n
)
$
is time complexity of building
$
S
$
,
\:
$
Q
_
S
(
n
)
$
is time complexity of query on
$
S
$
,
\:
$
S
_
S
(
n
)
$
is the space complexity of
$
S
$
.
\:
$
S
_
S
(
n
)
$
is the space complexity of
$
S
$
,
\medskip
\:
$
Q
_
D
(
n
)
$
is time complexity of query on
$
D
$
,
\:
$
S
_
D
(
n
)
$
is the space complexity of
$
D
$
,
...
...
@@ -210,19 +242,16 @@ decomposable search problem $f$ and the resulting dynamic data structure $D$:}
We assume that
$
Q
_
S
(
n
)
$
,
$
B
_
S
(
n
)/
n
$
,
$
S
_
S
(
n
)/
n
$
are all non-decreasing functions.
We decompose the set
$
X
$
into blocks
$
B
_
i
$
such that
$
|B
_
i|
\in
\{
0
,
2
^
i
\}
$
such that
$
\bigcup
_
i B
_
i
=
X
$
and
$
B
_
i
\cap
B
_
j
=
\emptyset
$
for all
$
i
\neq
j
$
. Let
$
|X|
=
n
$
. Since
$
n
=
\sum
_
i n
_
i
2
^
i
$
, its binary representation
uniquely determines the block structure. Thus, the total number of blocks is at
most
$
\log
n
$
.
We decompose the set
$
X
$
into blocks
$
B
_
i
$
such that
$
|B
_
i|
\in
\{
0
,
2
^
i
\}
$
,
$
\bigcup
_
i B
_
i
=
X
$
and
$
B
_
i
\cap
B
_
j
=
\emptyset
$
for all
$
i
\neq
j
$
. Let
$
|X|
=
n
$
. Since
$
n
=
\sum
_
i n
_
i
2
^
i
$
for
$
n
_
i
\in
\{
0
,
1
\}
$
, its
binary representation uniquely determines the block structure. Thus, the total
number of blocks is at most
$
\log
n
$
.
For each nonempty block
$
B
_
i
$
we build a static structure
$
S
$
of size
$
2
^
i
$
.
Since
$
f
$
is decomposable, a query on the structure will run queries on each
block, and then combine them using
$
\sqcup
$
:
$$
f
(
q, x
)
=
f
(
q, B
_
0
)
\sqcup
f
(
q, B
_
1
)
\sqcup
\dots
\sqcup
f
(
q, B
_
i
)
.
$$
TODO image
\lemma
{$
Q
_
D
(
n
)
\in
\O
(
Q
_
s
(
n
)
\cdot
\log
n
)
$
.
}
\proof
...
...
@@ -231,8 +260,6 @@ constant time, $Q_D(n) = \sum_{i: B_i \neq \emptyset} Q_S(2^i) + \O(1)$. Since $
\leq
Q
_
S
(
n
)
$
for all
$
x
\leq
n
$
, the inequality holds.
\qed
Now let us calculate the space complexity of
$
D
$
.
\lemma
{$
S
_
D
(
n
)
\in
\O
(
S
_
S
(
n
))
$
.
}
\proof
...
...
@@ -246,7 +273,7 @@ $$
\leq
{
S
_
S
(
n
)
\over
n
}
\cdot
\sum
_{
i
=
0
}^{
\log
n
}
2
^
i
\leq
{
S
_
S
(
n
)
\over
n
}
\cdot
n.
$$
\qed
\qed
math
It might be advantageous to store the elements in each block separately so that
we do not have to inspect the static structure and extract the elements from
...
...
@@ -258,7 +285,9 @@ with elements $B_0 \cup B_1 \cup \dots \cup B_{i-1} \cup \{x\}$. This new block
has
$
1
+
\sum
_{
j
=
0
}^{
i
-
1
}
2
^
j
=
2
^
i
$
elements, which is the required size for
$
B
_
i
$
. At last, we remove all blocks
$
B
_
0
,
\dots
, B
_{
i
-
1
}$
and add
$
B
_
i
$
.
TODO image
\figure
{
semidynamic-insert.pdf
}{}{
Insertion of
$
x
$
in the structure for
$
n
=
23
$
, blocks
$
\{
x
\}
$
,
$
B
_
0
$
to
$
B
_
2
$
merge to a new block
$
B
_
3
$
, block
$
B
_
4
$
is
unchanged.
}
\lemma
{$
\bar
I
_
D
(
n
)
\in
\O
(
B
_
S
(
n
)/
n
\cdot
\log
n
)
$
.
}
...
...
@@ -268,7 +297,7 @@ TODO image
As this function is non-decreasing, we can lower bound it by
$
B
_
S
(
n
)
/
n
$
. However, one element can participate in
$
\log
n
$
rebuilds during
the structure life. Therefore, each element needs to store up cost
$
\log
n
\cdot
B
_
S
(
n
)
/
n
$
to pay off all rebuilds.
\cdot
B
_
S
(
n
)
/
n
$
to pay off all rebuilds.
\qed
}
\theorem
{
...
...
@@ -278,10 +307,14 @@ Then there exists a semidynamic data structure $D$ answering $f$ with parameters
\tightlist
{
o
}
\:
$
Q
_
D
(
n
)
\in
\O
(
Q
_
S
(
n
)
\cdot
\log
_
n
)
$
,
\:
$
S
_
D
(
n
)
\in
\O
(
S
_
S
(
n
))
$
,
\:
$
\bar
I
_
D
(
n
)
\in
\O
(
B
_
S
(
n
)/
n
\cdot
\log
n
)
$
.
\:
$
\bar
I
_
D
(
n
)
\in
\O
(
B
_
S
(
n
)/
n
\cdot
\log
n
)
$
amortized
.
\endlist
}
In general, the bound for insertion is not tight. If
$
B
_
S
(
n
)
=
\O
(
n
^
\varepsilon
)
$
for
$
\varepsilon
>
1
$
, the logarithmic factor is dominated
and
$
\bar
I
_
D
(
n
)
\in
\O
(
n
^
\varepsilon
)
$
.
\example
If we use a sorted array using binary search to search elements in a static
...
...
@@ -295,10 +328,6 @@ We can speed up insertion time. Instead of building the list anew, we can merge
the lists in
$
\Theta
(
n
)
$
time, therefore speeding up insertion to
$
\O
(
\log
n
)
$
amortized.
In general, the bound for insertion is not tight. If
$
B
_
S
(
n
)
=
\O
(
n
^
\varepsilon
)
$
for
$
\varepsilon
>
1
$
, the logarithmic factor is dominated
and
$
\bar
I
_
D
(
n
)
\in
\O
(
n
^
\varepsilon
)
$
.
\subsection
{
Worst-case semidynamization
}
So far we have created a data structure that acts well in the long run, but one
...
...
This diff is collapsed.
Click to expand it.
vk-dynamic/semidynamic-insert.asy
0 → 100644
+
48
−
0
View file @
e62e5242
import ads;
int[] block_indices = {0,0,1,2,4};
real[] block_offs;
real[] block_widths;
real w = 0.4;
real h = 0.4;
real s = 0.1;
real draw_block(real offset, int ypos, int index) {
real width = 2^index * w;
draw(box((offset, ypos), (offset+width, ypos+h)), thin);
return width;
}
string b_i(int i) {
return "\eightrm $B_" + string(i) + "$";
}
int prev_i = 0;
real offset = -s;
for (int i : block_indices) {
offset += s;
if (i == 4) {
offset += 3*s;
}
real width = draw_block(offset, 0, i);
block_offs.push(offset);
block_widths.push(width);
offset += width;
prev_i = i;
}
for (int i = 0; i < 5; ++i) {
real x = block_offs[i] + block_widths[i]/2;
string name;
if (i > 0)
name = b_i(block_indices[i]);
else
name = "$x$";
label(name, (x, h/2));
draw((x, -0.1) -- (x, -1+h+0.1), thin, e_arrow);
}
real width = draw_block(0, -1, 3);
label(b_i(3), (width/2, h/2 - 1));
real width2 = draw_block(block_offs[4], -1, 4);
label(b_i(4), (block_offs[4] + block_widths[4]/2, h/2 - 1));
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment