Skip to content

Commit 80c8030

Browse files
ClémentClément
authored andcommitted
First notes on AVL tree.
1 parent d1a987a commit 80c8030

5 files changed

Lines changed: 355 additions & 0 deletions

File tree

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
public class AVLTree<T> where T : IComparable<T>
5+
{
6+
private class Node
7+
{
8+
public T Data { get; set; }
9+
public Node left;
10+
public Node right;
11+
private int height;
12+
public int Height
13+
{
14+
get { return height; }
15+
set
16+
{
17+
if (value >= 0)
18+
height = value;
19+
else
20+
throw new ApplicationException("TreeNode height can't be < 0");
21+
}
22+
}
23+
24+
public Node(T d = default(T), Node leftP = null, Node rightP = null, int heightP = 0)
25+
{
26+
Data = d;
27+
left = leftP;
28+
right = rightP;
29+
Height = heightP;
30+
}
31+
32+
33+
public override string ToString()
34+
{
35+
return Data.ToString();
36+
}
37+
}
38+
39+
private Node root;
40+
41+
public AVLTree()
42+
{
43+
root = null;
44+
}
45+
public void Clear()
46+
{
47+
root = null;
48+
}
49+
private int GetHeight(Node nodeP)
50+
{
51+
if (nodeP == null)
52+
return -1;
53+
else
54+
return nodeP.Height;
55+
}
56+
57+
public void Insert(T valueP)
58+
{
59+
root = Insert(valueP, root);
60+
}
61+
62+
/*
63+
* Before
64+
* nodeTop --> A
65+
* / \
66+
* nodeLeft--> B C
67+
* / \
68+
* D E <-- nodeLeft.right
69+
*
70+
* After
71+
* B
72+
* / \
73+
* D A
74+
* / \
75+
* E C
76+
*/
77+
private Node RotateleftChild(Node nodeTop) // Aka left-left rotation
78+
{
79+
Node nodeLeft = nodeTop.left;
80+
nodeTop.left = nodeLeft.right;
81+
nodeLeft.right = nodeTop;
82+
83+
// update heights
84+
nodeTop.Height = Math.Max(GetHeight(nodeTop.left), GetHeight(nodeTop.right)) + 1;
85+
nodeLeft.Height = Math.Max(GetHeight(nodeLeft.left), GetHeight(nodeTop)) + 1;
86+
87+
return nodeLeft; // attached to caller as the new top of this subtree
88+
}
89+
90+
/*
91+
* Before
92+
* nodeTop --> A
93+
* / \
94+
* B C <-- nodeRight
95+
* / \
96+
* D E
97+
*
98+
* After
99+
* C
100+
* / \
101+
* A E
102+
* / \
103+
* B D
104+
*/
105+
private Node RotaterightChild(Node nodeTop) // Aka right-right rotation
106+
{
107+
Node nodeRight = nodeTop.right;
108+
nodeTop.right = nodeRight.left;
109+
nodeRight.left = nodeTop;
110+
111+
// update heights
112+
nodeTop.Height = Math.Max(GetHeight(nodeTop.left), GetHeight(nodeTop.right)) + 1;
113+
nodeRight.Height = Math.Max(GetHeight(nodeRight.left), GetHeight(nodeTop)) + 1;
114+
115+
return nodeRight; // attached to caller as the new top of this subtree
116+
}
117+
118+
/*
119+
* Before
120+
* nodeP --> A
121+
* / \
122+
* B C
123+
* / \ / \
124+
* D E F G
125+
*
126+
* After RotaterightChild
127+
* A
128+
* / \
129+
* E C
130+
* / / \
131+
* B F G
132+
* /
133+
* D
134+
*
135+
* After
136+
* E
137+
* / \
138+
* B A
139+
* / \
140+
* D C
141+
* / \
142+
* F G
143+
*/
144+
145+
private Node DoubleleftChild(Node nodeP)
146+
{
147+
nodeP.left = RotaterightChild(nodeP.left);
148+
return RotateleftChild(nodeP);
149+
}
150+
151+
152+
private Node DoublerightChild(Node nodeP)
153+
{
154+
nodeP.right = RotateleftChild(nodeP.right);
155+
return RotaterightChild(nodeP);
156+
}
157+
158+
159+
private Node Insert(T valueP, Node nodeP)
160+
{
161+
if (nodeP == null)
162+
return new Node(valueP, null, null, 0);
163+
else if (valueP.CompareTo(nodeP.Data) < 0) // valueP < nodeP.Data --> go left
164+
{
165+
nodeP.left = Insert(valueP, nodeP.left);
166+
if ((GetHeight(nodeP.left) - GetHeight(nodeP.right)) == 2)
167+
{
168+
if (valueP.CompareTo(nodeP.left.Data) < 0)
169+
{
170+
nodeP = RotateleftChild(nodeP);
171+
}
172+
173+
else
174+
{
175+
nodeP = DoubleleftChild(nodeP);
176+
}
177+
}
178+
}
179+
else if (valueP.CompareTo(nodeP.Data) > 0) // valueP > nodeP.Data --> go right
180+
{
181+
nodeP.right = Insert(valueP, nodeP.right);
182+
if ((GetHeight(nodeP.right) - GetHeight(nodeP.left)) == 2)
183+
{
184+
if (valueP.CompareTo(nodeP.right.Data) > 0)
185+
{
186+
nodeP = RotaterightChild(nodeP);
187+
}
188+
189+
else
190+
{
191+
nodeP = DoublerightChild(nodeP);
192+
}
193+
}
194+
}
195+
else // valueP == nodeP.Data
196+
{
197+
throw new ApplicationException("Tree did not insert " + valueP + " since an item with that value is already in the tree");
198+
}
199+
200+
nodeP.Height = Math.Max(GetHeight(nodeP.left), GetHeight(nodeP.right)) + 1;
201+
return nodeP;
202+
}
203+
204+
public int Depth()
205+
{
206+
int depth = 0;
207+
if (root != null)
208+
{
209+
depth = Depth(root, 0);
210+
}
211+
return depth;
212+
}
213+
214+
private int Depth(Node nodeP, int depth)
215+
{
216+
// "Unless proven otherwise",
217+
// we assume that the depth of the
218+
// node is the depth it received
219+
// as argument.
220+
int result = depth;
221+
// We assume the depth of
222+
// its right sub-tree
223+
// is 0.
224+
int depthL = 0;
225+
if (nodeP.left != null)
226+
{
227+
// If its left sub-tree is not null,
228+
// we inquire about its depth,
229+
// knowing that it will be 1 more
230+
// than the depth of the current node.
231+
depthL = Depth(nodeP.left, result + 1);
232+
}
233+
// We proceed similarly for the
234+
// left sub-tree.
235+
int depthR = 0;
236+
if (nodeP.right != null)
237+
{
238+
depthR = Depth(nodeP.right, result + 1);
239+
}
240+
// Finally, if at least one sub-tree
241+
// is not null, we take the max of their
242+
// depths to be the depth of the tree
243+
// starting with our current node.
244+
if (nodeP.left != null || nodeP.right != null)
245+
{
246+
result = Math.Max(depthL, depthR);
247+
}
248+
return result;
249+
}
250+
251+
// The ToString method is simply here to help us debug.
252+
// It is not really pretty, but using pre-order and spaces
253+
// to make it easier to understand how the tree is
254+
// constructed. It also displays the depth of the tree
255+
// and the height of the nodes.
256+
257+
public override string ToString()
258+
{
259+
string returned = "Depth: " + Depth() + "\n";
260+
if (root != null)
261+
{
262+
returned += Stringify(root, 0);
263+
}
264+
return returned;
265+
}
266+
267+
private string Stringify(Node nodeP, int depth)
268+
{
269+
string returned = "";
270+
if (nodeP != null)
271+
{
272+
for (int i = 0; i < depth; i++)
273+
{
274+
returned += " ";
275+
}
276+
returned += nodeP + " (depth: " + depth + ")\n"; // Calls Node's ToString method.
277+
if (nodeP.left != null)
278+
{
279+
returned += "L" + Stringify(nodeP.left, depth + 1);
280+
}
281+
if (nodeP.right != null)
282+
{
283+
returned += "R" + Stringify(nodeP.right, depth + 1);
284+
}
285+
}
286+
return returned;
287+
}
288+
289+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
3+
class Program
4+
{
5+
static void Main()
6+
{
7+
Console.WriteLine("Test");
8+
AVLTree<int> test = new AVLTree<int>();
9+
for (int i = 1; i <= 100; i++)
10+
{
11+
test.Insert(i);
12+
}
13+
Console.WriteLine(test);
14+
15+
}
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
%%% The binary search tree obtained by inserting 1, 2, …, 1000 (in that order).
2+
3+
A((1))~~~H1:::hidden
4+
A-->B((2))
5+
B~~~H2:::hidden
6+
B-->C((3))
7+
C~~~H3:::hidden
8+
C-.->H4:::hidden
9+
H4~~~H5:::hidden
10+
H5-.->Z((100))

source/lectures/data/AVLtrees.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
tags:
3+
- datatypes/collections
4+
---
5+
6+
# AVL trees
7+
8+
## Introduction
9+
10+
An [AVL tree](https://en.wikipedia.org/wiki/AVL_tree) (for **A**delson-**V**elsky and **L**andis, their two inventors) is a particular type of binary search tree, with the *self-balancing* property. In a nutshell, self-balancing trees always thrive to keep their height as small as possible when inserting and deleting values.
11+
12+
Indeed, consider a binary search tree using the `Insert` method defined previously to insert the values 1, 2, 3, …, 100 (in that order). Then we obtain a tree that is a line, with each node having at most 1 child.
13+
14+
!include diag/gra/bstree_example_6.md
15+
16+
The resulting tree has for height the number of nodes, 100 in this case.
17+
Since looking up a value or inserting a value is linear in the height of the tree, this means that those operations takes 100 operations: the benefits of using a tree instead of a list is lost!
18+
19+
The resulting tree would be much more efficient if we were leveraging the property of binary search tree to "balance" the values and obtain something … more balanced, with a "maximal number of children" for each nodes.
20+
21+
!include diag/gra/bstree_example_7.md
22+
23+
This is precisely the point of AVL trees, which are binary search trees with the additional property:
24+
25+
> The heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property.
26+
27+
where
28+
29+
- the *height* of a node is the number of edges on the longest path from the node to a leaf.
30+
- the *depth* of a node is the number of edges from the node to the tree's root node.
31+
32+
A good way of [remembering the difference](https://stackoverflow.com/q/2603692) is to observe that we measure the height of a person from toe (leaf) to head (root), while we measure the depth (of an ocean) from earth's surface (root) to ocean bed (leaf).
33+
34+
## Possible Implementation
35+
36+
37+
```{download="./code/projects/AVLTree.zip"}
38+
!include code/projects/AVLTree/AVLTree/AVLTree.cs
39+
```

source/order

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
./lectures/data/stack.md
6666
./lectures/data/queue.md
6767
./lectures/data/trees.md
68+
./lectures/data/AVLtrees.md
6869
./lectures/misc/
6970
./lectures/misc/over_under_flow.md
7071
./lectures/misc/random.md

0 commit comments

Comments
 (0)