CS1010 Notes
  • Welcome
  • Lec/Tut/Lab/Exes
    • Lecture
      • Lec 01 - Computational Problem Solving
      • Lec 02 - Functions and Types
      • Lec 03 - Basic C Programming
      • Lec 04 - Conditionals
      • Lec 05 - Loops
      • Lec 06 - Call Stacks, Arrays
        • Diagnostic Quiz
      • Lec 07 - Pointers, Memory management
        • Diagnostic Quiz
      • Lec 08 - Multi-d Array, Efficiency
        • Diagnostic Quiz
      • Lec 09 - Searching and Sorting
        • Diagnostic Quiz
      • Lec 10 - More Recursion
        • Diagnostic Quiz
      • Lec 11 - Strcut & Standard I/O
        • Diagnostic Quiz
      • Lec 12 - Recap
    • Tutorial
      • Tut 01 - Computational Problem-Solving
      • Tut 02 - Functions and Conditionals
      • Tut 03 - More on Conditionals
      • Tut 04 - Loops
      • Tut 08 - Searching and Sorting
    • Lab
      • Lab 01 - Unix/Vim Setup
      • Lab 02 - Debugging
      • Lab 03 - Assert
      • Lab 04 - Test Cases
      • Lab 05 - Arrays
      • Lab 06 - Memory Errors
      • Lab 07 - Compiling with Clang
      • Lab 08 - C Preprocessor
      • Lab 09 - Backtracking
      • Lab 10 - Struct and Wrap up
    • Exercises
      • Exercise 3 - Fixed-Length Arrays
      • Exercise 4 - Dynamic Arrays and Strings
      • Exercise 6 - Searching and Sorting
      • Exercise 7 - More Recursion
      • Exercise 8 - Struct
  • Past Year Exam
    • Midterm PE
      • PE1 (AY18/19)
      • PE1 (AY20/21)
      • PE1 (AY21/22)
      • PE0 (AY22/23)
      • PE0 (AY23/24)
    • Midterm Paper
      • Midterm (AY18/19)
      • Midterm (AY20/21)
      • Midterm (AY21/22)
      • Midterm (AY22/23)
    • PE1 Review
      • PE1 (AY23/24)
    • PE2 Review
      • PE2 (AY18/19)
      • PE2 (AY20/21)
      • PE2 (AY21/22)
      • PE2 (AY22/23)
      • PE2 (AY23/24)
    • Final Paper
      • Final (AY18/19)
      • Final (AY20/21)
      • Final (AY21/22)
      • Final (AY22/23)
      • Final (AY23/24)
  • Current Year Exam
    • PE0 (AY24/25)
    • PE1 (AY24/25)
    • PE2 (AY24/25)
    • Final (AY24/25)
  • Toolbox
    • Vim & Unix
    • GDB
  • After CS1010
Powered by GitBook
On this page
  • Multi-dimensional Array
  • Pointer to a Fixed-Size Array
  • Array Name Decay (Multidimensional Array)
  • A Fixed-Size Array of Dynamically Allocated Array
  • Dynamically Size 2D Array
  • Efficiency
  • Comparing Rate of Growth
  • Time Complexity for Recursive Functions
Edit on GitHub
  1. Lec/Tut/Lab/Exes
  2. Lecture

Lec 08 - Multi-d Array, Efficiency

PreviousDiagnostic QuizNextDiagnostic Quiz

Last updated 6 months ago

Slides:

Multi-dimensional Array

Pointer to a Fixed-Size Array

Why we need this part?

In C, the normal method for us to declare an array is as follows:

long a[20];

However, as we have seen earlier, due to "array-decay", a will decay to a memory address (not a pointer). So, we cannot assign a to other memory address. For example, the following code in C is illegal

long a[20];
long b[20];
a = b; // Illegal

So, to "solve" this problem (actually for teaching only), C allows us to have a pointer that points to an array. Note: Not just an element, but the whole array. We can do so with:

long a[20];
long (*ptr)[20];
ptr = &a;

However, you still need to pay attention that the following code to define a fixed-size array are not exactly the same!

long (*a)[20];

long *a[20];

The first one defines a pointer to a fixed-size array of length 20 (Actually it allocates a space of 20 long on the stack first, then it defines a pointer pointing to that memory location). While the second defines an array of 20 pointers, and in this case, these 20 pointers point to a long. (But in other cases, it can point to another array too)

Array Name Decay (Multidimensional Array)

Since an array in C consists only of a contiguous region of memory that stores the elements of the array, the address of an array is the same as the address of the first of the array. The following five statements would print out exactly the same values.

cs1010_println_pointer(matrix);        // address of a 1D array
cs1010_println_pointer(matrix[0]);     // address of a long 
cs1010_println_pointer(&matrix);       // address of a 2D array 
cs1010_println_pointer(&matrix[0]);    // address of a 1D array
cs1010_println_pointer(&matrix[0][0]); // address of a long

From this example, by observing each pair of the new lines that have the same meaning. We can see the essence of array decay is: If we have a 2-D array matrix, then matrix will decay to &matrix[0]. Similarly, matrix[0] will decay to &matrix[0][0].

Add an & operator can be considered as .

A Fixed-Size Array of Dynamically Allocated Array

Contiguous Memory Allocation

In the code below, 10 is the num_of_rows. What we have done here is to allocate a chunk of memory with size num_of_cols * 10 once. After that, we iteratively point the remaining 9 pointers to the correct position.

double *buckets[10];
size_t num_of_cols = cs1010_read_size_t();
buckets[0] = calloc(num_of_cols * 10, sizeof(double));
for (size_t i = 1; i < 10; i += 1) {
  buckets[i] = buckets[i - 1] + num_of_cols;
}

To free this kind of 2-D array, just use

free(buckets[0])
Why we cannot use free(buckets) here?

Accoding to the Linux Programmer's Manual, void free(void *ptr) should follow:

The free() function frees the memory space pointed to by ptr, which must have been returned by a previous call to malloc(), calloc(), or realloc(). Otherwise, or if free(ptr) has already been called before, undefined behavior occurs. If ptr is NULL, no operation is performed.

In our case, due to array-decay, buckets is not a "heap-object" returned by malloc(), calloc(), or realloc(). Rather, it is an "stack-object", so we cannot pass buckets directly into free(). If so, we will get warnings from the compiler.

Dynamically Size 2D Array

This existence of this part in my note is only to serve as a example for Why we cannot use free(buckets) here?

Use the following code to allocate a dynamically size 2D array, we should free as shown in the code snippet.

double **canvas;
size_t num_of_rows = cs1010_read_size_t();
size_t num_of_cols = cs1010_read_size_t();
canvas = calloc(num_of_rows, sizeof(double *));
if (canvas == NULL) {
  cs1010_println_string("unable to allocate array");
  return 1;
}
canvas[0] = calloc(num_of_rows * num_of_cols, sizeof(double));
if (canvas[0] == NULL) {
  cs1010_println_string("unable to allocate array");
  free(canvas);
  return 1;
}

for (size_t i = 1; i < num_of_rows; i += 1) {
  canvas[i] = canvas[i-1] + num_of_cols;
}

// free
free(canvas[0]);
free(canvas);

Note that here we can free(canvas) because according to Line 4, it is a "heap-object".

Efficiency

I believe the examples in the notes for this part is pretty complete and well documented. Personally thinking, get yourself familiar with the math equation, especially review for the problem sets and examples are pretty enough.

Comparing Rate of Growth

Given two functions f(n)f(n)f(n) and g(n)g(n)g(n), how do we determine which one has a higher rate of growth? We say that f(n)f(n)f(n) grows faster than g(n)g(n)g(n) if we can find a n0n_0n0​, such that f(n)>cg(n)f(n)>cg(n)f(n)>cg(n) for all n>n0n>n_0n>n0​ and for some constant ccc.

For instance, which one grows faster? f(n)=nnf(n)=n^nf(n)=nn or g(n)=2ng(n)=2^ng(n)=2n? Pick n=1n=1n=1, we have f(1)<g(1)f(1)<g(1)f(1)<g(1). Pick n=2n=2n=2, we have f(2)=g(2)f(2)=g(2)f(2)=g(2). Pick n=3n=3n=3, we have f(3)>g(3)f(3)>g(3)f(3)>g(3) now, and we can see that for any n>3n>3n>3, nn>2nn^n>2^nnn>2n, so we can conclude that f(n)f(n)f(n) grows faster than g(n)g(n)g(n).

Time Complexity for Recursive Functions

Here I only talk about how to expand the recurrence relation. For example, we have

T(n)=2T(n2)+1=4T(n4)+2+1=8T(n8)+4+2+1=2iT(n2i)+2i−1+⋯+4+2+1\begin{aligned} T(n) &= 2T\left(\frac{n}{2}\right) + 1 \\ &= 4T\left(\frac{n}{4}\right) + 2 + 1 \\ &= 8T\left(\frac{n}{8}\right) + 4 + 2 + 1 \\ &= 2^i T\left(\frac{n}{2^i}\right) + 2^{i-1} + \dots + 4 + 2 + 1 \end{aligned}T(n)​=2T(2n​)+1=4T(4n​)+2+1=8T(8n​)+4+2+1=2iT(2in​)+2i−1+⋯+4+2+1​

To reach the base case, n⋅2−i=1n\cdot2^{-i}=1n⋅2−i=1, so 2i=n2^i=n2i=n, and 2i−1=n/22^{i-1}=n/22i−1=n/2. We have,

T(n)=nT(1)+n2+⋯+4+2+1=O(n)+n2+⋯+4+2+1\begin{aligned} T(n) &= nT(1) + \frac{n}{2} + \dots + 4 + 2 + 1 \\ &= O(n) + \frac{n}{2} + \dots + 4 + 2 + 1 \end{aligned}T(n)​=nT(1)+2n​+⋯+4+2+1=O(n)+2n​+⋯+4+2+1​

This term n2+⋯+2+1\frac{n}{2}+\cdots+2+12n​+⋯+2+1 is a geometric series with a coefficient of 1 and a common ratio of 2. Its sum can be represented as follows, which is less than nnn

n2+⋯+2+1=2log2n2−12−1=n2−1\begin{aligned} \frac{n}{2} + \cdots + 2 + 1 &=\frac{2^{log_2\frac{n}{2}}-1}{2-1} \\ &=\frac{n}{2}-1 \end{aligned}2n​+⋯+2+1​=2−12log2​2n​−1​=2n​−1​

When you expand the gemetric sequence, suppose the common ratio is qqq and the first term is a0a_0a0​. Then the sum can be expressed as a0⋅(qn−1)q−1\frac{a_0\cdot(q^n-1)}{q-1}q−1a0​⋅(qn−1)​, where nnn is the number of terms in this geometric sequence.

Knowing the last term in the geometric sequence, a quick way to get the number of terms in the geometric sequence is to do the logarithmic operation. e.g. Suppose the last term is ana_nan​, then n=logq(an)n=log_q(a_n)n=logq​(an​)f(x)=x∗e2piiξxf(x) = x * e^{2 pi i \xi x}f(x)=x∗e2piiξx\frac{q^n}

In a multi-dimensional array, the we have seen before still applies, but now it becomes a bit trickier.

CS1010 AY24/25 S1 Lecture 8
Lecture Slides
Array Name Decay
Pointer to a Fixed-array
Contiguous Memory Allocation