Skip to content

AVL tree #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2017
Merged

AVL tree #1

merged 1 commit into from
Oct 28, 2017

Conversation

gabi-250
Copy link
Owner

Contains the initial implementation of the search tree.

@gabi-250 gabi-250 requested a review from ltratt October 25, 2017 21:15
src/lib.rs Outdated
/// assert!(tree.contains(&String::from("world!")));
/// ```
pub fn insert(&mut self, value: T) {
if let Ok(node) = self.create_node(value) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If create_node returns an Err, is the insert swallowed or ...?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_node returns an Err if value can already be found in the tree. So if Err is returned, insert won't do anything.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, that's a weird return type to denote a non-bad thing :) Why not have create_node just return a bool (e.g. true means "this node already existed")?

src/lib.rs Outdated

fn create_node(&mut self, value: T) -> Result<Node<T>, usize> {
match self.get_node(&value) {
Ok(index) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can just be Ok(index) => Err(index) (without the curly brackets).

std::cmp::max(left_height, right_height)
}

fn balance_factor(&self, node: &Node<T>) -> i8 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this return a signed int?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The balance factor can be negative. In my code the balance factor is defined as height(left_child) - height(right_child). If it is negative and less than -2, the tree is 'right-heavy'. If it's positive and greater than 2, the tree is 'right-heavy'

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, got it. But can the balance exceed +/- 128?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should always be in the range [-2, 2]. If at some point it becomes +/- 2, the tree is (should be) rebalanced

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood... but how far can it exceed +/- 2 before it's rebalanced?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an AVL tree, the heights of the two child subtrees of any node differ by at most one
(wikipedia)

The 2 and -2 cases trigger rebalancing. You can't have < -2 or > 2 because you only ever insert/delete one node at a time.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, got it.

src/lib.rs Outdated
self.nodes[right_index].right_child);
self.nodes[right_index].parent = self.nodes[index].parent;
self.nodes[index].parent = Some(right_index);
//self.update_heights(right_index);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented code.

@ltratt
Copy link
Collaborator

ltratt commented Oct 25, 2017

Some relatively minor comments from me. Have you run clippy over this? https://github.com/rust-lang-nursery/rust-clippy

@gabi-250
Copy link
Owner Author

@ltratt Thank you for the review! I'll have a look at clippy

src/lib.rs Outdated
fn get_node(&self, value: &T) -> Result<usize, Option<usize>> {
let mut current_index = self.root;
let mut previous_index = None;
if !self.nodes.is_empty() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this check is needed. In the case of the tree being empty, root would be None, and thus he while loop would not be entered, right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

src/lib.rs Outdated
}
}

fn get_node(&self, value: &T) -> Result<usize, Option<usize>> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add comment indicating what the error state communicates?

src/lib.rs Outdated
}

fn update_heights(&mut self, start_index: usize) {
if !self.nodes.is_empty() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, is this needed?

Copy link
Owner Author

@gabi-250 gabi-250 Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is, since I call self.root.unwrap() at 148:

            self.nodes[self.root.unwrap()].height =
                self.compute_height(self.nodes[self.root.unwrap()].left_child,
                                    self.nodes[self.root.unwrap()].right_child);

I should probably store the root index though, instead of unwrapping it everywhere

}

fn left_rotation(&mut self, index: usize) {
if let Some(right_index) = self.nodes[index].right_child {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean if you don't enter the body of this conditional? i.e. if right_index is None?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps comment this section of code up more?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, the right child of index becomes the parent of index and the left child of right_index becomes the right child of index. However, right_index might not have any children, which is why this condition is necessary.

src/lib.rs Outdated
self.left_right_rotation(index);
}
} else if balance_factor < -2 || balance_factor > 2 {
panic!("Invalid balance factor for {}: {}", index, balance_factor);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ Ah, this confirms my above comment about the -2 <= height <= 2 thing. Is unreachable!() more appropriate here? Not sure.

src/lib.rs Outdated

fn is_balanced(&self, index: usize) -> bool {
let balance_factor = self.balance_factor(&self.nodes[index]);
balance_factor >= -1 && balance_factor <= 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you chain operators in Rust?

return -1 <= self.balance_factor(&self.nodes[index]) <= 1

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you can't you could do:

return abs(self.balance_factor(&self.nodes[index])) < 2

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be an open issue, I'll just use abs

src/lib.rs Outdated
/// assert!(tree.iter().zip(vec![1, 2, 5]).all(|(x, y)| *x == y));
/// ```
pub fn iter(&'a self) -> SearchTreeIter<'a, T> {
let mut start_node = None;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like this initialisation code should be encapsulated inside the iterator's ::new()

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It just walks down the left side of the tree to find the first node that should fall out of the iterator, right?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I didn't implement it like that because it felt a bit awkward to pass both the root node and the tree Vec to SearchTreeIter::new, but I guess it makes makes sense to do it that way

src/lib.rs Outdated
if let Some(right_child) = node.right_child {
self.current_node = Some(right_child);
node = &self.nodes[right_child];
while let Some(left_child) = node.left_child {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a recurring pattern.

Lots of code wants to find the deepest leftmost node in a given sub-tree. Should that be a function?

src/lib.rs Outdated
assert!(tree.is_balanced(node_index));
}
}
{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this extra scoping needed to silence the borrow checker?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it used to be needed with my older implementation of the test. It's no longer necessary, I'll remove it

@vext01
Copy link
Collaborator

vext01 commented Oct 26, 2017

This looks pretty good gabi!

@vext01
Copy link
Collaborator

vext01 commented Oct 26, 2017

All tests passing for me :)

@gabi-250
Copy link
Owner Author

@vext01 Thank you for your review! I've addressed your comments, let me know if there's anything else I need to change

// left child of `right_index` must become the new right child of
// `index`.
//
// A (index) (right_index) B
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like these ascii art diagrams :)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, thanks! Explaining the balancing algorithm would've been a lot harder without drawing

@vext01
Copy link
Collaborator

vext01 commented Oct 26, 2017

This looks good to me.

If @ltratt is OK too, then we can squash.

@ltratt
Copy link
Collaborator

ltratt commented Oct 26, 2017

Fine with me.

@gabi-250
Copy link
Owner Author

All done

@ltratt
Copy link
Collaborator

ltratt commented Oct 26, 2017

Is insert still returning Err? We really should fix that.

@gabi-250
Copy link
Owner Author

@ltratt insert is not returning anything currently. create_node returns a Result, which is unnecessary (thanks for pointing it out!). I'll fix that now

@ltratt
Copy link
Collaborator

ltratt commented Oct 26, 2017

Ah, yes, you're quite right -- create_node is the problem. [Sorry, all my comments disappeared so I couldn't easily find my old ones.]

@ltratt
Copy link
Collaborator

ltratt commented Oct 26, 2017

OK, that makes me happier :) If you squash that, I can merge.

@gabi-250
Copy link
Owner Author

@ltratt I've slightly changed the meaning of get_node: it now returns an Option which contains the index of the node that has the lowest value greater than the value argument (or None, if the tree is empty).

@ltratt
Copy link
Collaborator

ltratt commented Oct 27, 2017

This sounds sensible to me. If @vext01 is OK, then I think we're ready for the final squash!

@vext01
Copy link
Collaborator

vext01 commented Oct 28, 2017

Go ahead.

@ltratt
Copy link
Collaborator

ltratt commented Oct 28, 2017

OK, it's time to squash!

@gabi-250
Copy link
Owner Author

Okay, done!

@ltratt ltratt merged commit 770ac1c into master Oct 28, 2017
@ltratt ltratt deleted the avl-tree branch October 28, 2017 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants