1. Some Basic Datastructures

Last Update: 24. March 1998


1.0 Preface

'I want to program games, do I really need these stupid datastructures they lecture here at university?'. I once stumbled over a this question on some newsgroup and the answer is: datastructures are the basis on which games are usually build. Why? Because games usually handle a lot of information (where player is, where is he going, what does he see, where are all the hundreds of enemies, where are they going, in what 'mood' are they etc.) in real time and using the right datastructure in the right place can make a huge difference in speed. The datastructures presented here are really not optimized versions, but rather introductions to the consepts. I might write a chapter called 'Some Optimized Datastructures' one day, but we'll have to see about that later. The last structure (Finite Automata) is really not a data structure (in conventional sense), but I thought it would be good to present it here.

Code examples in this chapter were done before JDK 1.1 was finished, so they should compile ok on any current JDK version. If you wan't to 'port' these structure examples to make better use of the new features in JDK versions 1.1.x, you could start by moving all the 'node' classes as inner classes of those classes that use them and maybe renaming them as simply 'nodes' (instead of 'stackNode' you would have an inner class in the 'stack' class called 'node').

Datastructures organize information that is contained in same type of objects. This ordering can be achieved in many ways e.g..

Datastructures can be divided in:

1.1 Stack

Stack is a so called LIFO- structure (Last In First Out). That means that the last node added to the stack is the first one to be removed.

The basic operations affect the top of the stack:

A stack can be implemented in Java like this:

Graphical representation

We mark the null reference with symbol SymbolNullLink1.gif (937 bytes)

Stack

Stack3.gif (2845 bytes)

 

Adding an node to the stack (push)

StackPush2.GIF (3531 bytes)

 

Removing an node from the stack (pop)

StackPop2.GIF (3512 bytes)

 

 

Code example

Here is a directly compileable Java class, that uses any Java class as data. Just copy and paste it to your text editor and save it as 'stack.java'
(Note: This implementation is highly inefficient and rude as you have to typecast every object you get from the stack. This also means that you'll have to know the type of the stored data):

public class stack
    {
    private node TOP = null;
      // Add an node to the stack
    public void push (Object data)
        {
        stackNode newnode = new node (data);
        newnode.next = TOP;
        TOP = newnode;
        }
      // Remove an node reference from the
      // stack and return it to the caller
    public Object pop ()
        {
        stackNode pointer = TOP;
        if (TOP != null) TOP = TOP.next;
        return pointer.data;
        }
    } // End of class stack
class stackNode
    {
    Object data;
    stackNode next;
    public stackNode (Object data)
        {
        this.data = data;
        this.next = null;
        }
    } // End of class stackNode

1.2 Queue

Queue is a so called FIFO- structure (First In First Out). That means that the first node added to the queue is removed first.
All the basic operations affect the ends of the queue:

A queue can be implemented in Java like this:

 

Graphical representation

Queue

Queue.GIF (3307 bytes)

 

Adding an node to the queue (enqueue)

QueueEnqueue.GIF (3971 bytes)

 

Removing an node from the queue (dequeue)

QueueDequeue.GIF (4000 bytes)

 

Code example

Here is a directly compileable Java class, that uses any Java class as data. Just copy and paste it to your text editor and save it as 'queue.java'
(Note: This implementation is highly inefficient and rude as you have to typecast every object you get from the queue. You also have to know the type of the stored data at all times):

public class queue
    {
    queueNode HEAD = null;
    queueNode TAIL = null;
      // Add an node to the queue
    public void enqueue (Object data)
        {
        queueNode newnode = new queueNode(data);
        newnode.next = null;
          // Is the queue empty?
        if (HEAD != null)
            {
            TAIL = newnode;
            }
            else
                {
                  // First node is added to the queue
                HEAD = newnode;
                }
        TAIL = newnode;
        }
      // Remove an node reference from the
      // queue and return it to the caller
    Object dequeue ()
        {
        queueNode pt = HEAD;
        if (HEAD != null) HEAD = HEAD.next;
        return pt.data;
        }
    } // End of class queue
class queueNode
    {
    Object data;
    queueNode next;
      // Constructor is given the stored data as a parameter
    public queueNode (Object data)
        {
        this.data = data;
        this.next = null;
        }
    } // End of class queueNode

Queues are often used to implement buffers:

1.3 Doublelinked List

In doublelinked list structure every node has a reference to its immediate predecessor and successor.
The basic operations can affect any node in the list:

A double linked list can be implemented in Java like this:

Graphical representation

Doublelinked list

DblLinkedList.GIF (3675 bytes)

 

Adding an node to the double linked list (insert)

DblLinkedListInsert.GIF (4608 bytes)

 

Removing an node from the double linked list   (delete)

DblLinkedListDelete.GIF (4226 bytes)

 

Code example

Here is a directly compileable Java class, that uses any Java class as data. Just copy and paste it to your text editor and save it as 'linkedList.java'
(Note: This implementation is highly inefficient and rude as you have to typecast every object you get from the queue and you'll have to know the type of the stored data):

public class linkedList
    {
    linkedListNode HEAD = null;
      // Add an node as successor of some node
      //(curr = null means that the list is empty)
    void append (linkedListNode curr, Object data)
        {
        linkedListNode newnode = new linkedListNode(data);
        if (curr != null)
            {
              // The list was not empty
            newnode.prev = curr;
            newnode.next = curr.next;
            if (curr.next != null) curr.next.prev = newnode;
            curr.next = newnode;
            }
            else
                {
                  // The list was empty
                newnode.prev = newnode.next = null;
                HEAD = newnode;
                }
    }
      // Add an node as predecessor of some node
      //(curr = null means that the list is empty)
    void prepend (linkedListNode curr, Object data)
        {
        linkedListNode newnode = new linkedListNode(data);
        if (curr != null)
            {
              // The list was not empty
            newnode.next = curr;
            newnode.prev = curr.prev;
            if (curr.prev != null)
                {
                  // The node is NOT the first node
                curr.prev.next = newnode;
                }
                else
                    {
                      // The node IS the first node
                    HEAD = newnode;
                    curr.prev = newnode;
                    }
            }
            else
                {
                  // The list was empty
                newnode.prev = newnode.next = null;
                HEAD = newnode;
                }
        }
      // Remove an node from the list(delete)
    public Object remove(linkedListNode curr)
        {
        if (curr.prev != null)
            {
              // The node is NOT the first node
            curr.prev.next = curr.next;
            }
            else
                {
                  // The node IS the first node
                HEAD = curr.next;
                }
        if (curr.next != null)
            {
            curr.next.prev = curr.prev;
            }
        return curr.data;
        }
    } // End of class linkedList
class linkedListNode
    {
    Object data;
    linkedListNode prev;
    linkedListNode next;
    public linkedListNode (Object data)
        {
        this.data = data;
        this.next = null;
        this.prev = null;
        }
    } // End of class linkedListNode

1.4 Binary tree

Graphical representation

Tree.GIF (5687 bytes)

 

Traversal of the tree

A binary tree can be traversed in three ways:

inorder: left subtree, current element, right subtree

  1. Go through the left subtree in inorder order
  2. Go through this current node
  3. Go through the right subtree in inorder order

preorder: current element, left subtree, right subtree

  1. Go through this current node
  2. Go through the left subtree in preorder order
  3. Go through the rigth subtree in preorder order

postorder: left subtree, right subtree, current node

  1. Go through the left subtree in postorder order
  2. Go through the left subtree in postorder order
  3. Go throught this current node

 

An example: Counting how frequently a word appears in a text

 

Code example

Here is a directly compileable Java class, that uses any Java class as data. Just copy and paste it to your text editor and save it as 'stringTree.java':

import java.io.*;

public class stringTree
    {
    public stringTreeNode addNode(stringTreeNode curr, String word)
        {
        int cmp;
        if (curr == null)
            {
            curr = new stringTreeNode(word);
            curr.left = curr.right = null;
            }
            else
                {
                cmp = word.compareTo(curr.word);
                if ( cmp == 0)
                    {
                    curr.count++;
                    }
                    else if (cmp < 0)
                        {
                        curr.left = addNode(curr.left, word);
                        }
                        else
                            {
                            curr.right = addNode (curr.right, word);
                            }
                }
    return curr;
    }
    public void treeprint (stringTreeNode curr)
        {
        if (curr != null)
            {
            treeprint (curr.left); // Print the left subtree
            System.out.println(curr.count+"\t"+curr.word);
            treeprint (curr.right); // Print the right subtree
            }
        }
    } // End of class stringTree
class stringTreeNode
    {
    String word;
    int count = 0;
    stringTreeNode left = null;
    stringTreeNode right = null;
    public stringTreeNode (String word)
        {
        this.word = word;
        this.count++;
        }
    } // End of class stringTreeNode

 

Here is a directly compileable Java class, that uses any Java class as data. Just copy and paste it to your text editor and save it as 'wcount.java':

import java.io.*;
public class wcount
    {
    public static void main (String[] args)
        {
        stringTreeNode root = null;
        stringTree tree = new stringTree();
        StreamTokenizer inputSource = new StreamTokenizer(System.in);
        inputSource.eolIsSignificant(true);
        try
            {
            inputSource.nextToken();
            }
            catch (IOException ioe)
                {
                System.out.println("Exception!\n"+ioe+"\n");
                }
        while (inputSource.ttype != inputSource.TT_EOL)
            {
            if (inputSource.ttype == inputSource.TT_WORD)
                {
                root = tree.addNode(root, inputSource.sval);
                }
            try
                {
                inputSource.nextToken();
                }
                catch (IOException ioe)
                    {
                    System.out.println("Exception!\n"+ioe+"\n");
                    }
            }
        System.out.println("Tree root: \""+root.word+"\"");
        System.out.println("\nCount:"+"\t"+"Word:");
        tree.treeprint(root);
        }
    }  // End of class wcount

 

Some notes about binary trees:

1.5 Finite Automata

A finite automata consists of

  1. A finite group S = {s1,s2, ... ,sn} of states si
  2. A finite group C = {c1,c2, ... ,cn} of input characters ci
  3. Description t: S x C -> S

A finite automaton can be considered a machine that changes it state from si to sk = t(si, cj) when it reads an input character cj. Usually before the starting (before the machine has read a single character) it is in a defined starting state. The machine stops, when it goes to a so called terminal states (f, t(f, C) = 0).

Graphical representation

FiniteAutomata.GIF (5274 bytes)

 

Code example

Counting the words and lines in an input file:

Characters are classified in the class Chartype:

States are classified in the class State:

We define a class Counter that implements the finite automata presented above.

public class Counter
    {
    private State state = new State();
    private Chartype chartype = new Chartype();
    private int counterState;
    private int lines;
    private int words;
    public Counter()
        {
        Reset();
        }
      // Reset the classes state and both counters
    public void Reset()
        {
        counterState = state.START;
        lines = words = 0;
        }
      // Return the number of lines
    public int getLines()
        {
        return lines;
        }
      // Return the number of words
    public int getWords()
        {
        return words;
        }
      // Do counting based on given type and current state
    public boolean Count (int type)
        {
        switch (counterState)
            {
            case state.START:
            switch (type)
                {
                case chartype.EOF:
                counterState = state.STOP;
                return false;
                case chartype.CHARACTER:
                words++;
                counterState = state.WORD;
                return true;
                case chartype.NEWLINE:
                lines++;
                case chartype.WHITE:
                return true;
                }

            case state.WORD:
            switch (type)
                {
                case chartype.EOF:
                case chartype.NEWLINE:
                lines++;
                case chartype.WHITE:
                counterState = state.START;
                case chartype.CHARACTER:
                return true;
                }

            case state.STOP:
            return false;
            }
        return false;	// If this is reached there has been an error!
        }
    }  // End of class Counter
class State
    {
    public final int START = -1;
    public final int WORD  = -2;
    public final int STOP  = -3;
    }  // End of class State
class Chartype
    {
    public final int WHITE     = -1;
    public final int NEWLINE   = -2;
    public final int CHARACTER = -3;
    public final int EOF       = -4;
    }  // End of class Chartype
import java.io.*;
class CountInStream
    {
    Chartype chartype = new Chartype();
    public static void main(String[] args)
        {
        Counter lwcounter = new Counter();
        CountInStream countObject = new CountInStream();
        while ( lwcounter.Count( countObject.nextchar() ) );
        System.out.println("Words:"+lwcounter.Words()+"\nLines:"+lwcounter.Lines());
        }
    public int nextchar()
        {
        int input;
        char inputChar;
        try
            {
            input = System.in.read();
            if (input == -1)
                {
                return chartype.EOF;
                }
            inputChar = (char) input;
            if (inputChar =='\n')
                {
                return chartype.NEWLINE;
                }
            if ( (inputChar == ' ') || (inputChar == '\t') )
                {
                return chartype.WHITE;
                }
            }
            catch (IOException ioe)
                {
                System.out.println("Exception!\n"+ioe);
                }
        return chartype.CHARACTER;
        }
    }  // End of class CountInStream

Original text: ©1997 P. Pietiläinen & V. Halonen, University of Oulu:
From lecture material for course in scientific programming.
Material is used with permission from authors.
Conversion to Java: ©1997-1998, Pasi Keränen