CS代写 AMA 17.5

Linked Data Structures Optional Textbook Readings: CP:AMA 17.5
This section introduces a few new concepts, but mostly consists of examples to illustrate programming with linked data structures.
The primary goal of this section is to be able to use linked lists and trees.
CS 136 Winter 2022 11: Linked Data Structures 1

Copyright By PowCoder代写 加微信 powcoder

Linked lists
Racket’s list type is more commonly known as a linked list.
Each node contains an item and a link (pointer) to the next node in the list.
In C, the link in the last node is NULL pointer.
For convenience, we will initally store ints in our linked lists.
CS 136 Winter 2022 11: Linked Data Structures 2

Linked lists are usually represented as a structure that contains a link (pointer) to the front node.
Unlike arrays, linked list nodes are not arranged sequentially in memory. There is no fast and convenient way to “jump” to the i-th element. The list must be traversed from the front. Traversing a linked list is O(n).
CS 136 Winter 2022 11: Linked Data Structures 3

A significant advantage of a linked list is that its length can easily change, and the length does not need to be known in advance.
The memory for each node is allocated dynamically (i.e., using dynamic memory).
CS 136 Winter 2022 11: Linked Data Structures 4

Structure definitions
A llist points to the front node (which is NULL for an empty list).
Each llnode stores an item and a link (pointer) to the next node (which is NULL for the last node).
struct llnode {
struct llnode *next;
struct llist {
struct llnode *front;
llnode is a recursive data structure (it has a pointer to its own structure type). llist is not recursive.
CS 136 Winter 2022 11: Linked Data Structures 5

There is no “official” way of naming or implementing a linked list node in C.
The CP:AMA textbook and other sources use slightly different conventions.
The structure we present here is often called a “wrapper strategy”, because the llist structure “wraps” around the front of the list.
CS 136 Winter 2022 11: Linked Data Structures 6

Creating a linked list
// list_create() creates a new, empty list
// effects: allocates memory: call list_destroy
struct llist *list_create(void) {
struct llist *lst = malloc(sizeof(struct llist));
lst->front = NULL;
return lst;
int main(void) {
struct llist *lst = list_create();
CS 136 Winter 2022 11: Linked Data Structures 7

Adding to the front
// add_front(i, lst) adds i to the front of lst
// effects: modifies lst
void add_front(int i, struct llist *lst) {
struct llnode *newnode = malloc(sizeof(struct llnode)); newnode->item = i;
newnode->next = lst->front;
lst->front = newnode;
We could also add “allocates memory” as a side effect, but since most linked list functions use dynamic memory it becomes redundant. In addition, it is not necessarily memory that the client needs to worry about free-ing. Specifying that it modifies the list is sufficient.
CS 136 Winter 2022 11: Linked Data Structures 8

Traversing a list
We can traverse a list iteratively or recursively.
When iterating through a list, we typically use a (llnode) pointer to keep track of the “current” node.
int list_length(const struct llist *lst) {
int len = 0;
const struct llnode *node = lst->front;
while (node) {
node = node->next;
return len; }
Remember (node) will be false at the end of the list (NULL).
CS 136 Winter 2022 11: Linked Data Structures 9

When using recursion, remember to recurse on a node (llnode) not the list (llist).
Typically, there is a simple function that “unwraps” the llist, which then calls a core function that recurses on llnodes.
int node_length(const struct llnode *node) {
if (node == NULL) {
return 0; }
return 1 + node_length(node->next);
int list_length(const struct llist *lst) {
return node_length(lst->front);
CS 136 Winter 2022 11: Linked Data Structures 10

Destroying a list
In C, we don’t have a garbage collector, so we must be able to free our linked list. We need to free every node and the list itself.
When using an iterative approach, we are going to need two node pointers to ensure that the nodes are freed in a safe way.
void list_destroy(struct llist *lst) {
struct llnode *curnode = lst->front;
struct llnode *nextnode = NULL;
while (curnode) {
nextnode = curnode->next;
free(curnode);
curnode = nextnode;
free(lst); }
CS 136 Winter 2022 11: Linked Data Structures 11

With a recursive approach, it is more convenient to free the rest of the list before we free the current node.
void free_nodes(struct llnode *node) {
if (node) {
free_nodes(node->next);
free(node);
void list_destroy(struct llist *lst) {
free_nodes(lst->front);
free(lst);
CS 136 Winter 2022 11: Linked Data Structures 12

Duplicating a list
Recursive solution:
struct llnode *dup_nodes(const struct llnode *oldnode) { if (oldnode == NULL) {
return NULL;
struct llnode *newnode = malloc(sizeof(struct llnode)); newnode->item = oldnode->item;
newnode->next = dup_nodes(oldnode->next);
return newnode;
struct llist *list_dup(const struct llist *oldlist) {
struct llist *newlist = list_create();
newlist->front = dup_nodes(oldlist->front);
return newlist;
CS 136 Winter 2022 11: Linked Data Structures 13

Iterative solution:
struct llist *list_dup(const struct llist *oldlist) { struct llist *newlist = list_create();
if (oldlist->front) {
add_front(oldlist->front->item, newlist);
const struct llnode *oldnode = oldlist->front->next; struct llnode *newnode = newlist->front;
while (oldnode) {
newnode->next = malloc(sizeof(struct llnode));
newnode = newnode->next;
newnode->item = oldnode->item;
newnode->next = NULL;
oldnode = oldnode->next;
return newlist;
CS 136 Winter 2022 11: Linked Data Structures 14

Insert into the “middle”
When inserting into the “middle”, we need to find the node that will be before the new node we are inserting.
insert( 5, a);
insert(30, a);
CS 136 Winter 2022 11: Linked Data Structures 15

// insert(i, slst) inserts i into sorted list slst
// requires: slst is sorted [not asserted]
// effects: modifies slst
// time: O(n), where n is the length of slst
void insert(int i, struct llist *slst) {
if (slst->front == NULL || i < slst->front->item) {
add_front(i, slst);
// find the node that will be “before” our insert
struct llnode *before = slst->front;
while (before->next && i > before->next->item) {
before = before->next;
// now do the insert
struct llnode *newnode = malloc(sizeof(struct llnode)); newnode->item = i;
newnode->next = before->next;
before->next = newnode;
CS 136 Winter 2022 11: Linked Data Structures 16

Removing nodes
When removing nodes, make sure you do not create a memory leak.
void remove_front(struct llist *lst) {
assert(lst->front);
struct llnode *old_front = lst->front;
lst->front = lst->front->next;
free(old_front);
CS 136 Winter 2022 11: Linked Data Structures 17

This function can remove a node from the “middle”.
// remove_item(i, lst) removes the first occurrence of i in lst // returns true if an item is successfully removed
bool remove_item(int i, struct llist *lst) {
if (lst->front == NULL) return false;
if (lst->front->item == i) {
remove_front(lst);
return true;
struct llnode *before = lst->front;
while (before->next && i != before->next->item) {
before = before->next;
if (before->next == NULL) return false;
struct llnode *old_node = before->next;
before->next = before->next->next;
free(old_node);
return true;
CS 136 Winter 2022 11: Linked Data Structures 18

Caching information
Consider that we are writing an application where the length of a linked list will be queried often.
Typically, finding the length of a linked list is O(n). However, we can store (or “cache”) the length in the llist
structure, so the length can be retrieved in O(1) time.
struct llist {
struct llnode *front;
int length;
CS 136 Winter 2022 11: Linked Data Structures 19

Naturally, other list functions would have to update the length as necessary:
• list_create would initialize length to zero • add_front would increment length
• remove_front would decrement length
CS 136 Winter 2022 11: Linked Data Structures 20

Data integrity
The introduction of the length field to the linked list may seem like a great idea to improve efficiency.
However, it introduces new ways that the structure can be corrupted. What if the length field does not accurately reflect the true length?
For example, imagine that someone implements the remove_item function, but forgets to update the length field?
Or a naïve coder may think that the following statement removes all of the nodes from the list.
lst->length = 0;
CS 136 Winter 2022 11: Linked Data Structures 21

Whenever the same information is stored in more than one way, it is susceptible to integrity (consistency) issues.
Advanced testing methods can often find these types of errors.
If data integrity is an issue, it is often better to repackage the data structure as a separate ADT module and only provide interface functions to the client.
This is an example of security (protecting the client from themselves).
CS 136 Winter 2022 11: Linked Data Structures 22

A queue is like a “lineup”, where new items go to the “back” of the line, and the items are removed from the “front” of the line. While a stack is LIFO, a queue is FIFO (first in, first out).
Typical queue ADT operations:
• add_back: adds an item to the end of the queue
• remove_front: removes the item at the front of the queue • front: returns the item at the front
• is_empty: determines if the queue is empty
CS 136 Winter 2022 11: Linked Data Structures 23

A Stack ADT can be easily implemented using a dynamic array (as we did in Section 10) or with a linked list.
While it is possible to implement a Queue ADT with a dynamic array, the implementation is a bit tricky. Queues are typically implemented with linked lists.
The only efficiency concern with a linked list implementation is that an add_back operation is normally O(n).
However, if we maintain a pointer to the back (last element) of the list, in addition to a pointer to the front of the list, we can implement add_back in O(1).
CS 136 Winter 2022 11: Linked Data Structures 24

// queue.h (INTERFACE)
// all operations are O(1) (except destroy)
struct queue;
struct queue *queue_create(void);
void queue_add_back(int i, struct queue *q);
int queue_remove_front(struct queue *q);
int queue_front(const struct queue *q);
bool queue_is_empty(const struct queue *q);
void queue_destroy(struct queue *q);
CS 136 Winter 2022 11: Linked Data Structures 25

// queue.c (IMPLEMENTATION)
struct llnode {
struct llnode *next;
struct queue {
struct llnode *front;
struct llnode *back;
// <--- NEW struct queue *queue_create(void) { struct queue *q = malloc(sizeof(struct queue)); q->front = NULL;
q->back = NULL;
For an empty queue, both the front and back are NULL
CS 136 Winter 2022 11: Linked Data Structures 26

queue_add_back is now O(1) with the back pointer.
void queue_add_back(int i, struct queue *q) {
struct llnode *newnode = malloc(sizeof(struct llnode)); newnode->item = i;
newnode->next = NULL;
if (q->front == NULL) {
// queue was empty
q->front = newnode;
q->back->next = newnode;
q->back = newnode;
Common mistake: forgetting to update the front pointer when adding to an empty queue.
CS 136 Winter 2022 11: Linked Data Structures 27

int queue_remove_front(struct queue *q) {
assert(q->front);
int retval = q->front->item;
struct llnode *old_front = q->front;
q->front = q->front->next;
free(old_front);
if (q->front == NULL) {
// queue is now empty
q->back = NULL;
return retval;
Common mistake: forgetting to store the return value (retval above) before freeing the node.
CS 136 Winter 2022 11: Linked Data Structures 28

The remainder of the Queue ADT is straightforward.
int queue_front(const struct queue *q) {
assert(q->front);
return q->front->item;
bool queue_is_empty(const struct queue *q) {
return q->front == NULL;
void queue_destroy(struct queue *q) {
while (!queue_is_empty(q)) {
queue_remove_front(q);
free(q); }
CS 136 Winter 2022 11: Linked Data Structures 29

Node augmentation strategy
In a node augmentation strategy, each node is augmented to
include additional information about the node or the structure.
For example, a dictionary node can contain both a key (item) and a corresponding value.
Or for a priority queue, each node can additionally store the priority of the item.
CS 136 Winter 2022 11: Linked Data Structures 30

A common node augmentation for a linked list is to create a doubly linked list, where each node also contains a pointer to the previous node. When combined with a back pointer, a doubly linked list can add or remove from the front and back in O(1) time.
Many programming environments provide a Double-Ended Queue (dequeue or deque) ADT, which can be used as a Stack or a Queue ADT.
CS 136 Winter 2022 11: Linked Data Structures 31

At the implementation level, trees are very similar to linked lists. Each node can link to more than one node.
CS 136 Winter 2022 11: Linked Data Structures 32

Tree terminology
• the root node has no parent, all others have exactly one
• nodes can have multiple children
• in a binary tree, each node has at most two children
• a leaf node has no children
• the height of a tree is the maximum possible number of nodes from the root to a leaf (inclusive)
• the height of an empty tree is zero
• the number of nodes is known as the node count
CS 136 Winter 2022 11: Linked Data Structures 33

Binary Search Trees (BSTs)
Binary Search Tree (BSTs) enforce the ordering property: for every node with an item i, all items in the left child subtree are less than i, and all items in the right child subtree are greater than i.
CS 136 Winter 2022 11: Linked Data Structures 34

Our BST node (bstnode) is very similar to our linked list node definition.
struct bstnode {
struct bstnode *left;
struct bstnode *right;
struct bst {
struct bstnode *root;
In CS 135, BSTs were used as dictionaries, with each node storing both a key and a value. Traditionally, a BST only stores a single item, and additional values can be added as node augmentations if required.
CS 136 Winter 2022 11: Linked Data Structures 35

As with linked lists, we need a function to create a new BST. // bst_create() creates a new BST
// effects: allocates memory: call bst_destroy
struct bst *bst_create(void) {
struct bst *t = malloc(sizeof(struct bst));
t->root = NULL;
CS 136 Winter 2022 11: Linked Data Structures 36

Inserting into a BST
This is a recursive version of bst_insert. An iterative variant is provided later in this section.
struct bstnode *new_leaf(int i) {
struct bstnode *leaf = malloc(sizeof(struct bstnode)); leaf->item = i;
leaf->left = NULL;
leaf->right = NULL;
return leaf;
void bst_insert(int i, struct bst *t) {
if (t->root) {
insert_bstnode(i, t->root); // on following slide
t->root = new_leaf(i);
CS 136 Winter 2022 11: Linked Data Structures 37

For the core function, we recurse on nodes.
void insert_bstnode(int i, struct bstnode *node) {
if (i < node->item) {
if (node->left) {
insert_bstnode(i, node->left);
node->left = new_leaf(i);
} else if (i > node->item) {
if (node->right) {
insert_bstnode(i, node->right);
node->right = new_leaf(i);
} // else do nothing, as item already exists
CS 136 Winter 2022 11: Linked Data Structures 38

Trees and efficiency
What is the efficiency of bst_insert?
The worst case is when the tree is unbalanced, and every node in
the tree must be visited.
In this example, the running time of bst_insert is O(n), where n is the number of nodes in the tree.
CS 136 Winter 2022 11: Linked Data Structures 39

The running time of bst_insert is O(h): it depends more on the height of the tree (h) than the number of nodes in the tree (n).
The definition of a balanced tree is a tree where the height (h) is O(log n).
Conversely, an unbalanced tree is a tree with a height that is not O(log n). The height of an unbalanced tree is O(n).
Using the bst_insert function we provided, inserting the nodes in sorted order creates an unbalanced tree.
CS 136 Winter 2022 11: Linked Data Structures 40

With a balanced tree, the running time of standard tree functions (e.g., insert, remove, search) are all O(log n).
With an unbalanced tree, the running time of each function is O(n). A self-balancing tree “re-arranges” the nodes to ensure that tree is
always balanced.
With a good self-balancing implementation, all standard tree functions preserve the balance of the tree and have an O(log n) running time.
In CS 240 and CS 341 you will see self-balancing trees. Self-balancing trees often use node augmentations to store extra
information to aid the re-balancing.
CS 136 Winter 2022 11: Linked Data Structures 41

Count node augmentation
A popular tree node augmentation is to store in each node the count (number of nodes) in its subtree.
struct bstnode {
struct bstnode *left;
struct bstnode *right;
int count;
// *****NEW
This augmentation allows us to retrieve the number of nodes in the tree in O(1) time.
It also allows us to implement a select function in O(h) time. select(k) finds item with index k in the tree.
CS 136 Winter 2022 11: Linked Data Structures 42

example: count node augmentation
CS 136 Winter 2022 11: Linked Data Structures 43

The following code illustrates how to select item with index k in a BST with a count node augmentation.
int select_node(int k, struct bstnode *node) { assert(node && 0 <= k && k < node->count);
int left_count = 0;
if (node->left) left_count = node->left->count;
if (k < left_count) return select_node(k, node->left); if (k == left_count) return node->item;
return select_node(k – left_count – 1, node->right);
int bst_select(int k, struct bst *t) {
return select_node(k, t->root);
select(0, t) finds the smallest item in the tree.
CS 136 Winter 2022 11: Linked Data Structures 44

Dictionary ADT (revisited)
The dictionary ADT (also called a map, associative array, key-value store or symbol table), is a collection of pairs of keys and values. Each key is unique and has a corresponding value, but more than one key may have the same value.
Typical dictionary ADT operations:
• lookup: for a given key, retrieve the corresponding value or “not found”
• insert: adds a new key/value pair (or replaces the value of an existing key)
• remove: deletes a key and its value
CS 136 Winter 2022 11: Linked Data Structures 45

We provide an example Dictionary ADT that uses a BST with int keys and string values. Of course, many other possible types are possible.
// dictionary.h
struct dictionary;
struct dictionary *dict_create(void);
void dict_insert(int key, const char *val, struct dictionary *d); const char *dict_lookup(int key, const struct dictionary *d); void dict_remove(int key, struct dictionary *d);
void dict_destroy(struct dictionary *d);
CS 136 Winter 2022 11: Linked Data Structures 46

Using the same bstnode structure, we augment each node by adding an additional value field.
struct bstnode {
char *value;
struct bstnode *left;
struct bstnode *right;
struct dictionary {
struct bstnode *root;
// additional value (augmentation)
struct dictionary *dict_create(void) {
struct dictionary *d = malloc(sizeof(struct dictionary)); d->

程序代写 CS代考 加微信: powcoder QQ: 1823890830 Email: powcoder@163.com