Normal linked lists have the links going one way, so if we want to run a pointer through the elements of a list we have to start at one end and move to the other one element at a time. We have seen the case of a list structure with an extra pointer to the last element in the list which makes it easier to add new items to the back of a list. A doubly-linked list structure is useful where we might want to run both ways in a list with equal convenience.
One abstract data type where we might want to use a doubly-linked list for implementation is a list with an insertion point (which is also known as a "sequence"). In the abstract, we can think of this as something like [a,b^,c,d] where the ^ symbol represents the insertion point. The operations on the structure are:
Return the current element which is the element at the insertion point, in the above case, b.
Insert a new element. If the element we are inserting is e then the state from the above after the operation would be [a,b,e^,c,d]. The element is inserted following the insertion point, and this point becomes the new insertion point.
Move the insertion point forward. If we follow from after the previous insert operation, the state becomes [a,b,e,c^,d].
Delete the current element. This deletes the element which is the current insertion point. Following on from the situation above after the forward operation, we get [a,b,e,d^]. Note the insertion point becomes the element which was after the deleted element.
Move the insertion point backwards. Applying this operation next gives us [a,b,e^,d].
We also have an operation which says whether the list is empty, one which says whether the insertion point is the first element and one which says whether the insertion point is the last element. If the list is empty, neither the forward, backward or delete operations can work. If the current position is the first element, the backward operation cannot work, if the current position is the last element, the forward operation won't work.
The data structure representing this has a double linked list, with a pointer pointing to the cell of the current position as the field of an object:
Inserting a new element involves creating a new cell, and rearranging the pointers so they correctly reflect a doubly linked list with the current position pointer pointing to the new cell:
Moving the insertion point forward or backwards simply involves following one link in the appropriate direction down the chain, for example, forward from the above gives:
Deleting involves more rearrangement of pointers:
When dealing with inserting or deleting, special account needs to be taken of inserting or deleting at the end of ths list, or inserting into an empty list. When the last element in the list is deleted, the insertion point becomes the new last element since it cannot move forward to the next element.
Here is code for the abstract data type of a list with an insertion point, implemented as suggested above by a doubly-linked list:
class InsertList1There is also a method
{
private Cell ptr;
public InsertList1()
{
ptr=null;
}
public boolean isEmpty()
{
return ptr==null;
}
public boolean atEnd()
{
return ptr.next==null;
}
public boolean atStart()
{
return ptr.prev==null;
}
public void insert(Object obj)
{
if(ptr==null)
ptr = new Cell(null,obj,null);
else if(ptr.next==null)
{
ptr.next = new Cell(ptr,obj,null);
ptr = ptr.next;
}
else
{
ptr.next.prev = new Cell(ptr,obj,ptr.next);
ptr.next = ptr.next.prev;
ptr=ptr.next;
}
}
public void delete()
{
if(ptr.next==null)
{
ptr = ptr.prev;
if(ptr!=null)
ptr.next=null;
}
else
{
ptr.next.prev = ptr.prev;
if(ptr.prev!=null)
ptr.prev.next = ptr.next;
ptr=ptr.next;
}
}
public void forward()
{
ptr = ptr.next;
}
public void backward()
{
ptr = ptr.prev;
}
public Object current()
{
return ptr.content;
}
public String toString()
{
Cell temp;
String str="";
if(ptr!=null)
{
for(temp=ptr; temp.prev!=null; temp=temp.prev);
for(;temp!=null;temp=temp.next)
{
if(temp==ptr)
str+="> ";
else
str+=" ";
str+=temp.content+"\n";
}
}
return str;
}
private static class Cell
{
Cell prev,next;
Object content;
Cell(Cell p,Object obj,Cell n)
{
prev=p;
content=obj;
next=n;
}
}
}
toString
for giving a
string representation of the list. In this case, it puts the
string representation of each object in the list on a separate
line, with two blank spaces in front of each except the line
for the object at the insertion point which is preceded by
"> "
.
The command i
in the editor puts it in input mode.
Following
this, all text typed is added to the stored text in the editor
following
the insertion point until a line consisting of just the full stop
character is entered. This causes the editor to go back to command
mode, with the
insertion point the end of the new text.
The commands +
and -
move the insertion
position up and down the stored text. Each time it is moved, the
line which becomes the new insertion point is printed.
The command p
prints the line which is the current
insertion point without moving the insertion point.
The command d
deletes the line which is the current
insertion point and prints the next line which is the new insertion
point.
The command l
prints the entire stored text,
indicating the
insertion point with "> "
, and the command q
quits the editor.
Here is the code:
import java.io.*;
class Editor1
{
public static void main(String[] args)
throws IOException
{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
InsertList1 lines = new InsertList1();
char ch;
String command,line;
do {
System.out.print(": ");
command = in.readLine();
ch = command.charAt(0);
switch(ch)
{
case 'i' :
do {
line = in.readLine();
if(line.length()==1&&line.charAt(0)=='.')
break;
lines.insert(line);
}
while(true);
break;
case 'd' :
if(lines.isEmpty())
System.out.println("?");
else
{
lines.delete();
if(!lines.isEmpty())
System.out.println(lines.current());
}
break;
case '+' :
if(lines.isEmpty()||lines.atEnd())
System.out.println("?");
else
{
lines.forward();
System.out.println(lines.current());
}
break;
case '-' :
if(lines.isEmpty()||lines.atStart())
System.out.println("?");
else
{
lines.backward();
System.out.println(lines.current());
}
break;
case 'p':
if(lines.isEmpty())
System.out.println("?");
else
System.out.println(lines.current());
break;
case 'l':
System.out.print(lines);
break;
case 'q': break;
default:
System.out.println("?");
}
}
while(ch!='q');
}
}
before
and
after
, then moving the insertion point forward can be
implemented by taking the top item off the after
stack and putting it on the before
stack, while moving
the insertion point backwards can be
implemented by taking the top item off the before
stack and putting it on the after
stack.
The before
stack is a linked list of items
in the reverse order in which they occur in the list considered
as a whole list, while the after
stack holds the
items in the same order as they are considered in the list. Deleting an
item involves just taking the item off
the top of the before
stack, while adding an item
involves adding it to the top of the before
stack.
Here is some Java code that uses this implementation:
class InsertList2
{
// A sequence type implemented using two singly-linked lists.
private Cell before,after;
public InsertList2()
{
before=null;
after=null;
}
public boolean isEmpty()
{
return before==null&&after==null;
}
public boolean atEnd()
{
return after==null;
}
public boolean atStart()
{
return before.next==null;
}
public void insert(Object obj)
{
before = new Cell(obj,before);
}
public void delete()
{
before = before.next;
}
public void forward()
{
Cell ptr=after;
after=after.next;
ptr.next=before;
before=ptr;
}
public void backward()
{
Cell ptr=before;
before=before.next;
ptr.next=after;
after=ptr;
}
public Object current()
{
return before.content;
}
public String toString()
{
Cell ptr;
String str="";
if(before==null)
return "";
for(ptr=before.next; ptr!=null; ptr=ptr.next)
str=" "+ptr.content+"\n"+str;
str=str+"> "+before.content+"\n";
for(ptr=after; ptr!=null; ptr=ptr.next)
str=str+" "+ptr.content+"\n";
return str;
}
private static class Cell
{
Cell next;
Object content;
Cell(Object obj,Cell n)
{
content=obj;
next=n;
}
}
}
IntSequence
, for demonstration purposes reverting to
collections
of integers:
class IntSequenceWhen an integer is added to or deleted from a sequence in this representation, the entire contents of the array following the insertion point have to be copied to or from their next cell in the array. This means that insertion and deletion are O(N) in this representation. In the doubly-linked list representation, insertion and deletion are O(1) since the amount of pointer manipulation required is fixed, not dependent on the size of the list.
{
// Sequence class
private int[] array;
private int count,current;
final static int MAX=100;
public IntSequence()
{
array = new int[MAX];
}
public void delete()
{
if(current<count)
{
for(int i=current+1;i<count;i++)
array[i-1]=array[i];
count--;
}
}
public void add(int n)
{
for(int i=count;i>current;i--)
array[i]=array[i-1];
array[current]=n;
current++;
count++;
}
public void forward()
{
if(current<count)
current++;
}
public void backward()
{
if(current>0)
current--;
}
public String toString()
{
int i;
String str="[";
if(current==0)
str+="^";
if(count>0)
{
str+=array[0];
for(i=1;i<count;i++)
if(current==i)
str+=",^"+array[i];
else
str+=","+array[i];
if(current==count)
str+=",^";
}
return str+"]";
}
}
Here is a front-end which uses this class to give a simple sequence maintenance program:
import java.io.*;
class UseIntSequences
{
// Sequence maintenance program
public static void main(String[] args)
throws IOException
{
int n=0;
char ch;
String line;
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
IntSequence mySequence = new IntSequence();
do {
System.out.print(": ");
line = in.readLine();
ch = line.charAt(0);
if(line.length()>1)
n = Integer.parseInt(line.substring(1).trim());
switch(ch)
{
case 'q' :
break;
case 'd' :
mySequence.delete();
break;
case 'a' :
mySequence.add(n);
break;
case 'f' :
mySequence.forward();
break;
case 'b' :
mySequence.backward();
break;
case 'p':
System.out.println(mySequence);
break;
default:
System.out.print("d - delete, a - add, f - forward,");
System.out.println(" b - backward, p - print, q - quit");
break;
}
}
while(ch!='q');
}
}
Last modified: 12 March 2004