第一章习题1解析
以下代码均使用MSVC 6.0编译运行通过,为了便于学习,在原代码的基础上进行了一定的修改。
1.如何使用一个具有库的语言来实现一种排序算法以表示和排序集合,将代码实现可用
1 #include<stdio.h> 2 #include<stdlib.h> 3 int comp(const void *x,const void *y){ 4 return (*(int*)x-*(int*)y); 5 } 6 int a[5]={45,25,64,10,4}; 7 void main(){ 8 int i,n=0; 9 printf("排序前列表为: "); 10 for(i=0;i<5;i++){ 11 printf("%d ",a[i]); 12 } 13 qsort(a,5,sizeof(int),comp); 14 printf("排序后列表为: "); 15 for(i=0;i<5;i++){ 16 printf("%d ",a[i]); 17 } 18 19 }
在使用原书中代码会报错:
int intcomp(int *x,int *y){ return *x-*y; }
error C2664: 'qsort' : cannot convert parameter 4 from 'int (int *,int *)' to 'int (__cdecl *)(const void *,const void *)'
None of the functions with this name in scope match the target type
意思是不能把int (int *,int *)自动转换为int (__cdecl *)(const void *,const void *),按提示错误,在声明函数时改写为上述函数正确写法即可。
用C++标准模板库中的set容器完成相同的任务:
#include <iostream> #include<set> using namespace std; int main(void){ set<int> S; int i,n; cin>>n; for(i=0;i<n;i++){ int x; cin>>x; S.insert(x); } set<int>::iterator j;//迭代器用于遍历 for(j=S.begin();j!=S.end();j++) cout<<*j<<" "; //s.end()没有值 //s.begin()返回容器的第一个元素 cout<<"s.begin() "<<*S.begin ()<<endl; //lower_bound()返回第一个大于或等于给定关键值的元素 cout<<"lower_buond 10 "<<*S.lower_bound (10)<<endl; //upper_bound()返回第一个大于给定关键值的元素 cout<<"upper_bound 10 "<<*S.upper_bound (10)<<endl; //find()查找一个元素,如果容器中不存在该元素,返回值等于s.end() cout<<"find() 10 "<<*S.find (10)<<endl;
if(S.empty()) cout<<"容器为空"<<endl; if(S.count(10)) cout<<"10在容器中"<<endl;
cout<<"s.size() "<<S.size ()<<endl; return 0; }
2和3
这方法太巧妙了,先来讲解以下使用常量来设置、清除以及测试位值
#define BITSPERWORD 32 #define SHIFT 5 #define MASK 0x1F #define N 10000000 int a[1+N/BITSPERWORD]; void set(int i){a[i>>SHIFT]|=(1<<(i&MASK));}//指定bit位设为1 void clr(int i){a[i>>SHIFT]&=~(1<<(i&MASK));}//指定bit位设为0 int test(int i){return a[i>>SHIFT]&(1<<(i&MASK));}//取存入的值,5bit一组
补充C++运算符知识:
<<二进制左移运算符。左操作数的值向左移动右操作数指定的位数。如A原十进制值为60,二进制值为00111100,A << 2 将得到 240,即为 1111 0000
相当于60*4,也就是A*2*2,右移相当于做乘法
>>二进制右移运算符。左操作数的值向右移动右操作数指定的位数。A >> 2 将得到 15,即为 0000 1111
相当于60/4,也就是A/2*2,左移相当于做除法
2的倍数的乘除法用位移实现
&如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。给定B为13,二进制值为00001101,则(A & B) 将得到 12,即为 0000 1100
那么i&0x1f,0x1f十进制值为31,二进制值00011111,当一个数和该数做&运算,如果余数在二进制位有值,则结果位是1,否则为1。
&得到的是32的余数。
void set(int i){a[i/32]|=(1<<(i%32));} void clr(int i){a[i/32]&=~(1<<(i%32));} int test(int i){a[i/32]&(i<<(i%32));}
用2的5次方也就是32的原因是,int型为4个字节,32位,a为 int型数组,那么除以32可以知道是a数组中哪一个下标,32取模得到的是int具体的某一位
我的理解是这样的:
因此,a[i>>SHIFT]是第i位代表数组a的第几个int型数上 (1<<(i & MASK))是第i位在该int上的第几位上、
set将数组a中第几个int的数的第几个位(bit)赋值为1,clr赋值为0,test用于遍历。
整体代码思路是先将数组中的每个int数的bit位清零,然后循环输入数值,bit位赋值1,最后遍历输出。位图如示意图显示。
修订可运行代码:
#include<stdio.h> #define BITSPERWORD 32 #define SHIFT 5 #define MASK 0x1F #define N 128 int a[1+4]; void set(int i){a[i>>SHIFT]|=(1<<(i&MASK));} void clr(int i){a[i>>SHIFT]&=~(1<<(i&MASK));} int test(int i){return a[i>>SHIFT]&(1<<(i&MASK));} int main(void){ int i; for(i=0;i<N;i++) clr(i); printf("请输入4个数"); for(int j=0;j<4;j++) { scanf("%d",&i); set(i); } for(i=0;i<N;i++) if(test(i)) printf("%d ",i); return 0; }
此题解参考了https://blog.csdn.net/xuzhengzheng32/article/details/15026637给出的方法并进行修正补充
最后分析十分精妙:当n为10000000,且输入文件包含1000000个整数时,通用C++程序所需的内促成你和CPU时间是专用C程序的50倍,但是代码量仅有C程序的一半
该方法有个问题是,当有重复值出现时,结果只保存一次该值,也就是只输出一次该值