unit-2-linked-lists
unit-2-linked-lists
An array is a very useful data structure provided in programming languages. However, it has at least two
limitations:
1. Changing the size of the array requires creating a new array and then copying all data from the
array with the old size to the array with the new size.
2. The data in the array are next to each other sequentially in memory, which means that inserting
an item inside the array requires shifting some other items in the array.
This limitation can be overcome by using linked structures. A linked structure is a collection of nodes
storing data and links to other nodes. In this way, nodes can be located anywhere in the memory, and
passing from one node to another is accomplished by storing the reference to other node in the
structure.
Singly Linked List
If a node contains a data field that is a reference to another node, then many nodes can be strung
together using only one variable to access the entire sequence of nodes. Such a sequence of nodes is the
most frequently used implementation of a linked list, which is a data structure composed of nodes, each
node holding some information and a reference to another node in the list. If a node has a link only to
its successor in this sequence, the list is called a singly linked list.
Java Implementation of singly linked list
This is a Java Program to implement a Singly Linked List. A linked list is a data structure consisting of a group
of nodes which together represent a sequence. Under the simplest form, each node is composed of a data
and a reference (in other words, a link) to the next node in the sequence. This structure allows for efficient
insertion or removal of elements from any position in the sequence. In a singly linked list each node has only
one link which points to the next node in the list
The complete Java program to implement singly linked list is as follows:
class Node {
public int info;
public Node next;
public Node(){
next = null;
}
public Node(int el){
info = el;
next = null;
}
public Node(int el,Node ptr){
info =el;
next = ptr;
}
}
class LinkList
{
public Node head;
1
public Node tail;
public LinkList(){
head = null;
tail = null;
}
public boolean isEmpty(){
return head == null;
}
public void addToHead(int el){
head = new Node(el,head);
if(tail == null)
tail = head;
}
public void addToTail(int el){
if(!isEmpty()){
tail.next = new Node(el);
tail = tail.next;
}
else
head = tail = new Node(el);
}
public void printAll(){
Node temp;
for(temp = head;temp!=null;temp = temp.next)
System.out.print(temp.info+" ");
}
public int deleteFromHead(){
if(isEmpty())
return 0;
int el = head.info;
if(head==tail)
head = tail = null;
else
head = head.next;
return el;
}
public int deleteFromTail(){
if(isEmpty())
return 0;
int el = tail.info;
if(head == tail)
head = tail = null;
2
else{
Node temp;
for(temp = head; temp.next!=tail; temp = temp.next);
tail = temp;
tail.next = null;
}
return el;
}
public boolean isInList(int el){
Node temp;
for(temp = head;temp!=null&&temp.info!=el;temp = temp.next);
return temp!=null;
}
public void delete(int el){
if(!isEmpty())
if(head == tail&&el == head.info)
head = tail=null;
else if(el==head.info)
head = head.next;
else{
Node pred,temp;
for(pred = head,temp = head.next; temp!=null&&temp.info!=el;pred = pred.next,temp =
temp.next);
if(temp!=null){
pred.next = temp.next;
if(temp == tail)
tail = pred;
}
}
}
}
class LinkedList{
public static void main(String[] args) {
LinkList list = new LinkList();
list.addToHead(20);
list.addToHead(10);
list.addToTail(30);
System.out.println("Linked List ");
list.printAll();
list.delete(30);
System.out.println ("\nLinked List after deletion");
list.printAll();
3
System.out.println ("\n30 is in list "+list.isInList(30));
}
}
A node includes two fields: info and next. The info field is used to store information. The next filed is
used to join together nodes to form a linked list. It is an auxiliary filed used to maintain the linked list. It
is indispensible for implementation of the linked list. The link field is declared as Node. It is a reference
to a node of the same type that is being declared. Objects that include such a data fields are called self
referential objects.
Head tail
10 20 30 40
4
All these steps are executed in the if clause of the addToList () method. The else cause of this method is
executed only if the linked list is empty. If this case were not included, the program would crash because
in the if clause we make the assignment to the next filed of the node referred by tail. In the case of an
empty linked list, it is a reference to a nonexisting node, which leads to throwing the
NULLPointerException.
Deletion
One deletion operation consists of deleting a node at the beginning of the list and returning the value
stored in it. In this operation the information from the first node is temporarily stored in some local
variable and then head is reset so what was the second node becomes the first node. In this way, the
former node is abandoned to be processed later by garbage collector. Because the head node is
immediately accessible, deleteFromHead () takes constant time O(1) to perform its task.
The second deletion operation consists of deleting a node from the end of the list. After deletion, the
last node has to be moved backward by one node.
The deletion operation can be summarized in the following points.
1. An attempt to remove a node from an empty list, in which case the method is immediately
exited.
2. Deleting the only node from a one node linked list. Both head and tail are set to null.
3. Removing the first node of the list with at least two heads, which requires updating head.
4. Removing the last node of the list with at least two nodes, leading to the update of tail.
5. An attempt to delete a node with a number that is not in the list: Do nothing.
It is noted that the best case for delete () is when the head node is to be deleted, which takes O(1) time
to accomplish. The worst case is when the last node needs to be deleted, which reduces deleteFromTail
() to O(n) performance.
Search
The insertion and deletion operations modify linked lists. The searching operation scans an existing list
to learn whether a number is in it. We implement this operation with the Boolean method isInList(). The
method uses a temporary variable temp to through the list starting from the head node. The number
stored in each node is compared to the number being sought, and if the two numbers are equal, the
loop is exited; otherwise, temp is updated to temp.link so that the next node can be investigated. After
reaching the last node and executing the assignment temp = temp.link, temp becomes null, which is
used as an indication that the number el is not in the list. That is, if temp is not null, the search is
discontinued somewhere inside the list because el was found. That is why isInList () returns the result of
comparison temp!=null. If temp is not null, el was found and true is returned. If temp is null, the search
was unsuccessful and false is returned.
With reasoning very similar to that used to determine the efficiency of delete (), isInList () takes O(1)
time in best case and O(n) in the worst and average case.
5
A doubly linked list (DLL) is similar to singly linked list and it has two links one pointing to next node and
other pointing to previous node.
One problem in singly linked list is that a node in the list has no knowledge about its previous node. We
cannot go back to previous node from the current node. This problem is overcome is doubly linked list.
Methods for processing doubly linked lists are slightly more complicated than their singly linked
counterparts because there is one more reference filed to be maintained.
To add a node to a list, the node has to be created, its fields properly initialized and then the node needs
to be incorporated into the list. To insert a node at the end of the list we perform following five steps
A new node is created and then its three fields are initialized.
The info filed field to the number el being inserted.
The next filed to null.
The prev field to the value of tail so that this field refers to the last node in the list. But now, the
new node should become the last node; therefore, tail is set to reference the new node. But the
new node is not yet accessible from its predecessor, to rectify this.
The next field of the predecessor is set to reference the new node.
6
class DoublyLinkedList{
private DLLNode head, tail;
public DoublyLinkedList()
{
head = null;
tail = null;
}
public boolean isEmpty()
{
return head ==null;
}
public void setToNull()
{
head =null;
tail =null;
}
public int firstElement()
{
if(head!=null)
return head.info;
else
return 0;
}
public void addToHead(int el)
{
if(head!=null)
{
DLLNode temp= new DLLNode(el);
temp.next = head;
temp.prev = null;
head = temp;
}
else
head = tail = new DLLNode(el);
}
public void addToTail(int el)
{
if(tail!=null)
{
DLLNode temp = new DLLNode(el);
tail.next = temp;
temp.prev = tail;
7
tail = temp;
}
else
head = tail = new DLLNode(el);
}
public int deleteFromHead()
{
if(isEmpty())
return 0;
int el = head.info;
if(head==tail)
head = tail = null;
else
{
head = head.next;
head.prev = null;
}
}
public int deleteFromTail()
{
if(isEmpty()){
return 0;
}
int el = tail.info;
if(head==tail)
head = tail = null;
else{
tail = tail.prev;
tail.next = null;
}
}
public boolean find(int el)
{
DLLNode temp;
for(temp = head;temp!=null&&temp.info!=el;temp = temp.next);
return temp!=null;
}
public void printALL()
{
DLLNode temp;
for(temp = head;temp!=null;temp = temp.next)
System.out.print(temp.info+ " ");
8
}
}
public class DoublyLinkedListDemo {
public static void main(String[] args) {
DoublyLinkedList list = new DoublyLinkedList();
list.addToHead(30);
list.addToHead(20);
list.addToHead(10);
list.addToHead(0);
list.addToTail(40);
System.out.println("Doubly Linked List");
list.printALL();
System.out.println(" 0 is in list "+list.find(10));
}
}
Deleting the last node from the doubly linked list is straightforward because there is direct access from
the last node to its predecessor, and no loop is needed to remove the last node.
In an implementation of a circular linked list, we can use only one permanent reference, tail, to the list
even though operations on the list require access to the tail and its successor, the head.
The implementation has some problem. A method for deletion of the tail node requires a loop so that
tail can be set to its predecessor after deleting the node. This makes this method delete the tail node in
O(n) time. Moreover, processing data in the reverse order is not very efficient. To avoid the problem and
still be able to insert and delete nodes at the front and at the end of the list without using a loop, a
doubly linked circular list can be used. The list forms two rings: one going forward through next fields
and one going backward through prev fields. Deleting the node from the end of the list can be done
easily because there is a direct access to the next to last node that needs to be updated in case of such a
deletion. In this list, both insertion and deletion of the tail node can be node in O(1) time.
9
The following program implements the singly circular liked list.
class CNode {
public int info;
public CNode next;
public CNode(){
info =0;
next = null;
}
public CNode(int el){
info = el;
next = null;
}
public CNode(int el,CNode n){
info = el;
next = n;
}
}
class CLinkList
{
public CNode head;
public CNode tail;
public CLinkList(){
head = tail = null;
}
public boolean isEmpty(){
return tail == null;
}
public void addToHead(int el)
{
CNode temp = new CNode(el);
temp.next = head;
if(head == null)
{
head = temp;
//temp.next = head;
tail = head;
}
else
{
tail.next = temp;
head = temp;
}
10
}
public void addToTail(int el){
CNode temp = new CNode(el);
temp.next = head;
if(head==null){
head = temp;
temp.next = head;
tail = temp;
}
else
{
tail.next =temp;
tail =temp;
}
}
public void deleteAtHead(){
CNode temp;
if(head==tail)
head = tail = null;
else
{
head = head.next;
tail.next = head;
}
}
public void deleteAtTail(){
CNode temp;
if(head==tail)
head = tail = null;
else
{
for(temp = head;temp.next!=tail;temp = temp.next);
tail = temp;
tail.next = head;
}
}
public void printAll(){
CNode temp;
for(temp = head;temp.next!=head;temp = temp.next)
System.out.print(temp.info+"->");
11
System.out.println(temp.info);
}
}
class CicularLinkedList{
public static void main(String[] args) {
CLinkList list = new CLinkList();
list.addToTail(50);
list.addToHead(40);
list.addToHead(30);
list.addToHead(20);
list.addToHead(10);
list.addToTail(60);
list.addToTail(70);
list.addToTail(80);
list.addToTail(90);
list.addToTail(100);
System.out.println("Circular Linked before deletion ");
list.printAll();
list.deleteAtHead();
list.deleteAtTail();
System.out.println("Circular Linked List after deletion at both head and tail ");
list.printAll();
}
}
The output of the above program is:
Circular Linked before deletion
10->20->30->40->50->60->70->80->90->100
Circular Linked List after deletion at both head and tail
20->30->40->50->60->70->80->90
The doubly circular linked List can be implemented in java as follows:
class DCNode {
public int info;
public DCNode next;
public DCNode prev;
public DCNode(){
info =0;
next = null;
prev = null;
}
public DCNode(int el){
info = el;
next = null;
12
prev = null;
}
}
class DCLinkList
{
public DCNode head;
public DCNode tail;
public DCLinkList(){
head = tail = null;
}
public boolean isEmpty(){
return head == null;
}
public void addToHead(int el)
{
DCNode temp = new DCNode(el);
temp.next = head;
if(head == null)
{
head = temp;
head.prev = temp;
tail = temp;
tail.next =temp;
}
else
{
head.prev = temp;
tail.next = temp;
temp.prev = tail;
head = temp;
}
}
public void addToTail(int el){
DCNode temp = new DCNode(el);
temp.next = head;
if(head==null){
head = temp;
head.prev = temp;
tail = temp;
tail.next = temp;
}
else
13
{
head.prev = temp;
tail.next =temp;
temp.prev = tail;
tail =temp;
}
}
public void deleteAtHead(){
if(head==tail)
head = tail = null;
else
{
head = head.next;
head.prev = tail;
tail.next = head;
}
}
public void deleteAtTail(){
if(head==tail)
head = tail = null;
else
{
tail = tail.prev;
head.prev = tail;
tail.next = head;
}
}
public boolean find(int el){
DCNode temp;
boolean t= false;
for(temp=head;temp.next!=head;temp=temp.next)
{
if(temp.info==el)
{
t=true;
break;
}
}
return t;
}
14
public void printAll(){
DCNode temp;
for(temp = tail;temp!=head;temp = temp.prev)
System.out.print(temp.info+"->");
System.out.println(temp.info);
}
}
class DoublyCicularLinkedList{
public static void main(String[] args) {
DCLinkList list = new DCLinkList();
list.addToHead(4);
list.addToHead(3);
list.addToHead(2);
list.addToHead(1);
list.addToTail(5);
list.addToTail(6);
list.addToTail(7);
list.addToTail(8);
list.addToTail(9);
list.addToTail(10);
System.out.println("Circular Linked before deletion ");
list.printAll();
list.deleteAtHead();
list.deleteAtTail();
System.out.println("Circular Linked after deletion at both ends ");
list.printAll();
System.out.println("The data 7 is in list "+list.find(17));
}
}
The output of the above program is:
Doubly Circular Linked before deletion
10->9->8->7->6->5->4->3->2->1
Doubly Circular Linked after deletion at both ends
9->8->7->6->5->4->3->2
The data 7 is in list false
Skip Lists
Linked lists have one serious drawback. They require sequential scanning to locate a searched for
element. The search starts from the beginning of the list and stops when either a searched for element
is found or the end of the list is reached without finding this element. Ordering elements on the list can
speed up searching, but a sequential searching is still required. Therefore, we may think about lists that
allow for skipping certain nodes to avoid sequential processing. A skip list is an interesting variant of the
ordered linked list that makes such a nonsequential search possible.
15
In a skip list of n nodes, for each k and I such that 1≤k≤logn and [1≤i≤n/2k-1 ], the node in position 2k-1.i
points to the node in position 2k-1.(i+1). This means that every second node points to the node two
positions ahead, every fourth node points to the node four positions ahead, and so on. This is
accomplished by having different numbers of reference fields in nodes on the list. Half of the nodes have
just one reference field, one fourth of the nodes have two reference fields, one eighth of the nodes have
three reference fields, and so on. The number of reference fields indicates the level of each node, and
the number of levels is maxLevel = [logn]+1.
Searching for an element el consists of following the references on the highest level until an element is
found that finishes the search successful. In the case of reaching the end of the list or encountering an
element key that is greater than el, the search is restarted from the node preceding the one containing
key, but this time starting from a reference on a lower level than before. The search continues until el is
found, or the first level references are followed to reach the end of the list or find an element greater
than el.
Searching appears to be efficient, however, the design of skip lists can lead to very inefficient insertion
and deletion procedures. To insert a new element, all nodes following the node just inserted have to be
restructured; the number of reference fields and value of references have to be changed. In order to
retain some of the advantages that skip lists offers with respect to searching and to avoid problems with
restructuring the lists when inserting and deleting nodes, the requirement on the positions of nodes of
different levels is now abandoned and only the requirement on the number of nodes of different levels
is kept. The new least is searched exactly the same way as the original list. Inserting does not require list
restructuring, and nodes are generated so that the distribution of the nodes on different levels is kept
adequate.
16
class SkipList{
private int maxLevel;
private SkipListNode[] root;
private int[] powers;
private Random rd = new Random();
public SkipList(){}
public SkipList(int i){
maxLevel = i;
root = new SkipListNode[maxLevel];
powers = new int[maxLevel];
for(int j=0;j<maxLevel;j++)
root[j] = null;
choosePowers();
}
public boolean isEmpty(){
return root[0]==null;
}
public void choosePowers(){
powers[maxLevel-1] = (2<<(maxLevel-1))-1;
for(int i = maxLevel-2,j=0;i>=0;i--,j++)
powers[i] = powers[i+1]-(2<<j);
}
public int chooseLevel(){
int i,r = Math.abs(rd.nextInt())%powers[maxLevel-1]+1;
for(i=1;i<maxLevel;i++)
if(r<powers[i])
return i-1;
return i-1;
}
public int search(int key){
int lvl;
SkipListNode prev,curr;
for(lvl = maxLevel-1;lvl>=0&&root[lvl]==null;lvl--);
prev = curr = root[lvl];
while(true){
if(key==curr.key)
return curr.key;
else if(key<curr.key){
if(lvl == 0)
return 0;
else if(curr == root[lvl])
curr = root[--lvl];
17
else curr = prev.next[--lvl];
}
else{
prev = curr;
if(curr.next[lvl]!=null)
curr = curr.next[lvl];
else
{
for(lvl--;lvl>=0&&curr.next[lvl]==null;lvl--);
if(lvl>=0)
curr = curr.next[lvl];
else return 0;
}
}
}
}
public void insert(int key){
SkipListNode [] curr = new SkipListNode[maxLevel];
SkipListNode [] prev = new SkipListNode[maxLevel];
SkipListNode newNode;
int lvl,i;
curr[maxLevel-1] = root[maxLevel-1];
prev[maxLevel-1] = null;
for(lvl = maxLevel-1;lvl>=0;lvl--){
while(curr[lvl]!=null&&curr[lvl].key<key){
prev[lvl] = curr[lvl];
curr[lvl] = curr[lvl].next[lvl];
}
if(curr[lvl]!=null&&curr[lvl].key==key)
return;
if(lvl>0)
if(prev[lvl]==null){
curr[lvl-1] = root[lvl-1];
prev[lvl-1] = null;
}
else{
curr[lvl-1] = prev[lvl].next[lvl-1];
prev[lvl-1] = prev[lvl];
}
}
lvl = chooseLevel();
newNode = new SkipListNode(key,lvl+1);
18
for(i=0;i<=lvl;i++){
newNode.next[i] = curr[i];
if(prev[i]==null)
root[i] = newNode;
else prev[i].next[i] = newNode;
}
}
}
public class SkipListDemo {
public static void main(String[] args) {
SkipList list = new SkipList(4);
list.insert(30);
list.insert(20);
list.insert(10);
list.insert(0);
list.insert(40);
list.insert(50);
list.insert(60);
list.insert(70);
System.out.println("The number 10 is in list "+list.search(100));
}
}
19
S.N Element searched Plain Move to Transpose Count Ordering
For Front
1 A A A A A A
2 C C AC AC AC AC
3 B ACB ACB ACB ACB ABC
4 C ACB CAB CAB CAB ABC
5 D ACBD CABD CABD CABD ABCD
6 A ACBD ACBD ACBD CABD ABCD
7 A ACBD DACB ACBD DCAB ABCD
8 D ACBD ADCB CADB ADCB ABCD
9 A ACBD CADB ACDB CADB ABCD
10 C ACBD ACDB CADB ACDB ABCD
11 A ACBD CADB ACDB ACDB ABCD
12 C ACBD CADB CADB CADB ABCD
13 C ACBD CADB CADB CADB ABCD
14 E ACBDE CADBE CADBE CADBE ABCDE
15 E ACBDE CADBA CADEB CAEDB ABCDE
With the first three methods, we try to place the elements most likely to be looked for near the
beginning of the list, most explicitly with the move to front method and most cautiously with the
transpose method. The ordering method uses some properties inherent to the information stored in the
list. For example, if we are storing nodes pertaining to people, then the list can be organized
alphabetically by the name of the person, or city or in ascending or descending order using say, birthday
or salary. This is particularly advantageous when searching for information that is not in the list, because
the search can terminate without scanning the entire list. Searching all the nodes of the list, however, is
necessary in such cases using the other three methods. The count method can be subsumed in the
category of the ordering methods if frequency is part of the information.
Analysis of the efficiency of these methods customarily compare their efficiency to that of optimal static
ordering.
Sparse Tables
In many applications, the choice of a table seems to be the most natural one, but space considerations
may preclude this choice. This is particularly true if only a small fraction of the table is actually used. A
table of this type is called a sparse table because the table is populated sparsely by data and most of its
cells are empty. In this case, the table is replaced by a system of linked lists.
As an example, consider the problem of storing grades of storing for all students in a university for a
certain semester. Assume that there are 8000 students and 300 classes. A natural implementation is two
dimensional array grades where student numbers are indexes of the columns and class numbers are
indexes of rows. Any association of student names and numbers is represented by the one dimensional
array students and an association of class names and numbers is represented by the array classes.
Each cell of grades stores a grade obtained by each student after finishing a class.
The entire table occupies 8000 students.300 classes. Byte = 2..4 million bytes.
20
This table is very large but is sparsely populated by grades. Assuming that, on the average, students take
four classes a semester, each column of the table has only four cells occupied by grades, and the rest of
the cells, 296 cells or 98.7 are unoccupied and wasted.
Student
Index 0 1 2 …. 404 405 …. 5206 5207 5208 ….. 7999
0 3
1 1 4 7 1
…
Class ….
30 5
31 0 5
….
115 0 4 5
116 3
117
….
299
21