Skip to lab content

WEEK 3 · ADVANCED DATA STRUCTURES LAB

Quick sort in ascending order

B.Tech CSE & Allied Branches · II Year, I Semester · AR25C173 tested sample runs

THE SYLLABUS TASK

What you need to solve

Implement comparison-based Quick Sort for a list of integers in ascending order.
Input format & conventions

n (1–100), then n signed integers in the accepted range. This version uses the last element as a Lomuto pivot.

UNDERSTAND THE IDEA

Explanation

Partition chooses the last value as pivot. Values smaller than it are moved to a prefix, and the pivot is then placed between that prefix and the remaining values.

After partition, the pivot is in its final sorted position. Recursive calls exclude it and sort the two remaining ranges.

The algorithm uses in-place swaps but also uses a recursive call stack. A fixed last-element pivot is simple to trace and can perform poorly on sorted or duplicate-heavy input.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Read the array; stop on a range with at most one value.
  2. Choose a[high] as pivot.
  3. Scan low through high−1, growing a prefix of values strictly smaller than pivot.
  4. Swap the pivot into the next prefix position.
  5. Sort ranges left and right of that position and print the result.

SEE THE CONTROL FLOW

Flowcharts

Diamonds show decisions; labeled arrows select a branch. Returning arrows repeat work. Helper-function steps group the operations explained above and in the code. These charts focus on the named core operation. On a phone, scroll within the diagram or open the full-size version.

Recursive quick sort

Recursive quick sort

Open full-size flowchart ↗

C17

Complete C program

Download .c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
    int ch; size_t used = 0; int fits = 1;
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;
    do {
        if (ch == 0) fits = 0;
        if (used + 1 < capacity) text[used++] = (char)ch;
        else fits = 0;
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));
    text[used] = '\0';
    return fits;
}
int readInt(int *value) {
    char token[64], *end;
    if (!readToken(token, sizeof token)) return 0;
    errno = 0; long result = strtol(token, &end, 10);
    if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;
    *value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }

#define MAX_N 100
int readArray(int a[], int *n, int nonnegative) {
    if (!readInt(n) || *n < 1 || *n > MAX_N) return 0;
    for (int i = 0; i < *n; ++i)
        if (!readInt(&a[i]) || (nonnegative && a[i] < 0)) return 0;
    return 1;
}
void printArray(const int a[], int n) {
    printf("Sorted:"); for (int i = 0; i < n; ++i) printf(" %d",a[i]); putchar('\n');
}

void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }
int partition(int a[], int low, int high) {
    int pivot = a[high], i = low;
    for (int j = low; j < high; ++j) if (a[j] < pivot) swap(&a[i++],&a[j]);
    swap(&a[i],&a[high]); return i;
}
void quickSort(int a[], int low, int high) {
    if (low >= high) return;
    int p = partition(a,low,high);
    quickSort(a,low,p - 1); quickSort(a,p + 1,high);
}
int main(void) {
    int a[MAX_N], n; if (!readArray(a,&n,0)) return invalid();
    quickSort(a,0,n - 1); printArray(a,n); return 0;
}

Local compile: gcc -std=c17 quick-sort.c -o program -lm

Open in C compiler transfers the source. Copy the sample input into the compiler’s input box separately before running.

UNDERSTAND THE CODE

Line-by-line explanation

Follow the exact source above, from input handling through the algorithm and its result. Line numbers count blank lines; related statements are grouped into steps. Start with main to follow execution, then open the functions it calls.

Start with main → · Algorithm and data structure →

Lines 1–5Include input, conversion and validation declarations
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

stdio.h supplies terminal input/output. stdlib.h declares conversion and allocation functions. string.h supplies string and memory utilities. ctype.h supplies isspace, and errno.h supplies conversion error reporting. Headers declare facilities; including them does not execute an operation.

Lines 7–9Define a complete-token reader and initialise its state
/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
    int ch; size_t used = 0; int fits = 1;

readToken receives a writable buffer and its capacity. ch is int so it can represent EOF as well as input characters. used counts stored characters, and fits starts as a success flag. One slot must remain for the null terminator.

Lines 10–11Skip leading whitespace and recognise end-of-file
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;

Read until a non-whitespace character appears. The unsigned-char cast supplies an allowed value to isspace. Return 0 if EOF arrives before a token begins.

Lines 12–15Collect a token without silently truncating it
    do {
        if (ch == 0) fits = 0;
        if (used + 1 < capacity) text[used++] = (char)ch;
        else fits = 0;

The do body handles the character already read. An embedded zero byte marks the token invalid. Store a character only while there is room for it and a null terminator; otherwise clear fits. Continue reading even after the buffer fills.

Lines 16–20Consume the full token and terminate the buffer
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));
    text[used] = '\0';
    return fits;
}

Read through the next whitespace or EOF, add a null character after the stored prefix, then return fits. A too-long token returns 0 rather than pretending that its stored prefix is the complete token.

Lines 21–23Wrap token input as a checked integer read
int readInt(int *value) {
    char token[64], *end;
    if (!readToken(token, sizeof token)) return 0;

value is an output address. A 64-slot local buffer receives the token; a failed token read returns 0 before attempting conversion.

Lines 24–25Convert base-ten text and check the whole result
    errno = 0; long result = strtol(token, &end, 10);
    if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;

Reset errno before strtol. &end receives the stopping address. Reject conversion errors, no converted digits, leftover characters, or values outside -1,000,000 to 1,000,000. Thus 12x is not accepted as the integer 12.

Lines 26–28Return the accepted value or a standard error status
    *value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }

Store a validated integer through *value and return 1 from readInt. invalid is a separate helper that prints the error and returns 1 as main’s failure status; the same number has different roles in these two function contracts.

Lines 30–32Read a bounded nonempty array length
#define MAX_N 100
int readArray(int a[], int *n, int nonnegative) {
    if (!readInt(n) || *n < 1 || *n > MAX_N) return 0;

MAX_N is 100. readArray receives an array, the caller’s count address, and a flag requesting nonnegative values. Require a count from 1 to 100; return 0 before using an invalid count.

Lines 33–36Read every element and apply the requested sign rule
    for (int i = 0; i < *n; ++i)
        if (!readInt(&a[i]) || (nonnegative && a[i] < 0)) return 0;
    return 1;
}

readInt checks each complete integer token. If nonnegative is nonzero, negative elements are also rejected. Return 1 only after every requested element has been accepted.

Lines 37–39Display the used array elements
void printArray(const int a[], int n) {
    printf("Sorted:"); for (int i = 0; i < n; ++i) printf(" %d",a[i]); putchar('\n');
}

printArray reads the first n elements without modifying them. It prints the Sorted label, a space before each value, and a final newline.

Line 41Swap two caller-owned integers
void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; }

Save the first value in t, copy the second into the first, then restore t into the second. Pointer parameters allow the helper to modify the actual array elements.

Lines 42–43Choose the final element as pivot
int partition(int a[], int low, int high) {
    int pivot = a[high], i = low;

partition receives an inclusive range. pivot stores a[high], and i starts at low as the next destination for a value strictly smaller than the pivot.

Line 44Collect smaller values at the front of the range
    for (int j = low; j < high; ++j) if (a[j] < pivot) swap(&a[i++],&a[j]);

Scan j only before the pivot slot. For each smaller element, swap it into a[i] and then advance i. Equal values are not moved into this strictly-smaller group.

Lines 45–46Place the pivot and return its final index
    swap(&a[i],&a[high]); return i;
}

Swap the saved pivot position with a[i]. Values before i are smaller and values after it are at least as large. Return i so the recursive caller can exclude the pivot.

Lines 47–51Recursively sort only the two remaining sides
void quickSort(int a[], int low, int high) {
    if (low >= high) return;
    int p = partition(a,low,high);
    quickSort(a,low,p - 1); quickSort(a,p + 1,high);
}

Stop for empty or one-element ranges. Partition, then sort low..p-1 and p+1..high. Using p again as a recursive boundary could fail to shrink a range.

Lines 52–55Read, sort and print the input array
int main(void) {
    int a[MAX_N], n; if (!readArray(a,&n,0)) return invalid();
    quickSort(a,0,n - 1); printArray(a,n); return 0;
}

Allow signed input values, then call quickSort on the used range and display the result. This last-element pivot version is not automatically balanced; already ordered or equal-heavy inputs can make its partitions very uneven.

FOLLOW THE VALUES

Dry run

  1. For [5,2,9,1,3], the pivot is 3.
  2. Scanning brings 2 and 1 into the smaller prefix.
  3. Placing 3 gives [2,1,3,5,9], with pivot index 2.
  4. Sorting [2,1] and [5,9] produces [1,2,3,5,9].

CHECK THE BEHAVIOUR

Sample input & output

These outputs were captured by running the exact C source shown above. Commands use the stated input format; the programs omit repeated menu prompts so the same sample can be pasted and run.

Sample 1

INPUT
5
5 2 9 1 3
OUTPUT
Sorted: 1 2 3 5 9

Sample 2

INPUT
6
3 -2 3 0 -2 1
OUTPUT
Sorted: -2 -2 0 1 3 3

Sample 3

INPUT
5
4 4 4 4 4
OUTPUT
Sorted: 4 4 4 4 4

Common mistakes

  • Do not include the pivot again in either recursive range.
  • Do not describe total auxiliary space as always O(1).
  • This partition is not stable and can degrade on equal keys.

WHY THIS GROWTH RATE?

Time and space complexity

Best/expected balanced behavior O(n log n); worst O(n²). Recursion O(log n) balanced, O(n) worst.

Partition scans its range once. Balanced splits produce O(log n) levels with O(n) total scanning per level.

A pivot that repeatedly lands at an end creates ranges of sizes n−1, n−2 and so on. Their sum is O(n²), and the active recursion can grow to O(n).

This implementation does not randomize pivots, so expected O(n log n) requires an input-order assumption; it is not a guarantee for all inputs. Swapping uses O(1) local storage, separate from the call stack.

Big-O describes growth in the stated input quantity. The educational limits keep examples safe; complexity discussions state which bounds depend on a generalized input size. Arithmetic on the accepted native numeric types is treated as constant-time.

PREPARE FOR YOUR LAB VIVA

Related viva questions & answers

6 focused questions

Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.

What invariant does the partition prefix maintain?

Before each scan step, entries before i are strictly smaller than the pivot. Entries already scanned from i onward are greater than or equal to it.

Why is the last element excluded from the partition scan?

It stores the pivot until the scan finishes. Scanning or moving it as an ordinary entry would interfere with the final pivot-placement step.

Why is the returned pivot index excluded from recursion?

Partition has already placed that value in its final position. Excluding it also ensures each recursive range is smaller and the algorithm terminates.

Why can ascending input be a bad case for this pivot rule?

The last value is repeatedly the largest. Every other value enters the smaller partition, producing a range only one element shorter at each recursive level.

How does this strict-less partition behave when all keys are equal?

No scanned value enters the smaller prefix, so the pivot goes to the range’s first position. Repeating that unbalanced split causes quadratic work.

Would choosing a random pivot guarantee O(n log n) on every run?

No. Randomization gives an expected bound by reducing systematic bad choices, but an unlucky sequence can still be quadratic. This page retains the manual’s deterministic pivot for clarity.