|
1
|
|
|
2
|
- 13.1 Nodes and Linked Lists
- 13.2 Stacks and Queues
|
|
3
|
|
|
4
|
- A linked list is a list that can grow and shrink while the program is
running
- A linked list is constructed using pointers
- A linked list often consists of structs or classes that contain a
pointer variable connecting them to other dynamic variables
- A linked list can be visualized as items, drawn as boxes, connected to
other items by arrows
|
|
5
|
- The boxes in the previous drawing represent the nodes of a linked list
- Nodes contain the data item(s) and a pointer that can point to another
node of the same type
- The pointers point to the entire node, not an individual item that
might be in the node
- The arrows in the drawing represent pointers
|
|
6
|
- Nodes are implemented in C++ as structs or
classes
- Example: A structure to store
two data items and
a
pointer to another node of the same type,
along
with a type definition might be:
struct Node {
string data;
Node
*next;
};
typedef Node* NodePtr;
|
|
7
|
- The box labeled head, is not a
node, but a pointer variable that points to a node
- Pointer variable head is declared as:
NodePtr head;
|
|
8
|
|
|
9
|
- This one way to change the string in the first node from “Nissan 350z” to
“Porsche 911”:
(*head).data
= “Porsche 911”;
- head is a pointer variable so *head is the node that head points to
- The parentheses are necessary because the dot operator . has higher
precedence than the
dereference operator *
|
|
10
|
- The arrow operator -> combines the actions of the dereferencing operator * and the dot operator to specify
a member of a struct or object pointed to by a pointer
- (*head). data = “Porsche 911”;
can be written as
head->data = “Porsche 911”;
- The arrow operator is more commonly used
|
|
11
|
- The defined constant NULL is used as…
- An end marker for a linked list
- A program can step through a list of nodes by following the pointers,
but when it finds a node containing NULL, it knows it has come to the
end of the list
- The value of a pointer that has nothing to point to
- The value of NULL is 0 (zero)
- Any pointer can be assigned the value NULL:
int
*p = NULL;
|
|
12
|
- A definition of NULL is found in several
libraries, including <iostream> and <cstddef>
- A using directive is not needed for NULL
|
|
13
|
- A linked list is a list of nodes in which each node has a member
variable that is a pointer that points to the next node in the list
- The first node is called the head
- The pointer variable head, points to the first node
- The pointer named head is not the head of the list…it points to the
head of the list
- The last node contains a pointer set to NULL
|
|
14
|
- Let's begin with a simple node definition:
struct Node {
string data;
Node *next;
};
typedef Node* NodePtr;
|
|
15
|
- With the node defined and a type definition to make our code easier to
understand, we can declare the pointer variable head:
NodePtr head;
- head is a pointer variable that will point to the
head node when the node is created
|
|
16
|
- To create the first node, the operator new is used to create a new
dynamic variable:
head = new Node;
- Now head points to the first, and only, node in the list
|
|
17
|
- Now that head points to a node, we need to
give values to the member variables of the node:
head->data = “Toyota
Tercel”;
head->next
= NULL;
- Since this node is the first and last node, the next pointer is set to
NULL
|
|
18
|
|
|
19
|
- cur->next – incorrect
- When cur points to the last node of
nonempty list cur->next is NULL
- The loop would terminate before displaying the data in the last node
- for (NodePtr cur = head; cur != NULL; cur = cur->next)
- cout << cur->data << endl;
|
|
20
|
- struct Node {
- string data;
- Node *next;
- }; typedef Node* NodePtr;
- class LinkList {
- public: LinkList();
- ~LinkList();
- void insert(const string& aData);
- bool insert(const string& aData1, const string& aData2);
- NodePtr search(const string& target) const;
- void deleteNode(const string& aData);
- int getSize() const;
- private:
- NodePtr mHead; // users don't have access to the
- int mSize;
- };
|
|
21
|
- It would be better to create a function to insert
nodes at the head of a list, such as:
- void insert(const string&
aData);
- The first parameter is a string parameter that the data element for
the node
- insert() will create a new node for the string
- The string will be copied to the new node
- The new node will be inserted in the list as the new head node
|
|
22
|
- Create a new dynamic variable pointed to by
aNode
- Place the string in the new aNode->data
- Make aNode’s next pointer variable point to the head node (now it will
point to the original first node in the list)
- Make the head pointer point to aNode
|
|
23
|
|
|
24
|
- The pseudocode for head_insert can be written
in C++ using these lines in place of the lines of
pseudocode:
- // Construct a new node
- NodePtr aNode = new Node;
- aNode->data = aData; // set data string to the // node’s data
- aNode->next = head; // set next pointer to head
- head = aNode; // now head pts to the first
- // element aNode
|
|
25
|
- A list with nothing in it is called an empty list
- An empty linked list has no head node
- The head pointer of an empty list is NULL
head = NULL;
- Any functions written to manipulate a linked list
should check to see if it works on the empty list
|
|
26
|
- You might be tempted to write insert() using
the head pointer to construct the new node:
head
= new Node;
head->data = aData;
- Now to attach the new node to the list
- The node that head used to point to is now lost!
|
|
27
|
|
|
28
|
- Nodes that are lost by assigning their pointers a
new address are not accessible any longer
- The program has no way to refer to the nodes
and cannot delete them to return their memory
to the freestore (Heap)
- Programs that lose nodes have a memory leak
- Significant memory leaks can cause system crashes
|
|
29
|
- To design a function that will locate a particular node in a linked
list:
- We want the function to return a pointer to the node so we can use the
data if we find it, else return NULL
- The data (string) we wish to find is the argument
- This declaration will work:
- NodePtr& search(NodePtr& cur, const string& target) const
|
|
30
|
- Refining our function
- We will use a pointer variable, named cur, to move through the list
checking for the target
- The only way to move around a linked list is to follow pointers
- We will start with here pointing to the first node and move the pointer
from node to node following the pointer out of each node
|
|
31
|
- Parameter pointer variable cur point to the head node
- while(cur does not point to the last node) {
check if cur data is
the target
if so return address of
cur
make here point to the
next node
}
- return cur; // the empty pointer
|
|
32
|
- The pseudocode for search requires that pointer cur step through the
list
- How does cur follow the pointers from node to node?
- When cur points to a node, cur->next is the
address of the next node
- To make cur point to the next node, make the
assignment:
cur =
cur->next;
|
|
33
|
- The search function can be refined in this way:
- for(NodePtr cur = head; cur != NULL; cur = cur->next){
- if(cur->data == target)
- return cur;
- }
- return NULL;
|
|
34
|
- Does the search algorithm handle the empty list
- If the list is empty, and the pointer is not handled appropriately it
can lead to undefined pointer errors
- cur->data is undefined
- cur->next is undefined
- The search function that handles an empty list
|
|
35
|
- An iterator is a construct that allows you to
cycle through the data items in a data structure
to perform an action on each item
- An iterator can be an object of an iterator class,
an array index, or simply a pointer
- A general outline using a pointer as an iterator:
for (NodePtr
cur = head; cur != NULL; cur = cur->next)
//perform the
action on the node iter
- head is a pointer to the head node of the list
- This is what the search algorithm uses to transverse each node
|
|
36
|
- To insert a node after a specified node in the
linked list:
- Use cur again and iterator through the list to find the node with the
data (aData1) we’re looking for
- Construct a new node to place the new string (aData2)
- Point the new node’s next pointer to cur->next
- Set cur->next to the new node
- bool insert(const string& aData1, const string& aData2);
|
|
37
|
- Function insert creates the new node just as
previous insert did
- We do not want our new node at the head of the list however, so…
- We use the pointer cur to insert the new node
|
|
38
|
- This code will accomplish the insertion of the
new node,after the node pointed to by cur:
- aNode->next = cur->next;
- cur->next = aNode;
|
|
39
|
- The order of pointer assignments is critical
- If we changed cur->next to point to
aNode first, we would loose the rest of the list!
|
|
40
|
- NodePtr cur = mHead;
- bool found = false;
- while(cur != NULL) {
- if(cur->data == aData1) {
- found = true;
- break;
- }
- cur = cur->next; // go to the next node and test
- }
- if(found) {
- NodePtr aNode = new Node;
- aNode->data = aData2; // set the data to the string
- aNode->next = cur->next; // set next ptr to the node after cur
- cur->next = aNode; // set cur to point to the new node
- mSize++;
- }
- return false;
|
|
41
|
- Notice that inserting into a linked list requires
that you only change two pointers
- This is true regardless of the length of the list
- Using an array for the list would involve copying as many as all of the
array elements to new locations to make room for the new item
- Inserting into a linked list is often more efficient than inserting into
an array
|
|
42
|
- To remove a node from a linked list
- Position a pointer, prevPtr, to
point at the node prior to the node to remove
- Position a pointer, targetPtr, to
point at the node
to remove
- Perform: prevPtr->next = target->next;
- The node is removed from the list, but is still in memory
- Return targetPtr to the
freestore: delete targetPtr;
|
|
43
|
- NodePtr targetPtr = mHead;
- NodePtr prevPtr = NULL; // no previous for the first node
- if(targetPtr == NULL)
- return;
- for(;targetPtr != NULL;) {
- if(targetPtr->data == aData) {
- if(prevPtr == NULL) {
- mHead = targetPtr->next;
- } else {
- prevPtr->next = targetPtr->next;
- }
- delete targetPtr;
- targetPtr = NULL;
- mSize--;
- } else {
- prevPtr = targetPtr;
- targetPtr = targetPtr->next;
- }
- }
|
|
44
|
- The best way to understand how a link list works is to draw the nodes
out on paper
- Trace through your code and determine if it is handling the situations
appropriately
- Remember a picture is worth a thousand words
|
|
45
|
- If head1 and head2 are pointer variables and
head1 points to the head node of a list:
head2 = head1;
causes head2 and head1 to point to the same list
- If you want head2 to point to a separate copy,
you must copy the list node by node or
overload the assignment operator appropriately
|
|
46
|
- Can you
- Write type definitions for the nodes and pointers in a linked
list? Call the node type
NodeType and call the pointer type PointerType. The linked lists will be lists of
letters.
- Explain why inserting into an array can be less
efficient than inserting into a linked list?
|
|
47
|
|
|
48
|
- A stack is a data structure that retrieves data in
the reverse order the data was stored
- If 'A', 'B', and then 'C' are placed in a stack, they will be removed
in the order 'C', 'B', and then 'A'
- A stack is a last-in/first-out data structure like
the stack of plates in a cafeteria; adding a plate
pushes down the stack and the top plate is the
first one removed
|
|
49
|
|
|
50
|
- We will create a stack class to store characters
- Adding an item to a stack is pushing onto the stack
- Member function push will perform this task
- Removing an item from the stack is popping the the item off the stack
- Member function pop will perform this task
|
|
51
|
- StackRunner.cpp, Stack.h and Stack.cpp demonstrates the use of the stack
class
|
|
52
|
- The push function adds an item to the stack
- It uses a parameter of the type stored in the stack
void push(const
string& aString);
- Pushing an item onto the stack is precisely the same task accomplished
by function insert() of the linked list
- For a stack, a pointer named top is used instead of a pointer named
head
|
|
53
|
- The pop function returns the item that was at
the top of the stack
string
pop();
- Before popping an item from a stack, pop checks
that the stack is not empty
- pop stores the top item in a local variable data,
and the item is
"popped" by: top =
top->next;
- A temporary pointer must point to the old top item
so it can be "deleted" to prevent a memory leak in a
variable called, “frame”
- pop then returns variable data which is the string that was at the
original top
|
|
54
|
- An empty stack is identified by setting the
top pointer to NULL
top = NULL;
|
|
55
|
- Because the stack class uses a pointer and
creates new nodes using new, a copy constructor is needed
- The copy constructor must make a copy of each item in the stack and
store the copies in a new stack
- Items in the new stack must be in the same position in the stack as in
the original
- What happens if we don’t define a copy constructor?
|
|
56
|
- Because function pop calls delete each time an
item is popped off the stack, ~stack only needs
to call pop until the
stack is empty
- while(!isEmpty()) {
- pop();
- }
- top = NULL;
|
|
57
|
- The stack class implementation is
found in Stack.cpp
|
|
58
|
- A queue is a first in/first out (FIFO) data structure
- It utilizes two pointers one at the front of the queue and one at the
back of the queue
- We’ll define them as front and back
|
|
59
|
|
|
60
|
- class Queue {
- public:
- Queue();
- Queue(const Queue& aQueue);
- ~Queue();
- void enqueue(const string& aString);
- string dequeue();
- bool isEmpty() const;
- friend ostream& operator <<(ostream& out, const
Queue& aQueue);
- private:
- QueueNodePtr front;
- QueueNodePtr back;
- };
|
|
61
|
- Can you
- Give the definition of member function push?
- Create a definition for the stack class copy
constructor?
|