Upload
mindy
View
210
Download
3
Embed Size (px)
DESCRIPTION
Let’s first forget about ‘classes’, but only a dynamic list. We make lists with ‘classes’ afterwards. List, (dynamic) linked list. A simple list Example: using a dynamic array. concept of a list, e.g. a list of integers Print out info Empty test Search an element - PowerPoint PPT Presentation
Citation preview
List, (dynamic) linked list
Let’s first forget about ‘classes’, but only a dynamic list.
We make lists with ‘classes’ afterwards.
A simple list Example: using a dynamic array
concept of a list, e.g. a list of integers Print out info Empty test Search an element Insertion (at head, at end, any position) Deletion …
implemented by a static array (over-sized if necessary)
int list[1000]; int size; by a dynamic array
int list[size]; int size; by a linked list and more …
int main() {
cout << "Enter list size: ";int n;cin >> n;int* A = new int[n];
initialize(A, n, 0);print(A, n);A = addEnd(A,n,5); print(A, n);A = addHead(A,n,5); print(A, n);A = deleteFirst(A,n); print(A, n);selectionSort(A, n);print(A, n);delete [] A;}
How to use a list?
int A[10000];
int n;
Nothing compulsory in programming, only style matters!
Initialize
void initialize(int list[], int size, int value){
for(int i=0; i<size; i++)
list[i] = value;
}
void print(int list[], int size) {
cout << "[ ";
for(int i=0; i<size; i++)
cout << list[i] << " ";
cout << "]" << endl;
}
Print out a list
Delete the first element// for deleting the first element of the arrayint* deleteFirst(int list[], int& size){
int* newList;newList = new int[size-1]; // make new array
if(size){ // copy and delete old arrayfor(int i=0; i<size-1; i++)
newList[i] = list[i+1];delete [] list;
}size--;return newList;
}
Instead of
A = deleteFirst(A,n)
we can also just
deleteFirst(A,n) if we define as a void type function:
void deleteFirst(int*& A, int& size) {
…
A = newList;
}
Remark:
We can also B = deleteFirst(A,n) if we keep the original intact
Adding Elements// for adding a new element to end of arrayint* addEnd(int list[], int& size, int value){
int* newList;newList = new int [size+1]; // make new array
if(size){ // copy and delete old arrayfor(int i=0; i<size; i++)
newList[i] = list[i];delete [] list;
}newList[size] = value;size++;return newList;
}
// for adding a new element at the beginning of the arrayint* addHead(int list[], int& size, int value){
int* newList;newList = new int [size+1]; // make new array
if(size){ // copy and delete old arrayfor(int i=0; i<size; i++)
newList[i+1] = list[i];delete [] list;
}newList[0] = value;size++;return newList;
}
Add at the beginning:
Linked list: a dynamic list
Motivation list using static array
int myArray[1000]; int n;
We have to decide (to oversize) in advance the size of the array (list)
list using dynamic arrayint* myArray; int n;cin >> n;myArray = new int[n];
We allocate an array (list) of any specified size while theprogram is running
linked-list (dynamic size)size = ??The list is dynamic. It can grow and shrink to any size.
Array naturally represents a (ordered) list,
the link is implicit, consecutive and contiguous!
Now the link is explicit, any places!
20 45 75 85
Data
Link
20
45
75
85Data Link
20 45 75 85
Data Link
0 1 2 array
linked list
Linked Lists: Basic Idea
A linked list is an ordered collection of data Each element of the linked list has
Some data A link to the next element
The link is used to chain the data
Example: A linked list of integers:
20 45 75 85
Data Link
The list can grow and shrink
Linked Lists: Basic Ideas
20 45 75 85
20 45
addEnd(75), addEnd(85)
deleteEnd(85), deleteHead(20), deleteHead(45)
75
Original linked list of integers:
Insertion (in the middle):
Deletion (in the middle)
Linked Lists: Operations
20 45 75 85
20 45 75 85
20 45 75 85
60
old value
deleted item
struct Node{ int data;Node* next;
};
We can also:
typedef Node* NodePtr;
Definition of linked list type:
Linked List Structure Node : Data + Link
Definitionstruct Node {
int data; //contains useful information
Node* next; //points to next element or NULL
};
Create a NodeNode* p;
p = new Node; //points to newly allocated memory
Delete a Nodedelete p;
Access fields in a node(*p).data; //access the data field
(*p).next; //access the pointer field
Or it can be accessed this way
p->data //access the data field
p->next //access the pointer field
Representing and accessing linked lists
We define a pointer
Node* head;
that points to the first node of the linked list. When the linked list is empty then head is NULL.
20 45 75 85Head
Passing a Linked List to a Function
When passing a linked list to a function it should suffice to pass the value of head. Using the value of head the function can access the entire list.
Problem: If a function changes the beginning of a list by inserting or deleting a node, then head will no longer point to the beginning of the list.
Solution: When passing head always pass it by reference (not good!)
or using a function to return a new pointer value
It is roughly the same as for an array!!!
Implementation of an (Unsorted) Linked List
Start the first node from scratch
Node* newPtr;
newPtr = new Node;newPtr->data = 20;newPtr->next = NULL; head = newPtr;
Head
newPtr
20
Headhead = NULL;
Inserting a Node at the Beginning
newPtr = new Node;
newPtr->data = 13;
newPtr->next = Head;
head = newPtr;
Head
newPtr
13
20
Keep going …
Head
newPtr
50 40 13 20
void addHead(Node*& head, int newdata){
Node* newPtr = new Node;
newPtr->data = newdata;newPtr->next = Head;head = newPtr;
}
Adding an element to the head:
Call by reference, scaring!!!
NodePtr&
Node* addHead(Node* head, int newdata){
Node* newPtr = new Node;
newPtr->data = newdata;newPtr->next = Head;
return newPtr;}
Also written (more functionally) as:
Compare it with ‘addHead’ with a dynamic array implementation
(to delete)
Deleting the Head Node
Node* p;
p = head;
head = head->next;
delete p;
head
p
50 40 13 20
void deleteHead(Node*& head){
if(head != NULL){
NodePtr p = head;
head = head->next;
delete p;
}
}
Node* deleteHead(Node* head){
if(head != NULL){
NodePtr p = head;
head = head->next;
delete p;
}
return head;
}
As a function:
Displaying a Linked List
p = head;
p = p->next;
20 45head
p
20 45head
p
void displayList(Node* head){
NodePtr p;
p = head;
while(p != NULL){
cout << p->data << endl;
p = p->next;
} }
A linked list is displayed by walking through its nodes one by one,
and displaying their data fields (similar to an array!).
void displayArray(int data[], int size) { int n=0; while ( n<size ) {
cout << data[i] << endl; n++;
}
}
For an array:
//return the pointer of the node that has data=item//return NULL if item does not exist
Node* searchNode(Node* head, int item){NodePtr p = head;
NodePtr result = NULL;bool found=false;while((p != NULL) && (!found)){
if(p->data == item) {found = true;result = p;}
p = p->next;}return result;
}
Searching for a node (look at array searching first!)
void main() { const int size=8; int data[size] = { 10, 7, 9, 1, 17, 30, 5, 6 };
int value; cout << "Enter search element: ";
cin >> value; int n=0; int position=-1; bool found=false; while ( (n<size) && (!found) ) {
if(data[n] == value) { found=true; position=n;}
n++;}if(position==-1) cout << "Not found!!\n";else cout << "Found at: " << position << endl;
}
Remember array searching algorithm:
It is essentially the same!
Variations of linked lists
Unsorted linked lists
Sorted linked lists
Circular linked lists Doubly linked lists …
Further considerations for the unsorted lists:
Physical copy of list for operators like ‘deleteHead’ and ‘addHead’
‘deleteHead’ should be understood as a decomposition into a sub-list …
Node* deleteHead(Node* head){
// physically copy head into a new one, newhead
// so to keep the original list intact!
Node* newhead=NULL;
Node* temp=head;
while(temp!=NULL) {
newhead=addEnd(newhead,temp->data);
temp=temp->next;
}
if(newhead != NULL){
Node* p = newhead;
newhead = newhead->next;
delete p;
}
return newhead;
}
B = deleteHead(A);
Original linked list of integers:
Add to the end (insert at the end):
More operation: adding to the end
50 40 13 20
50 40 13 20 60
Last element
The key is how to locate the last element or node of the list!
void addEnd(NodePtr& head, int newdata){NodePtr newPtr = new Node;newPtr->data = newdata;newPtr->next = NULL;
NodePtr last = head;if(last != NULL){ // general non-empty list case
while(last->next != NULL) last=last->next;
last->next = newPtr;}else // deal with the case of empty list
head = newPtr;}
Add to the end:
Link new object to last->nextLink a new object to empty list
NodePtr addEnd(NodePtr head, int newdata){NodePtr newPtr = new Node;newPtr->data = newdata;newPtr->next = NULL;
NodePtr last = head;if(last != NULL){ // general non-empty list case
while(last->next != NULL) last=last->next;
last->next = newPtr;}else // deal with the case of empty list
head = newPtr;
return head;}
Add to the end as a function:
Implementation of a
Sorted Linked List
Inserting a Node
Head
cur
20
33
45 75
prev
...
newPtr
1. (a) Create a new node using: NodePtr newPtr = new node;
(b) Fill in the data field correctly.
2. Find “prev” and “cur” such that
the new node should be inserted between *prev and *cur.
3. Connect the new node to the list by using:
(a) newPtr->next = cur;
(b) prev->next = newPtr;
Finding prev and cur
Suppose that we want to insert or delete a node with data value newValue. Then the following code successfully finds prev and cur such that
prev->data < newValue <= cur->data
prev = NULL;
cur = head;
found=false;
while( (cur!=NULL) && (!found) ) {
if (newValue > cur->data) {
prev=cur;
cur=cur->next;
}
else found = true;
}
Prev is necessary as we can’t go back!
It’s a kind of search algo,
prev = NULL;
cur = head;
while( (cur!=NULL) && (newValue>cur->data) ) {
prev=cur;
cur=cur->next;
}
Logical AND (&&) is short-circuited, sequential, i.e. if the first part is false, the second part will not be executed.
Finally, it is equivalent to:
//insert item into linked list according to ascending orderNode* insertNode(Node* head, int item){
NodePtr newp, cur, pre; newp = new Node;newp->data = item;
pre = NULL;cur = head;while( (cur != NULL) && (item>cur->data)){
pre = cur;cur = cur->next;
}
if(pre == NULL){ //insert to head of linked listnewp->next = head;head = newp;
} else {pre->next = newp;new->next = cur;
}
return head;}
If the position happens to be the head
General case
// not recommended void type functionvoid insertNode(NodePtr& head, int item){
NodePtr newp, cur, pre; newp = new Node;newp->data = item;
pre = NULL;cur = head;while( (cur != NULL) && (item>cur->data)){
pre = cur;cur = cur->next;
}
if(pre == NULL){ //insert to head of linked listnewp->next = head;head = newp;
} else {pre->next = newp;new->next = cur;
}}
(to delete)
Deleting a Node To delete a node from the list
1. Locate the node to be deleted(a) cur points to the node.
(b) prev points to its predecessor
2. Disconnect node from list using: prev->next = cur->next;
3. Return deleted node to system: delete cur;
Head
cur
20 45 75 85
prev
...
Node* deleteNode(Node* head, int item){NodePtr prev=NULL, cur = head;while( (cur!=NULL) && (item > cur->data)){
prev = cur;cur = cur->next;
}
if ( cur!==NULL && cur->data==item) {
if(cur==head)head = head->next;
elseprev->next = cur->next;
delete cur; }
return head;}
Delete an element in a sorted linked list:
If the element is at the head
General case
We can delete only if the element is present!
If (cur==NULL || cur->data!=item) Item is not in the list!
Get the location
void deleteNode(NodePtr& head, int item){NodePtr prev=NULL, cur = head;while( (cur!=NULL) && (item > cur->data)){
prev = cur;cur = cur->next;
}
if ( cur!==NULL && cur->data==item) {
if(cur==Head)Head = Head->next;
elseprev->next = cur->next;
delete cur; }}
// in a void function, not recommended
If the element is at the head
General case
We can delete only if the element is present!
If (cur==NULL || cur->data!=item) Item is not in the list!
Get the location
Example of a (dynamic) class: linked list class
bool listEmpty(NodePtr head) {
}int getHead(NodePtr head) {
}NodePtr getRest(NodePtr head) {
}NodePtr addHead(NodePtr head, int newdata) {
}void delHead(NodePtr& Head){
}
linked lists: definition
struct Node{ int data;Node* next;
};
typedef Node* NodePtr;
NodePtr head;
void main(){
NodePtr Head1=NULL, Head2 = NULL, Head;
addHead(Head1, 50);
addHead(Head1, 40);
addHead(Head1, 30);
addHead(Head1, 20);
cout << "List 1: " << endl;
DisplayList(Head1);
cout << "Length of Head1 list: " << length(Head1) << endl;
cout << "Recursive length of Head1 list: " << lengthRec(Head1) << endl;
if(isPalindrome(Head1))
cout << "Head1 list is palindrome" << endl;
else
cout << "Head1 list is not palindrome" << endl;
addHead(Head2, 25);
addHead(Head2, 35);
addHead(Head2, 45);
addHead(Head2, 35);
addHead(Head2, 25);
cout << "List 2: " << endl;
DisplayList(Head2);
cout << "Length of Head2 list: " << length(Head2) << endl;
cout << "Recursive length of Head2 list: " << lengthRec(Head2) << endl;
if(isPalindrome(Head2))
cout << "Head2 list is palindrome" << endl;
else
cout << "Head2 list is not palindrome" << endl;
Head = mergeLists(Head1, Head2);
cout << "Merged List: " << endl;
DisplayList(Head);
cout << "Length of Merged list: " << length(Head) << endl;
cout << "Recursive length of Merged list: " << lengthRec(Head) << endl;
if(isPalindromeRec(Head))
cout << "Merged list is palindrome" << endl;
else
cout << "Merged list is not palindrome" << endl;
cout << "check the list again:" << endl;
DisplayList(Head);
}
Usage:
Make an Abstract Data Type
One more example of ADT: integer linked list using class
A class with dynamic objects: Copy constructor Destructor
struct Node{ public:
int data;Node* next;
};typedef Node* Nodeptr;
class list {public:
list(); // constructorlist(const list& list1); // copy constructor~list(); // destructor
bool empty() const; // boolean functionint headElement() const; // access functions
void addHead(int newdata); // add to the headvoid deleteHead(); // delete the head
int length() const; // utility functionvoid print() const; // output
private:Nodeptr head;
};‘old’ operations
‘new’ member functions
void main(){list L; // constructor called automatically here for LL.print(); { }L.addHead(30);L.print(); { 30 }L.addHead(13);L.print(); { 13 30 } L.addHead(40);L.print(); { 40 13 30 }L.addHead(50);L.print(); { 50 40 13 30 }list N(L);N.print(); { 50 40 13 30 }
list R;R.print(); { }if(R.empty())
cout << "List R empty" << endl;L.deleteHead();L.print(); { 40 13 30 }L.deleteHead();L.print(); { 13 30 }if(L.empty())
cout << "List L empty" << endl;else{
cout << "List L contains " << L.length() << " nodes" << endl;cout << "Head element of list L is: " << L.headElement() << endl;
}} // destructor called automatically here for L
How to use it
list::list(){head = NULL;
}
bool list::empty() const{if(head==NULL)
return true;else
return false;}
int list::headElement() const {if(head != NULL)
return head->data;else{
cout << "error: trying to find head of empty list" << endl;
exit(1);}
}
Some simple member functions:
Implementation
list::list(const list& list1) {
head = NULL;
Nodeptr cur = list1.head;
while(cur != NULL) {
// addEnd(cur->data);
addHead(cur->data); // inverse list order
cur = cur->next;
}
}
(explicitly defined) copy constructor:
Destructor: deallocation function
list::~list(){
Nodeptr cur;
while(head!=NULL){
cur = head;
head = head->next;
delete cur;
}
}
void list::addHead(int newdata){
Nodeptr newPtr = new Node;
newPtr->data = newdata;
newPtr->next = head;
head = newPtr;
}
Adding an element to the head:
void list::deleteHead(){
if(head != NULL){
Nodeptr cur = head;
head = head->next;
delete cur;
}
}
Deleting the head:
void list::print() const{cout << "{";Nodeptr cur = head;while(cur != NULL){
cout << cur->data << " ";
cur = cur->next;}
cout << "}" << endl;}
Print the list:
int list::length() const{
int n=0;Nodeptr cur = head;while(cur != NULL){
n++;cur = cur->next;
}return n;
}
Computing the number of elements of a given list:
struct Node{ public:
int data;Node* next;
};typedef Node* Nodeptr;
class list {public:
list(); // constructorlist(const list& list1); // copy constructorconst list& operator=(const list& list1); // assigment, l = l1;~list(); // destructor
bool empty() const; // boolean functionint head() const; // access functionslist remaining() const; // the list with the head removed void insert(int d); // insertionvoid delete(int d); // deletion
int length() const; // utility functionvoid print() const; //
private:Nodeptr head;
};
Interface functions
An almost ideal list class
list::list(const listClass& list1) {
head = NULL;
Nodeptr cur = list1.head;
while(cur != NULL) {
// addEnd(cur->data);
addHead(cur->data); // inverse list order
cur = cur->next;
}
}
copy constructor:
Const list& operator=(const list& list1) {
if (this != &list1) {
head = NULL;
Nodeptr cur = list1.head;
while(cur != NULL) {
// addEnd(cur->data);
addHead(cur->data); // inverse list order
cur = cur->next;
}
return *this;
}
Operator assignment, ‘deep copy’
Delete[] head;
Big three: copy constructor, operator=, destructor
list l1, l2;
l1.addEnd(5);
list l3(l1);
l3 = l2;
node* head1, head2;
head1 = NULL; head2 = NULL
addEnd(head1,5);
node* head3 = NULL;
copylist(head1, head3);
head3 = head2;
Usage difference
Doubly Linked List
Motivation
Doubly linked lists are useful for playing video and sound files with “rewind” and instant “replay”
They are also useful for other linked data where “require” a “fast forward” of the data as needed
list using an array: Knowledge of list size Access is easy (get the ith element) Insertion/Deletion is harder
list using ‘singly’ linked lists: Insertion/Deletion is easy Access is harder
But, can not ‘go back’!
Doubly Linked Lists
In a Doubly Linked-List each item points to both its predecessor and successor prev points to the predecessor next points to the successor
10 7020 5540
HeadCur Cur->nextCur->prev
struct Node{
int data;
Node* next;
Node* prev;
};
typedef Node* NodePtr;
Doubly Linked List Definition
Doubly Linked List Operations insertNode(NodePtr& Head, int item)//add new node to ordered doubly linked //list
deleteNode(NodePtr& Head, int item) //remove a node from doubly linked list
SearchNode(NodePtr Head, int item)
Print(nodePtr Head, int item)
Deleting a Node
Delete a node Cur (not at front or rear)
(Cur->prev)->next = Cur->next; (Cur->next)->prev = Cur->prev;
delete Cur;
10 7020 5540
HeadCur
void deleteNode(NodePtr& head, int item) {
NodePtr cur;
cur = searchNode(head, item);
if (head==NULL) { …
}
else if (cur->prev == NULL) { …
}
else if (cur->next==NULL) { …
}
else {
(cur->prev)->next = cur->next;
(cur->next)->prev = cur->prev;
delete cur;
}
}
Empty case
At-the-beginning case
At-the-end case
General case
A systematic way is to start from all these cases, then try to simply the codes, …
Inserting a Node
Insert a node New before Cur (not at front or rear)
10 7020 55
40Head
New
Cur
New->next = Cur;
New->prev = Cur->prev;
Cur->prev = New;
(New->prev)->next = New;
void insertNode(NodePtr& head, int item) {
NodePtr cur;
cur = searchNode(head, item);
if (head==NULL) { …
}
else if (cur->prev == NULL) { …
}
else if (cur->next==NULL) { …
}
else {
blablabla …}
}
Many special cases to consider.
Many different linked lists … singly linked lists
Without ‘dummy’ With dummy circular
doubly linked lists Without ‘dummy’ With dummy
Using ‘dummy’ is a matter of personal preference!
+ simplify codes (not that much - Less logically sounds
20Head 10 20 40 7055
Rear
10 20 40 7055
7020 5540
Head
10
7020 5540
Head
10Dummy
singly linked list
(singly) circular linked list
(regular) doubly linked list
doubly circular linked list with dummy
Doubly Linked Lists with Dummy Head Node
To simplify insertion and deletion by avoiding special cases of deletion and insertion at front and rear, a dummy head node is added at the head of the list
The last node also points to the dummy head node as its successor
Idea of ‘dummy’ object
Instead of pointing to NULL, point to the ‘dummy’!!! Skip over the dummy for the real list
7020 5540
Head
10Dummy Head Node
‘dummy object’ is also called a ‘sentinel’, it allows the simplification of special cases, but confuses the emptyness NULL!
Head
Dummy Head Node
Empty list:
Head->next = head; compared with head=NULL;
void createHead(NodePtr& head){
head = new Node;
head->next = head;
head->prev = head;
}
NodePtr head;
createHead(head);
NodePtr cur=head;
cur=cur->next;
cur=head; // dummy head
NodePtr head=NULL;
NodePtr cur=head;
cur=head;
cur=NULL; // or head=NULL;
operations Doubly linked with dummy Singly linked
creation
Empty test
Start from
reference
void print(NodePtr head){
NodePtr cur=head->next;
while(cur != head){
cout << cur->data << " ";
cur = cur->next;
}
}
Print the whole list:
NodePtr searchNode(NodePtr head, int item){
NodePtr cur = head->next;
while ((cur != head) && (item != cur->data)) cur=cur->next;
if (cur == head) cur = NULL; // we didn’t find
return cur;
}
Searching a node
(returning NULL if not found the element):
End of the list, empty
Deleting a Node
Delete a node Cur at front
7020 5540
Head
10Dummy Head Node
Cur
(Cur->prev)->next = Cur->next;
(Cur->next)->prev = Cur->prev;
delete Cur;
Delete a node Cur in the middle
(Cur->prev)->next = Cur->next;
(Cur->next)->prev = Cur->prev;
delete Cur; // same as delete front!
70
Head
10Dummy Head Node
20 5540
Cur
Delete a node Cur at rear
(Cur->prev)->next = Cur->next;
(Cur->next)->prev = Cur->prev;
delete Cur; // same as delete front and middle!
7020 5540
Head
10Dummy Head Node
Cur
void deleteNode(NodePtr head, int item){
NodePtr cur;
cur = searchNode(head, item);
if(cur != NULL){
cur->prev->next = cur->next;
cur->next->prev = cur->prev;
delete cur;
}
}
If we found the element, it does not mean any emptyness!
Inserting a Node Insert a Node New after dummy node and
before Cur
Head
Dummy Head Node
Cur
20
New->next = Cur;
New->prev = Cur->prev;
Cur->prev = New;
(New->prev)->next = New;
10
New
Insert a Node New at Rear (with Cur pointing to dummy head)
New->next = Cur;
New->prev = Cur->prev;
Cur->prev = New;
(New->prev)->next = New;
7020 5540
Head
10Dummy Head Node
Cur New
Insert a Node New in the middle and before Cur
New->next = Cur;
New->prev = Cur->prev;
Cur->prev = New;
(New->prev)->next = New;
55
Head
10Dummy Head Node
20
Cur
40
New
Insert a Node New to Empty List (with Cur pointing to dummy head node)
Head
Dummy Head Node
New
20
New->next = Cur;
New->prev = Cur->prev;
Cur->prev = New;
(New->prev)->next = New;
Cur
void insertNode(NodePtr head, int item){ NodePtr newp, cur; newp = new Node;
newp->data = item; cur = head->next; while ((cur != head)&&(!(item<=cur->data)))
cur = cur->next;
newp->next = cur; newp->prev = cur->prev; cur->prev = newp;
(newp->prev)->next = newp;}
It is similar to, but different from SearchNode!
(it returns NULL if no element)
creation
location
insertion
void main(){ NodePtr Head, temp; createHead(Head); insertNode(Head, 3); insertNode(Head, 5); insertNode(Head, 2); print(Head); insertNode(Head, 7); insertNode(Head, 1); insertNode(Head, 8); print(Head); deleteNode(Head, 7); deleteNode(Head, 0); print(Head); temp = searchNode(Head, 5); if(temp !=NULL) cout<<" Data is contained in the list"<<endl; else cout<<" Data is NOT contained in the list"<<endl; }
Result is
2 3 5
1 2 3 5 7 8
1 2 3 5 8
Data is contained in the list
Stacks and Queues
struct Node{ double data;Node* next;
};
class List {public:
List(); // constructorList(const List& list); // copy constructor~List(); // destructorList& operator=(const List& list); // assignment operator
bool empty() const; // boolean functionvoid addHead(double x); // add to the headdouble deleteHead(); // delete the head and get the head element// List& rest(); // get the rest of the list with the head removed// double headElement() const; // get the head element
void addEnd(double x); // add to the enddouble deleteEnd(); // delete the end and get the end element// double endElement(); // get the element at the end
bool searchNode(double x); // search for a given xvoid insertNode(double x); // insert x in a sorted listvoid deleteNode(double x); // delete x in a sorted list
…
void print() const; // outputint length() const; // count the number of elements
private:Node* head;
};
More complete list ADT
Stack Overview
Stack ADT Basic operations of stack
Pushing, popping etc.
Implementations of stacks using array linked list
Stack
A stack is a list in which insertion and deletion take place at the same end This end is called top The other end is called bottom
Stacks are known as LIFO (Last In, First Out) lists. The last element inserted will be the first to be retrieved
Push and Pop
Primary operations: Push and Pop Push
Add an element to the top of the stack
Pop Remove the element at the top of the stack
top
empty stack
Atop
push an element
top
push another
A
Btop
pop
A
Implementation of Stacks
Any list implementation could be used to implement a stack Arrays (static: the size of stack is given initially) Linked lists (dynamic: never become full)
We will explore implementations based on array and linked list
class Stack {public:
Stack(); // constructorStack(const Stack& stack); // copy constructor~Stack(); // destructor
bool empty() const; void push(const double x); double pop(); // change the stack
double top() const; // keep the stack unchanged
// bool full(); // optional// void print() const;
private:…
};
Stack ADT
Compare with List, see that it’s ‘operations’ that define the type!
inspection, access
Using Stack
int main(void) {Stack stack;stack.push(5.0);stack.push(6.5);stack.push(-3.0);stack.push(-8.0);stack.print();cout << "Top: " << stack.top() << endl;
stack.pop();cout << "Top: " << stack.top() << endl;while (!stack.empty()) stack.pop();stack.print();return 0;
}
result
struct Node{ public:
double data;Node* next;
};
class Stack {public:
Stack(); // constructorStack(const Stack& stack); // copy constructor~Stack(); // destructor
bool empty() const; void push(const double x); double pop(); // change the stack
bool full(); // unnecessary for linked listsdouble top() const; // keep the stack unchanged
void print() const;
private:Node* top;
};
Stack using linked lists
void List::addHead(int newdata){
Nodeptr newPtr = new Node;
newPtr->data = newdata;
newPtr->next = head;
head = newPtr;
}
void Stack::push(double x){
Node* newPtr = new Node;
newPtr->data = x;
newPtr->next = top;
top = newPtr;
}
From ‘addHead’ to ‘push’
Push (addHead), Pop (deleteHead)
Implementation based on ‘existing’ linked lists
Optional to learn Good to see that we may ‘re-use’ linked lists
Now let’s implement a stack based on a linked list To make the best out of the code of List, we implement Stack
by inheriting the List To let Stack access private member head, we make Stack
as a friend of List
class List {public:
List() { head = NULL; } // constructor~List(); // destructor
bool empty() { return head == NULL; }Node* insertNode(int index, double x);int deleteNode(double x);
int searchNode(double x);void printList(void);
private:Node* head;friend class Stack;
};
class Stack : public List {public:
Stack() {}~Stack() {}double top() {
if (head == NULL) {cout << "Error: the stack is empty." << endl;return -1;
}else
return head->data;}void push(const double x) { InsertNode(0, x); }double pop() {
if (head == NULL) {cout << "Error: the stack is empty." << endl;return -1;
}else {
double val = head->data;DeleteNode(val);return val;
}}
void printStack() { printList(); }};
Note: the stack implementation based on a linked list will never be full.
from List
Stack using arrays
class Stack {public:
Stack(int size = 10); // constructor~Stack() { delete [] values; } // destructor
bool empty() { return top == -1; }void push(const double x);double pop();
bool full() { return top == maxTop; }double top();void print();
private:int maxTop; // max stack size = size - 1int top; // current top of stackdouble* values; // element array
};
Attributes of Stack maxTop: the max size of stack top: the index of the top element of stack values: point to an array which stores elements of stack
Operations of Stack empty: return true if stack is empty, return false otherwise full: return true if stack is full, return false otherwise top: return the element at the top of stack push: add an element to the top of stack pop: delete the element at the top of stack print: print all the data in the stack
Allocate a stack array of size. By default, size = 10.
Initially top is set to -1. It means the stack is empty. When the stack is full, top will have its maximum value, i.e.
size – 1.
Stack::Stack(int size /*= 10*/) {values = new double[size];top = -1;
maxTop = size - 1;}
Although the constructor dynamically allocates the stack array, the stack is still static. The size is fixed after the initialization.
Stack constructor
void push(const double x); Push an element onto the stack Note top always represents the index of the top
element. After pushing an element, increment top.
void Stack::push(const double x) {if (full()) // if stack is full, print error
cout << "Error: the stack is full." << endl;else
values[++top] = x;}
double pop() Pop and return the element at the top of the stack Don’t forgot to decrement top
double Stack::pop() {if (empty()) { //if stack is empty, print error
cout << "Error: the stack is empty." << endl;return -1;
}else {
return values[top--];}
}
double top() Return the top element of the stack Unlike pop, this function does not remove the top
element
double Stack::top() {if (empty()) {
cout << "Error: the stack is empty." << endl;return -1;
}else
return values[top];}
void print() Print all the elements
void Stack::print() {cout << "top -->";for (int i = top; i >= 0; i--)
cout << "\t|\t" << values[i] << "\t|" << endl;cout << "\t|---------------|" << endl;
}
Stack Application: Balancing Symbols
To check that every right brace, bracket, and parentheses must correspond to its left counterpart e.g. [( )] is legal, but [( ] ) is illegal
How? Need to memorize Use a counter, several counters, each for a type of
parenthesis …
Balancing Symbols using a stack
Algorithm(1) Make an empty stack.(2) Read characters until end of file
i. If the character is an opening symbol, push it onto the stackii. If it is a closing symbol, then if the stack is empty, report an erroriii. Otherwise, pop the stack. If the symbol popped is not the corresponding opening symbol, then report an error
(3) At end of file, if the stack is not empty, report an error
Stack Application: postfix, infix expressions and
calculator Postfix expressions
a b c * + d e * f + g * + Operands are in a stack
Convert infix to postfix a+b*c+(d*e+f)*g a b c * + d e * f + g * + Operators are in a stack
Calculator Adding more operators …
Stack Application: function calls and recursion
Take the example of factorial! And run it.
#include <iostream>using namespace std;
int fac(int n){int product;if(n <= 1) product = 1;else product = n * fac(n-1);return product;
}
void main(){int number;cout << "Enter a positive integer : " << endl;;cin >> number;cout << fac(number) << endl;
}
Stack Application: function calls and recursion
Take the example of factorial! And run it.
#include <iostream>using namespace std;
int fac(int n){int product;if(n <= 1) product = 1;else product = n * fac(n-1);return product;
}
void main(){int number;cout << "Enter a positive integer : " << endl;;cin >> number;cout << fac(number) << endl;
}
Assume the number typed is 3. fac(3): has the final returned value 6
3<=1 ? No.
product3 = 3*fac(2) product3=3*2=6, return 6,
fac(2):2<=1 ? No.
product2 = 2*fac(1) product2=2*1=2, return 2,
fac(1):1<=1 ? Yes.return 1
Tracing the program …
fac(3) prod3=3*fac(2)
prod2=2*fac(1)fac(2)
fac(1) prod1=1
Call is to ‘push’ and return is to ‘pop’!
top
Array versus linked list implementations
push, pop, top are all constant-time operations in both array and linked list implementation Caveat: insertNode and deleteNode have to
be done at the beginning of the list! For array implementation, the operations are
performed in very fast constant time
Queue Overview
Queue ADT Basic operations of queue
Enqueuing, dequeuing etc.
Implementation of queue Linked list Array
Queue
A queue is also a list. However, insertion is done at one end, while deletion is performed at the other end.
It is “First In, First Out (FIFO)” order. Like customers standing in a check-out line in a
store, the first customer in is the first customer served.
Enqueue and Dequeue
Primary queue operations: Enqueue and Dequeue Like check-out lines in a store, a queue has a front
and a rear. Enqueue – insert an element at the rear of the
queue Dequeue – remove an element from the front of
the queue
Insert (Enqueue)
Remove(Dequeue) rearfront
Implementation of Queue
Just as stacks can be implemented as arrays or linked lists, so with queues.
Dynamic queues have the same advantages over static queues as dynamic stacks have over static stacks
(“static” should be interpreted as “non-dynamic” here!)
class Queue {public:
Queue();Queue(Queue& queue);~Queue();
bool empty();void enqueue(double x);double dequeue();
void print(void);// bool full(); // optional
private:…
};
Queue ADT
‘physical’ constructor/destructor
‘logical’ constructor/destructor
Using Queueint main(void) {
Queue queue;cout << "Enqueue 5 items." << endl;for (int x = 0; x < 5; x++)
queue.enqueue(x);cout << "Now attempting to enqueue again..." << endl;queue.enqueue(5);queue.print();double value;value=queue.dequeue();cout << "Retrieved element = " << value << endl;queue.print();queue.enqueue(7);queue.print();return 0;
}
Struct Node {double data;Node* next;
}
class Queue {public:
Queue();Queue(Queue& queue);~Queue();
bool empty();void enqueue(double x);double dequeue();
// bool full(); // optionalvoid print(void);
private:Node* front; // pointer to front nodeNode* rear; // pointer to last nodeint counter; // number of elements
};
Queue using linked lists
class Queue {public:
Queue() { // constructorfront = rear = NULL;counter = 0;
}~Queue() { // destructor
double value;while (!empty()) dequeue(value);
}bool empty() {
if (counter) return false;else return true;
}void enqueue(double x);double dequeue();
// bool full() {return false;};void print(void);
private:Node* front; // pointer to front nodeNode* rear; // pointer to last nodeint counter; // number of elements, not compulsary
};
Implementation of some online member functions …
Enqueue (addEnd)void Queue::enqueue(double x) {
Node* newNode = new Node;newNode->data = x;newNode->next = NULL;
if (empty()) {front = newNode;
}else {
rear->next = newNode;}
rear = newNode;counter++;
}
8
rear
rear
newNode
5
58
Dequeue (deleteHead)double Queue::dequeue() {
double x;if (empty()) {
cout << "Error: the queue is empty." << endl;exit(1); // return false;
}else {
x = front->data;Node* nextNode = front->next;delete front;front = nextNode;counter--;
}return x;
}
front
583
8 5
front
Printing all the elements
void Queue::print() {cout << "front -->";Node* currNode = front;for (int i = 0; i < counter; i++) {
if (i == 0) cout << "\t";else cout << "\t\t"; cout << currNode->data;if (i != counter - 1)
cout << endl;else
cout << "\t<-- rear" << endl;currNode = currNode->next;
}}
Queue using Arrays There are several different algorithms to
implement Enqueue and Dequeue Naïve way
When enqueuing, the front index is always fixed and the rear index moves forward in the array.
front
rear
Enqueue(3)
3
front
rear
Enqueue(6)
3 6
front
rear
Enqueue(9)
3 6 9
Naïve way (cont’d) When dequeuing, the front index is fixed, and the
element at the front the queue is removed. Move all the elements after it by one position. (Inefficient!!!)
Dequeue()
front
rear
6 9
Dequeue() Dequeue()
front
rear
9
rear = -1
front
A better way When enqueued, the rear index moves forward. When dequeued, the front index also moves forward
by one element
XXXXOOOOO (rear) OXXXXOOOO (after 1 dequeue, and 1 enqueue)OOXXXXXOO (after another dequeue, and 2 enqueues)OOOOXXXXX (after 2 more dequeues, and 2 enqueues)
(front)
The problem here is that the rear index cannot move beyond the last element in the array.
Using Circular Arrays
Using a circular array When an element moves past the end of a circular
array, it wraps around to the beginning, e.g. OOOOO7963 4OOOO7963 (after Enqueue(4))
How to detect an empty or full queue, using a circular array algorithm? Use a counter of the number of elements in the queue.
class Queue {public:
Queue(int size = 10); // constructorQueue(const Queue& queue);
~Queue() { delete [] values; } // destructor
bool empty(void);void enqueue(double x); // or bool enqueue();double dequeue();
bool full();void print(void);
private:int front; // front indexint rear; // rear indexint counter; // number of elementsint maxSize; // size of array queuedouble* values; // element array
};
full() is not essential, can be embedded
Attributes of Queue front/rear: front/rear index counter: number of elements in the queue maxSize: capacity of the queue values: point to an array which stores elements of the queue
Operations of Queue empty: return true if queue is empty, return false otherwise full: return true if queue is full, return false otherwise enqueue: add an element to the rear of queue dequeue: delete the element at the front of queue print: print all the data
Queue constructor
Queue(int size = 10) Allocate a queue array of size. By default, size = 10. front is set to 0, pointing to the first element of the
array rear is set to -1. The queue is empty initially.
Queue::Queue(int size /* = 10 */) {values = new double[size];maxSize = size;front = 0;rear = -1;counter = 0;
}
Empty & Full Since we keep track of the number of elements
that are actually in the queue: counter, it is easy to check if the queue is empty or full.
bool Queue::empty() {if (counter==0) return true;else return false;
}bool Queue::full() {
if (counter < maxSize) return false;else return true;
}
Enqueue
void Queue::enqueue(double x) {if (full()) {
cout << "Error: the queue is full." << endl;exit(1); // return false;
}else {
// calculate the new rear position (circular)rear = (rear + 1) % maxSize; // insert new itemvalues[rear] = x;// update countercounter++;// return true;
}}
Or ‘bool’ if you want
Dequeue
double Queue::dequeue() {double x;if (empty()) {
cout << "Error: the queue is empty." << endl;exit(1); // return false;
}else {
// retrieve the front itemx = values[front];// move front front = (front + 1) % maxSize;// update countercounter--;// return true;
}return x;
}
Printing the elements
void Queue::print() {cout << "front -->";for (int i = 0; i < counter; i++) {
if (i == 0) cout << "\t";else cout << "\t\t"; cout << values[(front + i) % maxSize];if (i != counter - 1)
cout << endl;else
cout << "\t<-- rear" << endl;}
}
Using Queueint main(void) {
Queue queue;cout << "Enqueue 5 items." << endl;for (int x = 0; x < 5; x++)
queue.enqueue(x);cout << "Now attempting to enqueue again..." << endl;queue.enqueue(5);queue.print();double value;value=queue.dequeue();cout << "Retrieved element = " << value << endl;queue.print();queue.enqueue(7);queue.print();return 0;
}
Results
Queue implemented using linked list will be never full!
based on array based on linked list
Queue applications
When jobs are sent to a printer, in order of arrival, a queue.
Customers at ticket counters …