Date post: | 20-Jan-2018 |
Category: |
Documents |
Upload: | kathleen-ray |
View: | 246 times |
Download: | 0 times |
C++ Notes 1995 Copyright Ivor Page 1995
1
Advanced C Programming
Ivor Page
C++ Notes 1995 Copyright Ivor Page 1995
2
Copyright NoticeThis Microsoft Powerpoint presentation data file is the sole
property of Ivor P. Page. It may only be viewed, or shown to individuals, or exhibited to a class, or broadcast by any other means, with the owner’s written permission. This Powerpoint data file may not be copied without the owner’s written consent. Printed notes may not be made from this data file for any use without the owner’s written consent. All copies of this file MUST BE DESTROYED by October 1st 1996. Copyrighted, Ivor P. Page, 1995, 410 Pleasant Valley Lane, Richardson, TX 75080
C++ Notes 1995 Copyright Ivor Page 1995
3
Data Structures: The Data
For almost all purposes, data structures are used to store many “records” of data, where the records have something in common. The records have many fields, such as in a personnel records system.
The records may be fixed or variable in size. If the size has a “small” bound, it may be that fixed sized records can be used, where every record is allocated space for the largest possible record. Alternatively the data structure must be designed for variable length records.
The maximum number of records to be held may be known or unknown.
C++ Notes 1995 Copyright Ivor Page 1995
4
Data Structures: The Data
In most application, there is at least one ordering relationship that can be applied to the data, i.e. names can be alphabetically ordered; personnel can be ordered by personnel-number within each department, and then the departments can be ordered by department code, etc.
It may be necessary to search the data given a key. A key is a value corresponding to one field of the records, such as a name, or a personnel-number. Sometimes we may need to search on more than one field.
C++ Notes 1995 Copyright Ivor Page 1995
5
Abstract Data Types
It is convenient (and valuable) to design all data structures using the same basic strategy. An Abstract Data Type is simply some data, together with a set of interface functions that manipulate the data.
Encapsulation: The actual data should never be manipulated (nor be seen if possible) by users of the data structure. There may also be some internal functions that directly operate on the data, but do not form part of the interface. These should be hidden if possible from the users.
C++ Notes 1995 Copyright Ivor Page 1995
6
Abstract Data Types
Abstraction: Every operation that the users need to perform on the data must be provided for by the interface. The interface functions should be very easy to use and to understand (unlike the detailed manipulations of the data itself) and should typically be cast in the syntax of the application to be supported. The users can then ignore the inner workings of the data type and concentrate on the application level.
C++ Notes 1995 Copyright Ivor Page 1995
7
Here is the structure that we will use for all data structures:
Abstract Data Types Cont’d
Data
Data
hf1
hf2
f1
f2
f3
hidden
Interface
ADT
~
caller
C++ Notes 1995 Copyright Ivor Page 1995
8
Interface: insert(index,data)delete(index)data = get_data(index)index = search(data)index = find_free_cell()
Table
Index
C++ Notes 1995 Copyright Ivor Page 1995
9
Queue
Interface: add_to_tail(data) data = remove_from_hd()
tail head
A B C D
C++ Notes 1995 Copyright Ivor Page 1995
10
Stack
Interface:push(data)data = pop()data = top()bool = empty()
C++ Notes 1995 Copyright Ivor Page 1995
11
Interface:n2 = parent(n1)n2 = leftmost_child(n1)n2 = right_sibling(n1)data = get_data(n)add_left_child(n,data)add_right_sibling(n,data)
remove_node(n)n = search(data)
Tree
. .
. .. .
C++ Notes 1995 Copyright Ivor Page 1995
12
Graph
Interface:n = search(data)add_neighbor(n,data)add_link(n1,n2)delete_link(n1,n2)delete_node(n)
C++ Notes 1995 Copyright Ivor Page 1995
13
All the above data structures can be implemented with arrays, but in most situations, the fixed size of an array makes it unsuitable, particularly when the amount of data to be held is unknown.
Tables:Tables include data dictionaries, for example a name and address table. Here the table may be searched given the name. The search maps the given name into an index corresponding to the entry containing that name.
N
Implementation with Arrays
name address age etc.index
C++ Notes 1995 Copyright Ivor Page 1995
14
Implementation with Arrays
Linear search: If the entries are unordered, the search takes O(N) time, where each “probe” requires a string comparison. Find_free_cell() also requires O(N) time. Given an index, insert() and delete() take constant time, O(1).
Binary search: For binary search, the entries must be ordered (sorted alphabetically by name if we want to search on the name.) Keeping the table entries ordered implies O(N) time for insertion and deletion. The search proceeds as follows in O(log N) time:
C++ Notes 1995 Copyright Ivor Page 1995
15
Base_Index = 0;Top_Index = N-1;found = 0; /* false */while (!found){Index = (Base_Index + Top_Index)/2; found = probe_element(Index); /* match? */if(found) break;if(Base_Index = = Top_Index ) break;if (key is beyond Index)Base_Index = Index+1;else Top_Index = Index-1;} /* value of found tells if the search was successful */
Binary Search
C++ Notes 1995 Copyright Ivor Page 1995
16
Hash Tables
In a closed hash table, there is a mathematical mapping from the key (name) to the initial probe index. For example, the hash function for a table of size N entries, might be:
hash = (sum of characters in name) % NThis function has the disadvantage that names such as Fred, redF, derF, etc, all hash to the same initial probe index. There are much better hash functions available:
hash = sum of (name[i]*prime[i])%NHere we sum the chars multiplied by prime numbers, so
IVOR gives (I*1 + V*2 + O*3 +R*5)%N =892%N.
C++ Notes 1995 Copyright Ivor Page 1995
17
The search is as follows:
index = hash(name); count = found = 0; while(!found && count < N) { found = probe(index); if(found)
break; index = rehash(name, index); count++; }
Hash Tables
C++ Notes 1995 Copyright Ivor Page 1995
18
Hash Table Performance
Average TimesInsertion or successful search takes -(1/a)log(1-a)Deletion or unsuccessful search takes 1/(1-a) where a = fraction of cells occupied
C++ Notes 1995 Copyright Ivor Page 1995
19Queues Using arrays
The circular list, or ring buffer is often used for a queue:
tail is the index of the first free cell
~ ~head
tail
01
N-1
yz
a
~ ~head
tail
01
N-1
ab
yz
b
abyz ~
C++ Notes 1995 Copyright Ivor Page 1995
20
Queues Using arrays
Insert at tail, if not full: put data in cell tail;tail = (tail+1) % N;
Delete from head, if not empty:remove data from cell head;head = (head+1) % N;
C++ Notes 1995 Copyright Ivor Page 1995
21
Queues Using arrays
When the queue is either fullor empty, the 2 indices havethe same value, so we cannotdistinguish these two cases.
A better way is to use an indexfor the head, and a count of the number of cells occupied.
tail~ ~
head
01
N-1
C++ Notes 1995 Copyright Ivor Page 1995
22
Queues Using arraysCode when we use an index for head and a count:
(head+count)%N gives the first free cellInsert at tail, if not full:
put data in cell (head+count)%N;count++;
Delete from head, if not empty:remove data from cell head;head = (head+1) % N;count--;
head
01
N-1
count
~~
C++ Notes 1995 Copyright Ivor Page 1995
23
We use an array in which the data doesn’t move when we do a push() or a pop():
push if not full: pop if not empty:
insert data at cell top+1; remove data from cell top;top++; top--;
Stacks using arrays
~ ~top
0
1
C++ Notes 1995 Copyright Ivor Page 1995
24
All trees can be implemented using binary trees. Here is an array structure for a binary tree:
Note that array elementzero is not used.
A node with index i (i>1) has its parent at node i/2. The left child of node with index i is at node 2*i.The right child of node with index i is at node 2*i+1.
Binary Trees using Arrays
a b c d e f g1 2 3 4 5 6 7Array Index =
a
b c
d e f g
1
2 3
4 5 6 7
C++ Notes 1995 Copyright Ivor Page 1995
25
Structures using pointers are particularly useful when the maximum number of elements that must be stored is not known. This is often the case in practice, especially when writing modules for a software library, where the actual users will have many different needs.
Linked List:
Each node contains some data d (a record), and a pointer p. A pointer head points to the first element of the list. The pointer of the last element of the list contains NULL. The nodes are implemented using structs.
Data Structures using Pointers
d NULLd p d phead ~
C++ Notes 1995 Copyright Ivor Page 1995
26
Here is the declaration of the class and the head pointer:
class List_node { char * name; int age; ~ List_node * next;};
List_node * head = NULL;
Linked Lists in C
d NULLd p d phead ~
C++ Notes 1995 Copyright Ivor Page 1995
27
Linked Lists in CHere is a function to add new_node at the head of the list:
void add_to_list(List_node * new_node){ new_node -> next = head; /* step 1 */ head = new_node; /* step 2 */}
head
new_node12
C++ Notes 1995 Copyright Ivor Page 1995
28
Function to remove the first element from the list and return a pointer to it:List_node * remove_first_element(void){ List_node *first = head; /* step 1 */ if(head!=NULL) head = head -> next; /* step 2 */ return first;}
Linked Lists in C cont’d
head
first 1
2
C++ Notes 1995 Copyright Ivor Page 1995
29
Function to search a linked list for a certain name and return a pointer to the node if a match is found:
List_node * search_list(char * key){ List_node * ptr = head; while(ptr != NULL) { if(strcmp(key, ptr -> name)= = 0) break; ptr = ptr -> next; } return ptr;}
Linked Lists in C cont’d
C++ Notes 1995 Copyright Ivor Page 1995
30
In a queue, data is added at one end (the tail) and removed from the other end (the head). A linked list is adequate for this purpose with the addition of a pointer to the tail:
class Queue_node {char * name;int age; ~Queue_node * next;};
class Queue {Queue_node * head;Queue_node * tail;};
Queues using Linked Lists
d p d p d NULL~
tail
head
Queue
C++ Notes 1995 Copyright Ivor Page 1995
31
Inserting at the queue tailvoid add_to_tail(Queue_node * elem) {
if(q.tail = = NULL) /* queue is empty */ q.head = q.tail = elem; else { q.tail -> next = elem; q.tail = elem; }
d p d p d NULL~
tail
head
Queue
d NULL1
2
elem
C++ Notes 1995 Copyright Ivor Page 1995
32
Data is added and removed from the same end (the head) of the list. Implementation is trivial using a singly linked list:class Stack_node {int datum;Stack_node * next;};
class Int_stack {List_node * front;};
Int_stack s;
Stacks Using Linked Lists
d p d p d NULL~
front
Int_stack
C++ Notes 1995 Copyright Ivor Page 1995
33
Stacks Using Linked Lists cont’dHere the interface uses the actual data, not pointers to nodes:
void push(int new_datum){Stack_node *ptr = new Stack_node; ptr -> datum = new_datum; ptr -> next = s.front; /* step 1 */ s.front = ptr; /* step 2 */}
d p d p d NULL~
d p new_datum
1
2
2
front
Int_stack
C++ Notes 1995 Copyright Ivor Page 1995
34
Stacks Using Linked Lists cont’dpop() must free the space occupied by the top node:int pop(void)
{ Stack_node *ptr = s.front; /* step 1 */ int result; if(s.front!=NULL) { result = s.front -> datum; s.front = s.front -> next; /* step 2 */ free(ptr); return result; /* step 3 */ } return ERROR; /* a special value to indicate
empty stack */ }
C++ Notes 1995 Copyright Ivor Page 1995
35
Stacks Using Linked Lists cont’d
Pop in action:
~
12
3
d p d p d NULL
ptr
2
front
Int_stack
C++ Notes 1995 Copyright Ivor Page 1995
36
The basic structure uses two pointers in each node:
class Tree_node {char * name;int age; ~Tree_node * left_child;Tree_node * right_child;};
In some applications it is also necessary to include a pointer to the parent node.
Binary Trees Using Pointers
name age
left_child right_child
~
C++ Notes 1995 Copyright Ivor Page 1995
37
Trees can be traversed in a number of standard orders:
void preorder(Tree_node *n) pass in pointer to the{ root, it visits all nodes if(n= =NULL) return; print(n); /* access data */ preorder(n -> left_child); preorder(n -> right_child); }
gives order 1, 2, 4, 5, 3, 6, 7
Preorder Traversals
a
b c
d e f g
1
2 3
4 5 6 7
C++ Notes 1995 Copyright Ivor Page 1995
38
Postorder Traversals
void postorder(Tree_node * n){ if(n= = NULL) return; postorder(n -> left_child); postorder(n -> right_child); print(n);}
gives order 4, 5, 2, 6, 7, 3, 1
a
b c
d e f g
1
2 3
4 5 6 7
C++ Notes 1995 Copyright Ivor Page 1995
39
void inorder(Tree_node * n){ We can tell if *n if(n is a leaf node) print(n); is a leaf node by else { testing to see if inorder(n -> left_child); both its child print(n); pointers are NULL inorder(n -> right_child); }
}
gives order 4, 2, 5, 1, 6, 3, 7
Inorder Traversal
a
b c
d e f g
1
2 3
4 5 6 7
C++ Notes 1995 Copyright Ivor Page 1995
40
Binary Search TreesIn order to enable fast searches, the elements of the left
and right sub-trees of any node n, must have key values that are related to the key value in that node. This relationship must apply to all interior nodes, including the root node.
In the binary search tree, the following relationship holds:
keys in left sub-tree of n < key of n < keys in right sub-tree of n 7
4 9
2 6 8 11
<x >x
key x
left sub-tree of n right sub-tree of n
node n
C++ Notes 1995 Copyright Ivor Page 1995
41
Binary Search Trees
Search, insert, and delete, can all be done in average time log n. If the tree remains balanced, these operations take O(log n).
Here is the class:class Tree_node {int key; ~Tree_node * left_child, * right_child;};
C++ Notes 1995 Copyright Ivor Page 1995
42
Tree_node * search(int skey, Tree_node * n){ if(n = = NULL) return NULL; if(skey = = n -> key) return ptr; if(skey < n -> key) return(search(skey, n -> left_child)); else return(search(skey, n -> right_child));}
Binary Search Tree: Search
7
4 9
2 6 8 11
C++ Notes 1995 Copyright Ivor Page 1995
43
void insert(Tree_node * new_node, Tree_node ** n){ if(*n = = NULL) { /* we have reached a leaf */ *n = new_node; /* n now pts to new_node */ new_node -> left_child = NULL; new_node -> right_child = NULL; } else if(new_node -> key < (*n) -> key) insert(new_node, &((*n) -> left_child)); else if(new_node -> key > (*n) -> key) insert(new_node, &((*n) -> right_child));}
Binary Search Tree: Insertion
C++ Notes 1995 Copyright Ivor Page 1995
44
Insertion exampleAssume ~ means NULL
The insertion begins with a search for the key value to be inserted. When this position is located, n contains the address of the left-child/right-child pointer of the parent node. This pointer value is changed to point to the new node.
10
root
2 ~ 40 ~
~ 1 ~ ~ 6 ~ 45
The insertionchanges the pointer right_child
n
C++ Notes 1995 Copyright Ivor Page 1995
45
We will use value semantics this time. This function deletes the node with the smallest key and returns its value:
int delete_min(Tree_node **n){ int result; if((*n) -> left_child = = NULL) {
result = (*n) -> key; *n = (*n) -> right_child; free(*n); return result;
} else return delete_min(&((*n)->left_child));}
Binary Search Trees, Delete_min
7
4 9
6 8 11
C++ Notes 1995 Copyright Ivor Page 1995
46
delete() removes the node holding a given key:void delete(int value; Tree_node **n) {
if((*n) != NULL) { if(value > (*n) -> key) delete(value, (*n) -> left_child); if(value < (*n) -> key) delete(value, (*n) -> right_child); if((*n) -> left_child = = NULL) if((*n) -> right_child = = NULL) *n = NULL; else *n = (*n) -> right_child; else if((*n) -> right_child = = NULL) *n = left_child; else (*n) -> key = delete_min((*n) -> right_child)
}}
Binary Search Trees, Deletion
C++ Notes 1995 Copyright Ivor Page 1995
47
Completely Balanced Binary TreesKeeping a binary tree completely balanced implies O(n)
time for insertion (actually rebalancing after insertion):
Insert 1
RebalanceThe rebalance operation here
required a change to every node, taking O(n) time. For this reasonwe use “almost balanced binarytrees,” such as AVL and 2-3 trees.
5
7
6
3
2 4
5
7
6
3
2 4
1
4
6
5
2
1 3 7
C++ Notes 1995 Copyright Ivor Page 1995
48
AVL TreesFirst we define the height of a binary tree to be the length of the
longest path from the root to a leaf node. The AVL property: if N is a node in a binary tree, node N has
the AVL property if the heights of its left and right sub-trees differ by no more than 1. If all nodes, including the root, have the AVL property, then the tree is an AVL tree.
It is possible to do insert, delete, and search in O(log n) time in an AVL tree.
AVL trees
C++ Notes 1995 Copyright Ivor Page 1995
49
2-3 Trees
A 2-3 tree has the following properties:• Each interior node has 2 or 3 children• Each path from the root to a leaf node has the same length.smallest descendent smallest descendentof 2nd child of 3rd child, or -
In this version, all data are recorded in leaf nodes
7 16
19 -
16 19
5 -
2 5
8 12
7 8 12
C++ Notes 1995 Copyright Ivor Page 1995
50
Searching a 2-3 Tree
Assume that the search has reached the y/z node belowand we are searching for the key x. If x<y, goto the leftchild next, if y<=x and x<z or there is no third child, goto the 2nd child next, and if x>z, goto the third child next.
y z
x<y y<=x<z z<x
y -
x<y x>=y
C++ Notes 1995 Copyright Ivor Page 1995
51
Insertion into 2-3 Trees
Say we wish to insert new_node with key x. We first search for x and stop at an interior node N, just above the leaf nodes where the node containing x would be if it existed.
If N has only 2 children, new_node becomes a new child of N, placed in the proper order:
+4 = +6 =5 -
2 5
4 5
2 54
5 -
2 5
5 6
2 65
C++ Notes 1995 Copyright Ivor Page 1995
52
Insertion into 2-3 Trees cont’d
A node has to be split if it already has 3 children:
+3 =
4 -
3 - 5 -
2 3 4 5
4 5
2 54
C++ Notes 1995 Copyright Ivor Page 1995
53
Insertion into 2-3 Trees cont’d
A special case occurs when the root has to be split:
+4 =
3 7
2 733 -
2 3
7 -
4 7
4 -
This only occurs when there are exactly 3 data elements in the tree before the insertion.
C++ Notes 1995 Copyright Ivor Page 1995
54
Deletion
When a leaf node is deleted, its parent P may be left with only one child. If P has a neighboring sibling Q with 3 leaves. The leaves of P and Q can be shared, 2 each between them:
2 44 7
3 - 6 7
4 -
P Q
6
-3 =4 - 7 -
6 -
P Q
2 3 6 7
This processdoes not leadto further recursion upthe tree.
C++ Notes 1995 Copyright Ivor Page 1995
55
DeletionIf P does not have a neighboring sibling with 3 children,
then it must have a neighboring sibling Q with 2 children. We combine P and Q, giving all three leaves to the combined node PQ:
-3 =
As we can see, this process mayleave the parentof PQ with 1 child,so the process must continue upthe tree recursively
3 -
2 3
7 -
4 7
4 -
P Q
- -
4 7
2 4 7
PQ
C++ Notes 1995 Copyright Ivor Page 1995
56
Deletion
When the root node has only one child, it may be deleted, its single child becomes the new root node:
- -
4 7
2 4 7
PQ 4 7
2 4 7
PQ
old root
new root
We may reach the root, leaving it in this condition, by recursion up the tree.
C++ Notes 1995 Copyright Ivor Page 1995
57
Struct for 2-3 treestypedef struct two_three_node { Could use a int kind; // 0=leaf, 1=interior union of the
int low_of_second, low_of_third; 2 node typesint key; ~two_three_node * left_child;two_three_node * right_child;};
two_three_node * root; // pointer to root node
We shall not pursue the details of the coding of the algorithms.
C++ Notes 1995 Copyright Ivor Page 1995
58
Rotations in AVL TreesRotations are used to reestablish the AVL property after
an insertion:
Single Rotation to the right
Single Rotation to the left
B
A
T1 T2
T3
A
B
T3T2
T1
A
B
T3T2
T1
B
A
T1 T2
T3
C++ Notes 1995 Copyright Ivor Page 1995
59
Double Rotations in AVL Trees
Double Rotation to Right
DoubleRotation to Left
C
T4
B
T2 T3
A
T1
A
T1
B
T3T2
C
T4
A
T1 T2
C
T3 T4
B
A
T1 T2
C
T3 T4
B
C++ Notes 1995 Copyright Ivor Page 1995
60
Example of building an AVL TreeSay we have the keys 19, 10, 3, 5, 20, 13, 17, 15, 1, 8, 6 to
be inserted in that order:
19 19
10
19
10
3
10
3 19
SRR
10
3 19
2013
17
5
+10= +3=
+5,20,13,17=
C++ Notes 1995 Copyright Ivor Page 1995
61
Example of building an AVL Tree
The addition of 15 causes an unbalance at node 13, so a double left rotation is req’d:
15
13
17
15
1713
DRL
10
19
2015
17
3
5
8
1
13
+1,8=
C++ Notes 1995 Copyright Ivor Page 1995
62
AVL Tree Example cont’dThe addition of 6 causes an
imbalanceat node 5. A double left rotation isneeded to fix it.
This gives the final AVL Tree:
10
19
2015
17
3
5
8
1
13
5
8
6
6
85
DRL
10
19
2015
17
3
6
8
1
135
C++ Notes 1995 Copyright Ivor Page 1995
63
The only addition to the nodes is a balance factor, which gives the difference between the heights of the left and right sub-trees, and should be +1, 0, or -1, in an AVL tree.
typedef struct AVL_node {int balance_factor;int key; ~AVL_node * left_child;AVL_node * right_child;};
We shall not pursue the details of the coding of the algorithms.
AVL Tree Implementation