Post

๐Ÿซ [CS50x 2025] 5 Data Structures

๐Ÿซ [CS50x 2025] 5 Data Structures

๐Ÿ”— Linked Lists

To implement a sorted linked list of numbers, we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
int main(void)
{
    // Memory for numbers
    node *list = NULL;

    // Build list
    for (int i = 0; i < 3; i++)
    {
        // Allocate node for number
        node *n = malloc(sizeof(node));
        if (n == NULL)
        {
            return 1;
        }
        n->number = get_int("Number: ");
        n->next = NULL;

        // If list is empty
        if (list == NULL)
        {
            list = n;
        }

        // If number belongs at beginning of list
        else if (n->number < list->number)
        {
            n->next = list;
            list = n; 
        }

        // If number belongs later in list
        else
        {
            // Iterate over nodes in list
            for (node *ptr = list; ptr != NULL; ptr = ptr->next)
            {
                // If at end of list
                if (ptr->next == NULL)
                {
                    // Append node
                    ptr->next = n;
                    break;
                }

                // If in middle of list
                if (n->number < ptr->next->number)
                {
                    n->next = ptr->next;
                    ptr->next = n;
                    break;
                }
            }
        }
    }

    // Print numbers
    ...

    // Free memory
    ...
}

This chunk is probably the trickiest.

1
2
3
4
5
6
if (n->number < ptr->next->number)
{
    n->next = ptr->next;
    ptr->next = n;
    break;
}

๐ŸŽฏ Letโ€™s visualize it

Letโ€™s say your list currently looks like this:

1
[2] โ†’ [5] โ†’ [8] โ†’ [10]

And the new number is 6.

We want to insert 6 in the right place so the list stays sorted.

Now we walk with a pointer (ptr) starting at the head:

  • ptr points at [2], ptr->next is [5] - 6 < 5? โŒ
  • ptr moves to [5], ptr->next is [8] - 6 < 8? โœ…

BOOM. This is where we need to insert 6!


๐Ÿ”ง So what does the code do?

1
n->next = ptr->next;
  • New node (n) is pointing to what ptr was previously pointing to (e.g. [8]).
1
ptr->next = n;
  • Now the current nodeโ€™s next is the new node!

So it becomes:

1
[2] โ†’ [5] โ†’ [6] โ†’ [8] โ†’ [10]

A perfect little insertion.


๐ŸŒฒ Trees

To make this 3 generations tree into code,

3 generations tree

we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Implements a list of numbers as a binary search tree

#include <stdio.h>
#include <stdlib.h>

// Represents a node
typedef struct node
{
    int number;
    struct node *left;
    struct node *right;
}
node;

void free_tree(node *root);
void print_tree(node *root);

int main(void)
{
    // Tree of size 0
    node *tree = NULL;

    // Add number to list
    node *n = malloc(sizeof(node));
    if (n == NULL)
    {
        return 1;
    }
    n->number = 2;
    n->left = NULL;
    n->right = NULL;
    tree = n;

    // Add number to list
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        free_tree(tree);
        return 1;
    }
    n->number = 1;
    n->left = NULL;
    n->right = NULL;
    tree->left = n;

    // Add number to list
    n = malloc(sizeof(node));
    if (n == NULL)
    {
        free_tree(tree);
        return 1;
    }
    n->number = 3;
    n->left = NULL;
    n->right = NULL;
    tree->right = n;

    // Print tree
    print_tree(tree);

    // Free tree
    free_tree(tree);
    return 0;
}

void free_tree(node *root)
{
    if (root == NULL)
    {
        return;
    }
    free_tree(root->left);
    free_tree(root->right);
    free(root);
}

void print_tree(node *root)
{
    if (root == NULL)
    {
        return;
    }
    print_tree(root->left);
    printf("%i\n", root->number);
    print_tree(root->right);
}

๐ŸŒณ Understand tree = n;

1
tree = n;

We earlier had this:

1
node *tree = NULL;

Which means:

โ€œIโ€™m declaring a pointer called tree that will eventually point to the root of the binary tree.โ€

Then created the first node (with number 2), like this:

1
2
3
4
node *n = malloc(sizeof(node));
n->number = 2;
n->left = NULL;
n->right = NULL;

Now this new node n exists in memory - itโ€™s the root of your future tree.

So when we do:

1
tree = n;

Weโ€™re saying:

โ€œLet tree point to the root node (which we just created and stored in n).โ€


๐Ÿซ† Print Recursion

1
2
3
4
5
6
7
8
9
10
11
12
void print_tree(node *root)
{
    if (root == NULL)
    {
        return;
    }
    print_tree(root->left);
    printf("%i\n", root->number);
    print_tree(root->right);
    // no need this line ๐Ÿ‘‡๐Ÿป
    // print_tree(root->right);
}

This is called: in-order traversal.

Left โ†’ Root โ†’ Right

So if your tree looks like:

    2
   / \
  1   3

The steps are:

  1. Go to left subtree โ†’ print 1
  2. Print 2
  3. Go to right subtree โ†’ print 3

This gives:

1
2
3

When we call:

1
print_tree(tree);  // tree points to root = 2

๐Ÿ” Recursion Steps (visually)

1
2
3
4
5
6
7
8
9
10
print_tree(2)
โ”œโ”€โ”€ print_tree(1)
โ”‚   โ”œโ”€โ”€ print_tree(NULL) โ†’ returns
โ”‚   โ”œโ”€โ”€ print 1
โ”‚   โ””โ”€โ”€ print_tree(NULL) โ†’ returns
โ”œโ”€โ”€ print 2
โ””โ”€โ”€ print_tree(3)
    โ”œโ”€โ”€ print_tree(NULL) โ†’ returns
    โ”œโ”€โ”€ print 3
    โ””โ”€โ”€ print_tree(NULL) โ†’ returns

๐Ÿงพ Printed Output:

1
2
3

Thatโ€™s recursion: the function calls itself, each time focusing on a smaller part of the tree. Once it hits NULL, it climbs back up the call stack and finishes printing the current node, then goes to the right.


๐Ÿงช random() % 2

It says: โ€œGive me a random number, but I only care whether itโ€™s 0 or 1.โ€

Because:

1
2
random() โ†’ gives you a **big random integer** (like 7834724 or 902394)
% 2      โ†’ turns it into either 0 or 1

๐ŸŽฒ This is like flipping a coin

random() valuerandom() % 2 result
45827380
92047371
82734180
โ€ฆ1 or 0 randomly

๐Ÿค” Why does it feel weird & What does random() % 2 actually do?

โ€œGive a big random numberโ€ฆ and then just mod 2 it into 0 or 1? Why not directly get 0 or 1?โ€

Thatโ€™s the illusion of complexity:

  • Weโ€™re using a full-sized random number generator to simulate something super small (a coin flip).
  • It feels wasteful, and it kinda is - but it works fine.

random() % 2:

  • Calls a system-level random number generator (which gives a big number, maybe 32-bit or 64-bit).
  • Then throws away most of that randomness by doing % 2, just keeping the least significant bit (either 0 or 1).

๐Ÿ”Ž A better method - random() & 1

1
2
3
4
int random_bit()
{
    return random() & 1; // faster, just grabs the lowest bit
}

This is bitwise AND, not regular math.

1
random() & 1

means:

Take the random number, and keep only its least significant bit (the very last bit, the โ€œ1s placeโ€).


โœ”๏ธ How it works

First, what is & ?

Itโ€™s called the bitwise AND operator. It compares each bit in two numbers:

Rule:

1 & 1 = 1
everything else is 0:

  • 1 & 0 = 0
  • 0 & 1 = 0
  • 0 & 0 = 0

Now letโ€™s say random() gives you this 8-bit number (pretend for simplicity):

1
random() = 01101110  (binary) = 110 in decimal

Now do:

01101110   (random number)
& 00000001   (binary for 1)
----------
00000000   โ†’ means even โ†’ result is 0

Another example:

   1 0 1 1 0 1 1 1   (random number in binary)
&  0 0 0 0 0 0 0 1   (only the last bit is 1)
-------------------
   0 0 0 0 0 0 0 1   โ†’ Result: 00000001 (which is just `1`)

So essentially:

  • If the last bit is 0 โ†’ result is 0 (even number)
  • If the last bit is 1 โ†’ result is 1 (odd number)

Everything else gets wiped out.

Thatโ€™s a fast way to flip a coin:

  • 0 = heads
  • 1 = tails

๐Ÿ•’ srandom(time(0));

Itโ€™s doing two things:

  1. time(0)

    This returns the current time in seconds since January 1, 1970 (Unix Epoch). So every time you run your program, this number changes - unless you run it again in the same second.

    Example:

    1
    
     time(0)  โ†’  1718501826   // A big changing number, like a timestamp
    
  2. srandom(...)

    This is like โ€œplanting a seedโ€ into your random number generator. It tells the system:

    โ€œHey, start the randomness with THIS specific number.โ€

    So:

    1
    
     srandom(time(0));
    

    is basically:

    โ€œUse the current time as the seed to start randomness!โ€

    Because if you donโ€™t do this, then your program will give the same โ€œrandomโ€ numbers every time you run it.


๐Ÿ’ก Since time(0) changes every second, why not just use it directly?

time(0) is just a number

If you write:

1
int r = time(0) % 2;

Youโ€™re not getting randomness - youโ€™re just splitting the current second into either 0 or 1. So the result only changes once per second, and itโ€™s 100% predictable.

Imagine:

1
2
time(0) = 1718502033   โ†’ 1718502033 % 2 = 1
time(0) = 1718502034   โ†’ 1718502034 % 2 = 0

Thatโ€™s not โ€œrandomโ€ - thatโ€™s a clock. Not chaos.

But with srandom(time(0)), weโ€™re doing this:

1
2
3
4
5
// Set the seed
srandom(time(0)); // ๐ŸŒฑ

int a = random(); // ๐ŸŽฒ fully chaotic
int b = random(); // ๐ŸŽฒ new chaos from same seed

This way, you initialize the random number generator using the current time - so:

  • Each time you run the program in a new second, it uses a different seed, and therefore gives totally different random numbers.
  • But within the same run, it produces a whole sequence of chaotic values.

๐Ÿ“Š Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
    srandom(time(0)); // ๐Ÿงช seed it with current time

    // Print 5 random numbers
    for (int i = 0; i < 5; i++)
    {
        printf("Random %d: %ld\n", i, random());
    }

    return 0;
}

Run this once:

1
2
3
4
5
Random 0: 123456789
Random 1: 219838201
Random 2: 882182838
Random 3: 498192388
Random 4: 388128127

Wait 2 secondsโ€ฆ

Run again:

1
2
3
4
5
Random 0: 129192139
Random 1: 473882019
Random 2: 892831002
Random 3: 192139103
Random 4: 182391281

Different every time.

This post is licensed under CC BY 4.0 by the author.