quadtree


Data Structure: Quad Tree

Comprehensive guide on the Quad Tree data structure in C++. This topic is particularly relevant to your interests in scientific computing and artificial intelligence, as quad trees are often used in spatial partitioning and image processing.

A Quad Tree is a tree data structure in which each internal node has exactly four children. It's most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions. Quad trees are particularly useful in applications involving spatial data, such as image processing, geographic information systems, and collision detection in games.

Key Characteristics

Example 1: Basic Quad Tree Implementation

This example demonstrates a basic implementation of a Quad Tree for storing points in a 2D space.

#include <iostream>
#include <vector>
#include <cmath>

class Point {
public:
    double x, y;
    Point(double x = 0, double y = 0) : x(x), y(y) {}
};

class QuadTree {
private:
    static const int MAX_CAPACITY = 4;
    static const int MAX_DEPTH = 6;

    struct Node {
        std::vector<Point> points;
        Node* children[4];
        double x, y, width, height;
        int depth;

        Node(double x, double y, double width, double height, int depth)
            : x(x), y(y), width(width), height(height), depth(depth) {
            for (int i = 0; i < 4; ++i) children[i] = nullptr;
        }

        ~Node() {
            for (int i = 0; i < 4; ++i) delete children[i];
        }
    };

    Node* root;

    bool isLeaf(Node* node) const {
        return node->children[0] == nullptr;
    }

    int getQuadrant(Node* node, const Point& p) const {
        double midX = node->x + node->width / 2;
        double midY = node->y + node->height / 2;
        if (p.x < midX) {
            return (p.y < midY) ? 0 : 2;
        } else {
            return (p.y < midY) ? 1 : 3;
        }
    }

    void split(Node* node) {
        double subWidth = node->width / 2;
        double subHeight = node->height / 2;
        int depth = node->depth + 1;
        node->children[0] = new Node(node->x, node->y, subWidth, subHeight, depth);
        node->children[1] = new Node(node->x + subWidth, node->y, subWidth, subHeight, depth);
        node->children[2] = new Node(node->x, node->y + subHeight, subWidth, subHeight, depth);
        node->children[3] = new Node(node->x + subWidth, node->y + subHeight, subWidth, subHeight, depth);
    }

    void insert(Node* node, const Point& p) {
        if (isLeaf(node)) {
            if (node->points.size() < MAX_CAPACITY || node->depth >= MAX_DEPTH) {
                node->points.push_back(p);
            } else {
                split(node);
                for (const auto& point : node->points) {
                    insert(node->children[getQuadrant(node, point)], point);
                }
                node->points.clear();
                insert(node->children[getQuadrant(node, p)], p);
            }
        } else {
            insert(node->children[getQuadrant(node, p)], p);
        }
    }

public:
    QuadTree(double width, double height) : root(new Node(0, 0, width, height, 0)) {}
    ~QuadTree() { delete root; }

    void insert(const Point& p) {
        insert(root, p);
    }

    void print(Node* node, int level = 0) const {
        if (node == nullptr) return;

        std::string indent(level * 2, ' ');
        std::cout << indent << "Node at (" << node->x << ", " << node->y << ") with size " 
                  << node->width << "x" << node->height << std::endl;

        if (isLeaf(node)) {
            for (const auto& p : node->points) {
                std::cout << indent << "  Point: (" << p.x << ", " << p.y << ")" << std::endl;
            }
        } else {
            for (int i = 0; i < 4; ++i) {
                print(node->children[i], level + 1);
            }
        }
    }

    void print() const {
        print(root);
    }
};

int main() {
    QuadTree qt(100, 100);

    qt.insert(Point(10, 10));
    qt.insert(Point(20, 20));
    qt.insert(Point(30, 30));
    qt.insert(Point(40, 40));
    qt.insert(Point(50, 50));
    qt.insert(Point(60, 60));
    qt.insert(Point(70, 70));
    qt.insert(Point(80, 80));

    qt.print();

    return 0;
}

Explanation

Example 2: Quad Tree for Image Compression

This example shows how a Quad Tree can be used for simple image compression.

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>

class Color {
public:
    unsigned char r, g, b;
    Color(unsigned char r = 0, unsigned char g = 0, unsigned char b = 0) : r(r), g(g), b(b) {}
};

class Image {
private:
    std::vector<std::vector<Color>> pixels;
    int width, height;

public:
    Image(int w, int h) : width(w), height(h), pixels(h, std::vector<Color>(w)) {}

    void setPixel(int x, int y, const Color& c) {
        if (x >= 0 && x < width && y >= 0 && y < height) {
            pixels[y][x] = c;
        }
    }

    Color getPixel(int x, int y) const {
        if (x >= 0 && x < width && y >= 0 && y < height) {
            return pixels[y][x];
        }
        return Color();
    }

    int getWidth() const { return width; }
    int getHeight() const { return height; }
};

class QuadTree {
private:
    struct Node {
        Color color;
        Node* children[4];
        int x, y, size;

        Node(int x, int y, int size) : x(x), y(y), size(size) {
            for (int i = 0; i < 4; ++i) children[i] = nullptr;
        }

        ~Node() {
            for (int i = 0; i < 4; ++i) delete children[i];
        }
    };

    Node* root;
    int threshold;

    bool isLeaf(Node* node) const {
        return node->children[0] == nullptr;
    }

    Color averageColor(const Image& image, int x, int y, int size) const {
        long long r = 0, g = 0, b = 0;
        int count = 0;
        for (int i = y; i < y + size; ++i) {
            for (int j = x; j < x + size; ++j) {
                Color c = image.getPixel(j, i);
                r += c.r; g += c.g; b += c.b;
                ++count;
            }
        }
        return Color(r / count, g / count, b / count);
    }

    int colorDifference(const Color& c1, const Color& c2) const {
        return std::abs(c1.r - c2.r) + std::abs(c1.g - c2.g) + std::abs(c1.b - c2.b);
    }

    void buildTree(Node* node, const Image& image) {
        Color avgColor = averageColor(image, node->x, node->y, node->size);
        node->color = avgColor;

        if (node->size == 1) return;

        bool shouldSplit = false;
        for (int y = node->y; y < node->y + node->size; ++y) {
            for (int x = node->x; x < node->x + node->size; ++x) {
                if (colorDifference(image.getPixel(x, y), avgColor) > threshold) {
                    shouldSplit = true;
                    break;
                }
            }
            if (shouldSplit) break;
        }

        if (shouldSplit) {
            int newSize = node->size / 2;
            node->children[0] = new Node(node->x, node->y, newSize);
            node->children[1] = new Node(node->x + newSize, node->y, newSize);
            node->children[2] = new Node(node->x, node->y + newSize, newSize);
            node->children[3] = new Node(node->x + newSize, node->y + newSize, newSize);

            for (int i = 0; i < 4; ++i) {
                buildTree(node->children[i], image);
            }
        }
    }

    void reconstruct(Node* node, Image& image) const {
        if (isLeaf(node)) {
            for (int y = node->y; y < node->y + node->size; ++y) {
                for (int x = node->x; x < node->x + node->size; ++x) {
                    image.setPixel(x, y, node->color);
                }
            }
        } else {
            for (int i = 0; i < 4; ++i) {
                reconstruct(node->children[i], image);
            }
        }
    }

public:
    QuadTree(const Image& image, int threshold) : threshold(threshold) {
        root = new Node(0, 0, std::max(image.getWidth(), image.getHeight()));
        buildTree(root, image);
    }

    ~QuadTree() { delete root; }

    Image reconstruct() const {
        Image result(root->size, root->size);
        reconstruct(root, result);
        return result;
    }
};

int main() {
    // Create a sample 8x8 image
    Image originalImage(8, 8);
    for (int y = 0; y < 8; ++y) {
        for (int x = 0; x < 8; ++x) {
            if (x < 4 && y < 4) {
                originalImage.setPixel(x, y, Color(255, 0, 0)); // Red quadrant
            } else if (x >= 4 && y < 4) {
                originalImage.setPixel(x, y, Color(0, 255, 0)); // Green quadrant
            } else if (x < 4 && y >= 4) {
                originalImage.setPixel(x, y, Color(0, 0, 255)); // Blue quadrant
            } else {
                originalImage.setPixel(x, y, Color(255, 255, 0)); // Yellow quadrant
            }
        }
    }

    // Create QuadTree with a threshold of 50
    QuadTree qt(originalImage, 50);

    // Reconstruct the image from the QuadTree
    Image reconstructedImage = qt.reconstruct();

    // Print original and reconstructed images
    std::cout << "Original Image:" << std::endl;
    for (int y = 0; y < 8; ++y) {
        for (int x = 0; x < 8; ++x) {
            Color c = originalImage.getPixel(x, y);
            std::cout << "(" << (int)c.r << "," << (int)c.g << "," << (int)c.b << ") ";
        }
        std::cout << std::endl;
    }

    std::cout << "\nReconstructed Image:" << std::endl;
    for (int y = 0; y < 8; ++y) {
        for (int x = 0; x < 8; ++x) {
            Color c = reconstructedImage.getPixel(x, y);
            std::cout << "(" << (int)c.r << "," << (int)c.g << "," << (int)c.b << ") ";
        }
        std::cout << std::endl;
    }

    return 0;
}

Explanation

Additional Considerations

  1. Performance: The efficiency of a Quad Tree depends on the distribution of data. In worst-case scenarios (e.g., all points in one quadrant), it can degenerate to O(n) complexity.

  2. Memory Usage: Quad Trees can be memory-intensive, especially for high-resolution images or dense point distributions.

  3. Balancing: For some applications, it might be necessary to implement balancing algorithms to maintain optimal tree structure.

  4. Variations: There are several variations of Quad Trees, such as Point Quad Trees, Region Quad Trees, and Compressed Quad Trees, each optimized for specific use cases.

Summary

Quad Trees are versatile data structures used in various applications involving 2D spatial data. They provide an efficient way to partition space and can significantly optimize operations like searching and collision detection in two-dimensional spaces.

In this guide, we explored two main applications of Quad Trees:

  1. A basic implementation for storing and organizing 2D points, which is useful for spatial indexing and nearest neighbor searches.
  2. An image compression technique that uses Quad Trees to represent images with varying levels of detail, potentially reducing storage requirements while preserving important features.

These examples demonstrate the flexibility of Quad Trees in handling different types of 2D data. The first example is particularly relevant to scientific computing applications, where efficient spatial data structures are often needed. The second example shows how Quad Trees can be applied to image processing tasks, which could be useful in computer vision applications within AI systems.

For further exploration, you might consider implementing more advanced features like efficient range queries, dynamic updates, or applying Quad Trees to specific problems in scientific simulations or game development.

Previous Page | Course Schedule | Course Content