编程珠玑 第一章 开篇

习题 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;
}

输出结果:15  30  38 49   49  60  70  90


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;
}

习题 3. 运行效率是设计目标的一个重要组成部分,所得到的程序需要足够高效。在你自己的系统上实现位图排序并度量其运行时间。该时间与系统排序的运行时间及习题1中排序的运行时间相比如何?假设n为10 000 000,且输入文件包含1 000 000个整数。

习题 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;
}

程序排序的计算时间为:8.703s.(测时利用了boost库)

方法二:利用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;
}

本方法,排序计算时间为:1.687s.

第三种方法:采用位向量排序法。代码如下:

#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;
}

 本方法,排序计算时间为:0.547s.

    三种排序方法的时间效率如下图所示:

                                          

    由图可以看出采用位向量方法使排序时间大大减少,程序效率有了质的飞跃!!!


转自:http://blog.csdn.net/silenough/article/details/6956758

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第一部分 基础 第1章 开篇 3 1.1 一次友好的对话 3 1.2 准确的问题描述 4 1.3 程序设计 4 1.4 实现概要 5 1.5 原理 6 1.6 习题 7 1.7 深入阅读 9 第2章 啊哈! 算法 11 2.1 三个问题 11 2.2 无处不在的二分搜索 12 2.3 基本操作的威力 13 2.4 排序 15 2.5 原理 16 2.6 习题 17 2.7 深入阅读 18 2.8 变位词程序的实现(边栏) 18 第3章 数据决定程序结构 21 3.1 一个调查程序 21 3.2 格式信函编程 23 3.3 一组示例 25 3.4 结构化数据 26 3.5 用于特殊数据的强大工具 27 3.6 原理 28 3.7 习题 29 3.8 深入阅读 30 第4章 编写正确的程序 33 4.1 二分搜索的挑战 33 4.2 编写程序 34 4.3 理解程序 36 4.4 原理 38 4.5 程序验证的角色 39 4.6 习题 40 4.7 深入阅读 42 第5章 编程小事 43 5.1 从伪代码到C程序 43 5.2 测试工具 44 5.3 断言的艺术 46 5.4 自动测试 48 5.5 计时 49 5.6 完整的程序 50 5.7 原理 51 5.8 习题 51 5.9 深入阅读 52 5.10 调试(边栏) 53 第二部分 性能 第6章 程序性能分析 57 6.1 实例研究 57 6.2 设计层面 59 6.3 原理 60 6.4 习题 61 6.5 深入阅读 61 第7章 粗略估算 63 7.1 基本技巧 64 7.2 性能估计 66 7.3 安全系数 68 7.4 Little定律 69 7.5 原理 70 7.6 习题 70 7.7 深入阅读 71 7.8 日常生活中的速算(边栏) 72 第8章 算法设计技术 73 8.1 问题及简单算法 73 8.2 两个平方算法 74 8.3 分治算法 75 8.4 扫描算法 77 8.5 实际运行时间 77 8.6 原理 79 8.7 习题 80 8.8 深入阅读 81 第9章 代码调优 83 9.1 典型的故事 83 9.2 急救方案集锦 84 9.3 大手术——二分搜索 88 9.4 原理 91 9.5 习题 92 9.6 深入阅读 94 第10章 节省空间 95 10.1 关键在于简单 95 10.2 示例问题 96 10.3 数据空间技术 99 10.4 代码空间技术 101 10.5 原理 103 10.6 习题 104 10.7 深入阅读 105 10.8 巨大的节省(边栏) 105 第三部分 应用 第11章 排序 109 11.1 插入排序 109 11.2 一种简单的快速排序 110 11.3 更好的几种快速排序 113 11.4 原理 115 11.5 习题 116 11.6 深入阅读 117 第12章 取样问题 119 12.1 问题 119 12.2 一种解决方案 120 12.3 设计空间 121 12.4 原理 123 12.5 习题 124 12.6 深入阅读 125 第13章 搜索 127 13.1 接口 127 13.2 线性结构 129 13.3 二分搜索树 132 13.4 用于整数的结构 134 13.5 原理 135 13.6 习题 136 13.7 深入阅读 137 13.8 一个实际搜索问题(边栏) 137 第14章 堆 141 14.1 数据结构 141 14.2 两个关键函数 143 14.3 优先级队列 145 14.4 一种排序算法 148 14.5 原理 150 14.6 习题 150 14.7 深入阅读 152 第15章 字符串 153 15.1 单词 153 15.2 短语 156 15.3 生成文本 158 15.4 原理 163 15.5 习题 163 15.6 深入阅读 164 第1版跋 165 第2版跋 167 附录A 算法分类 169 附录B 估算测试 173 附录C 时空开销模型 175 附录D 代码调优法则 181 附录E 用于搜索的C++类 187 部分习题提示 191 部分习题答案 195 索引 221

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值