Basic Data Structures in C Programming
Basic Data Structures in C Programming
2
Dereferencing a pointer
• Pointers have a type (int *ptr), the type is used when dereferencing the
pointer
• Dereferencing a pointer means read the contain of the address stored
in the pointer
• Example:
– int a = 8; int b; int *ptr;
– ptr = &a; // store the address of ‘a’ in the pointer ptr
– b = *ptr; //store in b the value of the address stored in ptr. This is
dereferencing the pointer ptr
• The type of the pointer ‘ptr’ indicates how many bytes have to be read
starting at the address in ptr.
– Here the type of the ptr is int, which means 4 bytes must be read when
dereferencing ptr
3
Variables and types
• Programs have variables. In a
This program (basic1-1.c) assigned the
number 16 to the 4 memory cells starting compiled/linked/loaded code,
at addr represented by “myvar” variable names are transformed
into memory addresses. CPU
only know memory addresses
#include <stdio.h> • Variables have types. Variables
#include <stdlib.h> are place holder, storage. The
type of a variable indicates the
int main(){ amount of storage used by the
int myvar; variable.
• Examples:
myvar = 16;
– int 4 bytes 5
– double 8 bytes 6.75
printf("addr myvar %8u, and value stored at
– char 1 bytes ‘b’
the addr of myvar %d\n",&myvar,myvar);
}
4
Pointers
5
Pointer initialization
6
Computer memory
• Computer memory is made of a
long sequence of memory cells,
each 8 bits (one byte) long
• Associated with each memory
cell is 8address
• Variable names in a program are
addresses, i.e. the cell where
the data is stored
– int myvar;
• The type of a variable “int”
defines the number of
consecutive memory cells used
to store the data
– int means 4 consecutive cells are
reserved to store the data of myvar
7
Example of program (pointer2.c)
#include <stdio.h>
int main() {
int *ptr, q; //declarations
q = 50;
ptr = &q; //initialization of pointer ptr
printf(“print addr of pointer ptr %8u\n\n",&ptr);
printf(“print addr of q %8u\n\n",&q);
printf(“print addr of q stored in ptr %8u\n\n",ptr);
printf(“print the value of the addr stored in ptr%d\n",*ptr); //dereferencing ptr
}
8
Rules of pointers
• A pointer variable can be assigned the address of
another variable
– int v; int * ptr; ptr = &v;
• A pointer variable can be assigned the address of
another pointer variable
– int *ptr1, **ptr2; ptr2 = &ptr1; (pointer3.c)
• A pointer variable can be initialized with NULL or 0 value
– int *ptr = NULL; int *ptr1 = 0;
9
ARRAYS
10
Declaring an one-dimensional array
To declare an array, we need to specify its data type, the array’s identifier and the
size:
Example:
int A[5];
declare an array A having 5 elements of integer type (4 bytes for each element)
int *A[5];
declare an array of pointers to integers. Each entry in the array is a pointer.
The declaration of array returns an address, the address of the first byte of the array
• int *ptr, A[5];
• ptr = A;
11
Initializing a one-dimensional array
• We can initialize fixed-length array elements when we
define an array.
• If we initialize fewer values than the length of the array, C
assigns zeroes to the remaining elements.
12
Accessing Elements
To access an array’s element, an integer is provided which is
the index of the element to access.
13
Example
In a C program, the index returns the address of an element in an 1D array:
#include <stdio.h>
int main()
{ int A[ ] = {5, 10, 12, 15, 4};
int rows=5;
/* print the address of 1D array using pointer */
int *ptr = A;
printf("Address Contents\n");
for (int i=0; i < rows; i++)
printf("%8u %5d\n", ptr+i, *(ptr+i));
}
(sizeof(int)=4)
ptr+i : address of element A[i]
*(ptr+i) : content of element A[i]
start_address=6487536
Arrays and pointers
15
Declaring two-dimensional array
• How to declare:
<element-type> <arrayName> [size1][size2];
Example: double a[3][4];
may be shown as a table
Example 3 x 4 array:
abcd
efgh
i jkl
Convert into 1D array Y by collecting elements by columns.
Within a column elements are collected from top to bottom.
Columns are collected from left to right.
Thus, we get Y[ ] =
{a, e, i, b, f, j, c, g, k, d, h, l}
Row- and Column-Major Mappings
2D array: r rows, c columns
Example: int a[3][6]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17};
a[0][0]=0 a[0][1]=1 a[0][2]=2 a[0][3]=3 a[0][4]=4 a[0][5]=5
a[1][0]=6 a[1][1]=7 a[1][2]=8 a[1][3]=9 a[1][4]=10 a[1][5]=11
a[2][0]=12 a[2][1]=13 a[2][2]=14 a[2][3]=15 a[2][4]=16 a[2][5]=17
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
0 6 12 1 7 13 2 8 14 3 9 15 4 10 16 5 11 17
11.
25
Contents
2.1. Array
2.2. Record
2.3. Linked List
2.4. Stack
2.5. Queue
26
27
28
29
30
Record
• A record is a type of data structure like arrays
• A record have different fields each with its own type and name
• In C, the declaration of a record starts with the reserved word “struct”
• Then the type and the name of the fields in the record are defined
• Finally, the record is given a name
• To assign a value to a field of a record, we must first name the record and
then the field
int main(){
struct {
int num;
int deno;
}fraction;
[Link] = 13;
[Link] = 17;
}
31
2.2. More examples of records
• These define two objects of type record
• Example:
struct {
int numerator;
int denominator;
} fraction;
[Link] = 13;
[Link] = 17;
32
An array of records
• The example below declare an array of records
int main(){
struct {
int id;
char* name;
char grade;
}student[3];
student[0].id = 2021;
student[1].name = "Big-X";
student[2].grade= 'A’;
printf("id %d, name %s, grade %c\n",student[0].id, student[1].name, student[2].grade);
}
33
Records as types
• The record type can be used by the programmer to define its own types
• In this case, in C, the declaration of a record type starts by “typedef struct”
• Below, student is a type, not an object
• Minh is declared as pointer to an object of type student
• This pointer will store the addr of the first byte of a record of type student
• malloc allocate memory cells for a data structure of type student
• Since Minh is a pointer, we must dereference the fields of the object to which it points using “->”
• Minh->grade means “take the address stored in the pointer Minh (not the address of the pointer), then add
to this address the offset of grade
int main(){
typedef struct {
int id;
char* name;
char grade;
}student;
student* Minh;
Minh = (student*)malloc(sizeof(student));
Minh->id = 2021;
Minh->name = "Minh";
Minh->grade= 'A';
} 34
Contents
2.1. Array
2.2. Record
2.3. Linked List
2.4. Stack
2.5. Queue
35
Linked lists
36
37
38
39
2.3. Three types of linked list
• Singly linked list
10 8 20
head
10 8 20
head
head
10 8 20
Nodes in linked lists
• A node is a type
• The type node is declared through a record, a typedef struct
• Then pointers of type node are declared, such as “node *head”, here head is a
pointer
• Then memory for an object of type node is allocated through the malloc instruction
• This object is only known to the programmer through a pointer
• The pointer is given the address of the first byte of the object by a malloc instruction
int main(){
typedef struct {
int data;
struct node* next;
}node;
node* head;
head = (node*)malloc(sizeof(node));
}
41
Create a second record of type node
• Here a second object of type node is created
– A pointer of type node is declared: “secondNode”
– Then malloc allocates memory for this second object, and returns the
address of the first byte of the object to the pointer secondNode
int main(){
typedef struct {
int data;
struct node* next;
}node;
node* head;
node* secondNode;
head = (node*)malloc(sizeof(node));
secondNode = (node*)malloc(sizeof(node));
42
Create a link list of 2 nodes
node* head;
node* secondNode;
head = (node*)malloc(sizeof(node));
secondNode = (node*)malloc(sizeof(node));
head->data = 1;
head->next = secondNode;
secondNode->data = 2;
secondNode->next = NULL;
}
43
Singly Linked list
• A singly linked list is a sequences of nodes, each node contains 2 parts: data and
reference (address) to the next node:
10 8 20
head
45
Elements of singly linked lists
• head: store the address of the first node in the linked list
• NULL: value of the pointer of the last node in the linked list
• cur: a pointer that stores the address of the current node
cur
head (or root)
NULL
46
Operations on singly linked Lists
• Traverse the singly linked list
• Insert a node into the singly linked list
• Delete a node from the singly linked list
• Search data in the singly linked list
Operations on singly linked Lists
• Traverse the singly linked list
• Insert a node into the singly linked list
• Delete a node from the singly linked list
• Search data in the singly linked list
Traversing a singly linked list
for ( cur = head; cur != NULL; cur = cur->next )
print(Data_Of_Current_Node( cur->data ));
cur
head
NULL
cur
head NULL
49
Operations on singly linked lists
• Traverse the singly linked list
• Insert a node into the singly linked list
• Delete a node from the singly linked list
• Search data in the singly linked list
Operations on singly linked list: Insertion
• 4 positions in a singly linked list where a node can be inserted :
– At the beginning of the list
– After the position pointed by the pointer cur
– Before the position pointed by the pointer cur
– At the end of the list
cur
head
…
Insertion on singly linked list
Insert a new node:
• At the beginning of the list, asymptotic cost is O(1)
…
node *Insert_ToHead(node *head, NodeType X)
{ node *new_node;
new_node = (node *) malloc(sizeof(node));
new_node->data = X;
new_nod new_node->next = head;
head=new_node;
e return head;
}
52
Insertion on singly linked list
• Insert a new node after the node pointed by the pointer cur, asymptotic cost
is O(1)
<create a new node new_node>;
new_node ->next = cur->next;
cur->next = new_node;
Write a function to insert a node with data = X (having the type «NodeType » after the
node pointed by the pointer cur. The function returns the address of the new node:
node *Insert_After(node *cur, NodeType X)
{ node *new_node;
new_node = (node *) malloc(sizeof(node)); //(1)
new_node -> data = X; //(1)
new_node->next = cur->next; //(2)
cur->next = new_node; //(3)
return new_node;
}
53
Insertion on singly linked list
Insert a new node before the node pointed by the pointer cur, asymptotic
cost is O(n)
<create a new node new_node>;
prev->next = new_node;
new_node->next = cur;
prev cur
head
…
Insert a new 54
node:
Operations on singly linked list: Insertion
Insert a new node: head
• At the beginning O(1)
• After the node pointed by cur O(1)
• Before the node pointed by cur O(n)
…
• At the end of the list O(n)
<create a new node new_node>;
if (head == NULL) { /* list does not have any node yet*/
head = new_node;
}
else {
//move the pointer to the end of the list:
node *last =head;
while (last->next != NULL) last = last->next;
//Change the pointer next of the last node:
last->next = new_node;
}
node *Insert_ToLast(node *head, NodeType X)
{ node *new_node;
new_node = (node *) malloc(sizeof(node));
new_node->data = X;
if (head == NULL) head = new_node;
else
{
node *last;
last=head;
while (last->next != NULL) // move to the last node
last = last->next;
last->next = new_node;
}
return head; 55
}
Delete the first node of the list
• Delete the node del that is currently the first node of the list
O(1):
head = del->next;
free(del);
del
head NULL
56
Operations on singly linked Lists
• Traverse the singly linked list
• Insert a node into the singly linked list
• Delete a node from the singly linked list
• Search data in the singly linked list
Delete the node in the middle/end of the list
Delete node del that is currently the middle/last node of the list O(n):
<Determine the pointer prev pointed to the previous node of del>;
prev->next = del->next; //modify the link
free(del); //delete node del to free memory
prev del
head
prev del
head
NULL
58
Insertion on singly linked list
• Insert a new node after the node pointed by the pointer cur, asymptotic cost
is O(1):
<create a new node new_node>;
new_node->next = cur->next;
cur->next = new_node;
cur
head
new_nod 59
Delete the node at the middle/end of the list
Delete node del that is currently the middle/last node of the list O(n):
prev del
head
…
60
Delete the node at the middle/end of the list
Delete node del that is currently the middle/last node of the list:
prev del
head
…
61
Check whether the singly linked list is empty or not
Write the function int IsEmpty(node *head)
to check whether the singly linked list is empty or not (the pointer head pointed to the
first node of the list).
The function returns 1 if the list is empty; 0 otherwise
10 8 20
head
10 8 20
head
tail
Doubly linked list
• A Doubly Linked List (DLL) contains an extra pointer, typically called previous
pointer, together with next pointer and data which are there in singly linked list
tail
10 8 20
head tail
typdedef struct {
int number;
struct dllist *next;
struct dllist *prev;
} dllist;
dllist *head, *tail;
Delete a node pointed by a pointer p, O(1)
void Delete_Node (ddlist *p){
if (head == NULL) printf(”Empty list”);
else {
if (p==head) head = head->next; //Delete first element
else p->prev->next = p->next;
if (p->next!=NULL) p->next->prev = p->prev;
else tail = p->prev;
free(p);}
}
8 5 12 5
head p tail
69
Delete a node pointed by a pointer p
void Delete_Node (ddlist *p){
if (head == NULL) printf(”Empty list”);
else {
if (p==head) head = head->next; //Delete first element
else p->prev->next = p->next;
if (p->next!=NULL) p->next->prev = p->prev;
else tail = p->prev;
free(p);
}
}
8 5 12 5
head p tail
70
Insert a node after the node pointed by pointer p O(1)
void Insert_Node (NodeType X, ddlist *p){
if (head == NULL){ // List is empty
head =(ddlist*)malloc(sizeof(ddlist));
head->data = X;
head->prev =NULL;
head->next =NULL;
}
else{
ddlist *newNode;
newNode=(ddlist*)malloc(sizeof(ddlist));
newNode->data = X;
newNode->next = NULL;
newNode->next = p->next;
newNode->prev=p;
if (p->next!=NULL)
p->next->prev=newNode; 12
p->next = newNode;
}
}
8 5 5
71
p
Several variants of linked lists
• Some common variants of linked list:
– Circular Linked Lists
– Circular Doubly Linked Lists
– Linked Lists of Lists
• Basic operations on these variants are built similarly to the singly linked list
and the doubly linked list that we consider above.
Circular linked list
list
typedef struct {
NodeType data;
struct node * next; Store data
}next;
Circular Doubly Linked Lists
list
typedef struct {
NodeType data;
struct node * prev;
Store data
}node;
Contents
2.1. Array
2.2. Record
2.3. Linked List
2.4. Stack
2.5. Queue
75
What is a stack?
• A stack is a data structure that only allows items to be inserted and removed at one end
– We call this end the top of the stack
– The other end is called the bottom
• Access to other items in the stack is not allowed
• The last element to be added is the first to be removed (LIFO: Last In, First Out)
Operation on stack
• Push: the operation to place a new item at the top of the stack O(1)
• Pop: the operation to remove the next item from the top of the stack O(1)
M
C C C
R push(M) R item = pop() R
item = M
X X X
A A A
Implementing a Stack
• At least two different ways to implement a stack
– array
– linked list
• Which method to use depends on the application
– what advantages and disadvantages does each implementation have?
Stack: Array Implementation
• Implementing a stack using an array is fairly easy:
– The bottom of the stack is at S[0]
– The top of the stack is at S[numItems-1]
– push onto the stack at S[numItems]
– pop off of the stack at S[numItems-1]
…
S
0 1 2 numItems N
Stack: Array Implementation
Basic operations: typedef .... Item;
static Item *s;
• void STACKinit(int); static int maxSize;//maximum number of elements that the stack could have
static int numItems; //current number of elements on stack
• int STACKempty(); void STACKinit(int maxSize)
{
• void STACKpush(Item); s = (Item *) malloc(maxSize*sizeof(Item));
• numItems = 0;
Item STACKpop(); }
int STACKempty(){return numItems==0;}
int STACKfull() {return numItems==maxSize;}
…
S
0 1 2 numItems maxSize
maxSize: maximum number of elements in the array
Implementing a Stack: using linked list
• Store the items in the stack in a linked list
• The top of the stack is the head node, the bottom of the stack is the end
of the list
• push by adding to the front of the list O(1)
• pop by removing from the front of the list O(1)
4.1 2.4 8.9 2.3 NULL
top
3.3
3.3 4.1 2.4 8.9 2.3 NULL
4.1
typedef struct {
2.4 top float item;
struct StackNode *next;
8.9 } StackNode;
typedef struct {
2.3 StackNode *top;
}Stack;
Operations
1. Init:
Stack *StackConstruct();
2. Check empty:
int StackEmpty(Stack* s);
3. Check full:
int StackFull(Stack* s);
4. Insert a new item into stack (Push): insert a new item at the top of stack
int StackPush(Stack* s, float* item);
5. Remove an item from stack (Pop): remove and return the item at the top of stack:
float pop(Stack* s);
6. Print out all items of stack
void Disp(Stack* s);
Initialize stack
Stack *StackConstruct() {
Stack *s;
s = (Stack *)malloc(sizeof(Stack));
if (s == NULL) {
return NULL; // No memory
}
s->top = NULL;
return s;
}
85
Push
Need to do the following steps:
(1) Create new node: allocate memory and assign data for new node
(2) Link this new node to the top (head) node
(3) Assign this new node as top (head) node
int StackPush(Stack *s, float item) {
StackNode *node;
node = (StackNode *)malloc(sizeof(StackNode)); //(1)
if (node == NULL) {
StackFull(); return 1; // overflow: out of memory
}
node->item = item; //(1)
node->next = s->top; //(2)
s->top = node; //(3)
return 0;
}
Pop
1. Check whether the stack is empty
2. Memorize address of the current top (head) node
3. Memorize data of the current top (head) node
4. Update the top (head) node: the top (head) node now points to its next node
5. Free the old top (head) node
6. Return data of the old top (head) node
88
Queues
• What is a queue?
– A sequential data structure where homonegeous items are inserted only at one end
and removed at the other end.
no changes of order
Example: A line at the supermarket
• Operations on queues:
– Enqueue - Add an item to the queue
– Dequeue - Remove an item from the queue
• A queue is called a FIFO (First in-First out) data structure.
Add/ (Remove/Dequeue)
Enqueue
Back/Rear Front/Head
Queue specification
Definitions: (provided by the user)
– maxSize: Max number of items that might be on the queue
– ItemType: Data type of the items on the queue
Operations:
• Q = init(); initialize empty queue Q
• isEmpty(Q); returns "true“ if queue Q is empty
• isFull(Q); returns "true“ if Q is full, indicates that we already use the maximum memory for
queue; otherwise returns “false”
• frontQ(Q); returns the item that is in front (head) of queue Q or returns error if queue Q is
empty.
• enqueue(Q,x); inserts item x into the back (rear) of queue Q. If before making insertion, the
queue Q is full, then give the notification about that.
• x = dequeue(Q); deletes the element at the front (head) of the queue Q, then returns x which
is the data of this element. If the queue Q is empty before dequeue, then give the error notification.
• print(Q); gives the list of all elements in the queue Q in the order from the front to the back.
• sizeQ(Q); returns the number of elements currently in the queue Q.
Implementing a Queue
• Just like a stack, we can implementing a queue in two ways:
– Using an array
– Using a linked list
Implementing a Queue: using Array
• Using an array to implement a queue is significantly harder than using an array
to implement a stack.
– A stack: we add and remove at the same end,
– A queue: we add to one end and remove from the other.
QUEUE
Array implementation of queues
0 1 2 3 4 5 6
7
Q: 17 23 97 44
front = 0 rear = 3
front = 0 rear = 3
Initial queue: 17 23 97 44
front = 1 rear = 4
• An array is circular when the first element is next of the last element
95
Queues with circular arrays
• Elements were added to this queue in the order 11, 22, 33, 44, 55, and will be
removed in the same order
0 1 2 3 4 5 6
7
Q: 44 55 11 22 33
rear = 1 front = 5
• The Dequeue and Enqueue operations are now defined
as follow
– Dequeue(Q) : Dequeue Q[front]; front = (front + 1)
% n;
– Enqueue(Q,x): rear = (rear + 1) % n; Q[rear] = x;
96
Queue full or empty
• If the queue become completely full, it would look like this:
0 1 2 3 4 5 6
Q: 7
44 55 66 77 88 11 22 33
rear = 4 front = 5
• If we remove all eight items, making the queue completely
empty, it will look like this:
0 1 2 3 4 5 6
7
Q:
rear = 4 front = 5
• Can’t tell whether the queue is full or empty 97
Queues full or empty: solutions
• Solution 1: Keep an additional variable count which
stores the current number of items in the queue
0 1 2 3 4 5 6
7
Q: 44 55 66 77 88 11 22 33
0 1 2 3 4 5 6
7
Q: 44 55 66 77 11 22 33
rear = 3 front = 5
98
Implementation of solution 1:
• Solution 1: Keep an additional variable
0 1 2 3 4 5 6
7
Q: 44 55 66 77 88 11 22 33
• Dequeue(Q) :
if (count == 0) return ‘queue is empty’;
rear = 3 front = 4
• Dequeue(Q) :
if (rear == front) return ‘queue is empty’;
0 1 2 3 4 5 6
7
Q: 44 55 66 77 11 22 33
rear = 3 front = 4
102
Solution 2: Make front point to the element preceding the front element in the queue
(one memory location will be wasted)
isEmpty(Q) // returns "true“ if queue Q is empty
{
if (rear == front) return true;
else return false;
}
isFull(Q) /*returns "true“ if Q is full, indicates that we already use the maximum memory for queue;
otherwise returns “false” */
{
if ((rear + 1) % maxSize == front) return true;
else return false;
}
frontQ(Q) //returns the item that is in front (head) of queue Q or returns error if queue Q is empty.
{
return Q[front + 1];
}
103
Solution 2: Make front point to the element preceding the front element in the queue
(one memory location will be wasted)
enqueue(Q,x) /*inserts item x into the back (rear) of queue Q. If the queue is full before making insertion, then give the
notification about that*/
{
if (isFull(Q)) ERROR(“Queue is FULL”);
else
{ rear ++;
if (rear == maxSize) rear = 0;
Q[rear] = x;
}
}
enqueue(Q,x)
{
if (isFull(Q)) ERROR(“Queue is FULL”);
else
{ rear = (rear + 1) % maxSize;
Q[rear] = x;
}
} 104
Solution 2: Make front point to the element preceding the front element in the queue
(one memory location will be wasted)
dequeue(Q) /*deletes the element at the front (head) of the queue Q, then returns x which is the data of this element. If the queue Q is empty
before dequeue, then give the error notification*/
{
if (isEmpty(Q)) ERROR(“Queue is EMPTY”);
else
{ front = (front + 1);
if (front == maxSize) front = 0;
return Q[front];
}
}
dequeue(Q)
{ if (isEmpty(Q)) ERROR(“Queue is EMPTY”);
else
{ front = (front + 1) % maxSize;
return Q[front];
}
}
105
Implementing a Queue: using a linked list
typedef struct {
DataType element;
struct node *next;
} node;
typedef struct {
node *front;
node *rear;
} queue;
where DataType is data type of the object need to store in the queue;
DataType need to be declared before declaring the queue.
• Implementing a queue using a linked list:
– Front of the queue is stored as the head node of the linked list, rear of the
queue is stored as the tail node.
– Enqueue by adding to the end of the list
– Dequeue by removing from the front of the list.
Example -
Given the sequence of operations on queue Q as following. Determine the output and the data
on the queue Q after each operation: