题目
思路
个人思路
- 暴力分类
O ( m 2 ) O(m^2) O(m2)的复杂度进行分类, O ( n ) O(n) O(n)的复杂度进行比较,总复杂度 O ( m 2 n ) O(m^2n) O(m2n)
- 广义表
结构体:
struct node { int sz; //以当前结点的前驱结点为前缀的向量个数 linklist *head; //存相同前缀的当前维度结点链表 };
每次插入一组向量是一个递归的过程,插入时候去搜索当前维度相同的值的结点,如果发现不存在我现在要插入的结点,就直接插入该链表即可,当前结点的长度+1,要把更新的信息传回去,更新前继结点的个数+1(相当于多了一条路径)。这样表头的sz个数即整体的向量分类数
复杂度:每次查找当前维度不同值的复杂度是 O ( m ) O(m) O(m),每次插入递归的深度最多是 O ( n ) O(n) O(n),有m条向量要插入所以总复杂度也是 O ( m 2 n ) O(m^2n) O(m2n)。不过不难发现,每一层的查询时间可以用一个哈希表把理论时间降到 O ( 1 ) O(1) O(1),总复杂度可以降到 O ( n ∗ m ) O(n*m) O(n∗m),但考虑到执行不同情况的稳定性用红黑树使复杂度变成 O ( n m l o g ( m ) ) O(nmlog(m)) O(nmlog(m)),并且可以利用STL的map也方便实现。
书中给的解法笔记
文中说复杂度是 O ( n 2 m ) O(n^2m) O(n2m),不过可能是我没理解它的算法精髓,故这里只是贴一下文中的方法,并加上自己对这个算法的理解:
主函数:
分别是读入,分类以及输出答案并释放空间
1,读入:
将m个n维向量存到一个二维数组vect[m][n]中;ind相当于一开始每个向量都属于自己,类似于并查集的初始化;classind[m][2]的第一个表示所在类的开始位置,第二个表示所在类结束的位置,所以初始化结束的位置是m-1,相当于假设只有一类,之后的算法就一直分割
2,第column维的分类:
每次把该列的相同的数交换到一块,我们可以这样想,用ind表示一个向量分类完所属的位置,然后每次用双指针维护,完成一次分类后会发现和第一个向量在该列相同的向量分别归到前几行,由于ind的原因,在逻辑上的可能分为一类的第一种向量已经归为一类了
3,对当前已有的cnt个类对下一维再进行分类,返回值为新增了多少类
关于该函数的详细解释:
index表示当前已经有的类,column表示要划分的维度
classind可以马上得出要进一步进行划分的区间,因为ind已经使得前 c o l u m n − 1 column-1 column−1维的向量逻辑上相邻了
cnt表示当前已有的类数量-1,因为把x-1类分完剩余的一个类逻辑上也分完最终答案是cnt+1个类
cc表示细分之前分成一类的相邻又多出来的类数
c表示每细分一种类多出来的数量,所以cc+=c-1相当于如果还是只有1类那么就没新增,接着进入下一个类的划分
4,迭代已分类完成的后几维进行划分,由于初始化的原因,刚开始默认划分为一类:
实现
根据分类的算法写的版本:
#include <bits/stdc++.h>
using namespace std;
const int N=100;
const int M=100;
int l[M], r[M]; //某一类逻辑上划分完成后的左右区间,也就是最终这个区间内向量属于一类
int ind[M]; //表示一个当前的位置
int vect[M][N], m, n; //存m个n维向量
void init() {
cin >> m >> n;
for(int i=0; i<m; i++)
for(int j=0; j<n; j++)
cin >> vect[i][j];
for(int i=0; i<m; i++) //每个向量一开始的位置不变
ind[i]=i;
l[0]=0; r[0]=m-1; //一开始只有一个类区间为[0, m-1]
}
//把和[left, right]区间的以left的第col列相同一类分到一块,并返回这个类的右区间标记
int merge(int left, int right, int col) {
int val=vect[ind[left++]][col]; //用ind代表left这个位置的向量
while(left <= right) {
while(left <= right && vect[ind[left]][col] == val) left++; //找到第一个不等于val的向量
while(left <= right && vect[ind[right]][col] != val) right--; //找到最后一个和val同类的向量
if(left < right) {
swap(ind[left], ind[right]);
right--;
left++;
}
}
return left; //和二分中upper类似
}
//当前cnt+1个向量以第col维进行再划分,返回增加的种类数
int split(int cnt, int col) {
int now_add=0; //cnt个类划分后新形成的类数
for(int i=0; i<=cnt; i++) {
int left=l[i], right=r[i]; //第i个类所在的区间
int cur_classfiy_sz=0; //第i个类通过col维能够分成的区间个数
while(left<=right) {
int pre_left = left; //之前的右区间
if(cur_classfiy_sz == 1) r[i] = left-1; //更新第一个区间
left=merge(left, right, col); //将新区间的某个类划分好(放到前面,并返回下一个区间的开始)
cur_classfiy_sz++; //在当前类的区间又新划分了一个类
if(cur_classfiy_sz > 1) {
l[cnt + now_add + cur_classfiy_sz - 1] = pre_left; //新找到一个类更新
r[cnt + now_add + cur_classfiy_sz - 1] = left - 1; //新的右区间
//这几个数据的意思 cnt+now_add表示得到之前的类划分的表尾,再偏移本轮之前找到的新类cur_classfiy-1
}
}
now_add += cur_classfiy_sz-1; //因为每一轮至少都有一个类,但不一定增加
}
return now_add;
}
//对已有的1个类进行再划分,返回划分后的类数
int classfiy() {
int cnt=0; //一开始有没有新的类
for(int i=0; i<n; i++)
cnt += split(cnt, i); //对每一维已有的类进行再划分 默认有一个类
return cnt+1; //找到cnt个新类,共cnt+1个类
}
//对划分后的每个类的样子输出
void output(int cnt) {
bool vis[M];
memset(vis, false, sizeof(vis));
cout << "\n共划分为" << cnt << "个类,分别如下:\n" << endl;
for(int i=0; i<cnt-1; i++) {
cout << "第" << i+1 << "个类为:" << endl;
for(int j=l[i]; j<=r[i]; j++) {
vis[j] = true;
for(int z=0; z<n; z++)
cout << vect[ind[j]][z] << " ";
cout << "原位置 : " << ind[j] << " 现位置 : " << j << endl;
}
cout << endl;
}
cout << "第" << cnt << "个类为:" << endl;
for(int i=0; i<m; i++)
if(!vis[i]) {
vis[i] = true;
for(int j=0; j<n; j++)
cout << vect[ind[i]][j] << " ";
cout << "原位置 : " << ind[i] << " 现位置 : " << i << endl;
}
cout << "以上为" << cnt << "个类的划分情况" << endl;
}
int main() {
init(); //初始化及输入
output(classfiy()); //对m个n维向量分类并输出
return 0;
}
/*
6 4
3 5 7 9
4 3 7 5
3 5 7 9
2 1 4 6
3 5 7 9
2 1 4 6
*/
/*
10 4
3 5 7 9
4 3 7 5
3 5 7 9
2 1 4 6
2 1 6 6
3 5 7 9
2 1 4 6
3 7 7 9
4 6 3 3
1 1 4 6
*/
/*
10 4
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
3 5 7 9
*/
/*
4 3
1 2 3
4 5 6
7 8 9
10 11 12
*/
效果展示
效果展示:
总结
这个算法根据不同类地划分再进行更深层次地进一步划分,有种分治的意味。