写在前面的话——
这篇的主体内容其实就是抄书,不过个人觉得我选的还是比较有意义的内容,书上也没有代码,所以代码是我自己写的,比较简单的题目了,算法前面的题目或者说知识点都比较初级
本片全文摘抄自我的课本《算法设计与分析》,吕国英主编,有兴趣的小伙伴可以去看看这本书!
第二篇——
论算法与数据结构的使用方法及技巧
现代计算机可以解决的问题种类繁多,计算机解决问题的实质是对“数据”进行加工处理,计算机处理的问题类型,粗略地可以分成数值计算问题和非数值性问题,随着计算机应用领域的扩大和软、硬件的发展,“非数值型问题”越来越显得重要。这类问题设计的数据结构就较为复杂,数据元素之间的相互关系往往无法用数学方程式加以描述。因此,解决此类问题的关键不仅仅是问题分析、数学建模和算法设计,还必须设计出合适的数据结构,才能有效地解决问题。
算法设计的实质是对实际问题要处理的数据选择一种恰当的存储结构,并在选定的存储结构上设计一个好的算法,实现对数据的处理。算法中的操作是以确定的存储结构为前提的,所以,在算法设计中选择好数据结构是非常重要的,选择了数据结构,算法才随之确定。好的算法在很大程序上取决于问题中数据所采用的数据结构。
常用的存储结构可以分为连续存储和链式存储。连续存储又分为静态分配和动态分配两种,对连续存储和链式离散存储两种存储结构各有长短,选择哪一种有具体的问题决定。
下面讨论一下信息存储中的一些技巧。
1、原始信息与处理结果的对应存储
解决一个问题时,往往存在多方面的信息。就算法而言,一般有输入信息、输出信息和信息加工处理过程中的中间信息。那么将哪些数据用数组存储,数组元素下标与信息怎么样对应等问题的确定,在很大程度上影响着算法的编写效率和运行效率
下面的例子恰当的选择了用数组存储的信息,并且把题目中的有关信息作为下标使用,使算法的实现过程大大简化。
【例】 编程统计身高(单位为厘米)。统计分150~154、155~159、160~164、165~169、170~174、175~179、低于150和高于179,共8档次进行。
算法设计:很明显该题目不需要将所有身高数据都存储入数组,仅需要8个档次即可,此时需要分析这8个档次之间的规律来合理的存储数据并且输出相应值,定义一个含有8个元素的数组,此时数组的下标需要和8个档次形成对应关系,观察每个档次的第一个数字,如果都执行/5-29的操作之后,正好分别为1、2、3、4、5、6,再将0和7分别作为低于150和高于179档的下标,如此正好将下标分配完成,找到这个规律之后通过一个循环进行存储,该问题即可解决。
程序代码见附录1.
算法说明:该算法的精华部分就在于找到各个档次之间的规律,身高/5-29这个运算很重要,通过这样计算之后需要联系到数组下标从而提高算法效率。
从这个例子中可以认识到,算法与数据结构是密切相关的,好的存储方式可以大大提高算法的效率。
2、数组使信息有序化
一个问题的提出,其中的数据可能一时间找不到规律或者没有规律,很难把重复的工作抽象成循环不变式来完成,但是如果通过先用数组存储这些信息之后,就变得有序从而可以解决问题。
【例】 编写算法将数字编号“翻译”成英文编号。
例如:将编号35706“翻译”成英文编号three-five-seven-zero-six。
算法设计:可以看出任意写一个数字编号,其各位之间不一定有规律可以找,所以关键在于找到如何将数字编号和英文编号对应起来,由此可以现将数组下标和英文编号对应起来,此时如果再将数字编号各位分别找出来,然后和数组下标对应,可以进行“翻译”工作了。
程序代码见附录2.
算法说明:该算法的关键在于想出将每个数字的英文存进数组,然后就可以将数组代替英文,从而出现规律可以通过循环来解决“翻译”问题,至于将如何数字和数组对应,可以通过循环或者递归法来取出各位的数,在找出以这个数作为下标的英文,即完成“翻译”,同时如果改用字符串类型来存储该数字编号,则该字符串的每一个元素正好对应该数字的各位的值,从而可以避免需要通过取余和整除来计算每位的数字。
3、数组记录状态信息
有的问题会限定现有数据每个数据只能被使用一次,那么在c语言中,要想判断数据是否重复使用,比较朴素的想法是用数组存储已经使用过的数据,然后每处理一个新的数据就与前面的数据逐一比较看是否重复,但是当数据量比较大时判断是否重复的工作效率就会越来越低,所以此时,可以开辟一个状态数组,专门记录数据使用情况,并且将数据信息与状态数组下标对应,就能较好的完成判断是否重复使用的操作。
【例】 12个小朋友手拉手站成一个圆圈,从某个小朋友开始报数,报到7的那个小朋友退到圈外,然后他的下一位重新报1。这样继续下去,直到最后只剩下一个小朋友。求解这个小朋友原来站在什么位置上。
算法设计:很明显这个问题需要开辟一个数组表示每个小朋友的圈内圈外的状态,以所在位置为下标,可以定义数组的值大于0,则为站在圈内,值等于0则为退到圈外,依次报数则实质上是通过循环计数来确认数组哪一个该被置为0,当循环到置为0的数组下标的时候,此时应该跳过直接对下一位进行计数,同时因为是循环计数,当到了数组最后一位的时候应该要回到数组第一位,循环退出的条件应该是通过定义一个变量来记录退出圈外的次数恰好比总人数少一个的时候,循环退出,输出结果,得到数组的下标。
程序代码见附录3.
算法说明:该算法一旦清楚了如何高效的表示圈内圈外的状态之后,问题就可以迎刃而解。
通过以上3点,可以明确在算法设计中选择好数据结构是非常重要的,灵活使用数组的存储的方式,对于程序的执行效率和运行效率都是极大的提升。
附录:
1.
#include
int main()
{
int i_height;
int a_level[8] = {0};
printf("请输入身高数据(整数):");
scanf("%d",&i_height);
while(1) {
if(i_height < 0) {//设定退出条件
break;
}
if(i_height > 179) {
a_level[7]++;
}else if(i_height < 150) {
a_level[0]++;
}else {
a_level[i_height/5- 29]++;
}
printf("请输入下一个:");
scanf("%d",&i_height);
}
printf("\n录入完毕,正在输出结果!\n");
int i = 0;
printf("低于150有学生%d人\n",a_level[0]);
for(i = 1; i < 7; i++) {
printf("%d~%d档有学生%d人\n",(i+29)*5,(i+29)*5+4,a_level[i]);
}
printf("高于179有学生%d人\n",a_level[7]);
return 0;
}
2.
#include
#include
char * cap_english[10] = {"zero","one","two","three","four","five","six","seven","eight","nine"};
int main()
{
char numberid[40];
printf("请输入待翻译编号:");
scanf("%s",numberid);
int n =strlen(numberid);
if(0 == n) {
printf("error\n");
}
else {
printf("%s翻译为%s",numberid, cap_english[numberid[0] - 48]);
int i = 0;
for(i = 1; i <= n - 1; i++) {
printf("-%s",cap_english[numberid[i] - 48]);
}
printf("\n");
}
return 0;
}
3.
#include
#include
int main()
{
int i_number, i_beginIndex, i_point;
printf("输入游戏人数,开始报数的编号和退出圈外的报数点:");
scanf("%d%d %d",&i_number, &i_beginIndex, &i_point);
while(i_number < 1 || i_beginIndex < 1 || i_point < 1) {
printf("输入错误,重新输入游戏人数,开始报数的编号和退出圈外的报数点:");
scanf("%d%d %d",&i_number, &i_beginIndex, &i_point);
}
char i_status[i_number + 1];
memset(i_status, 1,sizeof(i_status));//设置初始值为1,即都在圈内
int i_number_exit = 0;//退出圈外的人数
int i_index = i_beginIndex;//正在报数的人的数组下标
int i_sum = 0;//正在报的数字
while(1) {
if(i_number == (i_number_exit + 1)) {//设定退出条件,即此时还剩一个人没有退出
printf("游戏结束!\n");
break ;
}
while(0 == i_status[i_index]) {
i_index++;
if(i_index > i_number) {
i_index= 1;//数组到最后一个则回到第一个
}
}
i_sum++;
if(i_sum == i_point) {//此时到了退出圈外的报数点
i_status[i_index]= 0;
i_number_exit++;
i_sum= 0;
printf("%d号退出圈外\n",i_index);
}
i_index++;
if(i_index > i_number) {
i_index= 1;//数组到最后一个则回到第一个
}
}
for(i_index = 1; i_index <= i_number; i_index++) {
if(0 != i_status[i_index]) {
break;
}
}
printf("%d个人站成一个圈,从%d号开始报数1,报到%d的人退出圈外,从下一位开始继续从1开始报,最后留在圈内的是%d号\n", i_number, i_beginIndex, i_point, i_index);
return 0;
}
这个题,个人觉得比较有意思,而且在我看来真的是一个数学题,我简直想在纸上算一算了,不过最后还是觉的作为数学学渣,还是不要了,所以我没有没有办法验证我的方法是不是正确的,也没有用书上的伪代码写的代码,书上的比较有意思,启发了我一些思维,但是实际使用中我觉得我自己的话,第一时间并不会想到,只会在后期优化的时候优化出来,所以大概这就是学习算法的意义吧,第一时间就能在脑海中想到最有效率的方法吧!我还是贴出我自己写的代码。