* Ufds: Union-Find Disjoint Set :cpp:datastructure: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Write a minimal Ufds class with vector parent, constructor initializing parents to self ** Back #+begin_src c++ #include #include class Ufds { private: std::vector parent; public: Ufds(int n) : parent(n) { std::iota(parent.begin(), parent.end(), 0); } }; #+end_src * Ufds find() with path compression :cpp:datastructure: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Write the ~find()~ method for Ufds with path compression ** Back #+begin_src c++ int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } #+end_src * Concise Ufds find() :cpp:datastructure: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? What does it do? #+begin_src c++ int find(int x) { if (parent[x] == x) return x; return parent[x] = find(parent[x]); } #+end_src ** 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What is the difference between these two Ufds constructors? #+begin_src c++ // 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); } #+end_src ** 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What does ~resize(n)~ do on a ~std::vector~? ** 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 #+begin_src c++ std::vector v; v.resize(5); // v = {0, 0, 0, 0, 0} #+end_src * Ufds destructor: when to omit :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? #+begin_src c++ Ufds(int n) { p.resize(n); std::iota(p.begin(), p.end(), 0); } #+end_src ** 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? #+begin_src c++ std::vector p; Ufds(int n) { p = new std::vector(n); } #+end_src ** Back Wrong. ~new std::vector(n)~ returns a ~vector*~ (pointer), but ~p~ is a ~vector~ (not a pointer). Can't assign a pointer to a vector. * What does new return? :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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*~ #+begin_src c++ int* p1 = new int; // single int int* p2 = new int[10]; // array of 10 ints #+end_src * What does delete do? :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What does ~delete~ do? ~delete[]~? ** Back ~delete~ frees a single object allocated with ~new~ ~delete[]~ frees an array allocated with ~new[]~ #+begin_src c++ int* p1 = new int; int* p2 = new int[10]; delete p1; // free single object delete[] p2; // free array #+end_src Mismatching ~delete~ with ~new[]~ causes undefined behavior. * Is this correct Ufds with C-style array? :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? #+begin_src c++ class Ufds { private: int* parent; public: Ufds(int n) : parent(new int[n]) {} ~Ufds() { delete[] parent; } }; #+end_src ** 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? #+begin_src c++ #include #include class Ufds { private: std::array parent; public: Ufds() { std::iota(parent.begin(), parent.end(), 0); } }; #+end_src ** Back Correct for compile-time fixed size. But size ~10~ is hardcoded — not parameterized. ~std::array~ requires ~N~ be a compile-time constant. For runtime size, must use ~std::vector~. * std::array vs vector vs C-style array :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Compare ~std::array~, ~std::vector~, and ~T*~ for Ufds parent array ** Back | Type | Size | Cleanup | Use when | |------------------+--------------+-------------------+----------------------------| | ~std::array~ | compile-time | automatic | size known at compile time | | ~std::vector~ | 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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 #+begin_src c++ vector v(5); v.push_back(1); // may trigger reallocation if capacity exceeded #+end_src When capacity is exceeded, vector: allocates larger block, copies elements, deallocates old block. * Vector size vs capacity :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What is the difference between ~size()~ and ~capacity()~ in ~std::vector~? ** Back - ~size()~ — number of actual elements stored - ~capacity()~ — allocated storage space #+begin_src c++ std::vector 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 #+end_src ~reserve(n)~ pre-allocates capacity without resizing. * Comparison: which Ufds storage to use? :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front When would you choose ~std::array~, ~std::vector~, or ~T* new~ for Ufds parent array? ** Back ~std::array~ — only if N is known at compile time ~std::vector~ — 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What are key properties of C-style arrays (raw arrays)? ** Back #+begin_src c++ int arr[5]; // fixed size, stack-allocated int* p = new int[n]; // dynamic size, heap-allocated #+end_src - 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front How to use ~std::iota~ to initialize Ufds parent array to ~[0, 1, 2, ..., n-1]~ ** Back #+begin_src c++ #include std::vector parent(n); std::iota(parent.begin(), parent.end(), 0); #+end_src ~iota~ fills the range with consecutive values starting from given start value * Path compression in find() :cpp:datastructure: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front What does "path compression" mean in ~find()~? ** Back After finding the root, point each visited node directly to the root: #+begin_src c++ parent[x] = find(parent[x]); // compresses path #+end_src This flattens the tree structure, making future ~find()~ calls O(α(n)) ≈ O(1) * Original Ufds mistakes: is this correct? :cpp:cpp: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** Front Is this correct? #+begin_src c++ class Udfs { int parent[]; vector p; public: Ufds(int n) { p = new vector; } ~Ufds() { p ? free p; } }; #+end_src ** 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~ — 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: :PROPERTIES: :ANKI_NOTE_TYPE: Basic :END: ** 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. #+begin_src c++ int arr[10]; // stack — freed when function returns int* p = new int[10]; // heap — lives until delete[] #+end_src Heap needed when: size unknown at compile time, or object must outlive scope.