Gặp vấn đề khi giải bài toán Clustering points bằng giải thuật Kruskal

Bài toán Clustering: Cho mặt phẳng tọa độ Oxy, có n điểm và số nguyên k là số nhóm điểm, tính d sao cho d có giá trị lớn nhất và bất kỳ 2 điểm nào thuộc 2 nhóm khác nhau đều có khoảng cách ngắn nhất là d.
Ví dụ:


Các điểm trên mặt phẳng được chia làm 3 nhóm và khoảng cách ngắn nhất thỏa mãn là khoảng cách giữa 2 điểm (4,6) và (9,8).
Đây là bài giải của em:

// Week5Problem2.cpp : This file contains the 'main' function. Program execution begins and ends there.
#include "pch.h"
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <cassert>
#include <vector>
#include <cmath>
using namespace std; 
struct DisjointSetsElement {
	int size, parent, rank;

	DisjointSetsElement(int size = 0, int parent = -1, int rank = 0) :
		size(size), parent(parent), rank(rank) {}
};
struct DisjointSets {
	int size;
	int max_table_size;
	vector <DisjointSetsElement> sets;

	DisjointSets(int size) : size(size), max_table_size(0), sets(size) {
		for (int i = 0; i < size; i++)
			sets[i].parent = i;
	}
	int getParent(int table) {
		// find parent and compress path
		int result = table;
		while (sets[result].parent != result)
		{
			result = sets[result].parent;
		}
		return result;
	}
	void merge(int destination, int source) {
		int realDestination = getParent(destination);
		int realSource = getParent(source);
		if (realDestination != realSource) {
			// merge two components
			// use union by rank heuristic
			// update max_table_size
			if (sets[realSource].rank > sets[realDestination].rank)
			{
				sets[realDestination].parent = realSource;

				sets[realSource].size += sets[realDestination].size;
				sets[realDestination].size = 0;
				if (max_table_size < sets[realSource].size) max_table_size = sets[realSource].size;

			}
			else
			{
				sets[realSource].parent = realDestination;

				sets[realDestination].size += sets[realSource].size;
				sets[realSource].size = 0;
				if (max_table_size < sets[realDestination].size) max_table_size = sets[realDestination].size;

				if (sets[realSource].rank == sets[realDestination].rank)
				{
					sets[realDestination].rank = sets[realDestination].rank + 1;
				}
			}
		}
	}
};
class minPriorityQueue {
private:
	vector<pair<double, int>> data_;
	int parent(int i)
	{
		return (i - 1) / 2;
	}
	int leftChild(int i)
	{
		return 2 * i + 1;
	}
	int rightChild(int i)
	{
		return 2 * i + 2;
	}
	void siftDown(int i)
	{
		if (i > (data_.size() - 2) / 2)
			return;
		int minIndex = i;
		int l = leftChild(i);
		if (l <= data_.size() - 1 && data_[l].first < data_[minIndex].first)
			minIndex = l;
		int r = rightChild(i);
		if (r <= data_.size() - 1 && data_[r].first < data_[minIndex].first)
			minIndex = r;
		if (i != minIndex)
		{
			swap(data_[i], data_[minIndex]);
			siftDown(minIndex);
		}
	}
	void siftUp(int i)
	{
		while (i > 0 && data_[parent(i)].first >= data_[i].first)
		{
			swap(data_[i], data_[parent(i)]);
			i = parent(i);
		}
	}
public:
	void push(pair<double, int> i)
	{
		data_.push_back(i);
		siftUp(data_.size() - 1);
	}
	pair<double, int> extractMin()
	{
		pair<double, int> result = data_[0];

		swap(data_[0], data_[data_.size() - 1]);
		data_.pop_back();
		if (data_.size() != 0)
			siftDown(0);
		return result;
	}
	bool empty()
	{
		if (data_.size() == 0)
			return 1;
		return 0;
	}
	void changePriority(double distance, int neighbour)
	{
		for (int i = 0; i < data_.size(); i++)
		{
			if (data_[i].second == neighbour)
			{
				data_[i].first = distance;
				siftUp(i);
				siftDown(i);
				return;
			}
		}
	}
};
double clustering(vector<int> x, vector<int> y, int k) {
	//write your code here
	//if k == 1, there're only one cluster
	if (k == 1) return 0;
	minPriorityQueue heap;
	vector<double> distance;
	vector<pair<int, int>> vertexs;
	int numbedge = 0;
	for (int i = 0; i < x.size(); i++)
	{
		for (int j = i + 1; j < x.size(); j++)
		{
			double distance = sqrt(pow(x[i] - x[j], 2) + pow(y[i] - y[j], 2));
			heap.push(make_pair(distance, numbedge));
			numbedge++;
			vertexs.push_back(make_pair(i,j));
		}
	}
	DisjointSets tables(x.size());
	int groups = x.size();
	pair<double, int> current;
	pair<int, int> vertex;
	while (!heap.empty())
	{
		current = heap.extractMin();
		vertex = vertexs[current.second];
		if (groups == k)
			break;
		// if 2 points are the same group, skip it
		if (tables.getParent(vertex.first) == tables.getParent(vertex.second))
			continue;
		// union 2 point together
		tables.merge(vertex.first, vertex.second);
		groups--;	
	}
	
	return current.first;
}

int main() {
	size_t n;
	int k;
	cin >> n;
	vector<int> x(n), y(n);
	for (size_t i = 0; i < n; i++) {
		cin >> x[i] >> y[i];
	}
	cin >> k;
	cout << setprecision(10) << clustering(x, y, k) << endl;
}

Trong phần giải thuật, em có sử dụng 2 Function min priority queue và disjoint set. Cả 2 function này đều chạy ổn, vì em chỉ copy lại hàm của bài tập trước, và nó đều đã pass được hết test case.
Ý tưởng giải bài toán là tính khoảng cách tất cả các đoạn thẳng, và đưa vào min priority queue. Trong đó có lưu distance value và địa chỉ được lưu trong vector vertexs tương ứng với 2 điểm. Sau đó dùng disjoint sets Union by rank 2 điểm lại với nhau, cho đến khi chỉ còn lại k nhóm. Rồi return distance tiếp theo. Tuy vậy, có lẽ em đã không để ý đến trường hợp nào đó còn sót, nên bài này fall ở test case 9/40. Mong các anh chị xem và gợi ý giúp em, em xin cảm ơn nhiều. :3

3 Likes

Sao bạn không up full đề bài lên nhỉ? k có ý nghĩa gì trong bài toán này, và làm thế nào để phân ra các điểm?

3 Likes


Đây là đề bài full, trong phần giới thiệu, đề có bảo rằng Clustering là bài toán cơ bản trong data mining. Disjoint sets có thể phân ra được các nhóm điểm. Trong table có tạo parent của mỗi điểm, và 1 nhóm điểm sẽ có cùng parent (hàm getParent trong disjoint sets trả về real parent). Và chỉ cần nhóm lại cho đến khi còn k nhóm, 2 điểm có cùng parent thì bỏ qua.
Trong phần hướng dẫn, nó có ghi là: “Think about ways of adopting the Kruskal’s algorithm for solving this problem.” Anh xem giúp em, cảm ơn anh nhiều :3
Đây là visualization của disjoint sets:
https://www.cs.usfca.edu/~galles/visualization/DisjointSets.html

1 Like

Chà, lần sau khi tạo topic thì up full đề lên, đừng cắt bớt chữ nhé.

Bài này mình nghĩ là thay vì chỉ ra 1 khả năng chia k nhóm rồi mới tính d thì thử suy nghĩ nếu có d thì sẽ chia k nhóm như thế nào, vì đề bài bắt tìm the largest possible value of d.

Còn dùng Kruskal có lẽ dùng là để nhóm các điểm lại. Dù sao thì hướng dẫn đã ghi gợi ý vậy rồi, chẳng nhẽ không xài.

Bài này là bài toán cơ bản của cái gì thì mình không quan tâm lắm, bài nào mà chả là cơ bản. 1+1=2 cũng có thể là coi là cơ sở của môn toán bạn học ở trường đó thôi.

4 Likes

Vậy, nếu có d thì sẽ chia k nhóm như thế nào? Nếu có d thì mình return luôn rồi chia làm chi nữa hả anh? Với lại, em tưởng anh nói rằng, ko có ý nghĩa gì trong bài toán này, nên em mới trả lời ý nghĩa của bài toán này.

Thấy bạn có tâm đặt câu hỏi nên cố gắng chút xíu đọc code vậy.
Mình dân ngoại đạo nên không rõ phần domain knowledge này lắm, nhưng code thì có vấn đề thắc mắc thế này:
Chỗ // if 2 points are the same group, skip it, thì có khả năng nào cả 2 điểm này đều là 2 điểm chưa được group vào nhóm nào (parrent = -1) không bạn? Nếu vậy thì phần continue chỗ đó có vẻ chưa đúng lắm nhỉ?

4 Likes

Mình không viết teencode nên câu hỏi k có ý nghĩa gì là hỏi về tham số k đề bài cho, chứ không phải có nghĩa là không. Đề bài tiếng Việt bạn cung cấp không rõ ràng, nhưng đề bài gốc đã nói rõ.

Đây là 1 cách tiếp cận đề bài khác, mình muốn bạn thử suy nghĩ. Đề nói là tìm d lớn nhất, tức là phải có 1 số giá trị d thoả mãn trước rồi mới biết d nào lớn nhất.

4 Likes

Chỉ có parent trỏ vào chính nó thôi, chứ không có parent = -1 nha bạn. Nếu 2 điểm chưa được group vào nhóm nào, thì nó sẽ group lại với nhau :3 cảm ơn bạn đã đọc code.

1 Like

Trước hết, sorry anh vì hiểu nhầm ý của anh. Còn phần gợi ý của anh, anh có thể gợi ý thêm xíu xíu nữa được không, anh gợi ý xong em không hiểu gì luôn. :grin:

Vi dụ như hoàn thiện hàm này:

bool is_partitionable_into_k_groups(int k, double d) {
    // kiểm tra xem có thể chia n điểm thành k nhóm thoả mãn
    // khoảng cách của 2 điểm bất kì thuộc 2 nhóm khác nhau có >= d hay không
}

Cách hoàn thiện như thế nào thì mình vẫn đang suy nghĩ thêm, tuy nhiên hướng chia các nhóm của mình là giả sử n điểm nằm cùng một nhóm, rồi dần dần mới chia bớt một số điểm ra khỏi nhóm lớn đó.

3 Likes

Em hiểu ý của anh rồi, :))) có thể dùng max priority queue, ngược lại với bài của em :v

chạy Kruskal tới khi nào còn k trees thì nhánh nối 2 trees lại thành 1 tree tiếp theo là đáp số :V

n <= 200 thì bèo quá :V :V

4 Likes

Chuẩn luôn anh ơi, nhưng em nghĩ mãi cũng chẳng ra corner case nào đấy, hoặc là thuật toán của em sai gì nữa. :v Nhưng mà, 200 điểm tạo ra 199! cạnh rồi mà còn bèo nữa hả.

n điểm tạo ra O(n^2) cạnh thôi chứ :V

5 Likes

À à hihi lộn :v chắc vì bài này chỉ áp dụng giải thuật Kruskal thôi chứ ko phải cách hay nên người ta cho số bé bé xíu, chứ cho số bự quá đứng máy luôn á. :)))

a mới vừa coi lại bài của em

		if (groups == k)
			break;

điều kiện break này là ko đúng đâu :V Lỡ edge tiếp theo nó nối 2 điểm trong cùng 1 trees thì groups đâu có giảm :V Điều kiện dừng phải là groups trước khi thêm nhánh==k, rồi groups sau khi thêm nhánh < k. Khi đó nhánh vừa mới thêm là đáp án (ko phải nhánh tiếp theo).

5 Likes

OMG, chân lý, ôi giời, cảm ơn anh nhiều lắm :))))

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?