Files
cpp-flashcards/org/cpp/ufds.org
T

9.7 KiB
Raw Blame History

Ufds: Union-Find Disjoint Set   cpp datastructure

Front

Write a minimal Ufds class with vector parent, constructor initializing parents to self

Back

#include <vector>
#include <numeric>

class Ufds {
private:
  std::vector<int> parent;
public:
  Ufds(int n) : parent(n) {
    std::iota(parent.begin(), parent.end(), 0);
  }
};

Ufds find() with path compression   cpp datastructure

Front

Write the find() method for Ufds with path compression

Back

int find(int x) {
    if (parent[x] != x) {
        parent[x] = find(parent[x]);
    }
    return parent[x];
}

Concise Ufds find()   cpp datastructure

Front

Is this correct? What does it do?

int find(int x) {
    if (parent[x] == x) return x;
    return parent[x] = find(parent[x]);
}

Back

Yes, correct and equivalent to the longer version.

  • Base case: if x is its own parent, return x
  • Recursive case: find root of parent, then assign and return

The assignment parent[x] = find(parent[x]) returns the result while compressing the path.

Ufds constructor: initializer list vs body   cpp cpp

Front

What is the difference between these two Ufds constructors?

// Initializer list
Ufds(int n) : parent(n) {
  std::iota(parent.begin(), parent.end(), 0);
}

// Body style
Ufds(int n) {
  parent.resize(n);
  std::iota(parent.begin(), parent.end(), 0);
}

Back

Initializer list: direct constructs parent with size n (one allocation)

Body style: default constructs parent, then resize (potential double allocation)

Both produce same result, initializer list is more efficient.

What does std::vector::resize do?   cpp cpp

Front

What does resize(n) do on a std::vector<int>?

Back

Resizes the vector to have n elements:

  • If n > current size: adds elements (value-initialized, usually 0)
  • If n < current size: truncates the vector
  • Reallocates if capacity < n
std::vector<int> v;
v.resize(5);  // v = {0, 0, 0, 0, 0}

Ufds destructor: when to omit   cpp cpp

Front

Should Ufds have a destructor? Ufds() {} vs Ufds() = default

Back

Omit entirely if only using std::vector (auto-cleanup)

If you must write one, prefer = default to explicitly show intent

Empty {} works but = default is clearer intent for future maintenance

Correct Ufds constructor: is this correct?   cpp cpp

Front

Is this correct?

Ufds(int n) {
  p.resize(n);
  std::iota(p.begin(), p.end(), 0);
}

Back

Yes, correct. This is the body-style initialization.

One potential issue: p is default-constructed first, then resize may reallocate.

For efficiency, prefer initializer list: Ufds(int n) : p(n)

Incorrect Ufds constructor: correct or wrong?   cpp cpp

Front

Is this correct?

std::vector<int> p;
Ufds(int n) {
  p = new std::vector<int>(n);
}

Back

Wrong.

new std::vector<int>(n) returns a vector<int>* (pointer), but p is a vector<int> (not a pointer).

Can't assign a pointer to a vector.

What does new return?   cpp cpp

Front

What does new Type return? And new Type[n]?

Back

new Type returns a pointer to that type: Type* new Type[n] returns a pointer to an array: Type*

int* p1 = new int;      // single int
int* p2 = new int[10];  // array of 10 ints

What does delete do?   cpp cpp

Front

What does delete do? delete[]?

Back

delete frees a single object allocated with new delete[] frees an array allocated with new[]

int* p1 = new int;
int* p2 = new int[10];
delete p1;   // free single object
delete[] p2; // free array

Mismatching delete with new[] causes undefined behavior.

Is this correct Ufds with C-style array?   cpp cpp

Front

Is this correct?

class Ufds {
private:
  int* parent;
public:
  Ufds(int n) : parent(new int[n]) {}
  ~Ufds() { delete[] parent; }
};

Back

Correct. This manually manages the heap-allocated array.

  • new int[n] allocates array of n ints on heap
  • delete[] parent in destructor frees it

This works but requires manual cleanup — error prone compared to std::vector.

Equivalent std::array version: correct or wrong?   cpp cpp

Front

Is this correct?

#include <array>
#include <numeric>

class Ufds {
private:
  std::array<int, 10> parent;
public:
  Ufds() {
    std::iota(parent.begin(), parent.end(), 0);
  }
};

Back

Correct for compile-time fixed size.

But size 10 is hardcoded — not parameterized.

std::array<T, N> requires N be a compile-time constant.

For runtime size, must use std::vector.

std::array vs vector vs C-style array   cpp cpp

Front

Compare std::array<T, N>, std::vector<T>, and T* for Ufds parent array

Back

Type Size Cleanup Use when
std::array<T, N> compile-time automatic size known at compile time
std::vector<T> runtime automatic size known at runtime
T* p = new T[n] runtime manual (delete[]) legacy code only

Vector vs Array: is vector backed by array?   cpp cpp

Front

Is std::vector backed by an array? How does it support variable length?

Back

Yes, std::vector allocates a contiguous memory block (like a heap array).

Typical implementation: three pointers

  • start — pointer to data
  • finish — pointer to last element
  • end_of_storage — pointer to end of allocated capacity
vector<int> v(5);
v.push_back(1);  // may trigger reallocation if capacity exceeded

When capacity is exceeded, vector: allocates larger block, copies elements, deallocates old block.

Vector size vs capacity   cpp cpp

Front

What is the difference between size() and capacity() in std::vector?

Back

  • size() — number of actual elements stored
  • capacity() — allocated storage space
std::vector<int> v(3);  // size=3, capacity=3
v.push_back(1);        // size=4, capacity may be >=4
v.push_back(1);        // size=5, capacity may be >=5

reserve(n) pre-allocates capacity without resizing.

Comparison: which Ufds storage to use?   cpp cpp

Front

When would you choose std::array, std::vector, or T* new for Ufds parent array?

Back

std::array<T, N> — only if N is known at compile time

std::vector<T> — if size is determined at runtime (typical Ufds case)

T* new T[n] — legacy code only; vector is safer and equivalent performance

For Ufds: std::vector is the idiomatic choice because size is runtime-determined.

C-style array key properties   cpp cpp

Front

What are key properties of C-style arrays (raw arrays)?

Back

int arr[5];         // fixed size, stack-allocated
int* p = new int[n]; // dynamic size, heap-allocated
  • Decay to pointer on function call (lose size info)
  • No bounds checking
  • sizeof(arr) gives bytes, not element count
  • Must manage lifetime manually for heap arrays

C-style arrays in C++ are generally avoided in favor of std::array~/~std::vector.

std::iota for Ufds initialization   cpp cpp

Front

How to use std::iota to initialize Ufds parent array to [0, 1, 2, ..., n-1]

Back

#include <numeric>

std::vector<int> parent(n);
std::iota(parent.begin(), parent.end(), 0);

iota fills the range with consecutive values starting from given start value

Path compression in find()   cpp datastructure

Front

What does "path compression" mean in find()?

Back

After finding the root, point each visited node directly to the root:

parent[x] = find(parent[x]);  // compresses path

This flattens the tree structure, making future find() calls O(α(n)) ≈ O(1)

Original Ufds mistakes: is this correct?   cpp cpp

Front

Is this correct?

class Udfs {
  int parent[];
  vector<int> p;
public:
  Ufds(int n) {
    p = new vector<int>;
  }
  ~Ufds() {
    p ? free p;
  }
};

Back

Wrong. Multiple errors:

  1. int parent[] — illegal incomplete array type
  2. Ufds(int n) constructor but class is Udfs (typo)
  3. p = new vector<int> — can't assign pointer to vector
  4. free p — can't free a vector, and p is not a pointer
  5. Ufds destructor but class is Udfs (typo)

Heap allocation: what and why?   cpp cpp

Front

What does "heap allocated" mean? Why use it?

Back

Heap allocation with new lives until delete is called or program ends.

Stack allocation (local variables) is自动 freed when out of scope.

int arr[10];           // stack — freed when function returns
int* p = new int[10]; // heap — lives until delete[]

Heap needed when: size unknown at compile time, or object must outlive scope.