Singly Linked Lists What is a singlylinked list

  • Slides: 34
Download presentation
Singly Linked Lists • What is a singly-linked list? • Why linked lists? •

Singly Linked Lists • What is a singly-linked list? • Why linked lists? • Singly-linked lists vs. 1 D-arrays • Representation • Space Analysis • Creation, Append and Prepend • Traversal • Search • Insertion after and before an element • Deletion • Time Complexity: Singly-linked lists vs. 1 D-arrays 1

What is a Singly-linked list? • A singly linked list is a dynamic data

What is a Singly-linked list? • A singly linked list is a dynamic data structure consisting of a sequence of nodes, forming a linear ordering. • Each node stores: – Element (data object) – Reference (i. e. , address) to the next node Node: Singly-linked list: 2

Why linked lists? • Linked lists are used to implement many important data structures

Why linked lists? • Linked lists are used to implement many important data structures such as stacks, queues, graphs, hash tables, etc. • Linked lists are used as components of some data structures. Examples: B+ trees, skip lists, etc. • LISP An important programming language in artificial intelligence makes extensive use of linked lists in performing symbolic processing. • Memory management: An important role of operating systems. An operating system must decide how to allocate and reclaim storage for processes running on the system. A linked list can be used to keep track of portions of memory that are available for allocation. • Scrolled lists, components found in graphical user interfaces, can be implemented using linked lists. 3

Singly-linked lists vs. 1 D-arrays ID-array Singly-linked list Fixed size: Resizing is expensive Dynamic

Singly-linked lists vs. 1 D-arrays ID-array Singly-linked list Fixed size: Resizing is expensive Dynamic size Insertions and Deletions are inefficient: Elements are usually shifted Insertions and Deletions are efficient: No shifting Random access i. e. , efficient indexing No random access Not suitable for operations requiring accessing elements by index such as sorting No memory waste if the array is full or almost full; otherwise may result in much memory waste. Extra storage needed for references; however uses exactly as much memory as it needs Sequential access is faster because of greater locality of references [Reason: Elements in contiguous memory locations] Sequential access is slow because of low locality of references [Reason: Elements not in contiguous memory locations] 4

Representation • We are using a representation in which a linked list has both

Representation • We are using a representation in which a linked list has both head and tail references: public class My. Linked. List{ protected Element head; protected Element tail; public final class Element{ Object data; Element next; Element(Object obj, Element element){ data = obj; next = element; } public Object get. Data(){return data; } public Element get. Next(){return next; } } //. . . } 5

Representation: Space Analysis • Assume: – There are n nodes in the list –

Representation: Space Analysis • Assume: – There are n nodes in the list – All data references are null • Number of references in the list and space required: Reference Total Required space head 1 Size. Of. Singly. Linked. List. Element. Reference tail 1 Size. Of. Singly. Linked. List. Element. Reference next n n*Size. Of. Singly. Linked. List. Element. Reference data n n*Size. Of. Object. Reference • Total space = (n + 2)*Size. Of. Singly. Linked. List. Element. Reference + n*Size. Of. Object. Reference • Hence space complexity is O(n) 6

List Creation • An empty list is created as follows: My. Linked. List list

List Creation • An empty list is created as follows: My. Linked. List list = new My. Linked. List(); • Once created, elements can be inserted into the list using either the append or prepend methods: for (int k = 0; k < 10; k++) list. append(new Integer(k)); • Also if we have a reference to a node (an element), we can use the insert. After or Insert. Before methods of the Element class 7

Insertion at the end (Append) public void append(Object obj){ Element element = new Element(obj,

Insertion at the end (Append) public void append(Object obj){ Element element = new Element(obj, null); if(head == null) head = element; else tail. next = element; tail = element; } Complexity is O(1) 8

Insertion at the beginning (Prepend) public void prepend(Object obj) { Element element = new

Insertion at the beginning (Prepend) public void prepend(Object obj) { Element element = new Element(obj, head); if(head == null) tail = element; Complexity is O(1) head = element; } 9

Traversal Begin at the first node, then follow each next reference until the traversal

Traversal Begin at the first node, then follow each next reference until the traversal condition is satisfied or until you come to the end. To move an Element reference e from one node to the next use: e = e. next; Example: Count the number of nodes in a linked list. public int count. Nodes(){ int count = 0; Element e = head; while(e != null){ Complexity is count++; e = e. next; } return count; } O(n) 10

Searching • • To search for an element, we traverse from head until we

Searching • • To search for an element, we traverse from head until we locate the object or we reach the end of the list. Example: Count the number of nodes with data field equal to a given object. public int count. Nodes(Object obj){ int count = 0; Element e = head; while(e != null){ if(e. data. equals(obj)) count++; e = e. next; } return count; } Complexity is …. • The following reference relationships are useful in searching: However, it is important to ensure that next is not null in such expressions 11

Insertion after an element • To insert an object y after a node x:

Insertion after an element • To insert an object y after a node x: • Move a reference e from the beginning of the list to node x: Element e = head; if(e == null) throw new Illegal. Argument. Exception(“not found”); while(e != null && !e. data. equals(x)){ e = e. next; } if(e == null) throw new Illegal. Argument. Exception(“not found”); • Create a new node containing y as data and let its next reference refer to the node after node x: Element element = new Element(y, e. next); • Make the next reference of node x refer to node y: e. next = element; • If the new node was inserted at the end of the list, update the tail reference: if(element. next == null) tail = element; 12

Insertion after an element • The insert. After method of the Element class is

Insertion after an element • The insert. After method of the Element class is invoked as: My. Linked. List. Element e = list. find(obj 1); if(e != null) e. insert. After(obj 2); // insert obj 2 after obj 1 else System. out. println("Element to insert before not found"); Complexity is O(n) • Within the insert. After method this refers to obj 1 node: public void insert. After(Object obj) { // create a new node for obj 2 and make it refer to the node // after obj 1 node Element element = new Element(obj, this. next); // make obj 1 node refer to the new node this. next = element; // update tail if the new node was inserted at the end if(this == tail) tail = next; Complexity is O(1) } • Note: The total complexity of the insert after operation is O(n) because find is O(n) 13

Insertion before an element • To insert an object y before a node x:

Insertion before an element • To insert an object y before a node x: • Move a reference previous from the beginning of the list to the node before node x: Element e = head, previous; if(e == null) throw new Illegal. Argument. Exception(“not found”); while(e != null && ! e. data. equals(x)){ previous = e; e = e. next; } if(e == null) throw new Illegal. Argument. Exception(“not found”); • Create a new node containing y as data and let its next reference refer to the node x: Element element = new Element(y, e); • Make the next reference of the node before node x refer to node y: if(e == head) head = element; else previous. next = element; 14

Insertion before an element • The insert. Before method of the Element class is

Insertion before an element • The insert. Before method of the Element class is invoked as: My. Linked. List. Element e = list. find(obj 1); Complexity is if(e != null) e. insert. Before(obj 2); // insert obj 2 before obj 1 else System. out. println("Element to insert before not found"); O(n) • Within the insert. Before method this refers to obj 1 node: public void insert. Before(Object obj) { // create a new node for obj 2, make this node point to obj 1 node Element element = new Element(obj, this); if(this == head){ head = element; return; } Element previous = head; // move previous to node before obj 1 node while(previous. next != this) { Complexity is O(n) previous = previous. next; } previous. next = element; // insert } 15

Deletion • To delete a node x: • Move a reference previous from the

Deletion • To delete a node x: • Move a reference previous from the beginning of the list to the node before node x: Element e = head, previous; if(e == null) throw new Illegal. Argument. Exception(“not found”); while(e != null && ! e. data. equals(x)){ previous = e; e = e. next; } if(e == null) throw new Illegal. Argument. Exception(“not found”); • Bypass the node to be deleted: if(e == head){ if(head. next == null) head = tail = e = null; else{ head = head. next; } else{ previous. next = e. next; if(tail == e) tail = previous; } e = null; 16

Deletion – Deleting First and Last Element public void extract. First() { if(head ==

Deletion – Deleting First and Last Element public void extract. First() { if(head == null) throw new Illegal. Argument. Exception("item not found"); head = head. next; if(head == null) tail = null; Complexity } is … public void extract. Last() { if(tail == null) throw new Illegal. Argument. Exception("item not found"); if (head == tail) head = tail = null; else { Element previous = head; while(previous. next != tail) Complexity previous = previous. next; previous. next = null; tail = previous; } } is … 17

 • • Deletion of an arbitrary element To delete an element, we use

• • Deletion of an arbitrary element To delete an element, we use either the extract method of My. Linked. List or that of the Element inner class. The My. Linked. List extract method (code similar to that in slide 16): public void extract(Object obj) { Element element = head; Element previous = null; while(element != null && ! element. data. equals(obj)) { previous = element; element = element. next; } Complexity is … if(element == null) throw new Illegal. Argument. Exception("item not found"); if(element == head) head = element. next; else previous. next = element. next; if(element == tail) tail = previous; } • The method is invoked as: try{ list. extract(obj 1); } catch(Illegal. Argument. Exception e){ System. out. println("Element not found"); } 18

Deletion of an arbitrary element • The Element extract method invocation and implementation: My.

Deletion of an arbitrary element • The Element extract method invocation and implementation: My. Linked. List. Element e = list. find(obj 1); if(e != null) e. extract(); else System. out. println("Element not found"); public void extract() { Element element = null; if(this == head) head = next; else{ element = head; while(element != null && element. next != this){ element = element. next; } if(element == null) throw new Invalid. Operation. Exception(“Not found”); element. next = next; } if(this == tail) tail = element; Complexity is … } 19

Time Complexity: Singly-linked lists vs. 1 D-arrays Operation ID-Array Complexity Singly-linked list Complexity Insert

Time Complexity: Singly-linked lists vs. 1 D-arrays Operation ID-Array Complexity Singly-linked list Complexity Insert at beginning O(n) O(1) Insert at end O(1) if the list has tail reference O(n) if the list has no tail reference Insert at middle* O(n) Delete at beginning O(n) O(1) Delete at end O(1) O(n) Delete at middle* O(n): O(1) access followed by O(n) shift O(n): O(n) search, followed by O(1) delete Search O(n) linear search O(log n) Binary search O(n) Indexing: What is the element at a given position k? O(1) O(n) * middle: neither at the beginning nor at the end 20

Exercises • Using the Element extract method is less efficient than using the My.

Exercises • Using the Element extract method is less efficient than using the My. Linked. List extract method. Why? • For the My. Linked. List class, Implement each of the following methods: – – String to. String() Element find(Object obj) void insert. At(int n) //counting the nodes from 1. void delete. Before(Object obj) // delete node before obj node State the complexity of each method. • Which methods are affected if we do not use the tail reference in My. Linked. List class. 21

Doubly Linked Lists • What is a doubly-linked list? • Representation • Space Analysis

Doubly Linked Lists • What is a doubly-linked list? • Representation • Space Analysis • Doubly-linked lists vs. Singly-linked lists • Creation, Append and Prepend • Traversal • Insertion before an element • Deletion 22

What is a Doubly-linked list? • A doubly linked list is a dynamic data

What is a Doubly-linked list? • A doubly linked list is a dynamic data structure consisting of a sequence of nodes, forming a linear ordering. • Each node stores: – Element (data object) – Reference (i. e. , address) to the next node – Reference (i. e. , address) to the previous node Node: Doubly-linked list: 23

Representation public class Doubly. Linked. List{ protected Element head, tail; //. . . public

Representation public class Doubly. Linked. List{ protected Element head, tail; //. . . public class Element { Object data; Element next, previous; Element(Object obj, Element next, Element previous){ data = obj; this. next = next; this. previous = previous; } public Object get. Data(){return data; } public Element get. Next(){return next; } public Element get. Previous(){return previous; } //. . . } } 24

Doubly-Linked Lists: Space Analysis • Assume: – There are n nodes in the list

Doubly-Linked Lists: Space Analysis • Assume: – There are n nodes in the list – All data references are null • Number of references in the list and space required: Reference Total Required space head 1 Size. Of. Doubly. Linked. List. Element. Reference tail 1 Size. Of. Doubly. Linked. List. Element. Reference next n n*Size. Of. Doubly. Linked. List. Element. Reference previous n n*Size. Of. Doubly. Linked. List. Element. Reference data n n*Size. Of. Object. Reference • Total space = (2 n + 2)*Size. Of. Doubly. Linked. List. Element. Reference + n*Size. Of. Object. Reference • Hence space complexity is O(n) 25

Doubly-Linked Lists vs. Singly-linked lists • A doubly-linked list allows traversing the list in

Doubly-Linked Lists vs. Singly-linked lists • A doubly-linked list allows traversing the list in either direction. • Modifying a doubly-linked list usually requires changing more references, but is sometimes simpler because there is no need to keep track of the address of the previous node. In singly-linked list, this is required in delete and insert before operations. The extract. Last operation is O(1) in doubly-linked list whereas it is O(n) is singly-linked list • Doubly-linked lists are used to implement dequeues (double-ended queues that support insert or delete operations at either end). • A singly-linked list uses less memory than an equivalent doubly-linked list. 26

List Creation and Insertion • An empty doubly-linked list is created as follows: Doubly.

List Creation and Insertion • An empty doubly-linked list is created as follows: Doubly. Linked. List list = new Doubly. Linked. List(); • Like a singly-linked list, once created, elements can be inserted into the list using either the append or prepend methods: for (int k = 0; k < 10; k++) list. append(new Int(k)); • Also if we have a reference to a node (an element), we can use the insert. After or Insert. Before methods of the Element class. 27

Insertion at the end (append) public void append(Object obj){ Element element = new Element(obj,

Insertion at the end (append) public void append(Object obj){ Element element = new Element(obj, null, tail); if(head == null) head = tail = element; else { tail. next = element; tail = element; Complexity is … } } 28

Insertion at the beginning (prepend) public void prepend(Object obj){ Element element = new Element(obj,

Insertion at the beginning (prepend) public void prepend(Object obj){ Element element = new Element(obj, head, null); if(head == null) head = tail = element; else { Complexity is … head. previous = element; head = element; } } 29

Traversal For Doubly. Linked list, traversal can be done in either direction. Forward, starting

Traversal For Doubly. Linked list, traversal can be done in either direction. Forward, starting from the head, or backward starting from the tail. Element e = head; while (e != null) { //do something e = e. next; } Element e = tail; while (e != null) { //do something e = e. previous; } Example: Count the number of nodes in a linked list. public int count. Nodes(){ int count = 0; Element e = head; while(e != null){ count++; e = e. next; } return count; } Complexity is … 30

Traversal (cont’d) Example: The following computes the sum of the last n nodes: public

Traversal (cont’d) Example: The following computes the sum of the last n nodes: public int sum. Last. Nnodes(int n){ if(n <= 0) throw new Illegal. Argument. Exception("Wrong: " + n); if(head == null) throw new List. Empty. Exception(); int count = 0, sum = 0; Element e = tail; Complexity is while(e != null && count < n){ sum += (Integer)e. data; count++; e = e. previous; } if(count < n) throw new Illegal. Argument. Exception(“No. of nodes < "+n); return sum; … } 31

Insertion before an element • Inserting before the current node (this) that is neither

Insertion before an element • Inserting before the current node (this) that is neither the first nor the last node: Element element = new Element(obj, this. previous); this. previous. next = element; this. previous = element; Complexity is … 32

Deletion • To delete an element, we use either the extract method of Doubly.

Deletion • To delete an element, we use either the extract method of Doubly. Linked. List or that of the Element inner class. public void extract(Object obj){ Element element = head; while((element != null) && (!element. data. equals(obj))) element = element. next; if(element == null) Complexity is … throw new Illegal. Argument. Exception("item not found"); if(element == head) { head = element. next; if(element. next != null) element. next. previous = null; }else{ element. previous. next = element. next; if(element. next != null) element. next. previous = element. previous; } if(element == tail) tail = element. previous; } 33

Exercises • For the Doubly. Linked. List class, Implement each of the following methods

Exercises • For the Doubly. Linked. List class, Implement each of the following methods and state its complexity. – – – String to. String() Element find(Object obj) void Extract. Last() void Extract. First() void Extract. Last. N(int n) • For the Doubly. Linked. List. Element inner class, implement each of the following methods and state its complexity. – void insert. Before() – void insert. After() – void extract() • What are the methods of Doubly. Linked. List and its Element inner class that are more efficient than those of My. Linked. List class? 34