閱讀心得:
這本書早有耳聞,但是一直沒有落實去看,最近在給自己充電,於是把這本書看了一遍。總體來說,這本書寫得很生動有趣,比較適合零基礎的人入門,對於我來說內容有些簡單(因為我本科已經接觸過一些算法,里面的有些內容我之前已經掌握)。但是,這本書除了算法之外,帶給我最大的幫助就是更加熟悉了一點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
以上就是該游戲的算法,由於代碼較多,這里就不再給出完整代碼啦!