整数集合的实现
2016-07-29 00:00
387 查看
问题:很多应用都需要搜索一组数据,比如编译器查找变量名以得到其地址,拼写检查器查找字典,DNS查找域名来获得IP地址。这与如何存储这些数据紧密相关。比如存储一组整数,如果用集合来存储,如何实现整数集合?
我们一般实现有序整数集合,这样就可以使用二分搜索搜索来快速进行搜索。为简单起见,下面的各种方案只集中于算法的关键部分,并没有考虑错误处理、虚构函数释放资源、拷贝构造等问题。
(1)用标准模板库set来实现:通常标准模板库set是使用平衡二叉树来实现的。
(2)用数组实现:适用于事先知道集合大小的情况。使用了一个哨兵元素,放置一个充分大的数(比集合中任何的元素都大)。这样一旦搜索到这个数,表明到达列表末尾,从而可以简化插入代码,提高运行速度。数组的优点就是可以随机访问。
(3)用链表实现:使用递归遍历来进行有序插入。也使用了一个哨兵结点,其值大于所有实际的值。一旦到达哨兵结点,就表示到达链表末尾。
(4)用二叉树实现:这里用二分搜索树来实现,它是有序的二叉树。将集合元素按序写入数组时使用了中序遍历。为简便,这里没有使用平衡二叉树。平衡二叉树的主要优点就是能确保在最坏情况下也有较好的性能。二叉树的插入时间效率最高,为O(logn)。
(5)用位图实现:位图的优点就是使用移位运算,效率非常高。缺点是当n非常大时,位图会变得很大,占用大量的内存空间,这是一种空间换时间的策略。
(6)使用散列表来实现:箱是一种散列表结构,它结合了链表和位向量的优点。所有的箱用一个数组表示,每个箱中的所有整数用一个有序链表存储。整数t被散列到箱bin[1+maxval/bins]中,这里bins为箱的个数,maxval为充分大的数(存放在哨兵位置处)。
关键算法设计思想:线性结构(数组、链表、位图)、树形结构(二叉树、二分搜索树、平衡二叉树)、散列表结构、集合结构、递归、哨兵技术。
我们一般实现有序整数集合,这样就可以使用二分搜索搜索来快速进行搜索。为简单起见,下面的各种方案只集中于算法的关键部分,并没有考虑错误处理、虚构函数释放资源、拷贝构造等问题。
(1)用标准模板库set来实现:通常标准模板库set是使用平衡二叉树来实现的。
class IntSetSTL{ private: std::set<int> S; public: IntSetSTL(int maxelms,int maxval){ } int size(){ return S.size(); } //向集合中添加一个整数(如果集合中原先没有这个整数的话) void insert(int t){ S.insert(t); } void report(int *v){ //将元素写入到向量中 int j=0; std::set<int>::iterator i; for(i=S.begin();i!=S.end();++i) v[j++]=*i; } };
(2)用数组实现:适用于事先知道集合大小的情况。使用了一个哨兵元素,放置一个充分大的数(比集合中任何的元素都大)。这样一旦搜索到这个数,表明到达列表末尾,从而可以简化插入代码,提高运行速度。数组的优点就是可以随机访问。
class IntSetArray{ private: int n,*x; public: IntSetArray(int maxelms,int maxval){ x=new int[1+maxelms]; //多分配一个空间给哨兵用 n=0; x[0]=maxval; //maxval放在末尾的哨兵元素处 } int size() { return n; } void insert(int t){ for(int i=0;x[i]<t;i++) ; if(x[i]==t) //集合中已经存在元素t return; for(int j=n;j>=i;j--) //将大于t的元素向右移一个位置,将t插入到空出的位置处 x[j+1]=x[j]; x[i]=t; n++; } void report(int *v){ for(int i=0;i<n;i++) v[i]=x[i]; } };
(3)用链表实现:使用递归遍历来进行有序插入。也使用了一个哨兵结点,其值大于所有实际的值。一旦到达哨兵结点,就表示到达链表末尾。
class IntSetList{ private: int n; struct node{ int val; node *next; node(int v,node *p){ val=v; next=p; } }; node *head,*sentinel; //有序链表的插入,p表示当前遍历到的结点 node *rinsert(node *p,int t){ if(p->val<t){ p->next=rinsert(p->next,t); //遍历链表,找到插入位置 }else if(p->val>t){ p=new node(t,p); n++; } return p; } public: IntSetList(int maxelms,int maxval){ sentinel=head=new node(maxval,0); n=0; } int size(){ return n; } void insert(int t){ head=rinsert(head,t); } void report(int *v){ int j=0; for(node *p=head;p!=sentinel;p=p->next) v[j++]=p->val; } };
(4)用二叉树实现:这里用二分搜索树来实现,它是有序的二叉树。将集合元素按序写入数组时使用了中序遍历。为简便,这里没有使用平衡二叉树。平衡二叉树的主要优点就是能确保在最坏情况下也有较好的性能。二叉树的插入时间效率最高,为O(logn)。
class IntSetBST{ private: int n,*v,vn; struct node{ int val; node *left,*right; node(int v){ val=v; left=right=0; } }; node *root; //二分搜索树的插入,p表示当前遍历到的结点 node *rinsert(node *p,int t){ if(p==0){ p=new node(t); n++; }else if(t<p->val){ p->left=rinsert(p->left,t); }else if(t>p->val){ p->right=rinsert(p->right,t); } //do nothing if p->val==t return p; } void traverse(node *p){ //中序遍历:产生有序输出,并写入到向量中 if(p==0) return; traverse(p->left); v[vn++]=p->val; traverse(p->right); } public: IntSetBST(int maxelms,int maxval){ root=0; n=0; } int size(){ return n;} void insert(int t){ root=rinsert(root,t); } void report(int *x){v=x; vn=0; traverse(root); } };
(5)用位图实现:位图的优点就是使用移位运算,效率非常高。缺点是当n非常大时,位图会变得很大,占用大量的内存空间,这是一种空间换时间的策略。
class IntSetBitVec{ private: enum{ BITSPERWORD=32,SHIFT=5,MASK=0x1F }; int n,hi,*x; void set(int i){ x[i>>SHIFT] |= (1<<(i & MASK)); } void clr(int i){ x[i>>SHIFT] &= ~(1<<(i & MASK)); } void test(int i){ return x[i>>SHIFT] & (1<<(i & MASK)); } public: IntSetBitVec(int maxelms,int maxval){ hi=maxval; x=new int[1+hi/BITSPERWORD]; //位向量 for(int i=0;i<hi;i++) //初始化所有的位 clr(i); n=0; } int size(){ return n; } void insert(int t){ if(test(t)) return; set(t); n++; } void report(int *v){ int j=0; for(int i=0;i<hi;i++) if(test(i)) v[j++]=i; } };
(6)使用散列表来实现:箱是一种散列表结构,它结合了链表和位向量的优点。所有的箱用一个数组表示,每个箱中的所有整数用一个有序链表存储。整数t被散列到箱bin[1+maxval/bins]中,这里bins为箱的个数,maxval为充分大的数(存放在哨兵位置处)。
class IntSetBins{ private: int n,bins,maxval; struct node{ int val; node *next; node(int v,node *p){ val=v; next=p; } }; node **bin,*sentinel; node* rinsert(node *p,int t){ if(p->val<t){ p->next=rinsert(p->next,t); }else if(p->val>t){ p=new node(t,p); n++; } return p; } public: IntSetBins(int maxelms,int pmaxval){ bins=maxelms; //箱的个数 maxval=pmaxval; bin=new node*[bins]; //为箱数组分配空间 sentinel=new node(maxval,0); //为哨兵元素分配空间,并赋一个充分大的值 for(int i=0;i<bins;i++) bin[i]=sentinel; n=0; } int size(){ return n; } void insert(int t){ //将整数t放入合适的箱中,每个箱为一个有序链表 int i=t/(1+maxval/bins); bin[i]=rinsert(bin[i],t); } void report(int *v){ int j=0; for(int i=0;i<bins;i++) for(node *p=bin[i];p!=sentinel;p=p->next) v[j++]=p->val; } };
关键算法设计思想:线性结构(数组、链表、位图)、树形结构(二叉树、二分搜索树、平衡二叉树)、散列表结构、集合结构、递归、哨兵技术。
相关文章推荐
- 服务器设计系列:定时器
- Linux进程管理(2):进程创建的copy_process和进程销毁
- 开源软件架构:可扩展的Web架构与分布式系统
- C++ Primer学习系列(5):复制控制/重载操作与转换/面向对象编程
- C++ Primer学习系列(6):模板与泛型编程/用于大型程序的工具/特殊工具与技术
- 来自雅虎的网站优化加速最佳实践
- 字符串结构
- Java动态代理与Cglib库
- 我的C++实践(5):类型萃取技术
- dbm数据库源代码分析(16):测试程序和转换程序
- Linux内存管理(1): 内存模型
- 我的C++实践(17):代理类技术
- 开源软件架构:NoSQL生态系统
- 成为Java GC专家(5):Java应用性能调优的原则
- Java中的泛型
- Linux系统管理实践(8):网络配置 (续)
- Linux init程序分析
- Linux安装程序Anaconda分析
- C标准库源码解剖(6):字符串处理函数string.h和wchar.h(续)
- 深入理解Java类加载器(2):线程上下文类加载器