阅读心得:
这本书早有耳闻,但是一直没有落实去看,最近在给自己充电,于是把这本书看了一遍。总体来说,这本书写得很生动有趣,比较适合零基础的人入门,对于我来说内容有些简单(因为我本科已经接触过一些算法,里面的有些内容我之前已经掌握)。但是,这本书除了算法之外,带给我最大的帮助就是更加熟悉了一点C,因为我的C语言不太好,一直都是学习java,比较逃避C,但是在学习这本书的时候,我把里面出现的所有代码都自己消化并手写了一遍,虽然里面的代码十分浅显,但是一本书写下来,我已经对C没有那么恐惧了。所以,我从心里很喜欢这本书。希望想要入门的小伙伴也能把这本书好好看一看。
阅读总结:
【这本书一共有九章,第九章是一个思路引领,前八章是妥妥的干货。在这里我对这本书的内容,结合自己的理解做一些记录,方便日后能够复习】
第一章:排序(有多重要大家心里都知道,不会排序的人生是不完整的人生~)
1.桶排序
说实话,我是在这本书里第一次接触桶排序,之前学的排序算法上来都是直接选择、插入、快速、合并,看了这本书才知道还有桶排序这个神奇宝贝哈哈哈。桶排序堪称最快最简单的排序,它的原理是定义一个数组book[]来标记数字是否出现。比如我们现在要对从1到99之间的若干数字进行排序,那么就定义一个数组book[],每出现一个数字 i,就让对应的book[i]的值加1,输入所有的数字之后,我们按照顺序输出即可。
??让我们来看一下核心代码:
//排序
for(i=1;i<=n;i++) { scanf("%d",&t); book[t]++; } //输出 for(i=1;i<=100;i++){ for(j=1;j<=book[i];j++){ printf("%d", i); }
}
2.冒泡排序
排序界的鼻祖没人反对吧?反正它是我学的第一种排序嘿嘿嘿。简单来说,就是每次都比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。比如我们想对n个数进行从大到小的排序,我们就需要进行n-1趟排序(最后一个数不用排),每次排序都找出最小的一个数放在最后。冒泡排序的时间复杂度为O(N^2)。
??让我们来看一下核心代码:
//排序 for(i=1;i<=n-1;i++){ for(j=1;j<=n-i;j++){ if(a[j]<a[j+1]){ t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } } //输出 for(i=1;i<=n;i++) printf("%d",a[i]);
3.快速排序
号称最常用的排序,桶排序浪费空间,冒泡排序浪费时间,于是快速排序前来报到~快速排序的原理是这样的:假设我们现在要对一行数字进行从小到大排序,我们在这一行数中随便找一个数作为基准数(用来参照的数),然后让一个哨兵从这行数的最右边开始,一步一步移动,找比基准数还小的数,另外一个哨兵从最左边找比基准数最大的数,(前提是不能相撞哦),找到之后,将这两个数进行交换,然后继续找下去,直到碰头为止,最后,我们再把基准数与哨兵的位置交换,一次排序就完成啦!此次排序将基准数放在了最终位置上,我们接着对基准数左边和右边分别重复刚刚的过程,就可以完成所有数字的排序啦。快速排序的平均时间复杂度为O(NlogN)。
??让我们来看一下核心代码:
void quicksort(int left,int right){ int i,j,t,temp; if(left>right) return; temp=a[left]; i=left; j=right; while(i!=j){ while(a[j]>=temp&&i<j) j--; while(a[i]<=temp&&i<j) i++; if(i<j){ t=a[i]; a[i]=a[j]; a[j]=t; } } a[left]=a[i]; a[i]=temp; quicksort(left,i-1); quicksort(i+1,right); }
第二章:栈、队列、链表
1.解密QQ号——队列
问题是这样的:小哼想要小哈的QQ号,小哈给了小哼一串数字,并告诉小哼,这串数字已经加密,解密方法是:先删除第一个数字,再将第二个数字放在末尾,然后删除第三个数字,再将第四个数字放在末尾。。。以此类推,最后得到所有的删除数字就是正确的QQ号。
这是一个非常典型的队列问题,队列的特点就是先入先出(先进去的排在前面,出来的时候也是前面的先出来,可以把它想象成一个水平放置的水管),删除最前面的数字就像队列中把队首元素出列,将第二个数字放在末尾,就像把队首的元素放在队尾一样,所以实现起来就比较清晰了。
这里还有一个问题,就是小哈给的QQ 号是固定的,如果我们不知道QQ号,需要这个队列具有一定灵活性怎么办呢?于是我们引出了另一个概念——结构体。
什么是结构体呢?其实就是把一组相关联的数据放在一起变成一个整体,这个整体就是结构体。下面我们来定义一个结构体:
struct queue{ int data[100];//队列的主体,用数组来存储内容 int head;//队首 int tail;//队尾 }; //千万注意定义完结构体之后的;不能丢掉
定义好这个结构体之后,就相当于定义了一个“数据类型”,当我们想要创建这样的结构体时,可以直接声明struct queue q;这个q就是结构体的名称。
回到刚才的问题:如何让队列中的元素可变呢?很简单,因为队列的data[]数组存放的是队列中的数据,我们给data[]数组元素赋值即可,需要注意,队列是在队尾插入元素的。
for(i=1;i<=9;i++){ //依次向队列插入9个数 scanf("%d",&q.data[q.tail]); q.tail++; }
??让我们来看一下完整代码:
#include <stdio.h> struct queue{ int head; int tail; int data[100]; }; int main(){ struct queue q; int i; //初始化队列 q.head=1; q.tail=1; for(i=1;i<=9;i++){ scanf("%d",&q.data[q.tail]); q.tail++; } while(q.head<q.tail){ printf("%d",q.data[q.head]); q.head++; q.data[q.tail]=q.data[q.head]; q.tail++; q.head++; } getchar();getchar(); return 0; }
2.解密回文——栈
问题:如何判断一个字符串是否为回文(正读反读均相同的字符串)
我们知道,如果一个字符串是回文的,那么它一定是中间对称的,我们找到中点mid之后,将mid之前的字符全部入栈,然后将当前栈中的字符依次出栈(栈的特点是先入后出,即后入的在上面,所以弹出来时也先弹出来,可以把它想象成一个桶),并与mid之后的字符进行匹配,如果都能匹配则说明当前这个字符串是回文字符串。
既然队列有队尾和队首,那么栈也一定有特殊位置——栈顶top,注意top指向栈顶元素,每次入栈之前都要先top+1,栈顶元素表示为s[top]。
??我们来看一下完整代码:
#include <stdio.h> #include <string.h> int main(){ char a[101],s[101]; int i,len,mid,next,top; gets(a);//读入一行字符串 len=strelen(a);//字符串长度 mid=len/2-1;//字符串中点 top=0;//栈的初始化 for(i=0;i<=mid;i++); s[++top]=a[i]; //判断字符串长度是奇数还是偶数,偶数从mid+1开始比,奇数从mid+2开始比,中间的那个不用管 if(len%2==0) next=mid+1; else next=mid+2; //开始匹配 for(i=next;i<=len-1;i++){ if(a[i]!=s[top]) break; top--; } //如果全都匹配完,top为0 if(top==0) printf("YES"); else printf("NO"); getchar(); getchar(); return 0; }
3.纸牌游戏——小猫钓鱼
游戏规则是这样的:现在有从1到9的纸牌若干,将其平均分成两份给小哼和小哈,小哼先拿出第一张牌在桌子上,然后小哈也拿出一张牌在桌子上,并放在小哼出的牌的上面,就这样两人交替出牌。出牌时,如果某人打出的牌与桌子上某张牌相同,即可将两张相同的牌以及中间夹着的牌全部取走,并依次放到自己手中牌的末尾。当任意一人手中的牌全部出完时,游戏结束,对手获胜。请你写一个程序来自动判断谁将获胜。
我们来分析一下思路:首先小哼和小哈都有两种操作,也就是出牌和赢牌,出牌的动作很像出队,赢牌的动作很像入队(赢的牌又被放到自己手中牌的末尾),而桌子就像是一个栈,每打出一张牌就是入栈一次(后出的牌在先出的牌的上面),当有人赢牌的时候,就把上面一部分的牌拿走,这个过程就像出栈。综上,我们需要两个队列和一个栈来模拟本次的游戏。
首先,用一个结构体来实现队列:
struct queue{ int data[1000]; int head; int tail; };
接着,用一个结构体来实现栈:
struct stack{ //因为桌子上最多有9张牌,所以data的大小设置为10即可。 int data[10]; int top; }
然后,我们定义两个队列q1,q2来模拟小哼和小哈的牌,用一个栈来模拟桌子上的牌。
struct queue q1,q2; struct stack s;
接下来初始化队列和栈:
q1.head=1; q1.tail=1; q2.head=1; q2.tail=1; s.top=0;
然后分别读入小哼和小哈手中的牌,我们假设游戏开始时,小哼和小哈手中各有6张牌。
//先读入6张牌,放到小哼手上 for(i=1;i<=6;i++){ scanf("%d",&q1.data[q1.tail]); q1.tail++; } //再读入6张牌,放到小哈手上 for(i=1;i<=6;i++){ scanf("%d",&q2.data[q2.tail]); q2.tail++; }
现在准备工作基本做好,游戏正式开始,小哼先出牌。
//将小哼出的牌赋值给临时变量t t=q1.data[q1.head];
那么我们如何判断小哼出的牌是赢是输呢?很简单,我们只要把 t 与桌子上的牌(也就是栈中的元素)一个一个进行比较,如果有相同的牌,标志位flag就为1,跳出比较。
//标志位初始时为0 flag=0; for (i=1;i<=s.top;i++){ if(t==s.data[i]{ flag=1; break; } }
接下来分别对 flag=0和 flag=1的情况进行分析:
//如果标志位为0,则q1出列,s入栈 if(flag==0){ q1.head++; s.top++‘ s.data[s.top]=t; }
//如果标志位为1,需要将赢得的牌入队 if(flag==1){ //先把正在出的这张牌入队 q1.head++; q1.data[q1.tail]=t; q1.tail++; //再把桌子上的牌入队 while(s.data[s.top]!=t){ q1.data[q1.tail]=s.data[s.top]; q1.tail++; s.top--; } }
小哼出牌基本模拟完了,小哈出牌也是一样的,接下俩我们要判断游戏如何结束。只要两个人有一个人没有牌游戏就会结束。
//当队列不为空时才能继续游戏,这个while循环应该加在两人出牌的外面 while(q1.head<q1.tail&&q2.head<q2.tail)
最后一步,输出谁最终赢得了游戏,以及游戏结束后,获胜者手中的牌和桌子上的牌。
if(q2.head==q2.tail){ printf("小哼win\n"); ptintf("小哼当前手中的牌是"); for(i=q1.head;i<=q1.tail-1;i++) printf(" %d",q1.data[i]); //如果桌子上有牌则一次输入桌子上的牌 if(s.top>0){ printf("\n桌子上的牌是); for(i=1;i<=s.top;i++){ printf(" %d",s.data[i]); } } else printf("桌子上已经没有牌了"); }
小哈是否赢牌也跟上面一样的思路。
但是,上面的代码其实有一个可以优化的地方,还记得我们是怎么判断有没有赢牌的嘛?没错,我们是通过枚举桌子上每一张牌来判断的,也就是用了一个for循环,其实可以用一个更好的方法,就是用一个数组来记录当前桌子上有哪些牌。
int book[10]; //初始化 //一张牌也没有出现 for(i=1;i<=9,i++) book[i]=0;
//接下来,如果出的牌桌子上没有,就入栈并且将book[t]=1;而且要注意的是,在桌子上的牌出栈的时候,每出一张牌,就要将其对应的book[s.data[s.top]]=0;
//出牌时 t=q1.data[q1.head]; if(book[t]==0) { q1.head++; s.top++; s.data[s.top]=t; book[t]=1; } //出栈时别忘了标志为0
以上就是该游戏的算法,由于代码较多,这里就不再给出完整代码啦!