mergeSort algorithm with fewer global variables - c

I wrote a program that implements the mergeSort algorithm, which sorts across multiple threads.
The algorithm is able to spread the sorted vector over several threads
The program contains mutexes to protect global counters that are incremented by each thread.
I would like you to help me modify the program so that I do not use so many global variables. I also think that the program is making too many copies and that problems may occur
#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
pthread_mutex_t mutex_p = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_pi = PTHREAD_MUTEX_INITIALIZER;
int MAX; // number of elements in the array
int THREAD_MAX; // number of threads
int idx[20]; // hold the right end at each vector subdivision
int p_i = 0;
int *a;
int part = 0;
// mergeSort function to interclass two parts
void merge(int l1, int h1, int h2)
{
int count = h2 - l1 + 1;
int sorted[count];
int i = l1, k = h1 + 1, m = 0;
while (i <= h1 && k <= h2)
{
if (a[i] < a[k])
sorted[m++] = a[i++];
else if (a[k] < a[i])
sorted[m++] = a[k++];
else if (a[i] == a[k])
{
sorted[m++] = a[i++];
sorted[m++] = a[k++];
}
}
while (i <= h1)
sorted[m++] = a[i++];
while (k <= h2)
sorted[m++] = a[k++];
for (i = 0; i < count; i++, l1++)
a[l1] = sorted[i];
}
// mergeSort function
void merge_sort(int low, int high)
{
// calculates the middle of the array
int mid = low + (high - low) / 2;
if (low < high)
{
// I call the first half
merge_sort(low, mid);
// I call the second half
merge_sort(mid + 1, high);
// interclassification between the two halves
merge(low, mid, high);
}
}
// thread function for multi-threading
void *mergeSort(void *arg)
{
pthread_mutex_lock(&mutex_p);
int thread_part = part++;
pthread_mutex_unlock(&mutex_p);
// calculate the minimum and maximum
int low = thread_part * (MAX / THREAD_MAX);
int high = (thread_part + 1) * (MAX / THREAD_MAX) - 1;
// allocate the rest of the original array to the last thread
if (thread_part == THREAD_MAX - 1)
{
high = MAX - 1;
}
// stores the right edge for each split array
pthread_mutex_lock(&mutex_pi);
idx[++p_i] = high;
pthread_mutex_unlock(&mutex_pi);
// calculate the midpoint
int mid = low + (high - low) / 2;
merge_sort(low, mid);
merge_sort(mid + 1, high);
merge(low, mid, high);
return NULL;
}
void isSorted(int len)
{
if (len == 1)
{
printf("Sorting completed\n");
return;
}
int i;
for (i = 1; i < len; i++)
{
if (a[i] < a[i - 1])
{
printf("Sorting is not complete\n");
return;
}
}
printf("Sorting completed\n");
return;
}
// The main program
int main()
{
printf("Enter the number of items in the array:");
scanf("%d", &MAX);
printf("Enter the number of threads you want:");
scanf("%d", &THREAD_MAX);
// generates random number in array up to 1000
a = malloc(MAX * sizeof(int));
srand(time(NULL));
for (int i = 0; i < MAX; i++)
{
a[i] = rand() % 1000;
}
// t1 and t2 to calculate the time to mergeSort
clock_t t1 = clock();
pthread_t threads[THREAD_MAX];
// thread creation
for (int i = 0; i < THREAD_MAX; i++)
{
pthread_create(&threads[i], NULL, mergeSort, (void *)NULL);
}
// joining all threads
for (int i = 0; i < THREAD_MAX; i++)
{
pthread_join(threads[i], NULL);
}
// merging on the last elements
int p = 1;
int mid, high;
for (int q = 1; q < THREAD_MAX; q++)
{
mid = idx[p];
p++;
high = idx[p];
merge(0, mid, high);
}
clock_t t2 = clock();
printf("Time required: %f\n", (double)(t2 - t1) / CLOCKS_PER_SEC);
isSorted(MAX);
//sorted array display
printf("Sorted array: ");
for (int i = 0; i < MAX; i++)
printf("%d ", a[i]);
printf("\n");
free(a);
return 0;
}

The key to remove global variable is to locally pass data from functions to functions. First of all, pthread_create have a parameter called arg which enable you to pass a structure with many fields in argument to the created thread. The idea is to declare a structure like this:
// TODO: check all the fields are actually needed
struct ThreadContext
{
pthread_mutex_t mutex_p;
pthread_mutex_t mutex_pi;
int MAX; // number of elements in the array
int THREAD_MAX; // number of threads
int idx[20]; // hold the right end at each vector subdivision
int p_i = 0;
int *a;
int part = 0;
};
Then create it on the stack, then fill it (typically the mutex fields), before creating threads, and finally pass it to the threads using:
// &your_struct is a pointer to your_struct
pthread_create(&threads[i], NULL, mergeSort, (void*)&your_struct);
Your mergeSort function can then extract the data structure from the void* pointer.
void* mergeSort(void* arg)
{
struct ThreadContext* context = (struct ThreadContext*)arg;
pthread_mutex_lock(&context.mutex_p); // Example of usage
// [...]
}
You can also add new parameters to the merge and merge_sort functions if needed.
Note that int idx[20]; can be replaced by int* idx and allocated dynamically (using malloc and free) so not to have a predetermined limit to the number of threads that can be used in your program.
I am surprised to see mutexes in a merge sort. Indeed, the idea of a parallel merge sort is to split the work between thread so each work on different data. As a result, there is no need for critical sections. You can use the thread ID to select with thread do the merge instead of preventing other threads to do operations with critical sections.
Note that you can create an array of ThreadContext and fill it with different data so each thread have different information (filled by the main thread). For example, the thread ID can be different as well as the start/stop indices. This help you not to use any mutexes in your code. To avoid the replication of some fields in each thread you can even split the structure into 3 different structures:
struct SharedContext
{
// Shared fields like the number of threads, the array pointer, the total number of element, mutexes (if any), etc.
};
struct PrivateContext
{
// Thread private fields like the thread ID, the start/stop indices, etc.
};
struct ThreadContext
{
struct SharedContext* sharedCtx;
struct PrivateContext privateCtx;
};
Note that sharedCtx should be filled with the pointer to a SharedContext structure created by the main thread (eg. allocated on the stack).

You can remove the global variables by passing the appropriate arguments to each thread.
For this, you can define an array of structures, one for each thread, populate it with arguments before launching the thread and pass the structure pointer to pthread_create.
With each thread acting on its own data, you no longer need the global variables, nor even the mutexes.
Here is a modified version:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
struct thread_data {
int *a;
int low;
int high;
};
// mergeSort function to interclass two parts
void merge(int *a, int lo, int mid, int hi) {
int count = hi - lo;
int sorted[count];
int i = lo, k = mid, m = 0;
while (i < mid && k < hi) {
if (a[i] <= a[k])
sorted[m++] = a[i++];
else
sorted[m++] = a[k++];
}
while (i < mid)
sorted[m++] = a[i++];
for (i = 0; i < m; i++)
a[lo + i] = sorted[i];
}
// mergeSort function
void merge_sort(int *a, int low, int high) {
// calculates the middle of the array
int mid = low + (high - low) / 2;
if (high - low > 1) {
// I call the first half
merge_sort(a, low, mid);
// I call the second half
merge_sort(a, mid, high);
// interclassification between the two halves
merge(a, low, mid, high);
}
}
// thread function for multi-threading
void *mergeSort(void *arg) {
struct thread_data *data = arg;
merge_sort(data->a, data->low, data->high);
return NULL;
}
void isSorted(const int *a, int len) {
for (int i = 1; i < len; i++) {
if (a[i] < a[i - 1]) {
printf("Sorting is not complete\n");
return;
}
}
printf("Sorting completed\n");
}
void printArray(const int *a, int len) {
for (int i = 0; i < len; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
// The main program
int main() {
int *a; // array pointer
int MAX; // number of elements in the array
int THREAD_MAX; // number of threads
printf("Enter the number of items in the array:");
if (scanf("%d", &MAX) != 1)
return 1;
printf("Enter the number of threads you want:");
if (scanf("%d", &THREAD_MAX) != 1)
return 1;
// generates random number in array up to 1000
a = malloc(MAX * sizeof(int));
srand(time(NULL));
for (int i = 0; i < MAX; i++) {
a[i] = rand() % 1000;
}
// t1 and t2 to calculate the time to mergeSort
clock_t t1 = clock();
// thread creation
pthread_t threads[THREAD_MAX];
struct thread_data data[THREAD_MAX];
for (int i = 0; i < THREAD_MAX; i++) {
data[i].a = a;
data[i].low = i * MAX / THREAD_MAX;
data[i].high = (i + 1) * MAX / THREAD_MAX;
pthread_create(&threads[i], NULL, mergeSort, &data[i]);
}
// joining all threads
for (int i = 0; i < THREAD_MAX; i++) {
pthread_join(threads[i], NULL);
}
// merging on the last elements
// this merge phase is inefficient
for (int i = 1; i < THREAD_MAX; i++) {
int mid = data[i].low;
int high = data[i].high;
merge(a, 0, mid, high);
}
clock_t t2 = clock();
printf("Time required: %f\n", (double)(t2 - t1) / CLOCKS_PER_SEC);
isSorted(a, MAX);
//sorted array display
if (MAX < 100) {
printf("Sorted array: ");
printArray(a, MAX);
}
free(a);
return 0;
}

Related

Seg Fault calling mergesort on pointer array

I am trying to work with threads in C. I need one of my threads to work on one half of a whole array, and mergesort its half. To do this I created a global array pointer for the whole array and both halves. The is allocated at runtime with malloc.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
void *process1(void *arg);
void *process2(void *arg);
void *process3(void *arg);
void *process4(void *arg);
void *process5(void *arg);
void merge(int *arr, int l, int m, int r);
void mergeSort(int *arr, int l, int r);
int *array;
int *arr_first;
int *arr_second;
int arr_s;
size_t n_size;
int n_size_i;
pthread_mutex_t mutex;
main(int argc, char *argv[]) {
arr_s = atoi(argv[1]);
n_size = arr_s / 2;
n_size_i = arr_s / 2;
array = malloc (arr_s * sizeof (int));
arr_first = malloc(arr_s / 2 * sizeof(int));
if (arr_s % 2 == 0)
arr_second = malloc((n_size) * sizeof (int));
else
arr_second = malloc((n_size+1) * sizeof(int));
pthread_t tid1, tid2, tid3, tid4, tid5;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid1, &attr, process1, NULL);
pthread_join(tid1, NULL);
printf("---------------------------------------------------------------------------THREAD 2 AND 3 ARE CURRENTLY SORTING THE NUMBERS-------------------------------------------------------------------------\n");
pthread_create(&tid2, &attr, process2, NULL);
pthread_create(&tid3, &attr, process3, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_create(&tid4, &attr, process4, NULL);
pthread_join(tid4, NULL);
pthread_create(&tid5, &attr, process5, NULL);
pthread_join(tid5, NULL);
free(array);
free(arr_first);
free(arr_second);
exit(0);
}
Here is what the function looks like for my thread im having trouble on (thread 2) At the moment, all it does it takes the first half of the whole array, and one by one takes the value and places it into its own. Then i print out the array. Then finally call mergesort on the array pointer.
void *process2(void *arg) {
int j = 0;
for (; j < (arr_s / 2); j++) {
arr_first[j] = array[j];
}
int j2;
for (j2 = 0; j2 < (arr_s / 2); j2++) {
printf("*%d\t", arr_first[j2]);
if ((j2 + 1) % 25 == 0 && j2 > 0)
printf("\n");
}
mergeSort(arr_first, 0, (arr_s / 2) - 1);
pthread_exit (0);
}
Here are my merge and mergeSort functions:
//Merges two subarrays of arr[]
//First subarry is arr[l..m]
//Second subarry is arr[m+1..r]
void merge(int *arr, int l, int m, int r) {
int i, j, k;
int n1 = m - l + 1;
int n2 = r - m;
//create temp arrays
int L[n1], R[n2];
//copy data to temp array L[] and R[]
for (i = 0; i < n1; i++)
L[i] = arr[l+i];
for (j = 0; j < n2; j++)
R[j] = arr[m + 1 + j];
//merge the temp arrays back in arr[l..r]
i = 0; j = 0; k = l;
while (i < n1 && j < n2) {
if (L[i] <= R[j]) {
arr[k] = L[i];
i++;
} else {
arr[k] = R[j];
j++;
}
k++;
}
//copy remaining elements of L[], if there are any
while (i < n1) {
arr[k] = L[i];
i++;
k++;
}
//same for R[]
while (j < n2) {
arr[k] = R[j];
j++;
k++;
}
}
void mergeSort(int *arr, int l, int r) {
if (l < r) {
int m = 1 + (r - 1) / 2;
//Sort first and second halves
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
}
For some reason when I call merge sort, it will keep recursively calling many more times than there are even elements in the array. I put a print statement before anything else in the mergeSort function, and the print statement executes 50000+ times maybe more. However, if I put a print statement right before the merge call inside mergeSort it never gets executed.
The computation for the middle point in mergeSort is incorrect: there is a confusion between l and 1. It should be:
int m = l + (r - l) / 2;
Naming a variable l is very error prone. Use left or low instead:
void mergeSort(int *arr, int left, int right) {
if (left < right) {
int m = left + (right - left) / 2;
//Sort first and second halves
mergeSort(arr, left, m);
mergeSort(arr, m + 1, right);
merge(arr, left, m, right);
}
}

Quick sort recursive function in C - doesn't work with big number of elements

Has this function been written correctly?
It seems that something is wrong when I try to run the function with a large number of elements in the array (eg 1000).
Then its appears to stop.
int quick_sort(int n, int tablica[],int b, int a)
{
if(a==n-1 || n==0) return;
if(b==n-1)
{
b=0;
a++;
}
if(tablica[b]>tablica[b+1])
{
bufor=tablica[b];
tablica[b]=tablica[b+1];
tablica[b+1]=bufor;
}
b++;
return quick_sort(n,tablica,b,a);
}
Above code will not work even for a small array, unless the small array is unsorted in a particular way. It compares one element with the next element. If the array is say {4,3,8,7,1} the sort will fail, because it has no mechanism to push 1 to the start of the array.
For larger arrays there are too many recursion and the program hits the stack limit and simply fails.
You can use recursion in quicksort but the number of recursions has to be kept in check. For example for array of size 1000 you don't want to have to more than 1000 recursion. Example:
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
void quicksort(int arr[], int low, int high)
{
if(low < high)
{
int pivot = arr[high];
int i = (low - 1);
for(int j = low; j <= high - 1; j++)
{
if(arr[j] <= pivot)
{
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
int pi = i + 1;
quicksort(arr, low, pi - 1);
quicksort(arr, pi + 1, high);
}
}
int main()
{
int arr[] = { 7,3,6,1,4,8,9,2 };
int arrsize = sizeof(arr) / sizeof(*arr);
quicksort(arr, 0, arrsize - 1);
for(int i = 0; i < arrsize; i++)
printf("%d\n", arr[i]);
return 0;
}

Need to implement a recursive mergesort code in C using array pointer, its length and a workspace array for the merge function

I need to implement mergesort for arrays up to 100000 integers but the specifications are a bit troublesome: I need to use a pointer to an integer array, its length and an extra workspace array for merging,
The mergesort function should look something like this:
void merge_sort(int *a, int *w, int n)
where a is to be sorted and w is the workspace used for merging, cant use an array and two indexes between what I wanna sort
pseudocode:
merge_sort(int *a, int *w, int n) {
/* take care of stopping condition first */
if the array to be sorted has fewer than two elements then
return
merge_sort( first half of array a);
merge_sort( second half of array a);
merge the two halves of array a into array w
copy array w back into array a
}
merge(int *array, int *workspace, int len) {
initialise indices to point to the beginning of
the left and right halves of array
while there are elements in both halves of array {
compare the elements at the current left and right indices
put the smallest into workspace and increment both the index
it was taken from, and the index in workspace
}
add any remaining elements from left half of array to workspace
add any remaining elements from right half of array to workspace
}
Here is what I got so far:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAY_MAX 100000
void merge_sort(int *a, int *w, int n) {
if (n == 1)
return;
else {
int *temp;
merge_sort(a, w, n / 2);
merge_sort(a + (n / 2), w, (n - (n / 2)));
/** Cannot figure what to pass to merge since it has to be the two halves
and how to copy contents of a to w **/
}
}
void merge(int *a, int *w, int n) {
/** Cannot figure this out **/
}
int main(void) {
int my_array[ARRAY_MAX];
int work_space[ARRAY_MAX];
int count = 0;
int i;
while (count < ARRAY_MAX && 1 == scanf("%d", &my_array[count])) {
count += 1;
}
start = clock();
merge_sort(my_array, workspace, count);
end = clock();
merge_sort(my_array, work_space, count);
for (i = 0; i < count; i++) {
printf("%d\n", my_array[i]);
}
fprintf(stderr, "%d %f \n", count, (end - start) / (double)CLOCKS_PER_SEC);
return EXIT_SUCCESS;
}
The merging phase in function merge_sort iterates on both halves in parallel, taking the smallest element from either side at a time:
void merge_sort(int *a, int *w, int n) {
if (n < 2) {
return;
} else {
int i, j, k;
int n1 = n / 2;
int *b = a + n1;
int n2 = n - n1;
/* sort the left half */
merge_sort(a, w, n1);
/* sort the right half */
merge_sort(b, w, n2);
/* merge the halves into w */
for (i = j = k = 0; i < n1 && j < n2;) {
if (a[i] <= b[j]) {
/* get smallest value from a */
w[k++] = a[i++];
} else {
/* get smallest value from b */
w[k++] = b[j++];
}
}
/* copy remaining elements from a */
while (i < n1) {
w[k++] = a[i++];
}
/* copy remaining elements from b */
while (j < n2) {
w[k++] = b[j++];
}
/* copy sorted elements back to a */
for (i = 0; i < n; i++) {
a[i] = w[i];
}
}
}
The rest of the code has a few issues too:
2 arrays of 100000 ints might exceed the space available for automatic variables.
you sort the array twice
start and end are not defined
Here is a corrected version:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ARRAY_MAX 100000
int main(void) {
int my_array[ARRAY_MAX];
int work_space[ARRAY_MAX];
int i, count;
clock_t start, end;
count = 0;
while (count < ARRAY_MAX && scanf("%d", &my_array[count]) == 1) {
count += 1;
}
start = clock();
merge_sort(my_array, workspace, count);
end = clock();
for (i = 0; i < count; i++) {
printf("%d\n", my_array[i]);
}
fprintf(stderr,"%d %f\n", count, (end - start) / (double)CLOCKS_PER_SEC);
return EXIT_SUCCESS;
}
Remember that in C, when you send an array as an argument to a function, what is really sent is a pointer to the first element. Then you can use that pointer inside the function in a way very similar to an array. So if you are confused about the ”pointers” in the description of (I assume) your homework, perhaps that is the reason?

algorithms c code in time.h and files

I have a project for my university. In this project I must make a program in C language for sorting a huge table (30000 integers) with some sorting methods like bubble,quick,straight insertion and straight selection. In the output should be the number of changes in every sorting method and the time that was needed to completed. I have two problems:
I cannot show the time that was needed
I must redirect the output to a file but I don't know how to make it.
Here's the code:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 300
int getUniqueNumber(int p[N],int i);
int StraightInsertion(int p[]);
int StraightSelection(int p[]);
int BubbleSort(int p[]);
int quicksort(int left, int right, int p[]);
int main(int argc, char *argv[])
{
FILE *f;
int c,p[N],p2[N];
int i,b;
int t0,t1,dt;
int s=0;
do{
for (i=0;i<N;i++)
p2[i]=getUniqueNumber(p2,i);
for(i=0;i<N;i++)
p[i]=p2[i];
printf("\nTable after sorts:\n");
printf("\n\n\n");
printf("straight selection %d\n",s+1);
time(&t0);
c=StraightSelection(p);
time(&t1);
dt=t1-t0;
printf("\n Number of changes: %d\n",c);
printf(" Processing time: %d\n",dt);
// straight insertion table
for(i=0;i<N;i++)
p[i]=p2[i];
printf("\n\n\n");
printf("straight isertion %d\n",s+1);
time(&t0);
c=StraightInsertion(p);
time(&t1);
printf("\n number of changes: %d",c);
dt=t1-t0;
printf(" Processing time = %f\n",dt);
// Bubble Sort table
for(i=0;i<N;i++)
p[i]=p2[i];
printf("\n\n\n");
printf("Bubble sort %d\n",s+1);
time(&t0);
c=BubbleSort(p);
time(&t1);
printf("\n Number of changes: %d\n",c);
dt=t1-t0;
printf(" Processing time = %f\n",dt);
// Quick Sort table
for(i=0;i<N;i++)
p[i]=p2[i];
printf("\n\n\n");
printf("Quick sort %d",s+1);
time(&t0);
c=quicksort(0,N-1,p);
time(&t1);
dt=t0-t1;
printf("\n Number of changes: %d\n",c);
printf(" Processing time = %f\n",dt);
s++;
}
while(s<20);
return 0;
}
int getUniqueNumber(int p[N],int i)
{
int x,j, found;
srand(time(NULL));
do
{
x = rand();
found = 0;
j = 0;
while (j<=i && found == 0)
{
if (p[j] == x)
found = 1;
else
j++;
}
}while (found == 1);
return x;
}
// STRAIGHT SELECTION
int StraightSelection(int p[])
{
int i,j,k,min=0,a[N];
int count=0;
for (i=0; i<N-1; i++)
{
k = i;
min = p[i];
for (j = i+1; j<N; j++)
{
if (p[j] < min)
{
k = j;
min = p[j];
count ++;
}
}
p[k] = p[i] ;
p[i] = min;
}
return count;
}
//Straight Insertion
int StraightInsertion(int p[])
{
int i,j,x;
int count=0;
for(i=1; i<N; i++)
{
x = p[i];
j = i -1;
while(x<p[j] && j>=0)
{
p[j+1] = p[j];
j = j-1;
count ++;
}
p[j+1] = x;
}
return count;
}
//Bubble Sort
int BubbleSort(int p[])
{
int i,j,temp;
int count;
for (i=1; i<N; i++)
for (j=N-1; j>=i; j--)
if (p[j-1] > p[j])
{
temp = p[j-1];
p[j-1] = p[j] ;
p[j] = temp ;
count ++;
}
return count;
}
//Quick Sort
int quicksort(int left, int right, int p[])
{
int i, j, mid, x, temp;
int count=0;
if (left < right)
{
i = left;
j = right;
mid = (left+right)/2;
x = p[mid];
while (i < j)
{
while (p[i] < x)
i++;
while (p[j] > x)
j--;
if (i < j)
{
if (p[i] == p[j])
{
if (i<mid)
i++;
if (j>mid)
j--;
}
else
{
temp = p[i];
p[i] = p[j];
p[j] = temp;
}
count ++;
}
}
quicksort(left,j-1,p);
quicksort(j+1,right,p);
}
return count;
}
Output redirection in bash can be accomplished with >.
For example, if you want to redirect the output of myprog to the file myout, you would use:
./myprog >myout
To time a program's execution, you can use the time command, like so:
time ./myprog
It will count the total amount of time elapsed since your program started until it exited (usually referred to as the wall-clock time), the amount of time you spend in user-space code and the amount of time spent executing kernel code (executing syscalls, for example, as part of printing the output).
So, to time your program and redirect output to a file, you would do this:
time ./myprog >myout
As others have mentioned in the comments, these are relatively easy tasks, you could probably find this information in a few seconds. Please make sure to do some research in the future before posting new questions. Good luck with your project!
Note: This, of course, assumes you have a different program for each sorting algorithm. If you'd rather stick to a single executable, I suggest you look at Execution time of C program to learn how to time the execution of each function.

Number of Comparisons for Heap Sort

I wrote some C code to analyze the number of comparisons and runtime of building a heap and running heapsort. However, I'm not sure if the output of my code makes sense. Heapsort should perform at O(n log n), but the number of comparisons I'm seeing doesn't seem to be very close to that. For example, for an input of size n = 100, I'm seeing ~200 comparisons to build the heap and ~800 comparisons in heap sort. Am I just analyzing the data wrong, or is there something wrong with the way I'm collecting comparisons in my code?
I can provide a link to github if it would make a difference for anyone.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
void bottom_up_heap_sort(int*, int);
void heap_sort(int*, int);
void sift_up(int*, int);
void sift_down(int*, int);
void build_max_heap(int*, int);
void bottom_up_build_max_heap(int*, int);
void randomize_in_place(int*, int);
int* generate_array(int);
void swap(int*, int*);
int cmp(int, int);
void print_array(int*, int);
int heapsize;
unsigned long comparison_counter;
clock_t begin, end;
double time_spent;
int main() {
int k, N;
int* A;
int* B;
int i;
printf("Testing Sift_Down Heap Sort\n");
for(k = 2; k <= 5; k++) {
comparison_counter = 0;
N = (int)pow((double)10, k);
begin = clock();
A = generate_array(N);
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("Time Spent Generating Array: %f\n", time_spent);
// print the first unsorted array
//printf("Unsorted Array:\n");
//print_array(A, N);
begin = clock();
// call heap_sort on the first unsorted array
heap_sort(A, N);
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
// show that the array is now sorted
//printf("Sorted array: \n");
//print_array(A, N);
printf("Done with k = %d\n", k);
printf("Comparisons for Heap Sort: %lu\n", comparison_counter);
printf("Time Spent on Heap Sort: %f\n", time_spent);
printf("\n");
}
printf("----------------------------------\n");
printf("Testing Sift_Up Heap Sort\n");
for(k = 2; k <= 5; k++) {
comparison_counter = 0;
N = (int)pow((double)10, k);
begin = clock();
B = generate_array(N);
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("Time Spent Generating Array: %f\n", time_spent);
// print the unsorted array
//printf("Unsorted Array:\n");
//print_array(B, N);
begin = clock();
// call heap_sort on the unsorted array
bottom_up_heap_sort(B, N);
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
// show that the array is now sorted
//printf("Sorted array: \n");
//print_array(B, N);
printf("Done with k = %d\n", k);
printf("Comparisons for Heap Sort: %lu\n", comparison_counter);
printf("Time Spent on Heap Sort: %f\n", time_spent);
printf("\n");
}
printf("----------------------------------\n");
return 0;
}
void bottom_up_heap_sort(int* arr, int len) {
int i;
// build a max heap from the bottom up using sift up
bottom_up_build_max_heap(arr, len);
printf("Comparisons for heap construction: %lu\n", comparison_counter);
comparison_counter = 0;
for(i = len-1; i >= 0; i--) {
// swap the last leaf and the root
swap(&arr[i], &arr[0]);
// remove the already sorted values
len--;
// repair the heap
bottom_up_build_max_heap(arr, len);
}
}
void heap_sort(int* arr, int len) {
int i;
// build a max heap from the array
build_max_heap(arr, len);
printf("Comparisons for heap construction: %lu\n", comparison_counter);
comparison_counter = 0;
for(i = len-1; i >= 1; i--) {
swap(&arr[0], &arr[i]); // move arr[0] to its sorted place
// remove the already sorted values
heapsize--;
sift_down(arr, 0); // repair the heap
}
}
void sift_down(int* arr, int i) {
int c = 2*i+1;
int largest;
if(c >= heapsize) return;
// locate largest child of i
if((c+1 < heapsize) && cmp(arr[c+1], arr[c]) > 0) {
c++;
}
// if child is larger than i, swap them
if(cmp(arr[c], arr[i]) > 0) {
swap(&arr[c], &arr[i]);
sift_down(arr, c);
}
}
void sift_up(int* arr, int i) {
if(i == 0) return; // at the root
// if the current node is larger than its parent, swap them
if(cmp(arr[i], arr[(i-1)/2]) > 0) {
swap(&arr[i], &arr[(i-1)/2]);
// sift up to repair the heap
sift_up(arr, (i-1)/2);
}
}
void bottom_up_build_max_heap(int* arr, int len) {
int i;
for(i = 0; i < len; i++) {
sift_up(arr, i);
}
}
void build_max_heap(int* arr, int len) {
heapsize = len;
int i;
for(i = len/2; i >= 0; i--) {
// invariant: arr[k], i < k <= n are roots of proper heaps
sift_down(arr, i);
}
}
void randomize_in_place(int* arr, int n) {
int j, k;
double val;
time_t t;
// init the random number generator
srand((unsigned)time(&t));
// randomization code from class notes
for(j = 0; j < n-1; j++) {
val = ((double)random()) / 0x7FFFFFFF;
k = j + val*(n-j);
swap(&arr[k], &arr[j]);
}
}
// this function is responsible for creating and populating an array
// of size k, and randomizing the locations of its elements
int* generate_array(int k) {
int* arr = (int*) malloc(sizeof(int)*k-1);
int i, j, x, N;
double val;
time_t t;
// init the random number generator
srand((unsigned)time(&t));
// fill the array with values from 1..N
for(i = 0; i <= k-1; i++) {
arr[i] = i+1;
}
N = (int)pow((double)10, 5);
// randomize the elements of the array for 10^5 iterations
for(i = 0; i < N; i++) {
randomize_in_place(arr, k);
}
return arr;
}
// swap two elements
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int cmp(int a, int b) {
comparison_counter++;
if(a > b) return 1;
else if(a < b) return -1;
else return 0;
}
// print out an array by iterating through
void print_array(int* arr, int size) {
int i;
for(i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
O(n log n) (or in general O(f(x))) does not give you any idea about the expected value at a single point.
That's because big-O notation ignores constant factors. In other words, all of n * log(n), 0.000001 * n * log(n) and 1000000 * n * log(n) are in O(n log n). So the result for a particular value of n is completely undetermined.
What you can deduce from big-O notation is the effect of modify the control variable. If a function involves O(n) operations, then it is expected that doubling n will double the number of operations. If a function involves O(n2) operations, then it is expected that doubling n will quadruple the number of operations. And so on.
The actual number for such small values of n doesn't really matter, as the constant factors are omitted in the complexity. What matters is the growth of your algorithm, measuring for increasingly larger values of n, and plotting them should give roughly the same graph as your theoretical complexity.
I tried your code for a couple of n, and the increase in complexity was approximately O(n logn )

Resources