The linked list ADT consists of nodes that are linked together by a pointer. We can think of a node programmatically as a C Structure, Node, which contains 2 variables:
A variable called value , which is used to store the data in the list. Value can be set to any data type depending on what is needed. (Note that some implementations name this variable as ‘element’)
A variable called next, which is a pointer to a node.
Our linked list class contains one global variable of type node pointer, which points to the first item in the list.
The list can be thought of a queue of people in a line with name tags (data), however, in this queue, people can join at the front, back and cut in line.
Operations performed on a linked list are shown below:
Operation
Explanation
Example
add(value) or addLast(value)
Adds the value to the end of the list.
Someone joins the end of the line
addFirst(value)
Adds a value to the top/front of the list
Someone joins the line from the front.
insert(value, i)
Adds/inserts a value at location i counting from top/front
Someone cuts in line at the become the ith position in the list. If i exceeds the size of the list, the person is added to the end of the list
size()
Returns the size of the list
Find out the amount of people on the line
isEmpty()
Returns true if the queue is empty, i.e, size==0
Find out if the line is empty.
delete(i)
Deletes/removes a node at location i, and returns the value
Someone leaves the line from the ith location
Some operations that are performed on a linked list.
Some Linked List algorithms
Performing the operations above is not as straightforward as our previous ADTs. In this discussion, we examine the algorithms for performing operations in the list.
Initialization
First<--null
addFirst
AddFirst(newValue)
{
Create the new Node newListItem
newListItem.value <-- newValue
newListItem.next <--first
first<--newListItem
}
add last
AddLast(newValue)
{
Create the new Node newListItem
newListItem.value newValue
If the list is empty Then
first <-- newListItem
newListItem.next <-- NULL
Else
Create Nodes currentNode, previousNode
while(currentNode!=NULL)
{
previousNode=currentNode;
currentNode=currentNode->next;
}
previousNode->next=newNode;
}
In class assignment/homework 1
The rest of the operations on the linked list were omitted:
Number
Operation
1
size()
2
isEmpty()
3
insert(value, i)
4
delete(i)
Using to the algorithms above as a guide, write separate algorithms which implements the operation listed in the table above.
//Note that the operations from the in class assignment/homework are not included.
#include <stdio.h>
#include <stdlib.h>
/*keep track of the size of the list.
Initially, the size is 0,
thus the count is 0.*/
int count = 0;
/*create a structure called "Node_n"
and give it a type definition
named Node.
We do this to avoid using the
"struct" keyword everytime
we declare a Node_n structure.
It's simply a quality of life improvement
*/
typedef struct Node_n
{
int data;
struct Node_n * next;/*note that we cant
use the typedef here ,
e.g "struct Node * next;"
since the type definition
has not been created yet.
if used it will produce
a warning when you try to
assign Node Structures to
each other such as in the
add function etc:
"assignment from incompatible
pointer type
[-Wincompatible-pointer-types]|"
*/
} Node;
/*we declare Node structure to
represent the first item at the
start of the list.
It is assigned a mull pointer value,
which can be thought of a value of
0 for the pointer.
*/
Node * first;
/*functionDeclarations
that were IMPLEMWNTED
in this code*/
void addFirst(int value);
void addLast(int value);
int size();
void printList();
/*functionDeclarations
that were NOT IMPLEMENTED
in this code. It was left
for student discussion
on how this should be implemented*/
void deleteNode(int location);//for delete(i)
//algorithm from above
void insert(int value, int location);
int main()
{
//initialization
first=NULL;
//codetesting
addFirst(2);
addFirst(1);
addLast(3);
addLast(4);
printList();
return 0;
}
void addFirst(int value)
{
//create new node....
/*we use malloc
to allocate a portion of
RAM memory to store the
data contained within a node.
The amount of memory allocated is
the size of the node datatype.
the newly allocated node
Node structure's pointer is now assigned
to our pointer named "newNode"
note that "sizeof" is a unary operator
much like the address operator "&"
sizeof only requires parentheses
only when it is evaluating a data type
so :
int x;
printf("%d", sizeof x);
would complile and work fine.
*/
Node * newNode = malloc(sizeof (Node));
/*Question: how different would it be create
new node without a pointer and achieve the
same result in the line above?*/
//.....store the data in the new node
newNode->data=value;
/*It is best practice to check to see of the list
is empty and not based on the integer size being
zero. This is because most list traversal happens
through pointers and must be stopped when null
is reached. If using size to check if it's empty,
the programmer may forget to set the last link
in the list to be null resulting in infinite
traversal looping*/
//if the list is empty..
if( first==NULL)
{
first=newNode;
first->next=NULL;/*set next to null,
indicating that
this is the last item
in the list so far*/
}
else// the list contains items///
{
//create links..
newNode->next=first;
first=newNode;
//recall that the list is null terminated still.
}
count++;
}
/*Future exercise 3:
adde a node called last which always
stores the last item in the list.
modify addFirst to ensure that
add first is being tracked.
modify this code to omit the
traversal using the while loop*/
void addLast(int value)
{
/*What would happen if we used node
structures instead of pointers?
try to implement this function without
delaring pointers,
i.e declare newNode,currentNode,
previousNode as a node and not a pointer
to a Node
Answer: when current is set to null, we will
not be able to dereference the null value to
a node so that current can now bw assigned
to a NULL node. There is no such thing
as a null struct, only null pointers*/
Node * newNode = malloc(sizeof(Node));;
newNode->data = value;
newNode->next = NULL;
//if the list is empty
if(first==NULL)
first=newNode;
else
{
Node * currentNode , * previousNode;
currentNode=first;
previousNode=first;
/*while the end of the list
has not been reached*/
while(currentNode!=NULL)
{
previousNode=currentNode;
currentNode=currentNode->next;
}
/*loop terminates with previousNode
being the last one in the list
*/
previousNode->next=newNode;
}
count++;
}
void printList()
{
printf("Start list...\n");
Node * current = first;
while (current !=NULL)
{
printf("%d\n", current->data);
current=(Node *)(current->next);
}
printf("End list...\n");
}
int size()
{
return count;
}
In class assignment/homework 2
The rest of the operations on the linked list were omitted in the code:
Number
Operation
1
IsEmpty()
2
Insert(int value, int i)
3
deleteNode(i)
Using to the Code above as a guide, write functions which implements the operation listed in the table above. Update your main method to test the operations so that they behave as expected.
In a binary search, an ordered list is searched for a key starting with the item at the middle of the list. If the item is not found at the location , a determination is made on which half of the list may contain the key, either the upper or lower half. This method is applied to search the midpoint of the half of the list which may contain the key. This repeats until the key is found, or until the search space is reduced to a single location and the list can no longer be divided into halves. If the key is found, the location of the element is returned, otherwise a dummy value is returned (e.g -1)
This method of searching fall in the category of algorithms called “Divide and Conquer” since a large portion of the search space is split and discarded on each iteration.
Given a list of items of a finite size and a Key we traverse the list to find the key and output the location of the key found in the list.
Consider the scenario where we have a list, with unique items (in an array) and we wish to find a key:
Algorithm:
//initialization
//Assume our data is stored in an array named list of size N.
list[0..N-1] = {Assume a sorted list}
first=1
last=N-1
result=-1 //used to store the index of a successful search. If result is -1 , the search has failed
middle = (first-last)/2
while (first<=last)
if list[middle]==key then
result= middle
break
else if key<list[middle] then
last=middle-1;
else
first=middle+1;
endif
middle=(first-last)/2 + first
endif
endwhile
return result.
Implementation in C code
/*
Demo: Searching a sorted list using
binary search
*/
#include <stdio.h>
#include <stdlib.h>
int binarySearch(int key, int list[], int listSize);
int main()
{
int list[10];
list[0]=5;
list[1]=10;
list[2]=15;
list[3]=20;
list[4]=25;
list[5]=30;
list[6]=35;
list[7]=40;
list[8]=45;
list[9]=50;
int key;
printf("\nenter a search key\n");
scanf("%d",&key);
while (key!=-1)
{
int result = binarySearch(key,list,10);
printf("\nSearch result %d\n",result);
printf("\nenter a search key\n");
scanf("%d",&key);
}
return 0;
}
int binarySearch(int key, int list[], int listSize)
{
/*we use first and last to represent a
portion of the array. Initially, first and last
points to the start of the array and the end of
the array respectively. Middle indexes the location
to the middle of the array.
*/
int result=-1;//-1 represents a failed search
int first=0;
int last=listSize-1;
int middle = (last-first)/2;
while (first<=last)//while first and last have not crossed...
{
if(key==list[middle])//if the key is found....
{
result=middle;
return result;//return the current index and exit function
}
else//the key was not found
{
if(key<list[middle])//if the key would be found
//from first to middle
last=middle-1;//we now search the lower half
else //key would be found from middle to last
first=middle+1;//we now search the upper half
middle=((last-first)/2)+first;//find the new midpoint.
}
}
return result;
}
Alternate Implementation In C using Recursion
/*
Demo: Searching a sorted list using
binary search implemented using recursion
*/
#include <stdio.h>
#include <stdlib.h>
int binarySearch(int key, int list[], int first, int last);
int main()
{
int list[10];
list[0]=5;
list[1]=10;
list[2]=15;
list[3]=20;
list[4]=25;
list[5]=30;
list[6]=35;
list[7]=40;
list[8]=45;
list[9]=50;
int key;
printf("\nenter a search key\n");
scanf("%d",&key);
while (key!=-1)
{
int result = binarySearch(key,list,0,9);
printf("\nSearch result %d\n",result);
printf("\nenter a search key\n");
scanf("%d",&key);
}
return 0;
}
int binarySearch(int key, int list[], int first, int last)
{
/*we use first and last to represent a
portion of the array. Initially, first and last
points to the start of the array and the end of
the array respectively. Middle indexes the location
to the middle of the array.
*/
int result=-1;//-1 represents a failed search
int middle = (last-first)/2+first;
if (first<=last)//if first and last have not crossed...
{
if(key==list[middle])//if the key is found....
{
result=middle;
return result;//return the current index and exit function
}
else//the key was not found
{
if(key<list[middle])//if the key would be found
{ //from first to middle
//we now search the lower half
result= binarySearch(key,list,first,middle-1);
return result;
}
else //key would be found from middle to last
{
result= binarySearch(key,list,middle+1,last);//we now search the upper half
return result;
}
}
}
return result;
}
Updates
2023/12/26
Added animated image and link to its source post.
2023/12/17
Fixed binary search algorithm , the element at middle was included every time then a partition made:
else if key<list[middle] then
last=middle;
else
first=middle;
endif
middle=(first-last)/2 + first
It was changed to:
else if key<list[middle] then
last=middle-1;
else
first=middle+1;
endif
middle=(first-last)/2 + first
Added a paragraph on Divide and Conquer
2022/2/3
Added introduction paragraph “In a binary … value is returned (e.g -1)“
In this algorithm we compare the first two adjacent items in the list. If the first item is larger than the item next to it, we swap them, else we do nothing.
This process (step1) is repeated until we have reached the end of the list. At this point, the largest item is at the end of the list, we say it was “bubbled” up the list.
Repeat step 1 and 2 for items 1 .. n-1, then 1 .. n-2 and so on until we reach 1..2
It seems difficult to understand but we can observe the algorithm in action in the following flowchart:
Definition: Debugging is the process of finding and fixing errors in program code.
Definition: Factoring (Decomposition) is the breaking of a complex task to its atomized sub parts. The goal of factoring is to reach a level of detail that can be represented in an algorithm and eventually programming code.
Definition: Refactoring – Changing program code’s structure to improve readability or efficiency without changing it’s behavior; or, to change a code’s factoring
Instructions
Copy and paste the following code into the main.c file of a new project:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
float a,b,c,D,root1,root2;
printf("Please enter a value for a...\n");
scanf("%f",a);
printf("Please enter a value for b...\n");
scanf("%f",&b);
printf("Please enter a value for c...\n");
scanf("%f",&c);
printf("\n");
D=(b*b)-(4*a*c);
if(D<0)
{
printf("no roots\n Exiting program...\n");
}
else if (D=0)
{
root1=(b*b)/(2*a);
printf("Equation has only one root = %.2f \n Exiting program...\n ");
}
else
{
root1=((b*b)-sqrt(D))(2*a);
root2=((b*b)+sqrt(D))/(2*a);
printf("Roots are %.2f and %.2f \n Exiting program...\n",root1,root2);
}
return 1;
}
Debug your program so that it produces correct results in all cases.
Modify your code to use functions.
Move the code for calculating the Discriminant into another function. Do this by
Create a new function,which returns the value of the discriminant. The new function should implement the function declaration: float discriminant (acoef,bcoef,ccoeff);
Change the line which contains D=(b*b)-(4*a*c); to D=discriminant(a,b,c);
Move the code for calculating root 1 and root 2 into 2 separate functions. To do this, you must create suitable function declarations and accompanying function declarations which can accomplish the task.
Attempt the 2 c programming exercises below. If you are having a hard time starting, try:
Examining the solution the exercise 2 for guidance.
Attempt exercise 1 (no solution was provided here)
Ideally, attempt exercise 2 without looking at the solution.
Exercises:
1. Create a program which accepts the radius for a circle/sphere and outputs the volume and maximum cross sectional area. Your program must prompt for input, and produce relevant results.
2. Given the coefficient a b and c of a quadratic equation in the form y=ax^2+bx+c output the solution to the equation when y=0
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
float a,b,c,D,root1,root2;
printf("Please enter a value for a...\n");
scanf("%f",&a);
printf("Please enter a value for b...\n");
scanf("%f",&b);
printf("Please enter a value for c...\n");
scanf("%f",&c);
printf("\n");
D=(b*b)-(4*a*c);
if(D<0)
{
printf("no roots\n Exiting program...\n");
}
else if (D==0)
{
root1=(-b)/(2*a);
printf("Equation has only one root = %.2f \n Exiting program...\n ");
}
else
{
root1=((-b)-sqrt(D))/(2*a);
root2=((-b)+sqrt(D))/(2*a);
printf("Roots are %.2f and %.2f \n Exiting program...\n",root1,root2);
}
return 1;
}
Strategy for Coding using Exercise 1 as an example