CBDS2103 Data Structure - Smay19 (RS & MREP)
CBDS2103 Data Structure - Smay19 (RS & MREP)
Data Structure
Topic 1 Arrays 1
1.1 Concept of Array 3
1.2 One-dimensional Array 4
1.2.1 Declaring Array 4
1.2.2 Initialising Array Elements 7
1.2.3 Assigning and Accessing Array Elements 10
1.2.4 Operations on Array Elements 14
1.2.5 Array and Function 15
1.2.6 Sorting Array 16
1.3 Two-dimensional Array 18
1.3.1 Declaring 2D Array and Initialising 2D 18
ArrayÊs Elements
1.3.2 Assigning and Accessing 2D Array Elements 21
1.3.3 2D Array and Function 22
Summary 23
Key Terms 23
Topic 2 Structures 24
2.1 Concept of Structures 25
2.2 Declaring and Initialising Structure Elements 26
2.3 Referencing Structure Elements 29
2.4 Structure-type Array 31
2.5 Structure-type Structure 34
2.6 Structure and Function 36
2.6.1 Return Value Function 38
2.7 typedef Construction 40
Summary 41
Key Terms 41
Topic 3 Pointers 42
3.1 Overview of a Pointer 43
3.2 Parameter Passing Using Pointer 46
3.3 Pointer and Array 49
3.3.1 Pointer and String 50
3.3.2 Array of Pointer 52
3.4 Dynamic Memory 54
3.5 Pointer and Structure 56
Summary 58
Key Terms 58
Topic 5 Stacks 82
5.1 Stack Characteristics 84
5.2 Basic Stack Operations 85
5.3 Implementation Using Array 86
5.3.1 Declaring Stack Data Structure 87
5.3.2 Creating Stack 88
5.3.3 Checking Empty Stack 88
5.3.4 Checking Full Stack 89
5.3.5 Inserting Items into Stack 89
5.3.6 Removing Items from Stack 90
5.4 Stack Applications 91
5.4.1 Separating Even and Odd Numbers 92
5.4.2 Reverse Polish Notation (Postfix Notation) 97
Summary 104
Key Terms 104
COURSE GUIDE
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
"
INTRODUCTION
CBDS2103 Data Structure is one of the courses offered at Open University
Malaysia (OUM). This course is worth 3 credit hours and should be covered over
8 to 15 weeks.
COURSE AUDIENCE
This course is offered to learners undertaking the Bachelor of Information
Technology programme and other IT-related programmes.
As an open and distance learner, you should be able to learn independently and
optimise the learning modes and environment available to you. Before you begin
this course, please confirm the course material, the course requirements and how
the course is conducted.
STUDY SCHEDULE
It is a standard OUM practice that learners accumulate 40 study hours for every
credit hour. As such, for a three-credit hour course, you are expected to spend
120 study hours. Table 1 gives an estimation of how the 120 study hours can be
accumulated.
Study
Study Activities
Hours
Briefly go through the course content and participate in initial discussion 3
Study the module 60
Attend 3 to 5 tutorial sessions 10
Online participation 12
Revision 15
Assignment(s), Test(s) and Examination(s) 20
TOTAL STUDY HOURS ACCUMULATED 120
COURSE SYNOPSIS
This course is divided into 10 topics. The synopsis of each topic is presented
below:
Topic 1 introduces basic data structures using array. Array is the simplest data
structure that can be used in a program. In this topic, you will learn how to
declare, initialise, assign and operate an array. Both one-dimensional and two-
dimensional arrays will be discussed in this topic.
Topic 2 explains structures which hold information for a group of data. Unlike
arrays, structures can consist of different types of data type. These enable you to
write more efficient and readable programs. Understanding the concept of
structure is very important for you to comprehend the subsequent topics in this
module.
Topic 3 focuses on pointers. In the earlier topics, all the variable data types which
have been declared enable us to have direct access to the values that are kept in
storage by the program. In addition, C language allows us to declare variables
which keep the storage address. These are referred to as pointers.
Topic 4 covers lists and linked lists which are separate data structures. The
difference between both is that in a linked list, data items have links between
them but this is not the case in list data items which uses arrays. Last but not
least, the implementation of both data structures will be elaborated as well.
Topic 5 discusses stacks, which is another way to structure data using the last-in-
first-out (LIFO) approach. Basic stack operations such as create stack, test empty
stack, test full stack, remove element from stack and add element to stack will be
explained too in this topic.
Topic 6 touches on queues, which is a way to structure data using the first-in-
first-out (FIFO) approach. Basic queue operations such as create queue, test
empty queue, test full queue, remove elements from queue and add elements to
queue will be discussed in this topic too.
Topic 9 explores trees, which is one of the most important data structures and
which has many functions. It has been used in various applications, such as
gaming and intelligent systems. In this topic, you will learn the meaning of tree
data structure and how to implement binary search tree, which is a commonly
used type of tree.
Learning Outcomes: This section refers to what you should achieve after you
have completely covered a topic. As you go through each topic, you should
frequently refer to these learning outcomes. By doing this, you can continuously
gauge your understanding of the topic.
Summary: You will find this component at the end of each topic. This component
helps you to recap the whole topic. By going through the summary, you should
be able to gauge your knowledge retention level. Should you find points in the
summary that you do not fully understand, it would be a good idea for you to
revisit the details in the module.
Key Terms: This component can be found at the end of each topic. You should go
through this component to remind yourself of important terms or jargon used
throughout the module. Should you find terms here that you are not able to
explain, you should look for the terms in the module.
SOFTWARE
The software application required for this subject needs to be downloaded from
myINSPIRE. You can use this software for tutorials and completion of tasks at
home.
PRIOR KNOWLEDGE
Learners of this course need to have basic knowledge in C programming
language.
ASSESSMENT METHOD
Please refer to myINSPIRE.
REFERENCES
Gilberg, R. F., & Forouzan, B. (1998). Data structures: A pseudocode approach
with C. Boston, MA: PWS.
Gottfriend, B. (2010). Programming with C. New York, NY: McGraw-Hill.
Kelley, A., & Pohl, I. (2008). A book on C. San Francisco, CA: Benjamin
Cumming.
INTRODUCTION
Hello there and welcome to CBDS2103 Data Structure. Before we go further and
explain data structures, let us start with a basic question what is a computer?
The study of different structures for representation of data and the algorithms
that operate on these structures is known as a study of data structures. A „data
structure‰ which displays the relationship of adjacency between elements is called
„linear‰. This type of data structures is used to represent one of the simplest and
most commonly found data object, which is, an ordered or linear list.
Or
Or
A group of friends
[Sudha, Sze Hong, Neeraj, Azim, Salim]
If we view an ordered list more abstractly, we say that it is either empty or it can
be written as:
Did you know that there are a variety of operations that can be performed on linear
data structures? These operations include:
(e) Insert a new element at position i, 1 i n causing elements numbered i,
i 1, ⁄ n to become numbered i 1, i 2 ... n 1; and
(f) Delete the element at position i, 1 i n causing elements numbered i 1, ⁄
n to become numbered i, i 1, ... n 1.
Take note that the simplest linear data structures that makes use of computed
addresses to locate its elements is a one-dimensional array.
Therefore, this topic will discuss the concept of arrays, one-dimensional array and
two-dimensional arrays as well as how to use them in programming. So are you
ready? Let us start with the lesson!
How do we declare and use a variable? In order to declare and use a variable, we
need to associate a name and the data type to represent a particular value.
Similarly, an array also has a name and a data type that will identify it. However,
an array can hold multiple elements, whereas a variable holds a single value.
Arrays are data structures that contain data items of the same type.
Did you know that they are also called static data structures? This means that the
size of the array is fixed from the declaration all the way to the final program
implementation.
Now, let us relate this scenario to computer programming. Let us just say that we
would like to write a program to manage your expenses. Your program can declare
12 separate variables to represent the total monthly expenditure (just like how we
would use 12 files to store the receipts). However, we could further improve the
program by using an array that has 12 elements, that is, by storing the monthly
total spending in each of the array elements.
What is an index?
Did you know that an index can identify the number of elements in an array? This
is explained in the following subtopic.
The value of number_of_elements will decide the number of data values that
can be stored in the array. The number of this data values can also be referred to
as array size.
Take note that this statement will declare an array that is called number and is of
size 10. This declaration will instruct the compiler to reserve 10 memory spaces in
sequence, which can be referred to as number. This sequence of memory spaces is
also known as array elements. Since the data_type for number is declared as
being the int type, therefore the number array elements can contain data of int
type only. This relationship is illustrated in Figure 1.1.
Figure 1.1: Illustration of memory location for the declaration of int number[10]
The value within the [ ] brackets is also known as array index. Keep in mind that
the array index always begins with 0 and ends with the size of the array subtracted
by 1. In the previous example, the array index of number begins with 0 and ends
with 9. With this, the array element values can be referred to as number[0],
number[1], ... number[9].
Remember, the array size must be an integer and consists of an expression which
is evaluated to determine the index. If a program uses an expression as an index,
then the expression is evaluated to determine the index. For example, if a = 5 and
b = 6, then the statement:
Keep in mind that arrays may be declared to contain other data types. For example,
an array of type char can be used to store a character string.
ACTIVITY 1.1
SELF-CHECK 1.1
Remember, all the array elements that are not given initial values will be set
with the value of 0 automatically (see the following example):
int digit[10] = {1, 2, 3};
Here, the digit array will have sequential values as shown in Figure 1.3.
SELF-CHECK 1.2
Draw a diagram that illustrates the memory location for the declaration
of the digit array in Subtopic 1.2.2(b).
ACTIVITY 1.2
2. An array called Height has five values and is initialised with the
values as below.
float Height[ ] = {1.2, 1.45, 1.3, 1.55, 1.4};
We can refer to the first array element as digit[0], the fifth element as
digit[4] and so on. Here, observe that the value of digit[0] is 7, while the
value of digit[4] is 0 (see Figure 1.4).
We can assign any data values for each of the array element that has been defined
by using the assignment statement.
However, the value that is assigned must be the same data type with the type of
data contained within the array. Look at the following example:
Example 1.1:
We declared the digit array without initial values. Each of the following
assignment statement assigns values to the array element. As a result, we will have
an array that is the same array as in Figure 1.4.
Keep in mind that assigning values to array elements is the same as assigning
values to a variable. The difference is that we need to state the array index that is
being referenced.
Just as in normal assignment statements, the right-hand side part of the assignment
statement for the array element can also be an expression. Look at the following
example:
Example 1.2:
The expression on the right-hand side of the assignment statement can also contain
elements of the same array. Now, let us look at Example 1.3.
Example 1.3:
Can you figure out the values that are assigned to Y[0] to Y[4]?
The values that are assigned in these assignment statements are explained in
Figure 1.5.
Figure 1.5: Assignment statements that involve Y array elements for Example 1.3
Take note that array indexes do not necessarily have to be constant values. We can
also use int type expression as an array index.
However, the value of the index must be in the range of 0 and the value that is one
less than the size of the array (see Example 1.4).
Example 1.4:
int a = 0;
x[a] = 5;
SELF-CHECK 1.3
2. Referring to the arrays that are declared below, state the values that
are assigned to each array element.
3. Given the following program segments, what are the values of the
array elements at the end of the respective segments?
This is usually done by using the loop structure, where each loop will perform an
operation on a single array element. The number of loops is usually the same as
the number of elements in the array.
Example 1.5:
The code below shows how to copy the contents of array A to array B.
int A[5] = {2, 4, 6, 8, 10};
int B[5];
int i;
/* Copy one by one each array element in A to array B */
for (i = 0; i < 5; i++)
B[i] = A[i];
SELF-CHECK 1.4
A. A[i] = B[i] * 2;
B. B[i] = A[i] * 2;
ACTIVITY 1.3
There are many operations that can be performed on array
elements. One of the examples is the addition operation (visit
https://phanderson.com/C/arraysum.html to see the coding). Copy and
run the coding. Write out the output in the myINSPIRE forum.
We can also use this concept with arrays and functions. In fact, we can pass the
entire array to a function, as a parameter. However, the way that an array is passed
to a function is quite different from the way other types of data are passed to
functions. The array is passed to the function by reference. This means in order to
pass an array to a function, we need to state the array name only (without any
bracket symbols or index numbers) as a function parameter and the entire array
will be sent to the function. In the definition of the function, the name of the array
must be written in brackets but without stating the size of the array.
Example 1.6:
// function prototype
void modifyHours(int [], int);
// main function
// declaring array variable
int WorkingHours[24];
Refers to the same
// call the function
array
modifyHours (WorkingHours, 24);
// function definition
void modifyHours (int b[], int size)
{
...
}
The code shows a function called modifyHours that will get an array called
WorkingHours. Pay attention to how the function prototype, function call and
function definition are written. Take note that any changes made to the arrayÊs
values in the function, will change the original contents of the array. This is
important, as we are referencing the array in the function.
In the function prototype, the first parameter indicates that an array will be passed
to the function and it is not necessary to provide the name of the array at this time.
In the main function, the array WorkingHours is declared and then, in order to
pass this array to the function modifyHours, we put in only the name of the array
as the first parameter.
Lastly, in the function definition, you may create an array named b that will receive
the values of the WorkingHours array. Here, the name of the array b is written as
the first parameter in the brackets but without stating the size of the array.
ACTIVITY 1.4
Visit https://phanderson.com/C/arraysum.html again. This time,
examine how functions are used with arrays. Modify all the program
codes if functions are not used. Will the answers still be the same? Write
down your output (if any) in the myINSPIRE forum.
Example 1.7:
An integer array with 10 values are created and is assigned values entered by the
user. The array is then passed to a function where the values in the array are sorted.
// function prototype
void numberSort (int []);
// main function
// array declaration
int number[10];
// input values
for (i = 0; i < 10; i++)
scanf(“%d”, &number[i]);
// function call
numbersort(number);
// function definition
void numberSort (int num[]) {
int j, k, temporary;
for (j = 0; j < 10; j++) {
for (k = j + 1; k < 10; k++) {
if (num[j] > num[k]) {
temporary = num[j];
num[j] = num[k];
num[k] = temporary;
}
}
}
}
Pay attention to the way we write the function prototype, function call and the
function definition of the function numberSort. For complete examples of the
codes, please refer to myINSPIRE.
Remember, the two-dimensional array elements must also be of the same data
type, arranged in such a way that it looks like arrays on top of arrays. This array
has two indexes, one, for the number of rows and the other for the number of
columns.
Where:
Take note that the [ ] [ ] (brackets) identify the two-dimensional array and the
enclosed numbers indicate the number of rows and columns.
For example, the statement int table [3][4]; declares an integer type two-
dimensional that consists of three rows and four columns. The compiler creates the
array table, which reserves memory space for 12 integer type elements, which is
the number of row 3, multiplied by the number of column 4.
Let us assume that we would like to store a set of examination marks for
50 students and each student has 10 examination scores. The data of the marks can
be stored by using a two-dimensional array as follows:
int marks[50][10];
With this declaration, the compiler will set aside 500 memory cells (which is
50 10) as illustrated in Figure 1.6.
Figure 1.6: Memory location illustration for the declaration of int marks[50][10]
Each element can be referred to by using the name of the array, as well as the
row index and column index. For example, marks[1][9] refers to the
10th examination mark for the second learner, as shown in Figure 1.6.
The element contents of the square array can be illustrated as in Figure 1.7.
Figure 1.7: Memory cell content for the declared square array
ACTIVITY 1.5
Copy the coding given in the website and run it. Do you get the same
output?
SELF-CHECK 1.5
Declare a two-dimensional array that has three rows and five columns
called pqr. Assign the initial values for each row element of the first row
with value 1, the second row element with value 2 and each element in
the third row with value 3.
Example 1.8:
int square[4][4];
int i, j;
SELF-CHECK 1.6
State the values that are assigned to each following array element:
Example 1.9:
// function prototype
void read5YearRainfallDistribution (int rain[][12]);
// main function
// two-dimension array declaration
int rain[5][12];
// function call
read5YearRainfallDistribution(rain);
//function definition
void read5YearRainfallDistribution(int rain[][12])
{
...
}
Arrays are data structures that contain data items that are related and of the
same type. They can be in one or more dimensions.
We can set the initial values for each array element during declaration.
We can use functions in arrays so that our program code is more modular.
Array Initialising
Assigning One-dimensional array
Column Referencing
Declaring Row
Elements Sorting
Functions Two-dimensional array
Index
INTRODUCTION
In the previous Topic 1, we have learned about arrays. If you recall, arrays are data
structures that have all elements of the same type of data.
In this second topic, we shift our focus to structures that are groups of data that
can have elements consisting of different types of data. This data structure is very
important in writing programs relating to manipulating various types of data that
are similar to databases.
Unlike arrays, structures can combine the data of different types and can be
referred to using the same name. Let us assume that we want to create an account.
We would need the following information:
(a) Account number;
(b) Type of account;
(c) Name of account holder; and
(d) Savings balance.
After identifying each data element, we need to decide the data type to represent
each element. Table 2.1 shows the data that could possibly represent the account
information.
SELF-CHECK 2.1
where
data_*_definition Data type definition for each element that forms the
structure.
Example 2.1:
struct account {
int accountNo;
char accountType;
char name[30];
float balance;
};
Take note that before using the declaration of the structure in a program, we need
to declare the variable of struct type. This variable of type struct can be
declared as follows:
where
Observe that
Based on the previous variable declaration, the memory space that is reserved for
myAccount is as shown in Figure 2.1.
We can also declare the variable by listing the variable names at the end of the
structure declaration. Let us observe the declaration of the following variable
where oldAccount and newAccount are variables of type account (see
Example 2.2).
Example 2.2:
struct account {
int accountNo;
char accountType;
char name[30];
float balance;
} oldAccount, newAccount;
As with other types of data, we can also initialise the value of a structure. For
example:
struct account myAccount = {1234, ‘S’, “Nadiah”, 1998.88};
SELF-CHECK 2.2
1. Sketch the memory space for the account structure myAccount if
it is initialised in the following way:
struct account myAccount = {1234, ‘S’,
“Nadiah”, 1998.88};
A. anniversary
B. date
C. day, month, year
where
Example 2.3:
myAccount.accountNo = 1234;
myAccount.accountType = ‘S’;
strcpy(myAccount.name, “Nadiah”);
myAccount.balance = 1998.88;
This example assigns values to the structure elements. In actual fact, this type of
data assignment is equivalent to giving initial values to a structure.
Let us have a look at the following code. This code shows how the input and output
of structure elements are performed (see Example 2.4).
Example 2.4:
ACTIVITY 2.1
A. printf(“%d”, customerNo);
B. printf(“%d”, customer.customerNo);
C. printf(“%d”, customerInfo.customerNo);
A. customername = “Aisyah”;
B. customer.name = “Aisyah”;
C. puts(customer.name = “Aisyah”);
Example 2.5:
customer[14].accountNo = 122;
printf(“The 15th customer’s account number: %d\n”,
customer[14].accountNo);
We can also refer to the savings balance for the last customer as
customer[499].balance (see Example 2.6).
Example 2.6:
customer[499].balance = 8755.02;
printf(“The 500th customer’s balance: %.2f\n”,
customer[499].balance);
Besides that, we can refer the first character for the name of the first customer as
customer[0].name[0]. Let us now look at Example 2.7.
Example 2.7:
strcpy(customer[0].name, "Nadiah");
printf(“The name of the first customer begins with the
letter %c\n”, customer[0].name[0]);
SELF-CHECK 2.3
You have previously sketched the memory space for arrays in Topic 1.
How about the memory space for the account structure-type array? Can
you sketch it as well?
ACTIVITY 2.2
1. Given the following structure declaration:
struct book {
char title[50];
int year;
float price;
}books[50];
A. strcpy(books.title, “C programming”);
B. strcpy(books[0].title, “C programming”);
C. strcpy(book[50].title[50], “C programming”);
Example 2.8:
struct date {
int day;
int month;
int year;
};
struct account2 {
int accountNo;
char accountType;
char name[30];
float balance;
struct date transaction;
};
Notice that the date structure is written before it is used in the account2
structure. Here transaction represents the last transaction date that is declared
as a structure type date. This declaration is shown in the following Figure 2.2.
We can refer to the year of the last transaction for the 10th newCustomer as
newCustomer[9].transaction.year.
ACTIVITY 2.3
1. Given the following declaration:
struct information {
char telephoneNo[10];
char address[50];
char postcode[5];
char city[15];
char state[15];
};
struct customerInfo {
char name[30];
int customerNo;
struct information personal;
} customer;
Write the statement to access the following structure elements for:
(a) Customer name
(b) Customer number
(c) Telephone number
(d) Postcode
Example 2.9:
//Structure declaration
struct date{
int day;
int month;
int year;
The dte variable of date
}; structure type is passed
// function prototype to the increaseDate
function by value and the
void increaseDate (struct date t);
original dte value is still
// main function maintained.
void main( ) {
struct date dte = {20, 1, 2018};
increaseDate (dte);
printf(“%d/%d/%d”, dte.day, dte.month, dte.year);
}
// function definition
void increaseDate(struct date t) {
(t.year)++; /* The year element is increased */
}
The dte variable of the date structure type is passed by value to the
increaseDate() function. This increaseDate() function increases the year
element.
However, when the function ends, the value of the year element for the dte
structure in the main() function is still the same as the value before the
increaseDate() function was called. This means that the output that is printed
is 20/1/2018.
As with other arrays, the passing of the structure array to the function is performed
by reference.
Example 2.10:
// function prototype
struct date addYear(struct date dte, int);
// function call
t1 = addYear(dte, 3);
}
struct date addYear(struct date t, int i) {
t.year += i;
return t;
}
The dte variable of date structure type and value 3 is passed to the function
addYear() by value. This means that the value of the dte structure is copied to
the parameter t, while 3 is assigned to the parameter i in the function. This
function will add the i value to the year element (year = 2018 + 3 = 2021).
Then, the t variable of the date structure type (with the day = 31, month =
10, year = 2021 values) is returned with the return statement. The value
that is returned is assigned to the t1 structure.
SELF-CHECK 2.4
A structure, when passed to a function, will copy the structure value for
processing in the body of the function. Any changes made to the values
of the structure by the function would not give any effect on the values
of the structure variable copied. What is this called?
A. Passing by reference
B. Passing by value
C. Passing by variable
ACTIVITY 2.4
A. display (student);
B. display (fulltimestudent);
Example 2.11:
In statement (i) that contains the typedef statement, we have defined an integer
variable with the name input. We can then define other input type variables, as
shown in statement (ii). The positive_input and negative_input variables
in (ii) are actually integer types. In (ii), we have replaced the data type int with
input.
The typedef data type can also be used in the structure declaration. Consider the
following example (see Example 2.12).
Example 2.12:
Take note that the variable declaration is shorter. We can avoid using the word
struct in this declaration. This is because Bio is not a normal structure as learned
in the previous subtopics. It is a data type declaration of structure type.
Using structure is an easy way to group information from several related items
by using the same variable name. Unlike array, the structure elements do not
necessarily have to be of the same type.
We can also declare the variable by listing the variable names at the end of the
structure declaration.
To represent a group of data from the same type of structure, we use an array
representation of a structure type.
Arrays Referencing
Declaration struct
Initialisation typedef
INTRODUCTION
Did you know that all the variable data types that have been declared before this,
have enabled direct access to the values that are kept in storage by the program?
This is because other than declaring variables that hold simple data (integer, float,
double, char), the C language also allows us to declare variables that keep the
storage address. As we already know, each storage cell that is used has a specific
and unique address. Variables that keep these addresses are referred to as pointers.
So are you ready to discover more on this interesting topic? Let us continue with
lesson.
A pointer is just like another variable, the main difference is that it stores
address of another variable rather than a value.
This value refers to the memory location for a variable or an array element.
Pointers in C are used to allocate memory dynamically at run time. The pointer
variable may belong to any of the data types such as int, char, float or double.
For example, let us say that a is a variable that represents an integer data type,
while p is an integer pointer, as illustrated in the following variable declaration:
int a;
int *p;
If you are creating a pointer to store the address of an integer variable, the pointer
data type should also be type integer. The compiler will assign memory space for
these data automatically. These data items can be accessed if we know the location
or address. The address for memory space a is given by the form &a, where & is
the operator address. Therefore, we can write:
p = &a;
which means that p is a pointer variable that points to a. The value in p is the
address for a and not the value of a. Let us look at Figure 3.1 which illustrates this.
Observe that the address is underlined.
The value of the item represented by a can be accessed using *p, where * is the
reference operator. The declaration of pointer variables is as follows:
data_type *variable_name;
data_type are the data types found in C, such as int, char, float and
double, whereas variable_name is a valid name in C. The symbol * identifies
that the variable_name is of pointer type (not a normal variable).
Example 3.1:
Figure 3.2 shows an illustration for the p = &a statement in this code:
int a,b; // variables a and b of integer type
int *p; // Pointer p of integer type
a = 7; // Assigning 7 to a
b = 7; // Assigning 7 to b
p = &a; // p points to a
Observe that the addresses are underlined, and may be different depending upon
the compiler and platform.
Did you know that a pointer can be initialised using an address or even with a
NULL value (or 0)? Take note that a pointer with the NULL value does not point to
any memory location. A pointer variable can also be used to assign an address of
a variable as an initialisation of the pointer variable.
int data;
int *a = NULL; // Pointer to NULL value
int *b = &data; // b points to data
Example 3.2:
int a = 100;
int *p; // Pointer to the int variable
int **pp; // Pointer to the pointer
p = &a; // p points to a
pp = &p; // pp points to p that points to a
Figure 3.3 shows you the memory locations for a, p and pp.
In Figure 3.3, we assume that the address value for a is 62FE4C, while p has the
address 62FE48 and the address for pp is 62FE40. To get the pointer variable
value which is the address of a variable, use the %p format specification.
SELF-CHECK 3.1
1. What do you think will be displayed when you try to print out the
value of a null pointer? Will it contain a 0 or some other value?
What about the address of a random variable?
2. If you have created two variables and two pointer variables to point
at them, what is the difference in value of these two addresses?
Justify your answer.
ACTIVITY 3.1
A. 10 A. 3.5
B. 10 10 B. 7.5
C. 10 C. 17.5
10
Well, pointers can also be passed to functions too. This way, we pass the address
to a local variable that points to the actual location of the data item by the calling
function. Take note that the value that is changed in the function will change the
value at the actual location too. This method of passing is known as passing by
reference or reference passing.
Code 1 Code 2
void A(int num) void A(int *ptr)
{ {
num = 5; *ptr = 5;
} }
void B() void B()
{ {
int data = 3; int data = 3;
A(data); Data B(&data); Data
= 3 = 5
} }
These codes explain the concept of passing by reference, in which the first Code 1
is a simple program that uses a normal variable and Code 2 uses a pointer. Two
functions A and B have values passed between them. Observe that in Code 1, the
variable data is passed from function B to function A does not change the value
of the original data which is 3. In Code 2, since the variable data has been passed
using its address (&data) to the pointer in function A, the value of data is now 5
and not 3.
ACTIVITY 3.2
void main() {
int a = 10; int b = 5;
float x,y;
x = function1(a, b);
printf (“a = %d, b = %d\n”, a, b);
y = function2(&a, &b);
printf (“a = %d, b = %d\n”, a, b);
}
float function1(int i, int j) {
i = 70; j = 7;
return ((float) i / j);
}
float function2(int *i, int *j) {
*i = 50; *j = 4;
return ((float) *i / *j);
}
(a) What is the value assigned to a and b after the program has
completed its execution?
Therefore, if x is a one-dimensional array having five elements, the address for the
first array element can be written as &x[0] or x. Let us look at Figure 3.4 which
shows the illustration of array x.
Then, the address for the second array element can be written as &x[1] or (x +
1) and so on. In general, the address for the (n + 1) element can be written as
&x[n] or (x + n). Figure 3.5 shows you the memory location for the array x that
is declared as int x[5].
In Figure 3.5, x and &x[0] points to the first elementÊs address which is 62FE30.
Similarly, &x[1] is equivalent to (x + 1), &x[2] is equivalent to (x + 2),
&x[3] is equivalent to (x + 3) and &x[4] is equivalent to (x + 4). This is
called pointer arithmetic, where pointers can be added to and subtracted from.
When you add a value to the pointer, it means that the pointer will move forward
based on the value added.
In Figure 3.5, the expression (x + 4) will move the array pointer x forward four
times. Have you noticed that the addresses of the array indexes all increase by
four? Why did that happened? This is because, the integer value is stored in four
bytes of memory. It is easier to display the memory values in hexadecimal values,
as shown in Figure 3.5.
You can also create a pointer variable to point to an array. When assigning array
to a pointer, you can write the assignment statement in two ways:
p = x;
Or
p = &x[0];
Can you recall of the passing of an array to a function by reference? This has been
explained in Topic 1. Passing of an array to a function by reference can be done
because the array name is the address or pointer and the passing of the pointer to
the function is performed by the reference.
Note: An array name is a constant pointer. This means that it cannot be assigned
with a new value or in other words, it cannot point to another location other
than the first element of the array.
Since strings are a type of an array, we can also declare and refer to strings as a
pointer.
In the following declaration, rent is a pointer of char type that points to the
December string.
char *rent = “December”;
Figure 3.6 shows you the memory location for this declaration.
With this assignment, it means that rent will point to the new location that has
the May string (refer to Figure 3.7).
Take note that this method of assignment cannot be implemented when we declare
the string as a character type array. So how do we solve this? You can use strcpy
if you want to assign values to a character type array variable.
Assigning arrays to the pointer arrays makes use of pointer arithmetic. For
example, an array arr1 is declared with four integer values. These values can be
assigned to the *tts pointer as:
int arr1[4] = {1,2,3,4};
Figure 3.8 illustrates the memory location resulting from the previous declaration.
ACTIVITY 3.3
A. fg
B. efg
C. defg
2. Given
int n[5] = {10, 20, 30, 40, 50};
int *p;
p = n; // p = &n[0];
A. 10, 40
B. 13, 40
C. 13, 43
This means that we can declare the memory size that is needed by the program,
during the execution of the program. This way, when we write a program that
involves dynamic arrays, we do not need to give the size of the array during
declaration.
Let us understand the difference between static and dynamic memory allocation
(see Table 3.1).
We can also decide to input the size of the array during program execution and
the program will reserve sufficient space for the array elements. This is known as
reserving memory space dynamically.
(int *) is the cast into the pointer data type and sizeof will determine the
memory size that is provided in byte units depending on the data type. no is a
value that is multiplied with the size of the memory required. If sizeof(int)is
given, then four bytes of memory is created multiplied by no.
In C, the memory size of a type of data is different from one computer system to
another. Because of this, the sizeof() function is very important even though it
is only to determine the size of simple data types. For example, ptr = (int *)
malloc (100 * sizeof(int)); will allocate 400 bytes of memory and the
ptr points to the first address of the first byte of memory.
If successful, this function will assign the address for the memory that has been
reserved to the ptr. If the space is insufficient, the memory allocation will fail and
this function will return the NULL value.
Now, let us move on to the free() function. Conversely, the free() function
will release the memory that has been reserved by malloc(). The free()
function that frees the space allocated in the memory pointed by ptr is written as:
free(ptr);
Example 3.3:
This coding declares list as a pointer and then reserves memory space
dynamically. The memory space that is reserved by malloc() is shown by the
list. Now, we can operate the list just as how we would operate an array of size
no.
We can declare the variable holiday as a date struct. We can also declare ptr
as a pointer of struct date * type (that can point to the location of struct
date type). Look at the following declaration.
struct date holiday;
struct date *ptr;
Based on the previous declaration, the following statement would make ptr to
point to holiday. Here, the address for holiday is assigned to ptr. Look at the
illustration of memory space in Figure 3.9.
ptr = &holiday;
Table 3.2 shows the different ways of referencing and the actual action for the
related referenced variable.
Reference Action
ptr Pointer to holiday
*ptr The actual holiday structure
(*ptr).day Same as holiday.day
(*ptr).month Same as holiday.month
(*ptr).year Same as holiday.year
Take note that we can use the operator „->‰ to access structure elements through
ptr. Look at the following statements.
(ptr->day) /* Same as (*ptr).day */
(ptr->month) /* Same as (*ptr).month */
(ptr->year) /* Same as (*ptr).year */
ACTIVITY 3.4
A pointer stores address of another variable rather than a value. This value
refers to the memory location for a variable or an array element.
Passing variable data to functions from the main function or other functions is
called parameter passing.
Pointer can also be passed to functions the address is pass to a local variable
that points to the actual location of the data item by the calling function. The
value that is changed in the function will change the value at the actual
location. This method is called passing by reference or reference passing.
The malloc() function will provide dynamic memory space when the
program is executed.
The free() function will release the memory that has been reserved by
malloc().
INTRODUCTION
Static representation of linear order such as array may lead to wastage of memory
and in some cases, overflows. Now, let say we do not want to assign memory to
any linear list in advance. Instead, we want to allocate memory to elements as they
are inserted in list. This requires dynamic allocation of memory and it can be
achieved by using malloc() or calloc() function as we have seen in Topic 3
recently.
Take note that list and linked list are two different terms. Hopefully, you will not
be confused with these two terms. Let us learn more on these two terms in the next
subtopics. Happy reading!
4.1 LIST
Firstly, what does a list mean?
A list is a general term that is defined by one type of sequential data items.
Can you give some examples of list? Well, list of courses that is being offered to
learners by OUM and breakfast menu in McDonaldÊs are examples of list.
Example 4.1:
if (myList[0] == 0)
printf(“List is empty”);
In Example 4.1, the first element of the myList array is 0, so, we consider it
to be empty as there has not been any data assigned to the array yet. Keep in
mind that the array itself is not empty and cannot be empty, as it contains
the value 0s.
You do not need to test if the list is full or not. This is because the insertion
of data into the list is done by assigning values at the arrayÊs index.
Example 4.2:
Keep in mind that array insertion does not increase the size of the array. If
the data item is to be inserted early on in the list, then many shifting of data
needs to be done as compared to when an insertion is done in the middle of
the list. No shifting will occur if the data item is to be inserted at the end of
the list or if the list does not need to be in a sorted state and the new data
item can be added at the end. The prerequisite for an insertion operation is
that the list must not be full.
Let say we want to insert a value 8 into a list: 2, 5, 9, 14, 20 at the third position
(so that the list is sorted), data elements that are in the list need to be shifted
by one position (see Figure 4.3).
After inserting the value 8, the new list is shown as in Figure 4.4.
The opposite of inserting an item into the list is done for the deletion of data
elements, where the data elements are shifted by one into the empty
locations. Let say the value 8 is to be deleted from a list: 2, 5, 8, 9, 14, 20 (see
Figure 4.5).
After deletion operation, the values 9, 14 and 20 are shifted by one (see
Figure 4.6).
Figure 4.6: After deletion operation, the values 9, 14 and 20 are shifted by one
However, we shall not discuss it in detail here. It would be sufficient that you
know a little bit about this concept.
SELF-CHECK 4.1
A. Linear array
B. Linked list
C. Binary tree
A. int List[10] = 0;
B. CreateList (&List);
C. CreateList(List[10]);
Linked list is a specific term that is used in programs and is defined as a group
of data items that is sequential, with its data items having links between each
other.
In the previous subtopics, you learned how arrays could improve the readability
and efficiency of your programming codes.
However, arrays are not practical for updating or for inserting and deleting data
as it involves a lot of movement of data. Thus, this weaknesses can be overcome
by using linked list.
As stated before, linked list is a collection of data items which have links with each
other insertions and deletions can be made anywhere in a linked list. Linked list
consist of sequential data items known as nodes. Each node is made up of two
parts (see Figure 4.7).
(b) Pointers.
Take note that the implementation of linked list using array will not be discussed
in this course. Only the implementation of linked list using pointer will be
discussed.
Did you know that inserting at the beginning of the linked list is easier than
inserting in between two nodes in the linked list? This is because there is an
extra step of traversing up to the node where the new node will be inserted
after (or up to the previous node where the new node will be inserted before).
How about insertion at the end of the list? The insertion of a new node at the
end of the list involves traversing until the last element is reached, and then
inserting the new node at the end.
However, you do not need to test whether the linked list is full or not, as you
can add as many new nodes as you want.
Same as the insertion of items into the linked list, deletion can also be done
at the beginning, between two nodes in the linked list or at the end of the
linked list.
Example 4.3:
This linked list contains data items of type character. We define the variable
in Example 4.3 as structure type using the following statements:
NODE *list;
struct node *pnode;
Example 4.4:
The linked list list is sent by reference (observe the & symbol that is used).
This means, in the CreateList function, sList will receive the address of
the list pointer. At the end of this function, list will have the value
NULL, which is a new list that is empty (refer to Figure 4.9).
Example 4.5:
Figure 4.10 illustrates the node that will be returned after the execution of
this function. Let us just say that dataelement has a value of „A”.
Example 4.6:
Example 4.7:
Example 4.8:
Let us look at Figure 4.11 which shows you the TraverseList operation
will print each element in the list.
Example 4.9:
The following are steps to insert a node at the beginning of the list:
Figure 4.13: Step 1 and Step 2 in inserting a node at the beginning of the list
Figure 4.14: New list after „D‰ is inserted at the beginning of the list
The code to insert a new node at the beginning of the linked list is given
in Example 4.10.
Example 4.10:
At the end of this operation, newNode will be inserted after the node
that is identified (pointed) by temp. Figure 4.15 illustrates this situation
before the insertion.
Figure 4.15: Before insertion of newNode node in the middle of the linked list
Let us look at Figure 4.16 which shows you the effects of both steps.
The earlier temp.next link which was pointing to node „D‰ has been
replaced with the link to newNode.
Figure 4.17: Inserting newNode node at the end of the sList list
The function for inserting a node in a linked list at the end is as follows
(see Example 4.11).
Example 4.11:
Example 4.12:
The pointer temp points to the location where the node needs to be
inserted. If temp is null, the new node will be inserted at the start of the
list.
Deleting the middle and last nodes has the same characteristics. The pointer
temp is used to show the position of the node that is to be deleted. If temp
is null, this means that the first node is to be deleted and if the value of temp
is not null, then this means that the middle or last node is to be deleted
depending on the position of pointer temp. The following is the operation
for deleting a node (see Example 4.13).
Example 4.13:
temp = pnode->next;
pnode->next = temp->next;
}
free(temp);
}
ACTIVITY 4.1
Let us suppose that you are given a linked list as follows:
Linked list is linear and non-linear data structure. Linked list is linear for accessing
and non-linear for storing in memory. This means that the nodes of the linked list
are all joined, and is accessible one after another linearly through the link. Each node
in the linked list is connected with its previous node which is a pointer to the node.
However, the nodes are stored in memory based on available memory spaces as
and when the nodes are created. It is a complex process for updating the node in a
linked list. This is because the node has to be reached first linearly using a pointer
from the beginning of the linked list. If the node to be updated is at the end, the
process involves looking at all the nodes before the last node can be reached and
updated.
The nodes in the linked list can be added and deleted easily from the list, as
described in Subtopic 4.1.
To sum up, the differences between list and linked list can be simplified as shown
in Table 4.1.
SELF-CHECK 4.2
In your opinion, what is the difference between list and linked list? You
can go to https://goo.gl/yoEKkQ to know more about the differences
between list and linked list.
ACTIVITY 4.2
2. You are given the following two linked lists and the code:
temp = pList1;
while (temp->next != NULL);
temp = temp->next;
temp->next = pList2;
List and linked list are two data structures that are used to store information.
A one-dimensional array is similar to a list.
Basic list operations are to create empty list, test whether a list is empty or not,
traverse list, insert a new item to the list and delete an item from the list.
A linked list consists of a nodes and links. A node has data and a pointer that
points to another node. Using pointer to implement linked list is a little more
difficult than working with array.
The basic linked list operations are create empty linked list, test whether a
linked list is empty or not, traverse linked list, insert new item, delete item,
create new nodes and search linked list linearly.
List and linked list are different in many aspects, such as size, storage capacity,
order and sorting, accessing the element and searching.
Comparison Nodes
Deletion Pointers
Insertion Searching
Linked lists Traversal
Lists
INTRODUCTION
In the previous topic, we have been introduced to lists and linked lists. Among the
important things that were highlighted are the use of pointers and basic
operations.
Now, you will be introduced to another way of structuring data, that is, stack.
Stack can be pictured as a stack of arranged plates, where the last plate washed
will be put on top and it will be the first one to be used when we want to eat (refer
to Figure 5.1). The plate that was under the first plate used will then become the
plate at the top.
Similarly, a stack is also an array. Refer to Figure 5.2, the data „A‰ is inserted first,
followed by „B‰ and then „C‰. Likewise, the data „C‰ will be removed first,
followed by „B‰ and then „A‰. At the moment, the data „C‰ is at the top.
As you can see in Figure 5.2, there is a pointer named top that will point to the top
of the array (stack). This pointer will indicate where the next data will be inserted
or deleted. Removing data from a stack is called pop, and inserting data into a
stack is called push. Based on this discussion, can you define what a stack is?
A stack can be defined as a heap of arranged data items that can be accessed
from one end only.
(b) Stack is a LIFO (last in first out) structure, meaning that the last item inserted
will be the first item to be taken out.
(c) Push() function is used to insert new elements into the stack and pop()
function is used to remove an element from the stack. Both insertion and
removal are allowed at only one end of the stack called the top.
The simplest application of a stack is to reverse a word. You need to push a given
word into a stack, letter by letter, then pop the letters that are at the top from the
stack. For example, let us take the word „Hello‰ and push it into a stack. What is
the result? The word „olleH‰ which is the reverse word will pop from the stack.
Did you know that a common application of stack is an „undo‰ mechanism in text
editors? This operation is accomplished by keeping all text changes in a stack.
Have you notice it?
(c) Memory management where the call to and from a function is executed.
Operation Description
Create stack For creating an empty stack.
Performed before all the following operations can be carried
out.
Test empty stack To test whether the stack is empty or not.
Performed before the item removal operation is executed if
the stack is empty, then there is no item that can be removed.
Test full stack Performed if the implementation is done using arrays.
If the implementation is done using pointers, this test is not
needed.
To test whether a stack is full or not it is performed before the
insertion operation is carried out because arrays have an item
limit.
Items cannot be inserted in a stack if the stack is full.
Remove element To remove an item from a stack.
from stack The prerequisite is that the stack must not be empty.
Add element to For inserting an item into a stack.
stack The prerequisite is that the stack must not be full.
SELF-CHECK 5.1
A. First-in-first-out (FIFO)
B. Last-in-first-out (LIFO)
C. Last-in-last-out (LILO)
2. What is the name of the pointer that shows the value that will be
popped next?
A. pop
B. top
C. up
(b) Pointers.
Take note that the implementation using arrays is easier compared to the
implementation using pointers. Moreover, array representation of stack performs
pop and push functions easily. It is also efficient to use arrays for writing stacks as
all the stack nodes or data items are in contiguous memory and can be processed
quickly. Since modern processors has plenty of memory, the only problem with
array implementation of stack, which is the size of the array is not a problem
anymore.
Let us look at an example of how to add data into stack and remove data from a
stack (see Figure 5.3).
Example 5.1:
#define MAXSTK 10
In Example 5.1, the stack maximum is set to just 10 items. This limit is set at the
beginning, according to the needs of an application.
Example 5.2:
If the stack is empty, t->top is -1. For a full stack, t->top would be the
maximum for the stack, which is 10 (for this example). The CreateStk operation
is the initial operation that is performed, so the stack must be empty.
Example 5.3:
This function will return TRUE if the stack is empty and FALSE if the stack contains
items. This is because a stack is empty when the value of top is -1.
Example 5.4:
This function will return the value TRUE if the stack is full and the value FALSE if
the stack can still be filled with a new item. This is because a stack is full when the
value of top is MAXSTK which is 10 in this example.
Example 5.5:
Example 5.6:
ACTIVITY 5.1
Example 5.7:
#include <stdio.h>
main ()
{
int counter, number, temp;
STK odd, even;
/* Create even and odd stacks */
CreateStk(&odd);
CreateStk(&even);
/* Read data and insert into the related stack */
for (counter = 1; counter <= 10; counter ++)
{
printf("\nInput number: ");
scanf("\n%d", &number);
if (number%2 == 0)
push(number, &even);
else
push(number, &odd);
}
//FUNCTIONS
The program in Example 5.7 reads 10 numbers one by one from the keyboard and
test whether the number is even or odd.
If the number is odd, then that number will be inserted into the odd stack and if
the number is even, then that number will be inserted into the even stack. Later,
the contents of both stacks will be printed onto the screen. So what is the output of
this program? The output of Example 5.7 is shown in Figure 5.4.
In this example, a loop is used to input data from the user. The loop is repeated
10 times from 1 to 10. It is important to understand that the values pushed into the
stack is for array index 0 to 9 (depending on the input by the user).
Even though the FullStk codes to check if the stack is full is given in the push
function and the Emptystk codes to check if the stack is empty is given in the pop
function, these codes will not be executed.
You may practise writing the codes for stack data structure using pointers to see
the effects of EmptyStk and FullStk for pop and push functions respectively.
Notation Expression
Infix M+N
Postfix MN+
Let us look at on how to convert infix to postfix notation and how postfix notation
evaluation can be done.
Algorithm:
Scan infix expression from left to right
If an operand is encountered add to postfix string (P)
If an operator is found
Repeatedly pop the operator from stack which are having higher
precedence than the operator found and add to P
Add the new operator to stack
If a right parenthesis is found
Repeatedly pop the stack and add the popped operators to P until a left
parenthesis is found
Remove the left parenthesis
Stop
Let us look at Example 5.8 which shows you how infix notation ((3 + 5 * 1)/8)
* 14 is being converted to postfix notation.
Example 5.8:
Convert infix notation ((3 + 5 * 1)/8) * 14 to postfix notation.
Answer:
The steps in converting infix notation ((3 + 5 * 1)/8) * 14 to postfix notation is
shown in Figure 5.5.
Now, let us look at Example 5.9 which shows you the program to convert
infix notation to postfix notation.
Example 5.9:
#include <stdio.h>
#include <ctype.h>
#define SIZE 50 /* Size of stack */
char s[SIZE];
int top = -1; /* Global declarations */
So what is the output for Example 5.9? Let us look at Figure 5.6 that
represents the output for Example 5.9.
i=1
While i n
i=i+1
Output result from stack
Example 5.10:
Evaluation of postfix notation: 3 5 1 * + 8 / 14 *
Stack
empty
3
3 5
3 5 1
3 5
8
8 8
1
1 14
14
Answer: 14
SELF-CHECK 5.2
(a) J*K/L+M
(b) J*(K+L*M)/N
(a) 21 3 / 10 – 7 3 / *
(b) 35*26–*
ACTIVITY 5.2
The basic operation for inserting data into a stack can be written as:
/* Insert an item into a stack t or display an
error message if t is full */
void InsertStk (ELEMENT item, STK *t)
{
if (FullStk(t))
printf("\nStack Full \n");
else
{
xxx;
t->element[t->top] = item;
}
}
A. t->++top;
B. t->top++;
C. t->top--;
A stack can be defined as a heap of arranged data items that can be accessed
from one end only. It is a limited version of an array.
Among the characteristics of stacks are ordered list of similar data types, LIFO
(last in first out) structure, insert new elements into the stack using push()
function and using pop() function to remove an element from the stack.
Basic stack operations are create stack, test empty stack, test full stack, remove
element from stack and add element to stack.
The stack data structure can be implemented in two ways, namely using arrays
and using pointers.
Stack can be applied when we want to separate even and odd numbers, and
reverse Polish notation.
INTRODUCTION
Let us begin this topic with a saying that is hoped to motivate you in learning this
topic. It goes something like:
We have already gone through Topic 4 and Topic 5. Therefore, let us broaden our
knowledge in the data structures subject with this topic on queues.
Similar to stacks, queues also have a wide application. In our daily lives, we can
see queues almost every day as customer waiting lines in banks (see Figure 6.1) or
cars at toll gates.
Customers or cars that arrive first will be attended to first, while those that come
after will be attended to when their turn comes. What is the meaning of queues?
In general, we can define queues formally as an ordered list, where elements can
be added at only one point (which is called the back or end) and an item can only
be removed from the other end (which is called the front or head).
From the definition given, we can say that the main characteristic of a queue is also
known as first-in-first-out (FIFO) which is different from the main characteristic of
stack data structure which is last-in-first-out (LIFO). Let us look at an example of
queue with five elements (see Figure 6.2).
As we can see in Figure 6.2, the element „A‰ is added to the queue first, followed
by „B‰, „C‰, „D‰, and „E‰. The front and back of the queue is also shown in the
figure.
Operation Description
Create queue Queues need to be created before it can be used for any
operations. Queues are created by giving initial values for the
variables at the front and back of the queue.
Test empty queue Before removing an item from the queue operation, we need to
conduct a test first to see whether a queue is empty or not.
Test full queue Before adding an item to the queue operation, conduct a test
first to see whether a queue is full or not.
Remove one element All items or elements are always removed from the front of the
from queue (delete queue. The prerequisite for this operation is that the queue
queue dequeue) must not be empty.
Add one element to All items or elements are always added at the back of the queue.
queue (enter queue The prerequisite for this operation is that the queue must not be
enqueue) full, which means that there must still be room to insert another
item.
SELF-CHECK 6.1
(b) Arrays.
For the implementation of queues in this topic, we shall use arrays to keep queue
data items and two integer variables to represent the front and back queue
locations (we used the integer variable top to represent the top of the stack).
The value of the variable that represents the back of the queue will be increased
when an item is added to the queue (the item is added to the back of the queue).
Similarly, the value of the front variable will also be increased when an item leaves
the queue (the item leaves through the front of the queue).
Let us recall the basics of arrays. An array has a name and data type, and holds
multiple elements. The number of elements stored in an array is a fixed value
called the array size. The array index begins at 0 and ends at the size of the array
subtracted by 1. So, if we have an array with size 10, the index will be from 0 to 9.
Operation Description
Step 1: Creating a new queue
Function: CreateQueue
Function:
1. Enqueue (A)
2. Enqueue (B) When the first Enqueue A function was executed, the
3. Enqueue (C) value 0 is inserted into the index 0, which is the value
indicated by Back. After this insertion, Back is
increased by 1. For the second insertion, Enqueue B,
the value 0 is inserted into index 1, which is indicated
by Back and the value Back is increased to 2. So, for the
third insertion, the Enqueue C, the value 0 is inserted
into index 2 and the Back value is now increased to 3.
This shows where the next insertion (Enqueue)
function will take place. Before inserting new items into
the queue, the test full queue is always done.
Function:
1. Dequeue()
2. Dequeue()
In the Dequeue() function, the Front variable is used.
It will be increase by 1, after each Dequeue() function.
In this example, the first Dequeue() function
increased the Front value from 0 to 1, and the second
Dequeue() function increase it again to 2. You can see
that the Front is now showing the value that is in the
queue at the index 2. This is now the front of the queue
and will be the next value deleted from the queue. It is
important that before deleting items from the queue, the
test empty queue is always done.
Step 4: Insert two more items
into the queue.
Function:
1. Enqueue (D)
2. Enqueue (E) Since inserting new values into the queue will be from
the back of the queue, D is inserted into the index that is
indicated by Back which is 3. Back is now increased to
4, and the value E is inserted here. The Back value is
again increased, the value of Back now is 5. Before
inserting new items into the queue, the test full queue is
always done.
After the two items D and E have been added, the value of the back variable is
increased by 2 and this is over the limit of the maximum array index that was
defined as 5 (Remember! Array index starts at 0). Also observe that the number of
items in the queue is 3 and there are still two more empty spaces in the queue.
If we want to add more items and maintain the queue characteristics, one way is
by shifting the queue element, so that the first item is located at index 0, second
item at index 1 and third item at index 2. The values of the front and back variables
also need updating. This is difficult because every time a new insertion and
removal queue operation is performed, shifting also needs to be done.
We can solve this problem by assuming that the array element is arranged in a
circle. This arrangement is called a circular queue. Let us take the same case as in
Table 6.2. The circular queue is explained in Table 6.3.
Operation Description
Step 1: Insert three items
into the queue.
Function:
1. Enqueue (A)
2. Enqueue (B)
3. Enqueue (C)
Function:
1. Dequeue()
2. Dequeue()
Function:
1. Enqueue (D)
2. Enqueue (E)
Here, observe that after two more items have been inserted,
the value of the Back variable is now 0. This enables more
items to be inserted in the queue in the empty spaces.
However, it is important that before inserting new items
into the queue, the test full queue is always done.
However, this value can be changed according to the application. The queue data
structure can be defined as shown in Example 6.1.
Example 6.1:
#define QLimit 10
typedef int ELEMENTTYPE;
typedef struct queue
{
int front;
int back;
ELEMENTTYPE element[QLimit];
} QUEUE;
We can redefine the queue variable for use in the main program as:
QUEUE queue;
Example 6.2:
The function to test whether the queue is empty or not can be written as in
Example 6.3.
Example 6.3:
/* Receive: Queue g
Process: Test whether queue g is empty or not
Return: The value TRUE (1) if the queue is empty,
the value FALSE(0) if otherwise */
BOOL EmptyQ (QUEUE *g)
{
return ((BOOL) (g->front == g->back));
}
Example 6.4:
/* Receive: Queue g
Process: Test whether queue g is full or not
Return: The value TRUE (1) if the queue is full, the
value FALSE (0)if otherwise. */
BOOL FullQ (QUEUE *g)
{
return ((BOOL) (((g->back + 1) % QLimit) == g->front));
}
(a) Assigning the item that is inserted into the element with a back index value
(because the item is inserted from the back); and
(b) Then, the back value is increased. The value that is added needs to be a
modulo with QLimit so that the back value is always in the region of 0 and
QLimit - 1.
Example 6.5:
Example 6.6:
/* Receive: Queue g
Process: Remove data item from front of queue
Return: The data item is removed and the queue is
updated */
void Dequeue(ELEMENTTYPE *item, QUEUE *g)
{
if (EmptyQ(g))
printf(“ERROR: EMPTY QUEUE \n”);
else
{
*item = g->element[g->front];
g->front = (g->front + 1) % QLimit;
}
}
SELF-CHECK 6.2
1. Inserting an item into a queue when the queue is not full is called
an X operation and deletion of an item from the queue when the
queue is not empty is called a Y operation. Find out what is X and
what is Y.
A. X is insert, Y is delete
B. X is push, Y is pop
C. X is enqueue, Y is dequeue
A. Front
B. Back
C. Top
ACTIVITY 6.1
Go to https://goo.gl/J2k3d4, copy and run the programs given. Do
you get the same outputs? Using the same program, insert A, B, C, D
and E as the input. What is the result now?
Queue is an ordered list, where elements can be added at only one point (which
is called the back or end) and an item can only be removed from the other end
(which is called the front or head).
The queue basic operations are create queue, test empty queue, test full queue,
delete queue and enter queue.
There are two variables that identify the position of the front and the back of
the queue. These variables are called front and back. A queue is empty when
both the values of front and back variables are equal.
When adding items into the queue, the variable back is increased by 1. When
removing items from the queue, the variable front is increased by 1.
It is important to test for empty queue when deleting items, and test for full
queue when inserting items.
Back Front
Circular queue Increase
Delete queue (dequeue) Queues
Enter queue (enqueue) Test empty queue
First-in-first-out (FIFO) Test full queue
INTRODUCTION
In the previous topics, we have been introduced to data structures (lists, stacks and
queues) and algorithms. Both of these concepts go hand in hand and have a close
relationship. It can perhaps be illustrated as follows (see Figure 7.1).
There are also several algorithms that are different in efficiency for the same data
structure. In this case, consideration only lies in the selection of the most efficient
algorithm from the perspective of processing time. One of the most important
algorithms in programming is sorting. Therefore, this topic will discuss several
algorithms for sorting.
Did you notice that in our daily lives, we rarely use the word sorting? However,
we often perform the procedure. A clear example would be when a teacher would
write a studentÊs attendance list, where he would arrange the name of the students
beginning with the letter „A‰ and followed by the next letter, as illustrated in
Figure 7.2. The process of arranging according to a given rule is called sorting.
Throughout this topic, we will focus on four types of sorting namely simple
selection sort, linear insertion sort, quick sort and bubble sort (see Figure 7.3).
We will give attention to each type of sorting from the aspects of:
Simple selection sort chooses the smallest or biggest in a list and place it on
the most appropriate position.
Consider the following element list in Figure 7.4, that is to be sorted in ascending
order. There are seven numbers that are labelled with the respective position.
So how do we sort this list using simple selection sort? Let us look at Table 7.1
which shows you the answer.
Step Action
1 Compare Element 1 with Elements 2 to 7. Find the smallest element compared to
1. Element 3 is the smallest. Therefore, we change the position of Element 1 with
Element 3.
2 Compare Element 2 with Elements 3 to 7. Element 2 is the smallest, so no
changing of positions of elements is necessary.
3 Compare Element 3 with Elements 4 to 7. Element 5 has the smallest value, so an
exchange of positions between Element 3 and Element 5 occurs.
4 Compare Element 4 with Elements 5 to 7. Element 6 has the smallest value, so an
exchange of positions between Element 4 and Element 6 occurs.
5 Compare Element 5 with Elements 6 to 7. Element 5 is the smallest, so no
changing of positions of elements is necessary.
6 Compare Element 6 with Element 7. Element 7 has the smallest value, so an
exchange of positions between Element 6 and Element 7 occurs.
7 The result that is obtained is a sorted list of numbers.
Remember, simple selection sort is a selection sort because the process of selection
and exchanging of positions occurs at the same time.
Example 7.1:
The SelectSort function will receive an array arr and the size of the array n.
The function starts with assigning the minIndex to the first index value which
is 0.
There is a loop in the main loop, which is essentially to find the smallest value in
the elements from index 1 to the end of the list. The first if statement is to compare
the values of the elements, and the second if statement is to swap or exchange
them. As you can see in Figure 7.5, the list is sorted in Step 7.
Finding the next lowest element (2nd smallest) requires scanning the remaining
n 1 elements (n 2 comparisons) and so on. Therefore, the total number of
comparisons are given in the form of:
n n 1
(n – 1) + (n – 2) + . . . + 2 + 1 =
2
Let us use the elements in Figure 7.4 to show you the calculations:
77 1
= 21
2
or
(n 1) + (n 2) + (n 3) + (n 4) + (n 5) + (n 6)
= 6 + 5 + 4 + 3 + 2 + 1
= 21
(b) Know when and how to change the position of the element.
SELF-CHECK 7.1
How does the simple selection sorting method work if the data is to be
sorted in ascending order?
A. The smallest data in the list is placed at the front of the list.
B. The smallest data in the list is placed at the back of the list.
C. The smallest data in the list is placed in the middle of the list.
In the linear insertion sort, the list is divided into two parts: sorted and
unsorted. In each pass, the first element of the unsorted sublist is transferred
to the sorted sublist by inserting it at the appropriate place.
If we have a list of n elements, it will take at most n 1 passes to sort the data.
This concept is shown in Figure 7.6.
In other words, the method of linear insertion sort is the method of inserting a new
element in a sorted list. The element is inserted at the position that is suitable and
the final list is the sorted list.
Let us use the data elements in Figure 7.6 with the assumption that the first element
is the element for a sorted sublist. In the first loop, the first two elements are
compared. It is found that the second element is smaller than the first element, so
we shift the first element to the right to insert the second element. Now (Step 2 in
Figure 7.6), the second element has become the first element of the sorted sub-list.
We then compare the third element with the elements in the sublist. Since Element
3 is the smallest compared to the elements of the sublist, we then shift number 33
and 67 in order to insert number 21. The process repeats where small numbers are
inserted in the correct positions and the elements to the right will be shifted to the
right.
Example 7.2:
The insertion of x[2] requires two comparisons, that is, comparison with x[1]
and x[0], inserting x[3] requires three comparisons and so on. The number of
comparisons that are required is:
n n 1
1 + 2 + 3 + ... + n =
2
SELF-CHECK 7.2
A. Insertion sort
B. Quick sort
C. Selection sort
(a) If there are one or less elements in the array to be sorted, return immediately;
(b) Pick an element in the array to serve as a „pivot‰ point (usually the left-most
element in the array is used);
(c) Split the array into two parts – One with elements larger than the pivot and
the other with elements smaller than the pivot; and
(d) Recursively repeat the algorithm for both halves of the original array.
Let us use the same data used in the sorting method previously (Figure 7.4). This
is to show to you the difference in efficiencies of sorting methods. The steps in
quick sort are listed as follows:
Step 1
(b) Since the index number for 50 is larger than the index number for 84, so an
exchange in positions occurs for those numbers. Or, we look at both the
arrows above. If the arrow pointing to the right and the arrow pointing to the
left still have not met, then repeated exchanging of positions occur between
the two values. This can be seen in Figure 7.10 as follows.
Step 2
(a) We repeat the search from the right of the list to find a value smaller than the
pivot and we find number 49. We search from the left of the list to find a value
that is larger than the pivot and we find number 84. At this time, we find that
the index value for 49 is smaller than the index value for number 84.
Therefore, we exchange number 49 with the pivot value. Or, when the arrow
pointing to the right and the arrow pointing to the left overlaps one another,
the smallest value will be exchanged with the pivot value. This step is
illustrated in Figure 7.11.
(b) Now, the pivot value is between the left sublist and right sublist. Look at
Figure 7.12 for evidence.
Step 3
(a) We repeat Step 1 for the left sublist, that is, we determine the pivot value. We
choose the first number again as the pivot, which is number 49. We search
from the right to find a smaller value than the pivot value and we find
number 21. We search from the left to find a larger value than the pivot value
and we find number 50. Figure 7.13 shows this execution.
(b) Since the index value for number 49 is smaller than the index value for 21, so
we exchange number 21 with the pivot value. Look at Figure 7.14 for
evidence of this execution.
(c) Repeat the search operation and exchanges for the left sublist for the pivot
value of 67, also for the left and right sublists for the pivot value of 49. Look
at the following Figure 7.15 which explains all the steps for quick sort fully.
Example 7.3:
Take note that the partition function is for arranging elements so that the values
smaller than the pivot will be on the list and values that are larger than the pivot
will be on the right (see Example 7.4).
Example 7.4:
Bubble sort is a sorting algorithm that will compare adjacent pair of data in a
list, swapping them if they are out of order, continuing down the list until all
the adjacent pairs are in order.
The whole process is repeated again in the next loop. For a list of 10 items, bubble
sort will go through the list nine times (n 1) for it to be sorted.
Example 7.5:
In order to understand the algorithm in Example 7.5, let us consider the following
number list (see Figure 7.16).
n n 1
n + n 1 + n 2 + ... + 1 =
2
ACTIVITY 7.1
Show each of the steps on how the list {25, 57, 48, 37, 12} is sorted using
the function.
Simple selection sort is the easiest method of sorting we will choose the
smallest or biggest in the list and place it at the most appropriate position.
In linear insertion sort, the list is divided into two parts: sorted and unsorted.
In each pass, the first element of the unsorted sublist is transferred to the sorted
sublist by inserting it at the appropriate place.
Bubble sort is a sorting algorithm that will compare adjacent pair of data in a
list, swapping them if they are out of order, continuing down the list until all
the adjacent pairs are in order. The whole process is repeated again in the next
loop.
In general, all sorting procedures will produce the same results and the only
thing that differentiates them is from the aspect of efficiency.
INTRODUCTION
There are a variety of applications that require searching a key value in a given
data structure. In fact, searching is a costly computational activity. Therefore, a
number of efficient algorithms have been developed for searching.
In this topic, you will be introduced to two techniques that can be used in data
search namely sequential search technique and binary search technique. So are you
ready to do some „searching‰? Let us continue with the lesson.
One of the fields is nominated as the record key, which is a unique signature for
that record, for instance, your IC or MyKad 12-digit numbers.
The relationship between the key and record can be simple or complex. The key
found in our record is named as the internal key. Meanwhile, a key that is kept
outside of the record is called external key. For the external key cases, we use
pointers to refer to the matching record. Did you know that there are two types of
keys? These are further explained in Table 8.1.
Search algorithm is the algorithm that receives an argument a and tries to find
the record key that has the value a.
That algorithm will either return the entire record or a pointer value that points to
the required record, if the search was successful to begin with. If not, the null value
will be returned.
There are also cases where, if the search is not successful, the key value that is
being searched will be inserted into the data structure and this is called an insertion
operation.
For successful searches, we call this an access operation. For the operation to delete
a key value, it is called deletion operation. You should already know these terms
from the previous topics. Do you still remember?
There are several techniques that can be used in data search. Among them are:
(c) Binary tree search technique (which will be covered in the next subtopic).
Our next subtopics will focus on sequential search and binary search. Let us
continue the lesson.
In addition, the sequential searching method works well with small and unsorted
arrays. Searching begins with the first record and is done sequentially until the
data is found. Let us look at Example 8.1 which demonstrates this type of search
technique.
Example 8.1:
Let say A[1], A[2], ... , A[n] are array elements that consist of n keys Ki;
(1 <= i <= n) that are different.
Keys K1 K2 K3 K4 ... Kn
Array Elements A[1] A[2] A[3] A[4] ... A[n]
Solution:
The K key is the argument that is searched (key search). The following algorithm
will find the smallest integer i (array index) where Ki is its key.
For i = 0 until key number n
Start for
If search value = K(i)
Return the i value
End for
SELF-CHECK 8.1
Which of the following search techniques begins with the first record and
is done continuously until the data is found or end of records is reached?
A. Binary search
C. Sequential search
If the search value is less than the middle value of the array, the first half is
searched, otherwise the second half is searched. This continues until the search
value is equal to the middle value of the array or until the search value is not found
and the search is unsuccessful.
If not
Test whether the value of search key > value of middle
key
If YES
The search process continues for the element
above the middle key in the table
If NO
The search process continues for the elements
below the middle key in the table
Example 8.2:
Find search key 39 in the following array (see Figure 8.1).
Solution:
Number of data = 10
Initial value = 0 (array index value starts from 0)
Final value = 9
(a) Round 1
We find the middle value first.
Middle value = (Initial + Final)/2
= (0 + 9)/2
= 9/2
=4
Since the search key value (39) is bigger than the middle key (20), the focus
of the search will only be on the right of the middle key, which is location
k(5) until k(9).
(b) Round 2
The result of Round 1 found that the focus location for the search is on the
right of the middle key, which is the location that goes towards the end.
Therefore, we need to change the initial value to the location to the right of
the middle value, which is the middle value plus 1.
In this round, we found that the value of the search key (39) is less than
the middle key (40). Therefore, the focus of the search will only be to the
left of that middle key, which is location k(5) and k(6).
You need to give your full attention to follow this algorithm. Let us continue
to the next round.
(c) Round 3
The result of Round 2 has revealed that the focus of the search is to the left of
the middle key, which is the location leading towards the start value.
Therefore, we need to change the end value of the location to the left of the
middle value, which is the middle value minus one.
In this round, we found that the search key value (39) is bigger than the
middle key (25), so the focus of the search is only to the right of the middle
key, which is at location k(6).
(d) Round 4
From the result of Round 3, the search location focus is now on the right of
the middle key, which is the location leading towards the end. Here, we need
to change the initial value of the location to the right of the middle key value,
which is the middle value plus one.
SELF-CHECK 8.2
A. Sequential search starts from the first element to the last element in
the array, while binary search starts from the middle of the array.
B. Sequential search starts from the middle of the array, while binary
search starts from the beginning of the array.
C. Both sequential search and binary search starts from the middle of
the array and searches each element till the data is found.
ACTIVITY 8.1
4 7 8 10 4 21 22 6 62 77 81 91
91 81 77 62 36 22 21 14 10 8 7 4
The search algorithm is the algorithm that receives an argument a and tries to
find the record key that has the value a.
The basic search techniques that can be applied in solving related problems
include sequential search, binary search and binary tree search (this will be
covered in Topic 9).
Sequential search is applied to records that are arranged using arrays or linked
lists. It works well with small and unsorted arrays.
Binary search can only be used for sorted arrays, but it is fast compared to
sequential search. In binary search, we need to find the position for a K search
key in a table.
INTRODUCTION
In the previous topics, we looked at linear data structures, which include lists,
linked lists, stacks and queues. Now in this topic, we shall look at the tree data
structure, which is different from the other data structures previously discussed.
Before you proceed further into this topic, make sure that you really understand
the linked list topic and the concept of pointer that have been learnt previously.
Did you know that elements in a tree are arranged in a hierarchical structure? What
else do we need to know about tree? Let us proceed with the lesson.
Tree is a structure that consists of nodes that are linked with directed pointers.
Take note that there are several important terms that you need to know before the
tree structure can be explained. A node without a pointer pointing at it is called
root. In Figure 9.1, M is the root node. A node that has a pointer to the left or/and
to the right are the parent node, while the nodes that is being referred to is the
child nodes. In Figure 9.1, node O and W are the child nodes for node P, while
node P is the parent node for nodes O and W.
Nodes with the same parent are called siblings. Therefore, nodes O and W in
Figure 9.1 are siblings. Nodes that do not have any child nodes are called leaves.
So, nodes G, O and W in Figure 9.1 are leaves. The directed pointer that links one
node to another is called a branch. The depth of the tree refers to the level value
that is the highest in the tree. For Figure 9.1, the depth of the tree is 3. There are
two types of trees that are important:
These two types of trees are further explained in the next subtopics.
A binary tree is a tree in which a node can only have a maximum of two
subtrees.
In other words, a node can be zero, one or two subtrees. These subtrees are
designated as the left subtree and right subtree. Figure 9.3 shows you an example
of a binary tree.
We can also say that a binary tree is a tree structure where each node has not more
than two child nodes. The example given in Figure 9.3 shows a binary tree that has
a character in each node. The maximum number of nodes in a binary tree with a
depth of K is 2k 1 for K 1. For Figure 9.3, the maximum number of nodes is
23 1, which is 8 1 = 7.
Example 9.1:
typedef struct BinaryTree {
int data;
struct BinaryTree *left_child;
struct BinaryTree *right_child;
} BINARY_TREE;
BINARY_TREE *tree;
Each node has two pointers or left_child and right_child and each will
point to a left subtree and a right subtree. The data in the node can contain any
format of data, like integers, floats, characters or strings.
A binary search tree is a more specific binary tree, where the value of a node
is bigger than the value of its left child node, but smaller than the value of its
right child node.
Example 9.2:
typedef struct BinaryTree {
int data;
struct BinaryTree *left_child;
struct BinaryTree *right_child;
} BINARY_TREE;
BINARY_TREE *BST;
What are the operations that can be performed on BST? The operations that can be
performed on BST are:
The following subtopics will discuss these operations. The programming codes
that are used in the subtopics have been modified from the book Struktur Data
Menggunakan C by Marini Abu Bakar (1998).
How do we achieve this? Here, the user should give the values to the data content
for each node in the BST. The following is the function that can achieve this (see
Example 9.3).
Example 9.3:
3. If not
3.1 Insert data or item at a suitable position (as a
left or right child)
BST can be built by calling the InsertBST function repeatedly and inserting the
node in the BST by starting with the root that holds the NULL value. This function
is similar to the SearchBST, with an added pointer that points to the parent node
while it is executed. Consider the BST in Figure 9.6.
Let us suppose that we want to insert the character „A‰ in the BST. To insert this
data, we will begin with the root node that is referred to by pointer p 2 and compare
A with the data content in the root node (see Figure 9.7).
Since A < M, we then move down the left subtree. The pointer b points to the
parent node of the current node being evaluated (see Figure 9.8).
Then, we compare A with C. Since A < C then, we move to the left subtree for C,
which is NULL. The NULL value means that A does not exist in the BST and needs
to be inserted as the left child of node C that is pointed to by b (see Figure 9.9).
Example 9.4:
t = *p2;
b = NULL;
b = t;
if (item < t->data)
t = t->left_child;
else t = t->right_child;
}
if (t != NULL)
printf(“\nError: Data already exists”);
else {
t = NewBSTNode(item);
if (b == NULL)
*p2 = t;
else {
if (item < b->data)
b->left_child = t;
else b->right_child = t;
}
}
}
This function will receive a BST and the data value that needs to be inserted. Then,
the BST will be searched to determine the insertion point and whether the node
exists. If the node with the item exists, an error message will be displayed. If
otherwise, the node will be inserted in a suitable position.
This function will call the NewBSTNode function, to reserve the node space
dynamically by using the library function malloc(). This function will receive
the data for the node and will return a pointer that refers to the node (see
Example 9.5).
Example 9.5:
if (p == NULL)
printf(“ERROR”);
else {
p->data = item;
p->left_child = NULL;
p->right_child = NULL;
}
return p;
}
The sequence of nodes inserted into the BST will determine the shape of the BST,
even though the data content is the same. For example, the BST that is produced
when the data with 3, 4, 5, 6, 1, 2 is inserted is completely different from the BST
produced when data with 5, 1, 3, 4, 2, 6 is inserted, because the succession of data
insertion has changed.
SELF-CHECK 9.1
Where is the insertion of a new data item „R‰ into this binary search tree
(BST)?
A. To the left of H
B. To the right of P
C. To the left of T
If the BST in Figure 9.10 is traversed in this way, what will be the output?
5 8 9 10 16 18 20
The function to perform this in-order traversal is as follows (see Example 9.6):
Example 9.6:
Let us use the example in Figure 9.10. If the BST in Figure 9.10 is traversed
using the pre-order traversal, the output that would be produced is:
10 8 5 9 18 16 20
Example 9.7:
Let us use again Figure 9.10 as an example. If the BST in Figure 9.10 is
traversed using the post-order traversal, the output produced would be:
5 9 8 16 20 18 10
Example 9.8:
2. Free p
After deleting node „i‰, the new BST is shown in Figure 9.12.
2. Free x
a b c i k m n o p s
2. Assign the contents that are pointed to by pointer p, which is node „n‰
with the value stored in node „o‰; and
3. Delete node „o‰ (node „o‰ has no child according to Condition 1).
SELF-CHECK 9.2
Consider the following binary search tree:
A. 20
B. 30
C. 70
Expression trees are binary trees that are used to represent arithmetic
expressions.
The operator is the middle node (root) and operand1 and operand2 are the
left child and right child nodes, respectively. When you are given an arithmetic
expression, add the parentheses to confirm the precedence execution.
The operator that has the lowest precedence is chosen to become the root node of
the expression tree.
A / (B * C) + D
Add parentheses.
((A / (B * C)) + D)
Can you see that at the operator node, the left and right parentheses are added? If
you traverse through the expression tree in order, you will get an infix notation.
ACTIVITY 9.1
(a) A*(B+C)+D
(b) A+B*C–D*E
Trees are structures that consist of nodes that are linked with directed pointers.
A node that has a pointer to the left or/and to the right are the parent node,
while the node that is being referred to is the child node.
Nodes that do not have any child nodes are called leaves.
The directed pointer that link one node to another is called a branch.
The depth of the tree refers to the level value that is the highest in the tree.
There are two types of trees: binary tree and binary search tree (BST).
A binary tree is a tree in which a node can only have a maximum of two
subtrees. In other words, a node can be zero, one or two subtrees.
A binary tree can be represented using a linked structure that is very similar to
a linked list. The only difference is that each binary node has two pointers that
each points to its left and right child nodes.
A binary search tree is a more specific binary tree, where the value of a node is
bigger than the value of its left child node, but smaller than the value of its
right child node.
Expression trees are binary trees that are used to represent arithmetic
expressions.
Marini Abu Bakar. (1998). Struktur data menggunakan C. Petaling Jaya, Malaysia:
Prentice Hall.
INTRODUCTION
Did you know that graphs are natural models used to represent a relationship
among data objects? We often need to represent such relationship among data
objects while dealing with problems in computer science, engineering and many
other disciplines.
Therefore, the study of graphs as one of the basic data structures is important. So
are you ready to discover more? Let us start the lesson.
Take note that there are no limitations on the number of vertices in a graph or the
number of connections a vertex can have to another vertex. The number of vertices
is represented by V , whereas number of edges is denoted by E . An edge e in
a graph that is associated with a pair of vertices v and w, e = (v, w ), are declared
as to be incident on e. Two vertices v and w in a graph G, are said to be adjacent
vertices (or neighbours), if (v, w ) is an edge in G.
For any two vertices, they are connected if there is a path between vertices. A graph
is considered as connected if there is a path from any vertex to another vertex. A
directed graph is considered as strongly connected if there is a path from every
vertex to every other vertex in the digraph. A directed graph is weakly connected
if there are at least two vertices that are not connected. A graph is called a disjoint
graph if it is not connected.
The degree of a vertex is the number of lines (or edges) incident to it. It is denoted
by deg(v ). The outdegree of a vertex in directed graph is the number of arcs
leaving the vertex. Meanwhile, the indegree of a vertex in directed graph is the
number of arcs entering the vertex. However, a loop at a vertex will contribute
twice, which is of degree two.
Term Definition
Loop An edge incident on a single vertex.
Parallel edge Edges e 2 and e 3 in graph G that are associated with the vertex v 2
and v 3 (see Figure 10.3).
Isolated vertex A vertex that is not incident on and edges.
Directed edges are indicated by arrows. In a digraph, there is a line with direction
(arrow head) to its successor.
The flow in digraph must follow the indicated direction illustrated by the arcs
between two vertices.
However, v 1 and v 3 are not adjacent vertices. In an undirected graph, a path may
traverse in any direction. Do you know, what is a path?
A cycle is a path which consists of at least three vertices that starts and ends
with the same vertex.
Keep in mind that a loop is a special case of a cycle, where the cycle begins and
ends with the same vertex.
The outdegree of a vertex (v 4) in a digraph (refer to Figure 10.3) is two and the
indegree of a vertex (v 4) in a digraph is one. In an undirected graph (Figure 10.4),
the degree of a vertex (v 4) is three.
Remember, there must be an edge between every pair of distinct vertices. The
following Figure 10.6 denotes K 3, K 4 and K 5 respectively.
10.2.5 Cycle
Lastly, let us look at cycle graph.
As shown in Figure 10.7, C 4 consists of four vertices and edges, while C 6 contains
six vertices and edges.
SELF-CHECK 10.1
SELF-CHECK 10.2
Figure 10.15 illustrates the breadth first search first by visiting the root, v, then
u 1, u 2, u 3, y 1 and y 2.
Algorithm 1:
Breadth first search
1 if (graph->first null)
2 return
// First set all processed flags to not processed
// Flag: 0—Not processed, 1—Enqueued, 2—Processes
3 queue = createQueue
4 walkPtr = graph->first
loop (walkPtr not null)
5 walkPtr->processed = 0)
6 walkPtr = walkPtr->nextVertex
// Process each vertex in vertex list
7 walkPtr = graph->first
8 loop (walkerPtr not null)
9 if (walkPtr->processed < 2)
if (walkPtr->processed < 1)
10 // Enqueue and set process flag to queued (1)
11 Enqueue (queue, walkPtr)
12 walkPtr->processed = 1
// Now, process descendents of vertex at queue front
13 loop (not emptyQueue (queue))
14 dequeue(queue, vertexPtr)
15 // Process vertex and flag as processed
16 process(vertexPtr)
17 vertexPtr->processed = 2
// Enqueue all vertices from adjacent list
18 arcPtr = vertexPtr->arc
19 loop (arcPtr not null)
20 toPtr = arcPtr->destination
21 if (toPtr->processed zero)
22 enqueue (queue, toPtr)
23 toPtr->processed = 1
24 arcPtr = arcPtr->nextArc
25 walkPtr = walkPtr->nextVertex
26 destroyQueue(queue)
27 return
end breadthFirst
Then, we shall next visit u 1 and keep u 2, u 3, ..., uk waiting. After visiting u1, we
traverse all vertices to which it is adjacent, before returning to u 2, u 3, ..., uk.
Algorithm 2:
Depth first search (using recursive approach)
Algorithm depthFirstSearch
Visit (v)
For all vertices u adjacent to v do
Traverse (u, Visit)
Figure 10.16 illustrates the depth first search first by visiting the root, v, then u 1,
y 1, y 2, u 2 and u 3.
Algorithm 3:
Depth first search (using stack approach)
1 if (empty graph)
2 return
// Set processed flags to not proceed
3 walkPtr = graph->first
4 loop(walkPtr)
5 walkPtr->processed = 0
6 walkPtr = walkPtr->nextVertex
// Process every vertex in the list
7 stack = createStack()
8 walkPtr = graph->first
9 loop (walkPtr not null)
10 if (walkPtr->processed < 2)
11 if (walkPtr->processed < 1)
// Push and set flag to stack
12 pushStack (stack, walkPtr)
13 walkPtr->processed = 1
// Process vertex at stack top
14 loop (not empty stack)
15 vertexPtr = popStack(stack)
16 process(vertexPtr->dataPtr)
17 vertexPtr->processed = 2
/* Push all vertices from adjacent
list */
18 arcWalker = vertexPtr->arc
19 loop(arcWalkPtr not null)
20 vertToPtr = arcWalkPtr->destination
21 if (vertToPtr->processed is 0)
22 pushStack(stack, vertToPtr)
23 vertToPtr->processed = 1
24 arcWalkPtr = arWalkPtr->nextArc
25 walkPtr = walkPtr->nextVertex
26 destroyStack(stack)
27 return
end depthFirst
ACTIVITY 10.1
1. Select the most appropriate term for a graph with numbers labelled
on the edges.
A. Directed graph
B. Simple graph
C. Weighted graph
2. The breadth first search algorithm has been implemented using the
queue data structure. One possible order of visiting the nodes in
Figure 10.17 is:
A. MNOPQR
B. NQMPOR
C. QMNPRO
Graphs are natural models used to represent a relationship among data objects.
A graph is a collection of vertices (or nodes) and the connections between them.
– Simple graph;
– Weighted graph;
– Cycle.
There are three ways to represent graphs, namely adjacency list, adjacency
matrix and incidence matrix.
The six basic operations of graph are add vertex, delete vertex, add edge, delete
edge, find vertex and traverse graph.
Breadth first search processes all the vertices on a certain level, before going to
the next level.
Depth first search will visit all the vertices in the graph vy processing a vertex
and is descendants before processing the adjacent vertex.
Gilberg, R. F., & Forouzan, B. (1998). Data structures: A pseudocode approach with
C. Boston, MA: PWS.
OR
Thank you.