第一章_引论

原帖 http://m.blog.csdn.net/blog/warringah1/8871471

利用五一的空闲时间,看了下第一章的引论,网上都说这本书不错,看了第一章后感觉还好吧,至少不是在概念上的泛泛而谈,总有一点干货给你思考。

下面总结下引论的内容:

一开始就给出了两个例子:寻找第K大的数和字谜游戏。我对寻找第K大的数并不陌生,之前有编过线性时间找出第K大数的程序,偷笑中。第二个字谜游戏看似比较复杂,到时给出想法就不写代码了。

接下来是数学知识的复习,换底公式,级数求和,级数收敛,模运算,归纳法证明,反证法证明,这些都是之前有过接触过的知识,上手很快的。

最后一个是介绍了递归,个人感觉递归是个看似简单但用起来比较难的东东,但一旦用顺手了,能大大简化代码量增加可读性,但是不精心设计的递归有时也会拖垮CPU。记住几个法则吧:1.基准情形2.不断推进3.设计法则4.合成效益法则。注意第四个合成效益法则,就不担心会拖垮CPU啦。

最后给点干货吧,看此书不编程那不白看么~

 

1.1编写一个程序解决选择问题。令k=N/2。画出表格显示你程序对于N为不同值得运行时间。

书中给了两个解法,可都是O(n^2)运行时间的。第一个种解法是将这N个数读进一个数组中,再通过冒泡排序以递减的顺序将数组排序,最后输出第k位置上的元素。

<span style="padding: 0px; margin: 0px; font-size: 18px;">void BubbleSort(int arr[], int n)
{
	for (int i=n-1; i>0; --i) {
		for (int j=0; j<i; ++j) {
			if (arr[j]<arr[j+1]) {
				int temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}
}
</span>

第二种解法稍微好一点,可以先把前k个元素读入数组并以递减的顺序对其排序,剩下的元素再逐个读入,当新元素被读到时,如果她小于数组中的第k个元素则忽略,否则将其放在数组中的正确位置上,同时将数组中的一个元素挤出数组。细想想,这种解法是冒泡排序和插入排序的一个合体,当k=1时就退化为插入排序,本质上没有时间上的优化。

<span style="padding: 0px; margin: 0px; font-size: 18px;">int SortKMethod(int arr[], int n, int k)
{
	int *tempArr = new int[n];
	memcpy(tempArr, arr, sizeof(arr)*n);
	BubbleSort(tempArr, k);

	for (int i=k; i<n; ++i) {
		if (tempArr[i]>tempArr[k-1]) {
			for (int j=0; j<k; ++j) {
				if (tempArr[j]<tempArr[i]){
					for (int m=k-1; m>j; --m)
						tempArr[m] = tempArr[m-1];
					tempArr[j] = tempArr[i];
					break;
				}
			}
		}
	}
	int result = tempArr[k-1];
	delete[] tempArr;
	return result;
}
</span>

第三种解法就比较巧妙。还记得算法导论上快速排序那一讲么?有个很经典的程序叫Partition。我觉得这个经典的程序应该背住在脑海里,因为它的思想很经典而且程序很精巧。它的作用是以数组中最后一个元素为基准,前面的元素都是大于它的,后面的元素都是小于它的,但是它前后的元素不一定按顺序排列。这样一来,不管它前后的元素怎么排列,它的位置总是固定下来了。它前面恰有k-1个数,那第k大的不正是它么?它的运行时间是O(n),线性时间。有人会问,为什么选择最后一个数作为基准,当然这不是一定的,你也可以选择第一个数,中间一个数,甚至是编一个随机数算法进行随机选择,这样,对极端的输入你的程序也不会运行时间太糟,这个我就不展开了,可以参考算法导论那一章,或者July大神的博客,一定会有收获的。

这时结合之前递归的思想,若你选取的基准数恰好是第k个数,则返回它本身,若它的位置为q(小于k),说明你应该对后一部分做递归,但是,应该选取后一部分的第k-q个位置;若它的位置为p(大于k),则还是对前一部分做递归,但应该选择前一部分的第k个位置。好了,这应该是很简单的,被我说复杂了没直接上代码:

<span style="padding: 0px; margin: 0px; font-size: 18px;">int Partition(int arr[], int p, int r)
{
	int x = arr[r];
	int i = p - 1;//i保存的是大于x的数的下标
	for (int j=p; j<=r-1; ++j) {
		if (arr[j]>x) {
			++i;
			swap(arr[i],arr[j]);
		}
	}
	swap(arr[i+1],arr[r]);
	return i+1;
}

int LinearSelectK(int arr[], int p, int r, int k)
{
	if (p==r)
		return arr[p];
	int q = Partition(arr, p, r);
	int m = q - p + 1;
	if (m==k)
		return arr[q];
	else if (m<k)
		return LinearSelectK(arr, q+1, r, k-m);
	else
		return LinearSelectK(arr, p, q-1, k);
}
</span>

第一题大概就是如此了。有更好的想法或者有待改进的地方,请不吝留言啊,大家一起讨论。

 

1.2编写一个程序求解字谜游戏问题。

这个问题看着就觉得比较复杂。想了下如果按照书上的提示做for循环那不把自己绕晕了。参考了下网上的思路,有一点我比较同意,就是遍历前也需要建立文件存放所有单词的所有前缀。然后遍历。下面是摘自http://blog.csdn.net/golden_shadow/article/details/5972867一段话,大家可以去参考。

“思想,就是,记录下单词的所有前缀,这样可以减少扫描次数.我的实现方式,新建一个文件存放所有单词的所有前缀,之后同单词一起装进表中.添加一个指示域来指示表中的字符串是单词还是前缀.之后在主循环中先进行单词判断,而后进行前缀判断.如果当前的字符串不是单词并且也未作为前缀出现在表中,那么就跳出当前方向的循环.”还有就是据说用Trie树解决比较方便,对Trie树我没有详细研究过,有同志可以详细讲讲么?

 

1.3只使用处理I/O的PrintDigit函数,编写一个过程以输出任意实数。

一开始我的想法很简单,由于有了PrintOut这个函数,我们就可以利用它来输出一个double类型的数,只要知道小数点所在的位置,就可以用PrintOut这个函数分别输出整数部分和小数部分。例如123.456,先用int方法取整得到123,PrintOut输出,然后将0.456乘以1000得456,再利用PrintOut输出。求小数点的位置不是很难,我想可以利用double转成string类型,find小数点的位置,再找到小数点到最后一个数的长度就行了。利用pow函数就能得到456。这个想法想起来很容易,但是其实是有问题的。因为计算机精度的问题,double在计算机里不能精确的存储,比如0.456可能在计算机里是以0.45599999999…的方式存放的。这样就比较尴尬了,可能需要四舍五入等等,就比较麻烦,大家有什么好想法?至于负数就很好处理,判断是负数后输出“-”,然后区反当成正数处理就是了。上代码,小数点的位置我就当输出参数输进去了。

<span style="padding: 0px; margin: 0px; font-size: 18px;">#include <iostream>
#include <cmath>
using namespace std;

void PrintDigit(unsigned int i)
{
	cout<<i;
}

void PrintOut(unsigned int i)
{
	if (i>=10)
		PrintOut(i/10);
	PrintDigit(i%10);
}

void PutOut(double n, int d)
{
	if (n<0) {
		cout<<"-";
		n = -n;
	}
	int integerPart = int(n);
	PrintOut(integerPart);
	if (n!=integerPart) {
		double fractionPart = n - integerPart;
		cout<<".";
		int scaled = fractionPart * pow(10.0,d);
		PrintOut(scaled);
	}

}
int main(int argc, char **argv)
{
	PutOut(-123.34,2);
	system("pause");
	return 0;
}
</span>

1.4C提供形如#includefilename的语句,它读入文件filename并插入到include语句处。include语句可以嵌套,换句话说,文件filename还可以包含include语句,但是显然一个文件在任何链接中都不能包含它自己。编写一个程序,使它读入被include语句修饰的一个文件并输出这个文件。

一眼看到这个问题就想到马士兵讲过的java教程里有个输出文件名字的例子,文件里嵌套文件,和这个有点像,不用递归用什么???

但是要处理自身包含的问题,我的想法是建立一个list,将每次输出的filename存入,但是存入前先要看看list中有没有,若有则存在自身包含的问题。这个list可以用最经典的~~~~容器~~~~来解决,不错,就是set容器,这个容器的特点是每个元素的值是唯一的,当只想知道一个值是否存在时,使用set容器最合适(摘自C++Primer),它有个count运算可以在set容器中查找是否有这样一个值存在。

在编代码时,发现我对一些容器(set,string等)的操作还不是很熟悉,对问价的操作也不是很熟练,好吧,四个月不碰c++果然是生疏了。Labview用多了也会让人对c++编程产生生疏感。

在编程时遇到一个让我很纠结的问题,在定义set<string> fileNameSet;前我包含了#include <cstring>和#include <set>,但是编译老是出错(error C2784: “boolstd::operator <(const std::_Tree<_Traits>),我理解的意思是因为set中涉及到了排序的操作所以在重载<操作符,但是cstring文件里可能就没有重载它。我换成了#include <string>后这个问题就消失了。后来查了一下cstring和string文件的区别,网上是这么说的,cstring和string.h差不多,一个是c++版本的头文件,一个是c版本的头文件。里面都包含了诸如strcpy之类的字符串处理函数,而string,包含了std::string的定义,属于STL范畴。所以用到string容器我当然要包含string了!不然编译器就不认识string!!上代码。

<span style="padding: 0px; margin: 0px; font-size: 18px;">#include <iostream>
#include <fstream>
/*#include <cstring>*/
#include <string>
#include <set>

using namespace std;

set<string> fileNameSet;
void ProcessFile(const char* FileName)
{
	ifstream fin;
	fin.open(FileName);
	if (!fin) {
		cerr<<"error:unable to open input file:"
		<<FileName<<endl;
		return;
	}

 	fileNameSet.insert(FileName);
	cout<<FileName<<endl;
	char str[50];
	while (!fin.eof()) {
		fin.getline(str,50);
		string strLine = str;
		string constStr("#include ");
		if (strLine.find(constStr)==strLine.npos)
			break;
		else {
			string fname = strLine.substr(constStr.length()+1,
				strLine.length()-constStr.length()-2);
			if (fileNameSet.count(fname)==0)
 				ProcessFile(fname.c_str());
			else {
				cerr<<"Self-referential includes is detected:"
					<<fname<<endl;
				return;
			}
		}
	}
	fin.close();

}

int main(int argc, char **argv)
{
	ProcessFile("A.txt");
	system("pause");
	return 0;
}
</span>

好了,四道编程题就这样了,以纪念我五月的第一天大早就开始写博客,写博客花的时间比编程还多啊,果然是苦差事,但是把自己的思路整理一下并和别人分享也是快乐的~


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值