/*
*2015.3.31 14:00更新
*上午刚写完这篇博客,下午就读到迭代器了。C++ primer中讲迭代器那节说道了->符号的意思,即(*ptr). 及将指针解引用之后再调用成员函数。这样一来理解就完全没有问题了,因为我定义collection *a; 实际上是定义了一个指针a,指向一个collection类
*所以在引用collection的成员函数时需要先解引用,得到a指针指向的collection类变量*a然后再调用它的成员函数。
*/
最近尝试写一下NFA到DFA转换的函数,用C++,但是想到集合的表示比较麻烦,一些高级的容器也还没学到,因此自己先写一个简单的collection类,1.0版本。
能实现的功能有:存储数字集合;能进行集合间比较O(N)及加O(M+N)减法O(N);能向集合中添加元素O(N)或删除元素O(N);能返回集合中第X小的元素O(1)
因为目前对C++理解较为有限,特别是写一个类的时候感到力不从心,因此很多地方应该都不是很符合规范,先将这个能实现基本功能的代码贴上来,再想一下以后的改进空间,之后写出更加完善的collection类。
/* *ColKang v1.0 *这个类用来定义集合,因为未学到容器,因此自己写一个类来实现 *此类仅用于存储数字 *目前使用数组存储,可用跳跃表优化。 */ #include<iostream> using namespace std; class collection { public: //构造函数 collection() { for (int i = 0; i < 1000; i++) this->data[i] = 0; this->length = 0; } //初始化集合 void init() { for (int i = 0; i < 1000; i++) this->data[i] = 0; this->length = 0; } //判断两个集合是否相等 bool equal(collection *b) { if (this->length != b->length) return false; else { for (int i = 0;i < this->length;i++) if (this->data[i] != b->data[i]) return false; } return true; } //得到集合的长度 void print_collection() { for (int i = 0; i<this->length; i++) cout << this->data[i] << ' '; cout << endl; } int search_element(int b) { int i = 0, j = this->length - 1, mid = 0; while (i<j) { mid = (i + j) / 2; if (this->data[mid] == b) break; else if (this->data[mid] > b) j = mid - 1; else i = mid + 1; } if (i > j) { cout << "There is not a element that equals with " << b << "in this collection" << endl; return -1; } } int get_length() { return this->length; } //得到集合中第X+1小的元素 int get_element(int x) { return this->data[x]; } //集合添加元素,时间复杂度为O(N) void add_element(int b) { int i = 0; while (this->data[i] <= b&&this->data[i] != -1) i++; for (int j = this->length - 1; j >= i; j--) this->data[j + 1] = this->data[j]; this->data[i] = b; this->length++; } //删除指定元素 void delete_element(int b) { int t; if (t = this->search_element(b)) { while (t < this->length - 1) { this->data[t] = this->data[t + 1]; t++; } length--; } } //进行集合加法 collection add_collection(collection *b) { //使用归并排序的合并部分 collection *c; int i = 0, j = 0; while (i <= this->length && j <= b->length) { c->length++; if (this->data[i] = b->data[j]) j++; else if (this->data[i] < b->data[j]) c->data[c->length] = this->data[i]; else c->data[c->length] = b->data[j]; } if (i <= this->length) while (i <= this->length) { c->length++; c->data[c->length] = this->data[i]; i++; } else while (j <= b->length) { c->length++; c->data[c->length] = b->data[j]; } return *c; } //集合减法 void sub_collection(collection *b) { //对每个b集合中的元素调用delete_element for (int i = 0; i<b->length; i++) this->delete_element(b->data[i]); } private: int data[1000], length; };
其中用到的一些小方法:search_element(int x)使用了二分查找,使查找的复杂度降低到O(lgn),集合加法使用了归并排序中的合并部分,实现了O(M+N)而不是N次插入。
遇到的困难:其实还挺多的,第一次写类,感觉好多地方都不太懂。
1.最开始的时候所有的->都用.来表示,因为我记得语法中是这么来引用成员函数或者成员变量的,但是后来vs报错,又去网上查了一下才知道,原来如果这个类定义的时候是指针:collection *p ,那么引用成员函数及成员变量的时候是需要用->来引用的,如果定义的时候是collection p,那么直接使用.就可以了。而我发现因为各个地方都要传参数进去,出于方便考虑就是用了指针的方式。
2.add_collection函数。首先是this指针,之前C++课程开设的时候就对this指针理解的比较模糊,只记得它是指向当前对象,对于一个类来说就是指这个类。我在写add_collection函数的时候,最初是这么写的: collection * add_collection(collection*a.collection*b),后来想到一个问题:这个函数是collection的成员函数,那么调用的时候就得用 collection *collectionAns = collectionA.add_collection(*collectionA,*collectionB)这么蛋疼的方式,而且collectionA是必定要参与运算的,那么为什么还要多此一举使用两个参数呢,因此就改成了只有一个参数的函数。与是就成了collection *collectionAns = collectionA.add_collection(*collectionB)。然后又想了一下,感觉可以把返回值去掉(当时考虑的是既然this指向的就是这个类,那么只要让this指向加法产生后的结果不就可以了)结果vs继续报错,查了一下说是this指针指向的位置是固定的,不能以任何方式更改。这个道理仔细想了一下,确实是一个很合理的规定,与是就沿用了上面的方式,但是因为我想让集合a和b的加和结果存在a中,与是现在调用就成了a = a.add_collection(*b);如果要把结果存在第三个集合中,就是一种更为纠结的方式:collection *c = a.add_collection(*b)。后来想了一下,能否把函数add_collection写在collection类的外面呢?但是因为这个类中调用了collection类的私有成员变量,写在外面是无法调用到这些变量的,因此就只能做到目前这样了,感觉C++不会留这样纠结的方式吧~以后等学到能解决这些问题的地步再来重写这个函数。
基本就是这两个问题让我最为头大,其他小问题也多多少少出现了不少,感觉语法还是最为基础也是最需要掌握好的,不然第一个问题.和->就不会困扰我那么久了(之前报错从来没想过是这个的问题~_~)
目前优化的方向来讲,主要有这几个方面吧:
1.存储的方式。现在是数组存储,因为考虑到如果用链表的话不能直接获取下标,这样get_element(int x)函数的时间复杂度会变成0(N),而且我个人对C++指针部分学习还不是很到位。以后拟使用跳跃表来存储,并稍作调整:每一个非底层链表中存有一个域指出当前元素在最底层链表中的位置,这样add_collection,sub_collection,add_element,这几个元素的时间复杂度都会有提升,而且search_element的时间复杂度也会比较理想(应该是O(sqrt(n),当时看跳跃表的时候记得高度是sqrt(n),底层链表中每个区间长度期望值也是sqrt(n))
2.扩展集合的类型。现在只支持数字,希望能以后扩展到字母,符号,再深入一点扩展到数组,字符串,甚至类。
3.运算符重载。这个我目前只是理解一个概念,但是并未深入,如果能弄好这个,我感觉那个add_collection函数的蛋疼之处就能解决掉啦~