0% found this document useful (0 votes)
52 views36 pages

CSCI 104 Linked Lists: Mark Redekopp David Kempe

Linked lists allow data structures to grow and shrink dynamically by allocating memory for each item individually (rather than in large blocks like arrays). A linked list contains items that point to the next item in the list via pointers. Appending an item adds it to the end of the list by either setting the head pointer (for an empty list) or updating the next pointer of the last item to point to the new item.

Uploaded by

Rui Veleiro
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
52 views36 pages

CSCI 104 Linked Lists: Mark Redekopp David Kempe

Linked lists allow data structures to grow and shrink dynamically by allocating memory for each item individually (rather than in large blocks like arrays). A linked list contains items that point to the next item in the list via pointers. Appending an item adds it to the end of the list by either setting the head pointer (for an empty list) or updating the next pointer of the last item to point to the new item.

Uploaded by

Rui Veleiro
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 36

1

CSCI 104
Linked Lists
Mark Redekopp
David Kempe
2

Array Problems
• Once allocated an array cannot grow or shrink
• If we don't know how many items will be added we could just allocate an
array larger than we need but…
– We might waste space
– What if we end up needing more…would need to allocate a new array and
copy items
• Arrays can't grow with the needs of the client
21
append(21) =>
0 1 2 3 4 5
Old, full array 30 51 52 53 54 10

0 1 2 3 4 5 6 7 8 9 10 11
Allocate new
array
0 1 2 3 4 5 6 7 8 9 10 11
Copy over items 30 51 52 53 54 10
0 1 2 3 4 5 6 7 8 9 10 11
Add new item 30 51 52 53 54 10 21
3

Motivation for Linked Lists


• Can we create a list implementation that can easily grow or
shrink based on the number of items currently in the list
• Observation: Arrays are allocated and deallocated in LARGE
chunks
– It would be great if we could allocate/deallocate at a finer granularity
• Linked lists take the approach of allocating in small chunks
(usually enough memory to hold one item)

Bulk Item Single Item


(i.e. array) (i.e. linked
list)
4

Linked List
• Use structures/classes and pointers #include<iostream>
using namespace std;
to make ‘linked’ data structures
struct Item {
• A List is… int val; Item blueprint:
Item* next; int Item*
– Arbitrarily sized collection of }; val next
values class List
– Can add any number of new values {
public:
via dynamic memory allocation List();
~List();
– Supports typical List ADT void push_back(int v); ...
operations: private:
Item* head_;
• Insert };
• Get
• Remove
• Size head 0x148
0x148 0x1c0 0x168
• Empty 0x0
3 0x1c0 9 0x168 2 (Null)
• Can define a List class val next val next val next

Rule of thumb: Still use ‘structs’ for objects that are


purely collections of data and don’t really have
operations associated with them. Use ‘classes’ when
data does have associated functions/methods.
5

Don't Need Classes


#include<iostream>
• We don't have to use using namespace std;
struct Item {
int val;
classes… Item* next;
}; Item blueprint:
– The class just acts as a wrapper int Item*
around the head pointer and the void append(Item*& head, int v); val next
bool empty(Item* head);
operations int size(Item* head);
– So while a class is probably the
int main()
correct way to go in terms of {
organizing your code, for today we Item* head1 = NULL;
Item* head2 = NULL;
can show you a less modular, int size1 = size(head1);
procedural approach bool empty2 = empty(head2);
...
• Define functions for each }

operation and pass it the class List:


head_
head pointer as an argument 0x0

Rule of thumb: Still use ‘structs’ for objects that are


purely collections of data and don’t really have
operations associated with them. Use ‘classes’ when
data does have associated functions/methods.
6

Linked List Implementation


#include<iostream>
using namespace std;
struct Item {
• To maintain a linked list you need only int val;
Item* next;
keep one data value: head };
– Like a train engine, we can attach any void append(Item*& head, int v);
number of 'cars' to the engine
int main()
– The engine looks different than all the {
others Item* head1 = NULL;
Item* head2 = NULL;
• In our linked list it's just a single pointer
to an Item }

• All the cars are Item structs


• Each car has a hitch for a following car
(i.e. next pointer) head1
0x0
NULL

Engine = Each car =


"head" "Item"
7

A Common Misconception
• Important Note:
– 'head' is NOT an Item, it is a pointer to the
first item
– Sometimes folks get confused and think
head is an item and so to get the location
of the first item they write 'head->next' head
– In fact, 'head->next' evaluates to the 2nd 0x148
items address
0x148 0x1c0 0x168
0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next
8

Append
#include<iostream>
using namespace std;
• Adding an item (train car) to the struct Item {
int val;
back can be split into 2 cases: Item* next;
};
– Attaching the car to the engine (i.e.
void append(Item*& head, int v)
the list is empty and we have to {
change the head pointer) if(head == NULL){
head = new Item;
– Attaching the car to another car (i.e. }
head->val = v; head->next = NULL;

the list has other Items already) and else {...}


}
so we update the next pointer of an
Item int main()
{
Item* head1 = NULL;
Item* head2 = NULL;
append(head1, 3)
}

head1

0x148
0x0 0x148

3 NULL

val next
9

Linked List
#include<iostream>

• Adding an item (train car) to the using namespace std;


struct Item {
int val;
back can be split into 2 cases: Item* next;
};
– Attaching the car to the engine (i.e.
void append(Item*& head, int v)
the list is empty and we have to {
change the head pointer) if(head == NULL){
head = new Item;
– Attaching the car to another car (i.e. head->val = v; head->next = NULL;
}
the list has other Items already) and else {...}
}
so we update the next pointer of an
Item int main()
{
Item* head1 = NULL;
Item* head2 = NULL;
append(head1, 3)
}

head

0x148 0x148 0x1c0


0x0
3 0x1c0
NULL 9 NULL
val next val next
10

Linked List
#include<iostream>
• Adding an item (train car) to the using namespace std;
struct Item {
back can be split into 2 cases: int val;
Item* next;
};
– Attaching the car to the engine (i.e.
the list is empty and we have to void append(Item*& head, int v)
{
change the head pointer) if(head == NULL){
head = new Item;
– Attaching the car to another car (i.e. head->val = v; head->next = NULL;
}
the list has other Items already) and else {...}
so we update the next pointer of an }

Item int main()


{
Item* head1 = NULL;
Item* head2 = NULL;
append(head1, 3)
}

head

0x148 0x148 0x1c0 0x168


0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next
11

Append()
void append(Item*& head, int v)
• Look at how the head parameter is {
Item* newptr = new Item;
passed…Can you explain it? newptr->val = v; newptr->next = NULL;

– Head might need to change if it is the 1st if(head == NULL){


item that we are adding head = newptr;
}
– We've passed the head pointer BY VALUE else {
Item* temp = head;
so if we modify 'head' in append() we'll // iterate to the end
only be modifying the copy ...
}
– We need to pass the pointer by reference }

– We choose Item*& but we could also pass


void append(Item** head, int v)
an Item** {
Item* newptr = new Item;
newptr->val = v; newptr->next = NULL;
head
if(*head == NULL){
0x200 0x0 0x148 *head = newptr;
}
else {
0x148
Item* temp = *head;
0x0 // iterate to the end
3 NULL
...
val next }
}
12

Arrays/Linked List Efficiency


• Arrays are contiguous pieces of memory #include<iostream>
using namespace std;
• To find a single value, computer only needs
int main()
– The start address {
int data[25];
• Remember the name of the array evaluates to data[20] = 7;
the starting address (e.g. data = 120) return 0;
}
– Which element we want
• Provided as an index (e.g. [20]) data = 100
100 104 108 112 116 120
– This is all thanks to the fact that items are 45 31 21 04 98 73 …
contiguous in memory Memory
• Linked list items are not contiguous
– Thus, linked lists have an explicit field to head

indicate where the next item is 0x148

– This is "overhead" in terms of memory usage


0x148 0x1c0 0x168
– Requires iteration to find an item or move to 0x0
the end 3 0x1c0 9 0x168 2 (Null)
val next val next val next
13

Append()
void append(Item*& head, int v)

• Start from head and iterate to


{
Item* newptr = new Item;
newptr->val = v; newptr->next = NULL;
end of list if(head == NULL){
– Allocate new item and fill it in }
head = newptr;

– Copy head to a temp pointer else {


Item* temp = head;
– Use temp pointer to iterate through // iterate to the end
...
the list until we find the tail (element }
}
with next field = NULL)
head
– Update old tail item to point at new 0x148
tail item
0x148 0x1c0 0x168
0x168
0x0 0x0
3 0x1c0 9 NULL 2 (Null)
val next val next val next

0x148 0x1c0

temp temp

I don’t know where the list ends so I have


14

Iterating Over a Linked List


void append(Item*& head, int v)
• To iterate we probably need {
Item* newptr = new Item;
to create a copy of the head newptr->val = v; newptr->next = NULL;

pointer (because if we if(head == NULL){


head = newptr;
}
modify 'head' we'll never else {
Item* temp = head;
remember where the list while(temp->next){
temp = temp->next;

started }
temp->next = newptr;
}
• How do we take a step }

(advance one Item) given


the temp pointer head 0x148 0x148 0x1c0

– temp = temp->next;
0x0
3 0x1c0 9 NULL
val next val next

0x148 0x1c0

temp temp
15

Using a For loop

void append(Item*& head, int v) void append(Item*& head, int v)


{ {
Item* newptr = new Item; Item* newptr = new Item;
newptr->val = v; newptr->next = NULL; newptr->val = v; newptr->next = NULL;

if(listPtr == NULL){ if(listPtr == NULL){


head = newptr; head = newptr;
} }
else { else {
Item* temp = head; // init Item* temp;
while(temp->next){ // condition for(temp = head; // init
temp = temp->next; // update temp->next; // condition
} temp = temp->next); // update
temp->next = newptr;
} temp->next = newptr;
} }
}
16

Printing Out Each Item

void print(Item* head) void print(Item* head)


{ {
Item* temp = head; // init Item* temp;
while(temp) { // condition for(temp = head; // init
cout << temp->val << endl; temp; // condition
temp = temp->next; // update temp = temp->next){ // update
} cout << temp->val << endl;
} }
}
17

RECURSION & LINKED LISTS


18

Recursion and Linked Lists


• Notice that one Item's next pointer looks like a head
pointer to the remainder of the linked list
– If we have a function that processes a linked list by
receiving the head pointer as a parameter we can
recursively call that function by passing our 'next' pointer
as the 'head'
head 0x148

head2 0x1c0

0x148 0x1c0 0x168


0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next
19

Recursive Operations on Linked List


• Many linked list operations can be recursively defined
• Can we make a recursive iteration function to print items?
– Recursive case: Print one item then the problem becomes to print the n-1 other items.
• Notice that any 'next' pointer can be though of as a 'head' pointer to the remaining sublist
– Base case: Empty list
(i.e. Null pointer)
• How could you print values in reverse order?
void print(Item* ptr)
0xbe8 0x0 ptr {
print
Return
if(ptr == NULL) return;
0xbec 004001844 link else {
cout << ptr->val << endl;
print(ptr->next);
0xbe8 0x1c0 ptr }
print
0xbec Return }
004001844 link int main()
{ Item* head;
0xbf0 ...
print
0x148 ptr
print(head);
0xbf4 Return }
004001844 link

0xbf8 head 0x148 0x148 0x1c0


0x148 head
main 0x0
0xbfc 00400120
Return 3 0x1c0 9 NULL
link
val next val next
20

Summing the Values


• Write a recursive routine to sum the values of a
linked list
– Head Recursion (recurse first, do work on the way
back up)
– Tail Recursion (do work on the way down, then
recurse)
0x148

0x148 0x1c0 0x168


0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next
21

Head Recursion
• Recurse to the end of the chain (head == NULL) and then start
summing on the way back up
– What should the base case return
– What should recursive cases (normal nodes) return?
Main()

head 0x148

sum(0x148) sum(0x1c0) sum(0x168) sum(0x0)


0x148 0x1c0 0x168
0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next

3+11=14 2+9=11 0+2=2 0

What would the prototype of this recursive function be?


22

Tail Recursion
• Produce sum as you walk down the list then just
return the final answer back up the list

Main()

head 0x148

sum(0x148) sum(0x1c0) sum(0x168) sum(0x0)


0x148 0x1c0 0x168
3 12 14
0x0
3 0x1c0 9 0x168 2 (Null)
val next val next val next

14 14 14 14

What would the prototype of this recursive function be?


23

Exercises
• llsum_head
• llsum_tail
24

Recursive Copy
• How could you make a copy of a linked list using
recursion struct Item {
int val;
0x148
Item* next;
oldhead 0x148 0x1c0
Item(int v, Item* n){
0x0 val = v; next = n;
3 0x1c0 9 NULL
}
val next val next };

Item* copyLL(Item* head)


{
if(head == NULL) return NULL;
else {

}
}
int main()
newhead ??? { Item* oldhead, *newhead;
...
newhead = copyLL(oldhead);
}
25

Recursive Copy
• How could you make a copy of a linked list using
recursion
struct Item {
oldhead 0x148 0x148 0x1c0 int val;
0x0 Item* next;
3 0x1c0 9 NULL Item(int v, Item* n){
val next val next
val = v; next = n;
}
};

Item* copyLL(Item* head)


{
if(head == NULL) return NULL;
else {
return new Item(head->val,
copyLL(head->next));
}
}
int main()
newhead ??? { Item* oldhead, *newhead;
...
newhead = copyLL(oldhead);
}
26

Recursive Copy
• How could you make a copy of a linked list using
recursion
struct Item {
oldhead 0x148 int val;
Item* next;
0x148 0x1c0 Item(int v, Item* n){
0x0 val = v; next = n;
3 0x1c0 9 NULL }
val next val next
};

Item* copyLL(Item* head)


copyLL(0x148) copyLL(0x1c0) copyLL(0x0) {
0x840 0x7c0 if(head == NULL) return NULL;
0x0 else {
3 0x7c0 9 NULL return new Item(head->val,
val next val next copyLL(head->next));
}
0x7c0 0x0 }
int main()
{ Item* oldhead, *newhead;
...
newhead = copyLL(oldhead);
0x840 }
newhead
27

INCREASING EFFICIENCY OF
OPERATIONS + DOUBLY LINKED
LISTS
28

Adding a Tail Pointer


• If in addition to maintaining a head head tail tail

0x148 0x1c0 0x168


pointer we can also maintain a tail
0x148 0x1c0 0x168
pointer 0x168
2
0x0
3 0x1c0 9 NULL
(Null)
• A tail pointer saves us from val next val next val next

iterating to the end to add a new


item
• Need to update the tail pointer
when…
– We add an item to the end (fast)
– We remove an item from the end
(slow)
29

Removal
• To remove the last item, we need to update the 2nd
to last item (set it's next pointer to NULL)
• We also need to update the tail pointer
• But this would require us to traverse the full list
• ONE SOLUTION: doubly-linked list
tail

0x1c0
head


0x148 0x200 0x1c0
0x148
3 0x200 5 0x1c0 9 NULL

val next val next val next


30

Doubly-Linked Lists
• Includes a previous pointer #include<iostream>

in each item so that we can using namespace std;


struct Item blueprint:
struct DLItem {
traverse/iterate backwards int val; DLItem *
prev
int
val
DLItem *
next
DLItem* prev;
or forward DLItem* next;
};
• First item's previous field int main()
should be NULL {
DLItem* head, *tail;

• Last item's next field should };

be NULL

tail
0x210

head
0x148 0x1c0 0x210
0x148
NULL 3 0x1c0 0x148 9 0x210 0x1c0 6 NULL

prev val next prev val next prev val next


31

Doubly-Linked List Add Front


• Adding to the front requires you to update…
• …Answer
– Head
– New front's next & previous
– Old front's previous

0x190
head
12
0x148
prev val next

0x148 0x1c0 0x210

NULL 3 0x1c0 0x148 9 0x210 0x1c0 6 NULL

prev val next prev val next prev val next


32

Doubly-Linked List Add Front


• Adding to the front requires you to update…
– Head
– New front's next & previous
– Old front's previous

head
0x148

0x190 0x148 0x1c0 0x210

NULL 12 0x148 0x190 3 0x1c0 0x148 9 0x210 0x1c0 6 NULL

prev val next prev val next prev val next prev val next
33

Doubly-Linked List Add Middle


• Adding to the middle requires you to update…
– Previous item's next field
– Next item's previous field
– New item's next field
– New item's previous field

0x190

head 12

0x148 prev val next

0x148 0x1c0 0x210

NULL 3 0x1c0 0x148 9 0x210 0x1c0 6 NULL

prev val next prev val next prev val next


34

Doubly-Linked List Add Middle


• Adding to the middle requires you to update…
– Previous item's next field
– Next item's previous field
– New item's next field
– New item's previous field

head
0x148

0x148 0x1c0 0x190 0x210

NULL 3 0x1c0 0x148 9 0x190 0x1c0 12 0x210 0x190 6 NULL

prev val next prev val next prev val next prev val next
35

Doubly-Linked List Remove Middle


• Removing from the middle requires you to update…
– Previous item's next field
– Next item's previous field
– Delete the item object

head
0x148

0x148 0x1c0 0x210

NULL 3 0x1c0 0x148 9 0x210 0x1c0 6 NULL

prev val next prev val next prev val next


36

Doubly-Linked List Remove Middle


• Removing from the middle requires you to update…
– Previous item's next field
– Next item's previous field
– Delete the item object

head 0x1c0

0x148 0x148 9 0x210

prev val next


0x148 0x210

NULL 3 0x210 0x148 6 NULL

prev val next prev val next

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy