您的位置:首页 > 其它

整数集合的实现

2016-07-29 00:00 387 查看
问题:很多应用都需要搜索一组数据,比如编译器查找变量名以得到其地址,拼写检查器查找字典,DNS查找域名来获得IP地址。这与如何存储这些数据紧密相关。比如存储一组整数,如果用集合来存储,如何实现整数集合?
我们一般实现有序整数集合,这样就可以使用二分搜索搜索来快速进行搜索。为简单起见,下面的各种方案只集中于算法的关键部分,并没有考虑错误处理、虚构函数释放资源、拷贝构造等问题。
(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;
}
};


关键算法设计思想:线性结构(数组、链表、位图)、树形结构(二叉树、二分搜索树、平衡二叉树)、散列表结构、集合结构、递归、哨兵技术。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: