Sign in Join
Wiki copytemporary

AVL Tree

TAVLTree

Implemented in the FCL unit avl_tree. This is the predecessor of TAvgLvlTree.

TAvgLvlTree

Where to get: Unit AvgLvlTree of Lazarus package LazUtils of the Lazarus sources.

The AVL trees - Average Level Trees are sorted self balancing binary trees. Similar to a list like TPFList a TAvgLvlTree can store arbitrary data (Pointer), but contrary to TFPList the tree is always sorted and balanced. Therefore searching is very fast. Features:

Creating a tree

By default the tree is sorted for the pointers. That means similar to TFPList you can add any pointers or objects:

pascal
uses AvgLvlTree; // in package lazutils

Tree:=TAvgLvlTree.Create;
Tree.Add(AnObject1);
Tree.Add(AnObject2);
...
// If you want to know if an item is already in the tree use Find:
if Tree.Find(AnObject1)<>nil then
  writeln('AnObject is in the tree');
...
// To remove an item from the tree use:
Tree.Remove(AnObject1);

To create a tree for your custom data you only need a compare function. The following example demonstrates how to sort TMyData objects via their Filenames:

pascal
uses AvgLvlTree; // in package lazutils
type
  TMyData = class
  public
    Filename: string;
    Content: string;
  end;

function CompareMyData(Data1, Data2: Pointer): integer;
begin
  Result:=CompareFilenames(TMyData(d1).Filename,TMyData(d2).Filename);
end;

...
var
  MyData1: TMyData;
  Tree: TAvgLvlTree;
begin
  Tree:=TAvgLvlTree.Create(@CompareMyData);

  MyData1:=TMyData.Create;
  MyData1.Filename:='SomeFile';
  Tree.Add(MyData1);
end;

Enumerating all items of a tree

Enumerate from lowest to highest

pascal
var
  MyData: TMyData;
begin
  for Node in Tree do begin
    MyData:=TMyData(Node.Data);
    writeln(MyData.Filename);
  end;
end;

Notes:

Enumerate from highest to lowest

pascal
var
  MyData: TMyData;
begin
  for Node in Tree.GetEnumeratorHighToLow do begin
    MyData:=TMyData(Node.Data);
    writeln(MyData.Filename);
  end;
end;

Delete all nodes that fits a condition

If you need full control over the enumeration you can use Node.Sucessor and Node.Precessor. Here is an example how to delete all nodes that fits a filter:

pascal
var
  Node: TAvgLvlTreeNode;
begin
  Node:=Tree.FindLowest;
  while Node<>nil do begin
    NextNode:=Node.Successor;
    if TMyData(Node.Data).Flag=true then
      Tree.Delete(Node); // Note: this frees the Node, not the Node.Data. Use Tree.FreeAndDelete(Node) to free the Data.
    Node:=NextNode;
  end;
end;

Note:

Searching items

You can search an item with the same key:

pascal
var
  Node: TAvgLvlTreeNode;
begin
  MyData.Filename:='test';
  Node:=Tree.Find(MyData); // finds a node with a Filename = 'test'
                 // Note: The above function CompareFilenames works case insensitive under Windows
                 // so under Windows it can find a node with Filename 'TEST'
end;

Note: Find will find a node where your compare function returns 0.

You can also search directly with a filename, without creating a TMyData. The function FindKey takes as arguments a key (here: the filename) and a special compare function:

pascal
function CompareFilenameWithMyData(Filename, MyData: Pointer): integer;
begin
  Result:=CompareFilenames(String(Filename),TMyData(MyData).Filename);
end;

...
var
  Filename: string;
  Node: TAvgLvlTreeNode;
begin
  Filename:='Test';
  Node:=Tree.FindKey(Pointer(Filename),@CompareFilenameWithMyData);

end;

Resorting, changing the order

You can change the compare function. The tree will automatically rebuild itself. The Nodes will be destroyed and all Data will be added into new nodes. For example you can switch from comparing case sensitive to case insensitive:

pascal
type
  TMyData = class
  public
    Name: string;
    constructor Create(aName: string);
  end;

constructor TMyData.Create(aName: string);
begin
  Name:=aName;
end;

function CompareMyDataCaseSensitive(Data1, Data2: Pointer): integer;
begin
  Result:=CompareStr(TMyData(Data1).Name,TMyData(Data2).Name);
end;

function CompareMyDataCaseInsensitive(Data1, Data2: Pointer): integer;
begin
  Result:=AnsiCompareText(TMyData(Data1).Name,TMyData(Data2).Name);
end;

procedure Demo;
var
  Tree: TAvgLvlTree;
  Node: TAvgLvlTreeNode;
begin
  Tree:=TAvgLvlTree.Create(@CompareMyDataCaseSensitive);
  Tree.Add(TMyData.Create('ABC'));
  Tree.Add(TMyData.Create('Test'));
  Tree.Add(TMyData.Create('abc'));
  Tree.Add(TMyData.Create('test'));
  writeln('sorted case sensitive:');
  for Node in Tree do
    writeln('  ',TMyData(Node.Data).Name);
  // sort case insensitivie
  Tree.OnCompare:=@CompareMyDataCaseInsensitive;
  writeln('sorted case insensitive:');
  for Node in Tree do
    writeln('  ',TMyData(Node.Data).Name);
end;

Output:

pascal
sorted case sensitive:
  ABC
  Test
  abc
  test
sorted case insensitive:
  ABC
  abc
  Test
  test

Associative arrays using AVL trees

An associative array is an array where instead of a the position an arbitrary key is used to find an item. For example a string. The unit AvgLvlTree implements several associative arrays using AVL trees:

Here is an example demonstrating how to use a TStringToStringTree:

pascal
uses
  AvgLvlTree;
var
  NameToValue: TStringToStringTree;
  S2SItem: PStringToStringItem;
begin
  // create
  NameToValue:=TStringToStringTree.Create(false); // false = Names are compared case insensitive via ''AnsiCompareText''

  // set values
  NameToValue['First'] := 'One';
  NameToValue['Second'] := 'Two';

  // get values
  writeln('First=',NameToValue['first']); // writes One
  writeln('Second=',NameToValue['second']); // writes Two
  writeln('Third=',NameToValue['third']); // third is not yet set, it writes the empty string

  // check if a key is defined
  writeln('First exists: ',NameToValue.Contains('first')); // gives true
  writeln('Third exists: ',NameToValue.Contains('third')); // gives false

  // enumerate all values
  for S2SItem in NameToValue do 
    writeln('Name=',S2SItem^.Name,' Value=',S2SItem^.Value);

  // free
  NameToValue.Free;
end;

Indexed AVL Tree

TIndexedAVLTree extends TAvgLvlTree with the ability to access the nodes via their index (0=lowest, Count-1=highest) similar to an array. In other words it is like an array that is always sorted.

pascal
uses
  AvgLvlTree;
type
  TMyData = class
  public
    Name: string;
    constructor Create(aName: string);
  end;
 
constructor TMyData.Create(aName: string);
begin
  Name:=aName;
end;
 
function CompareMyDataCaseInsensitive(Data1, Data2: Pointer): integer;
begin
  Result:=AnsiCompareText(TMyData(Data1).Name,TMyData(Data2).Name);
end;

var
  SortedArray: TIndexedAVLTree;
  Node: TIndexedAVLTreeNode;
begin
  // create
  SortedArray:=TIndexedAVLTree.Create(@CompareMyDataCaseInsensitive);
  SortedArray.Add(TMyData.Create('Mars'));
  SortedArray.Add(TMyData.Create('Earth'));
  SortedArray.Add(TMyData.Create('Venus'));

  // access items via index
  for i:=0 to SortedArray.Count-1 do
    writeln(i,': ',TMyData(SortedArray[i]).Name);

  // access nodes via GetNodeAtIndex
  for i:=0 to SortedArray.Count-1 do
    writeln(i,': ',TMyData(SortedArray.GetNodeAtIndex(i).Data).Name);

  // get the index of a node
  i:=2;
  Node:=SortedArray.GetNodeAtIndex(i);
  writeln(i,'=',SortedArray.NodeToIndex(Node));

  SortedArray.FreeAndClear; // free nodes and objects
  SortedArray.Free; // free the tree
end;

See also


From the Free Pascal wiki, page "AVL Tree", as it stood on 2022-02-09 after a change by Mark Paley, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 3, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.