3.3.7. QuadTree
3.3.7.1. Image compression With QuadTree
Quadtree is a tree data structure in which each internal node either is a leaf node or has exactly 4 children. Quadtrees are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions. One of the common use cases of quadtree is image compression.
If all the 4 leaf nodes have the same value, we can use one leaf node to represent all of them. for example:
Input Image: Quadtree representation:
+---------+--------+ +---------+--------+
| 2 | 2 | 3 | 3 | | | |
+----|----|----|---| | 2 | 3 |
| 2 | 2 | 3 | 3 | | | |
+----+----|--------| ====> +----+----|--------|
| 4 | 2 | 5 | 5 | | 4 | 2 | |
+----|----|----|---| +----|----| 5 |
| 2 | 3 | 5 | 5 | | 2 | 3 | |
+----+----+--------+ +----+----+--------+
Design the quadtree data structure and write a function that builds a quadtree for an input image, where image will be given as a two-d array of integers.
We can split the matrix recursively to 4 sub-matrices a, b, c and d as the following:
+-----------------------------> (Y Axis)
| y1 ym y2
| x1 +---------+--------+
| | | |
| +- a -|- b -|
| | | |
| xm +----+----|--------|
| | | |
| +- c -|- d -|
| | | |
| x2 +----+----+--------+
|
v (X Axis)
Please be noted that the x1, y1, x2, y2 are inclusive indices.
1#include <gtest/gtest.h>
2using namespace std;
3
4namespace ns_quadtree_image_compression {
5 struct quadtree {
6 int val; // leaf node has value
7 quadtree *cn[4] = {}; // children number is 4, so it is called quad
8 quadtree(int v) : val(v) {} // construct a leaf node
9 quadtree(quadtree *a, quadtree *b, quadtree *c, quadtree *d) { // construct an inner node
10 cn[0] = a, cn[1] = b, cn[2] = c, cn[3] = d;
11 }
12 bool is_leaf() {return all_of(cn, cn + 3, [](quadtree *x) { return x == nullptr; });}
13 };
14
15 bool valid_index(vector<vector<int>> &m, int x1, int x2, int y1, int y2) {
16 return x1 <= x2 and y1 <= y2 and x2 < m.size() and y2 < m[0].size();
17 }
18
19 quadtree* dfs(vector<vector<int>> &m, int x1, int x2, int y1, int y2) {
20 if (x1 == x2 and y1 == y2) { return new quadtree(m[x1][y1]); }
21 quadtree *a = nullptr, *b = nullptr, *c = nullptr, *d = nullptr;
22 int xm = x1 + (x2 - x1) / 2, ym = y1 + (y2 - y1) / 2;
23 if (valid_index(m, x1, xm, y1, ym)) a = dfs(m, x1, xm, y1, ym);
24 if (valid_index(m, x1, xm, ym + 1, y2)) b = dfs(m, x1, xm, ym + 1, y2);
25 if (valid_index(m, xm + 1, x2, y1, ym)) c = dfs(m, xm + 1, x2, y1, ym);
26 if (valid_index(m, xm + 1, x2, ym + 1, y2)) d = dfs(m, xm + 1, x2, ym + 1, y2);
27 if (a == nullptr or b == nullptr or c == nullptr or d == nullptr)
28 return new quadtree(a, b, c, d);
29 if (a->val == b->val and b->val == c->val and c->val == d->val) // merge the same value leaf nodes
30 return new quadtree(a->val);
31 return new quadtree(a, b, c, d); // not merge
32 }
33
34 quadtree *make_quadtree(vector<vector<int>> &m) {
35 if (m.empty() or m[0].empty()) return nullptr;
36 int R = m.size(), C = m[0].size();
37 return dfs(m, 0, R - 1, 0, C - 1);
38 }
39} // namespace ns_quadtree_image_compression
40using namespace ns_quadtree_image_compression;
41TEST(quadtree, a) {
42 vector<vector<int>> m = {{2, 2, 3, 3}, {2, 2, 3, 3}, {4, 2, 5, 5}, {2, 3, 5, 5}};
43 quadtree *q = make_quadtree(m);
44 EXPECT_EQ(q->cn[0]->val, 2);
45 EXPECT_TRUE(q->cn[0]->is_leaf());
46 EXPECT_FALSE(q->is_leaf());
47 m = {{2, 2, 3, 3, 3}, {2, 2, 3, 3, 3}, {4, 2, 5, 5, 5}, {2, 3, 5, 5, 5}};
48 quadtree *q2 = make_quadtree(m);
49 EXPECT_EQ(q2->cn[0]->val, 2);
50 EXPECT_TRUE(q2->cn[0]->is_leaf());
51 EXPECT_FALSE(q2->is_leaf());
52}
Why is this question good?
Quadtree is a relatively new concept to most candidates. This can test candidate’s ability to handle a new concept. Also this is a decent recursion problem. If candidate can understand this very well and sort out the recursion steps and termination condition, this problem can be solved with very simple and elegant code. Otherwise, the code could be very messy. So, ability to handle new concept and clean coding would be two main things to test from this question.
👽 Possible follow up: Speed up the construction algorithm: build sub-trees concurrently
3.3.7.2. K Nearest Neighbours
The self-driving car needs refueling or maintenance. Please find and return to the gas station within K distance near the car.
y
^
| (tl)
| +-----------
| | |
| | |
| -----------+
| (br)
+-------------------> x (coordinates of 1st quadrant)
1#include <gtest/gtest.h>
2#include <bits/stdc++.h>
3
4using namespace std;
5namespace ns_spatial_indices{
6struct Point {
7 double x, y;
8 Point(double _x=0, double _y=0) : x(_x), y(_y) {}
9 bool operator==(const Point& p) {return x==p.x and y==p.y;}
10};
11
12struct BB { // bounding box or RECT
13 Point tl, br; // top-left and bottom-right
14 BB(Point x={}, Point y={}) { tl = x, br = y; }
15 int width() { return br.x - tl.x; }
16 int height() { return br.y - tl.y; }
17 Point centroid() { return Point{(br.x - tl.x) / 2.0, (br.y - tl.y) / 2.0}; }
18 bool overlapped(const BB &o) {
19 return !(o.tl.x > br.x or o.br.x < tl.x or o.tl.y > br.y or o.br.y < tl.y);
20 }
21 bool contain(const Point &p) const {
22 return tl.x <= p.x and p.x <= br.x and tl.y <= p.y and p.y <= br.y;
23 }
24};
25
26struct Node { // quadtree node contains point + data
27 Point pt;
28 int data;
29 Node(Point _pt, int _data) : pt(_pt), data(_data) {}
30 Node(double x, double y, int _data) : pt(x, y), data(_data) {}
31 Node() : data(0) {}
32};
33
34struct comp {
35 bool operator()(pair<int, Node*> a, pair<int, Node*> b) { return a.first > b.first; }
36};
37
38using PriorityQueueT = priority_queue<pair<int, Node*>, vector<pair<int, Node*>>, comp>;
39
40int distance_square(const Point &a, const Point &b) {
41 int x = a.x - b.x, y = a.y - b.y;
42 return x * x + y * y;
43}
44
45class quadtree { // The main quadtree class
46 BB bb; // bounding box
47
48 vector<Node*> nodes; // all nodes located in bb/quadtree
49 array<quadtree*, 4> children; // Children of this tree - *tl, *tr, *bl, *br
50 const int MIN_BB_LEN = 4;
51
52 Node* nn_dfs(const Point &p, int &mx2);
53 void knn_dfs(const Point &p, int &mx2, PriorityQueueT &);
54 void neighbors_in_circle_dfs(const Point &p, int distance, vector<Node*> &);
55 void neighbors_in_rect_dfs(const BB &o, vector<Node*>&);
56
57public:
58 quadtree(Point topL, Point botR);
59 quadtree(const BB &b);
60 void insert(Node*);
61 // point query
62 Node*point_query(Point); // search point to get Node(and data)
63 // range_query
64 vector<Node*> neighbors_in_rect(const BB &);
65 vector<Node*> neighbors_in_circle(const Point &p, int distance); // neighbors in k miles
66 Node*nn(const Point &); // nearest neighbor
67 vector<Node*> knn(const Point &p, int k); // k nearest neighbor
68 bool inBB(Point); // inside bounding box
69};
70
71quadtree::quadtree(Point topL, Point botR) { // constructor 1
72 bb = {topL, botR};
73 fill(children.begin(), children.end(), nullptr);
74}
75
76quadtree::quadtree(const BB &b) { // constructor 2
77 bb = b;
78 fill(children.begin(), children.end(), nullptr);
79}
80
81// Check if current quadtree contains the point
82bool quadtree::inBB(Point p) { // depends on the direction of coordinate system
83 return (p.x >= bb.tl.x and p.x <= bb.br.x and p.y >= bb.tl.y and p.y <= bb.br.y);
84}
85
86// Insert a node into the quadtree
87void quadtree::insert(Node*node) {
88 if (node == NULL or !inBB(node->pt)) // Current quad cannot contain it
89 return;
90 // We are at a quad of unit area, We cannot subdivide this quad further
91 if (bb.width() <= MIN_BB_LEN and bb.height() <= MIN_BB_LEN) {
92 nodes.push_back(node);
93 return;
94 }
95
96 if (bb.centroid().x >= node->pt.x) {
97 if (bb.centroid().y >= node->pt.y) { // Indicates tl
98 if (children[0] == NULL)
99 children[0] = new quadtree(BB{bb.tl, {bb.centroid().x, bb.centroid().y}});
100 children[0]->insert(node);
101 } else { // Indicates bl
102 if (children[1] == NULL)
103 children[1] = new quadtree(Point(bb.tl.x, (bb.tl.y + bb.br.y) / 2),
104 Point((bb.tl.x + bb.br.x) / 2, bb.br.y));
105 children[1]->insert(node);
106 }
107 } else { // Indicates tr
108 if ((bb.tl.y + bb.br.y) / 2 >= node->pt.y) {
109 if (children[2] == NULL)
110 children[2] = new quadtree(Point((bb.tl.x + bb.br.x) / 2, bb.tl.y),
111 Point(bb.br.x, (bb.tl.y + bb.br.y) / 2));
112 children[2]->insert(node);
113 } else { // Indicates br
114 if (children[3] == NULL)
115 children[3] = new quadtree(Point((bb.tl.x + bb.br.x) / 2, (bb.tl.y + bb.br.y) / 2),
116 Point(bb.br.x, bb.br.y));
117 children[3]->insert(node);
118 }
119 }
120}
121
122// Find a node in a quadtree
123Node*quadtree::point_query(Point p) {
124 // Current quad cannot contain it
125 if (!inBB(p)) return NULL;
126 // We are at a quad of unit length, We cannot subdivide this quad further
127 if (!nodes.empty()) { // ONLY leaf nodes have data information
128 for (Node*nd : nodes) {
129 if (nd->pt.x == p.x and nd->pt.y == p.y) return nd;
130 }
131 return NULL;
132 }
133 if ((bb.tl.x + bb.br.x) / 2 >= p.x) {
134 if ((bb.tl.y + bb.br.y) / 2 >= p.y) { // Indicates tl
135 return children[0] ? children[0]->point_query(p) : NULL;
136 } else { // Indicates bl
137 return children[1] ? children[1]->point_query(p) : NULL;
138 }
139 } else {
140 if ((bb.tl.y + bb.br.y) / 2 >= p.y) { // Indicates tr
141 return children[2] ? children[2]->point_query(p) : NULL;
142 } else { // Indicates br
143 return children[3] ? children[3]->point_query(p) : NULL;
144 }
145 }
146}
147
148vector<Node*> quadtree::neighbors_in_rect(const BB &o) {
149 vector<Node*> v;
150 neighbors_in_rect_dfs(o, v);
151 return v;
152}
153
154void quadtree::neighbors_in_rect_dfs(const BB &o, vector<Node*> &res) {
155 if (bb.overlapped(o)) {
156 if (!nodes.empty()) {
157 for (Node*n : nodes) {
158 if (o.contain(n->pt)) {
159 res.push_back(n);
160 }
161 }
162 } else {
163 for (int i = 0; i < 4; i++) {
164 if (children[i] == nullptr) continue;
165 children[i]->neighbors_in_rect_dfs(o, res);
166 }
167 }
168 }
169}
170
171vector<Node*> quadtree::neighbors_in_circle(const Point &p, int distance) {
172 vector<Node*> res;
173 neighbors_in_circle_dfs(p, distance * distance, res);
174 return res;
175}
176
177void quadtree::neighbors_in_circle_dfs(const Point &p, int dd, vector<Node*> &res) {
178 if (!inBB(p)) { //////
179 // compare with (point ~ BB) distance; ~ To be improved!
180 int dis_square_to_BB = min(min((bb.tl.x - p.x) * (bb.tl.x - p.x),
181 (bb.br.x - p.x) * (bb.br.x - p.x)),
182 min((bb.tl.y - p.y) * (bb.tl.y - p.y),
183 (bb.br.y - p.y) * (bb.br.y - p.y)));
184 if (dd < dis_square_to_BB) return;
185 }
186 if (!nodes.empty()) { // quadtree leaf node
187 for (Node*n : nodes) {
188 int d = distance_square(p, n->pt);
189 if (d <= dd) res.emplace_back(n);
190 }
191 } else { // other qts
192 for (int i = 0; i < 4; i++) {
193 if (children[i] == nullptr) continue;
194 children[i]->neighbors_in_circle_dfs(p, dd, res);
195 }
196 }
197};
198
199Node*quadtree::nn(const Point &p) {
200 if (!inBB(p)) return NULL;
201 int distance = INT_MAX;
202 return nn_dfs(p, distance);
203}
204
205Node*quadtree::nn_dfs(const Point &p, int &mx2) {
206 if (!inBB(p)) { //////
207 // compare with (point - BB) distance; ~ To be improved!
208 int dis_square_to_BB = min(min((bb.tl.x - p.x) * (bb.tl.x - p.x),
209 (bb.br.x - p.x) * (bb.br.x - p.x)),
210 min((bb.tl.y - p.y) * (bb.tl.y - p.y),
211 (bb.br.y - p.y) * (bb.br.y - p.y)));
212 if (mx2 < dis_square_to_BB) return NULL;
213 }
214 Node*res = NULL;
215 if (!nodes.empty()) { // leaf qt node
216 for (Node*n : nodes) {
217 int d = distance_square(p, n->pt);
218 if (d < mx2) mx2 = d, res = n;
219 }
220 } else { // other qts
221 for (int i = 0; i < 4; i++) {
222 if (children[i] == nullptr) continue;
223 auto tmp = children[i]->nn_dfs(p, mx2);
224 if (tmp) {
225 res = tmp;
226 }
227 }
228 }
229 return res;
230}
231
232vector<Node*> quadtree::knn(const Point &p, int k) {
233 if (k <= 0) return {};
234 vector<Node*> res;
235 priority_queue<pair<int, Node*>, vector<pair<int, Node*>>, comp> q;
236 int distance = INT_MAX;
237 knn_dfs(p, distance, q);
238 while (k-- and !q.empty()) {
239 res.push_back(q.top().second), q.pop();
240 }
241 return res;
242}
243
244void quadtree::knn_dfs(const Point &p, int &mx2, PriorityQueueT &q) {
245 if (!inBB(p)) { //////
246 // compare with (point - BB) distance; ~ To be improved!
247 int dis_square_to_BB = min(min((bb.tl.x - p.x) * (bb.tl.x - p.x),
248 (bb.br.x - p.x) * (bb.br.x - p.x)),
249 min((bb.tl.y - p.y) * (bb.tl.y - p.y),
250 (bb.br.y - p.y) * (bb.br.y - p.y)));
251 if (mx2 < dis_square_to_BB) return;
252 }
253 if (!nodes.empty()) { // leaf qt node
254 for (Node*n : nodes) {
255 int d = distance_square(p, n->pt);
256 q.emplace(d, n);
257 }
258 } else { // other qts
259 for (int i = 0; i < 4; i++) {
260 if (children[i] == nullptr) continue;
261 children[i]->knn_dfs(p, mx2, q);
262 }
263 }
264}
265}
266
267using namespace ns_spatial_indices;
268
269TEST(all_points_in_Kmiles, a) {
270 quadtree qt(Point(0, 0), Point(8, 8));
271 vector<Node> vn = {{1., 1., 1},
272 {2., 5., 2},
273 {7., 6., 3},
274 {3., 0., 0},
275 {7., 7., 7}};
276 for (auto &n: vn) qt.insert(&n);
277 auto ns = qt.neighbors_in_rect({Point(0, 0), Point(8, 8)});
278 EXPECT_EQ(ns.size(), 5);
279 ns = qt.neighbors_in_rect({Point(0, 0), Point(1, 1)});
280 EXPECT_EQ(ns.size(), 1);
281 auto r = qt.knn(Point(3, 3), 2);
282 EXPECT_EQ(r[0]->data, 2);
283 EXPECT_EQ(r[1]->data, 1);
284 EXPECT_TRUE(qt.point_query(Point(1, 1))->data == 1);
285 EXPECT_TRUE(qt.point_query(Point(2, 5))->data == 2);
286 EXPECT_TRUE(qt.point_query(Point(7, 6))->data == 3);
287 EXPECT_TRUE(nullptr == qt.point_query(Point(5, 5)));
288 EXPECT_TRUE(qt.nn(Point(3, 3))->pt == Point(2, 5));
289 EXPECT_TRUE(qt.nn(Point(2, 3))->pt == Point(2, 5));
290 EXPECT_TRUE(qt.nn(Point(2, 4))->pt == Point(2, 5));
291 EXPECT_TRUE(qt.nn(Point(2, 1))->pt == Point(1, 1));
292 auto nns = qt.neighbors_in_circle(Point(3, 3), 5);
293 EXPECT_EQ(nns.size(), 4);
294}
https://blog.insightdatascience.com/planting-quadtrees-for-apache-flink-b396ebc80d35
http://danielblazevski.github.io/assets/player/KeynoteDHTMLPlayer.html#12
https://developer.apple.com/documentation/gameplaykit/gkquadtree
https://www.1point3acres.com/bbs/forum.php?mod=viewthread&tid=434773&highlight=cruise