习题 1. 如果不缺内存,如何使用一个具有库的语言来实现一种排序算法以表示和排序集合?
由于目前只学习了C和C++,所以只讨论C和C++中具有排序算法的库。对于C,有stdlib.h中的快速排序qsort函数;对于C++,其STL中的algorithm包含的一个排序算法sort函数。
#include <stdlib.h> void qsort( void *buf, size_t num, size_t size, int (*compare)(const void *elem1, const void *elem2 ));功能: 对buf 指向的数据(包含num 项,每项的大小为size)进行快速排序。如果函数compare的第一个参数小于第二个参数,返回负值;如果等于返回零值;如果大于返回正值。函数默认对buf指向的数据按升序排序,若想对buf 指向的数据按降序排序,则需反转函数compare的返回值,如果第一个参数小于第二个参数,则返回正值;相等则返回零;如果大于则返回负值。
Example:
#include <stdlib.h> #include <iostream> using namespace std; int compare(const void * elem1, const void * elem2) { if (*(int *)elem1 < *(int *)elem2) { return -1; //若需要按降序排列,这里返回1 } else if (*(int *)elem1 == *(int *)elem2) { return 0; } else { return 1; //若需要按降序排列,这里返回-1 } } int _tmain(int argc, _TCHAR* argv[]) { int num[]={49, 38, 60 , 90 , 70, 15, 30, 49}; int len = sizeof(num) / sizeof(int); qsort(num, len, sizeof(int), compare); for (int i = 0; i < len; i++) { cout<< num[i]<<" "; } cout << endl; return 0; }
输出结果:
C++ STL中的排序算法sort函数:
template <class RandomAccessIterator> void sort ( RandomAccessIterator first, RandomAccessIterator last ); template <class RandomAccessIterator, class Compare> void sort ( RandomAccessIterator first, RandomAccessIterator last, Compare comp );功能:将容器中迭代器指向的[first, last)范围中(注意:不包括last指向的元素)的元素按升序排列。在第一版函数中使用operator<来比较容器中的元素,第二版函数中使用comp, comp可以是一个函数指针也可以是一个函数对象。容器中相等的元素在排完序后不能保证它们的相对位置不会发生变化。
定义了调用操作符的类,其对象常称为函数对象,它们是行为类似函数的对象(使用该对象时要为该对象传入实参)。
#include <iostream> #include <algorithm> #include <vector> using namespace std; bool myFunction(int i, int j) { return i < j; //若要降序排序则return i > j; } class myClass { public: bool operator() (int i, int j) { return i < j; //若要降序排序则return i > j; } }; int _tmain(int argc, _TCHAR* argv[]) { int num[]={49, 38, 60 , 90 , 70, 15, 30, 49}; vector<int> myVector(num, num + 8); vector<int>::iterator it; myClass myObject; //函数对象 // using default comparison (operator <): sort(myVector.begin(), myVector.begin() + 4); //(38 49 60 90) 70 15 30 49 // using default comparison (operator <): sort(myVector.begin() + 4, myVector.end()); //38 49 60 90 (15 30 49 70) // using function as comp: sort (myVector.begin(), myVector.end(), myFunction); //(15 30 38 49 49 60 70 90) for (it = myVector.begin(); it != myVector.end(); it++) { cout<< *it<<" "; } cout << endl; vector<int> myVector2(num, num + 8); //using object as comp: sort(myVector2.begin(), myVector2.end(), myObject); //(15 30 38 49 49 60 70 90) for (it = myVector2.begin(); it != myVector2.end(); it++) { cout<< *it<<" "; } cout << endl; return 0; }
习题 2 . 如果使用位逻辑(例如与、或、移位)来实现位向量。
对于一个int型变量其大小为4字节,一个字节等于8bits,随意一个int型变量占32bits。可以充分利用这32个位,利用一个int型变量即可表示一个所有元素都小于32([0, 32))的非负整数的集合。
例如,对于集合A={0,4,8,15,21,28}, 用int a来表示该集合,则有:
a的二进制表示为: 10001000100000010000010000001000,其实十进制为 a = 2290156552从上面的分析可知,若要表示一个至多含有1000万记录,且每个记录为7位整数([0, 10E7-1 )),每个整数最多出现一次的集合,则可以一个用一个int型的数组表示。因为
10000000 / 32 = 312500,所以数组大小可定义为312500,即:
int bitArry[312500]; //用int型数组来模拟位数组,具有1000 0000个位,初始的时候将每个位置为false
下面的重点是如何进行位的操作。设x是属于[0, 10E7-1 )区间内的任意一个数,则 x%32 = m.......n,即将元素bitArry[m]的第n+1位(从低位到高位)置为1,表示非负整数x存在于集合[0, 10E7-1 )中。
#include <iostream> #include <stdlib.h> using namespace std; #define BITSPERDWORD 32 //表示一个整形变量具有32个位 #define SHIFT 5 //单次位移量,右移一位相当于除以2,左移一位相当于乘以2 #define MASK 0x1f //掩码,其十进制即为31 #define N 10000000 //表示10000000个7为非负整数 //将位数组从0开始的第i个位置为0 void reset(int * arry, int i) { arry[i >> SHIFT] &= ~(1 << (i & MASK)); //m%n,n = 2^x时,m%n = m & (n-1) } //将位数组从0开始的第i个位置为1 void set(int * arry, int i) { arry[i >> SHIFT] |= (1 << (i & MASK)); } //检测i是否在集合中 int test(int * arry, int i) { return arry[i >> SHIFT] & (1 << (i & MASK)); } int _tmain(int argc, _TCHAR* argv[]) { int arry[1 + N / BITSPERDWORD]; //加1是因为N不一定被BITSPERDWORD整除 int i; for (i = 0; i < N; i++) { reset(arry, i); } /*对位数组的初始置位0操作可以这样写: int size = 1+ N / 32; for (i =0; i < size; i++) { arry[i] = 0; } */ while (scanf("%d", &i) != EOF) { set(arry, i); } for (i = 0; i < N; i++) //从0到n-1,检测i是否出现过,若出现过则输出,若没有出现则不输出。 { //由于i从0到n-1递增,所以数据输出后便自动排好了序 if (test(arry, i)) { cout<<i; } } return 0; }
习题 4. 如果认真考虑了习题3,你将会面对生成小于n且没有重复的k个整数的问题。最简单的方法就是使用前k个正整数。这个极端的数据集合将不会明显的改变位图方法的运行时间,但是可能会歪曲系统排序的运行时间。如何生成位于0至n-1之间k个不同的随机顺序的随机数?尽量使你的程序简短且高效。
首先解决如何生成位于0至n-1之间k个不同的随机顺序的随机数。rand()是c语言库函数提供一个产生伪随机整数的函数,在使用该函数前应该调用srand()种下一个伪随机数发生器种子,一般这样调用:
#include <stdlib.h> srand((unsigned)time(NULL));
rand()函数返回的随机数在0~RAND_MAX(0x7FFF)之间,一般只有15个随机位。下面定义一个函数,让该函数返回的随机数至少具有30个随机位:
//rand()通常返回约15个随机位,其最大值为RAND_MAX = 0x7fff。 //bigrand()则至少返回30个随机位 int bigrand() { return RAND_MAX * rand() + rand(); }同时定义一个函数,该函数能返回[l, u]范围内的一个随机整数:
//返回[l, u]范围内的一个随机整数 int randint(int l, int u) { return (l + rand() % (u - l + 1)); }有了以上的函数,我们就有两种方案来产生随机数了。
方案一:
//产生m个小于n的随机整数, 该函数直接利用bigrand()返回的随机对n求模 //即可得到[0, n)之间的随机数(可能有重复) void geneRand1(int m , int n) { int i = 0; srand((unsigned)time(NULL)); while (i < m) { cout << bigrand() % n << endl; i++; } }
//把包含整数0~n-1的数组顺序打乱,然后把前m个元素排序输出。 //即首先生成0~n-1的整数数组,然后产生随机数索引,引用该索引取出对应的数组元素。 //数组的前m个元素即为生成的随机数(无重复)。 void geneRand2(int m ,int n) { int i; int *pArry = new int[n]; for (i = 0; i < n; i++) //生成数组 { pArry[i] = i; } //筛选随机数 srand((unsigned)time(NULL)); for (i = 0; i < m; i++) { j = randint(i, n - 1); int t = pArry[i]; pArry[i] = pArry[j]; pArry[j] = t ; } delete [] pArry; }下面别分采用三种方法来完成随机数排序,并测试它们的效率(只考察时间复杂度)。
方法一:利用C++ STL 中关联容器set.
set容器是一种存储单一元素的关联容器,元素本身即为键值。关联容器是一种通过其键值来高效访问其元素的容器。在set内部,其元素按照其构造函数设置的规则严格的以弱序从低到高存储。典型的利用二分搜索树实现的。关联容器set的特殊特征:
1) 独一无二的元素值:容器中没有两个元素是相等的;
2)元素值本身即为其键值;
3)所有元素始终以弱序排列(存储)。
template < class Key, class Compare = less<Key>, class Allocator = allocator<Key> > class set;利用关联容器set本身的特征,即可完成随机数的排序,代码如下:
#include <stdlib.h> #include <time.h> #include <iostream> #include <set> #include <boost/timer.hpp> #include <boost/progress.hpp> using namespace std; using namespace boost; //返回[l, u]范围内的一个随机整数 int randint(int l, int u) { return (l + rand() % (u - l + 1)); } //函数对象类 class comp { public: bool operator ()(int i,int j) { if (i < j) //若要降序排列,则i > j { return true; } else { return false; } } }; int * geneRand2(int m ,int n) { int i, j; int *pArry = new int[n]; for (i = 0; i < n; i++) //生成数组 { pArry[i] = i; } //筛选随机数 srand((unsigned)time(NULL)); for (i = 0; i < m; i++) { j = randint(i, n - 1); int t = pArry[i]; pArry[i] = pArry[j]; pArry[j] = t ; } return pArry; } void mySort(int m ,int n) { set<int,comp> s; int i = 0; int * p = geneRand2(m ,n); progress_timer t; while (s.size() < (set<int,comp> ::size_type)m) { s.insert(p[i]); i++; } cout << t.elapsed()<<endl; // 生成位于0至n-1之间m个有序且不重复的随机整数所用的时间 delete [] p; } int _tmain(int argc, _TCHAR* argv[]) { mySort(1000000,10000000); return 0; }
方法二:利用C++ STL <algorithm>提供的sort排序函数,代码如下.
#include <stdlib.h> #include <time.h> #include <iostream> #include <algorithm> #include <boost/timer.hpp> #include <boost/progress.hpp> using namespace std; using namespace boost; //返回[l, u]范围内的一个随机整数 int randint(int l, int u) { return (l + rand() % (u - l + 1)); } int * geneRand2(int m ,int n) { int i, j; int *pArry = new int[n]; for (i = 0; i < n; i++) //生成数组 { pArry[i] = i; } //筛选随机数 srand((unsigned)time(NULL)); for (i = 0; i < m; i++) { j = randint(i, n - 1); int t = pArry[i]; pArry[i] = pArry[j]; pArry[j] = t ; } return pArry; } void mySort(int m ,int n) { int i; int * p = geneRand2(m ,n); progress_timer t; sort(p, p + m); cout << t.elapsed()<<endl; // 生成位于0至n-1之间m个有序且不重复的随机整数所用的时间 delete [] p; } int _tmain(int argc, _TCHAR* argv[]) { mySort(1000000,10000000); return 0; }
第三种方法:采用位向量排序法。代码如下:
#include <stdlib.h> #include <time.h> #include <iostream> #include <boost/timer.hpp> #include <boost/progress.hpp> using namespace std; using namespace boost; #define BITSPERDWORD 32 //表示一个整形变量具有32个位 #define SHIFT 5 //单次位移量,右移一位相当于除以2,左移一位相当于乘以2 #define MASK 0x1f //掩码,其十进制即为31 #define N 10000000 //表示10000000个7为非负整数 //将位数组从0开始的第i个位置为0,即将整数i在位向量中对应位置的位置为false void resetBits(int * arry, int i) { arry[i >> SHIFT] &= ~(1 << (i & MASK)); //m%n,n = 2^x时,m%n = m & (n-1) } //将位数组从0开始的第i个位置为1,即将整数i在位向量中对应位置的位置为true void setBits(int * arry, int i) { arry[i >> SHIFT] |= (1 << (i & MASK)); } //检测i是否在集合中 int testBits(int * arry, int i) { return arry[i >> SHIFT] & (1 << (i & MASK)); } //返回[l, u]范围内的一个随机整数 int randint(int l, int u) { return (l + rand() % (u - l + 1)); } int * geneRand2(int m ,int n) { int i, j; int *pArry = new int[n]; for (i = 0; i < n; i++) //生成数组 { pArry[i] = i; } //筛选随机数 srand((unsigned)time(NULL)); for (i = 0; i < m; i++) { j = randint(i, n - 1); int t = pArry[i]; pArry[i] = pArry[j]; pArry[j] = t ; } return pArry; } void mySort(int m ,int n) { int i, j; int *pBitsArry = new int[1 +n / BITSPERDWORD]; int * p = geneRand2(m ,n); progress_timer t; for (i = 0; i < n; i++) { resetBits(pBitsArry, i); } for (i = 0; i < m; i++) { setBits(pBitsArry, p[i]); } for (i = 0, j = 0; i < n; i++) { if (testBits(pBitsArry, i)) { p[j++] = i; } } cout << t.elapsed()<<endl; // 生成位于0至n-1之间m个有序且不重复的随机整数所用的时间 delete [] p; delete [] pBitsArry; } int _tmain(int argc, _TCHAR* argv[]) { mySort(1000000,10000000); return 0; }
三种排序方法的时间效率如下图所示:
由图可以看出采用位向量方法使排序时间大大减少,程序效率有了质的飞跃!!!