《啊哈!演算法》閱讀筆記-----第二章《棧、佇列、連結串列》
緊接著上一章的閱讀,現在我來自習室開始了第二章(今天下雨了天氣很涼爽,太適合學習啦)。那麼就一起開始吧~
第二章----棧、佇列、連結串列
第一節----解密qq號----佇列
這本書的引入是很有趣的,就算有時有點煩躁,但看這本書我不會覺得這本書看不下去,真的是很適合我這種懶孩子。那我就直接總結重要知識點,應該會很枯燥的hhhh … 我們要進行的解密操作是: 將序列的第一個數刪除,將第二個數移到序列末尾; 將序列的第三個數刪除,將第四個數移到序列末尾; 將序列的第五個數刪除,將第六個數移到序列末尾; … 直到刪除完所有數再按照刪除的序列整理所得,就是解密後的序列。 … 我們用陣列來儲存資料序列,那麼陣列是怎麼刪除的呢?想想,是通過移動後續元素向前,覆蓋掉當前要刪除的元素來達到刪除的效果的。這樣的移動操作是非常消耗時間的。所以引入倆個整形變數(這裡注意和指標的區別),head和tail。 head記錄序列第一位的下標,tail記錄序列最後一位元素的下一位的下標。 注意哈,這裡的tail不是標記序列的最後一個元素。是為了避免當頭標記和尾標記相遇在一個元素上時產生誤會。因為我們規定倆標記相遇時就代表隊列為空。 有個兩個標記之後,一切就變得很明瞭了。 刪除頭標記所標記的元素,只需:
head++;
往佇列尾部新增元素就只需:
q[tail] = x;
tail++;
…
分析結束了,實現程式碼也就很好寫了。
//當頭標記小於尾標記時
while(head<tail){
printf("%d,",q[head]); //列印被刪除出列的資料
head++; //然後進行真正的刪除操作
q[tail] = q[head]; //尾標記當前標記的位置的值換成頭標記當前標記的值
tail++; //尾標記後移
head++; //頭標記移到下一個待處理的資料
}
… 經過一個小demo我們其實已經在用佇列的概念了。 佇列:是線性結構的一種。 但比較特殊的是: 只允許在隊首進行刪除操作,也就是“出隊”; 只允許在隊尾進行新增操作,也就是“入隊”。 當head=tail時,規定為空佇列。(此處呼應tail為什麼不標記末元,而是末元的下一個位置)。
第二節------解密迴文-----棧
回憶一下,佇列是一個先進先出的資料結構,我們用佇列實現了一個解密qq號的demo。現在我們繼續來看一種先進後出的線性結構----棧。 … 我們要思考的問題是:如何判斷一個字串是不是迴文字串? 迴文:abba abcba 分析: 1.輸入一個字串 2.計算字串的長度len 3.計算字串的mid,注意mid並不是長度的一半,只是被我們用來方便判斷迴文的一個特殊位置 4.把字串的前部分入棧 5.之後再出棧,邊出棧邊和字串對應的值進行對比 程式碼實現:
//判斷是否迴文---用棧來實現 #include<stdio.h> #include<string.h> int main(){ char str[20],s[20]; int i,len,mid,top,next; //1.輸入一個字串 printf("請輸入一個字串:"); scanf("%s",&str); //2.計算字串的長度len len = strlen(str); //3.計算字串的mid,注意mid並不是長度的一半,只是被我們用來方便判斷迴文的一個特殊位置 mid = len/2 -1; //4.把字串的前部分入棧 top=0; for(i=0;i<=mid;i++){ top++; s[top]=str[i]; } //為之後的對比操作做準備,計算出需要進行字元匹配的起始下標 if(len%2==0){ next = mid + 1; }else{ next = mid + 2; } //5.之後再出棧,邊出棧邊和字串對應的值進行對比 for(i = next;i<len;i++){ if(str[i]!=s[top]){ break; } top--; } //6.輸出結果 if(top==0){ printf("yes"); }else{ printf("no"); } }
第三節 撲克遊戲
接下來,我們要去試著用棧和佇列的思想做一個綜合的小demo. demo是一個小遊戲:甲和乙一起玩一個簡單的撲克遊戲。遊戲開始,倆人手中各持有1~9牌號中的6張牌,甲先出牌,按照牌的到手順序依次輪流將牌放在桌上, 過程中,若有剛出的牌與桌上已有的另一張牌值相同,則出牌的人贏走倆張牌之間的所有牌(包括這倆張牌)。誰先把對方手中的牌玩得一個不剩就算贏。 分析:
1.拿在手裡的牌是按順序出的,所以我們用兩個佇列來儲存。 2.桌上的牌需要判斷與剛出的牌是否一致,考慮到迴圈遍歷判斷太麻煩,正好本質就是“除重”,我們參照桶排序的方法來實現除重。 3.桌上所有的牌資訊用棧來存。
程式碼實現:(本垃圾敲了好一下午,debug滴得我慌慌)
#include<stdio.h>
//1.定義佇列來儲存手中的牌
struct queue{
int data[9];
int head;
int tail;
};
//2.定義棧來儲存桌上的牌
struct stack{
int data[10];
int top; //指向棧頂的標記
};
int main(){
struct queue q1,q2;
struct stack poker;
int i,j,t,book[10];
//3.初始化倆人手中的牌和桌上的牌
q1.head=1;q1.tail=1;
q2.head=1;q2.tail=1;
poker.top=0;
//4.分別給倆人發6張牌
printf("請給甲發牌:");
for(i = 1; i <= 6;i++){
scanf("%d",&q1.data[q1.tail]);
q1.tail++;
}
printf("請給乙發牌:");
for(j = 1; j <= 6;j++){
scanf("%d",&q2.data[q2.tail]);
q2.tail++;
}
//5.初始化標記桌面上的牌的桶
for(i = 1;i<10;i++){
book[i]=0;
}
//6.當倆人佇列都不為空的時候進行出牌迴圈
while(q1.head<q1.tail && q2.head<q2.tail){
//甲出牌
t = q1.data[q1.head];
//判斷甲是否贏牌
if(book[t] == 0){
//甲不贏牌,要進行牌出甲佇列、入桌子棧、桶賦值三個操作
q1.head++;
poker.top++;
poker.data[poker.top]=t;
book[t]=1;
} else{
//甲贏牌:剛出的牌出甲佇列進入甲佇列隊尾、倒序遍歷桌子的牌,依次進甲佇列、取消桶中的標記
q1.head++;
q1.data[q1.tail] = t;
q1.tail++;
while(poker.data[poker.top]!=t){
book[poker.data[poker.top]] = 0;
q1.data[q1.tail] = poker.data[poker.top];
poker.top--;
q1.tail++;
}
//收回桌子上另外一張t
book[poker.data[poker.top]] = 0;
q1.data[q1.tail] = poker.data[poker.top];
q1.tail++;
poker.top--;
}
//7.若甲手中的牌已打完
if(q1.head==q1.tail){
break;
}
//8.乙出牌
t = q2.data[q2.head];
if(book[t] == 0){
q2.head++;
poker.top++;
poker.data[poker.top] = t;
book[t] = 1;
} else{
q2.head++;
q2.data[q2.tail] = t;
q2.tail++;
while(poker.data[poker.top]!=t){
//每個拿回的牌的book都要置零
book[poker.data[poker.top]] = 0;
q2.data[q2.tail] = poker.data[poker.top];
poker.top--;
q2.tail++;
}
book[poker.data[poker.top]]=0;
q2.data[q2.tail] = poker.data[poker.top];
q2.tail++;
poker.top--;
}
}
//9.當倆人中有一個的牌被贏完時
if(q1.head == q1.tail){
//乙贏了
printf("乙贏得了這場撲克遊戲");
//輸出乙現在手裡的牌
printf("乙現在手中的牌是:");
for(i = q2.head;i<q2.tail;i++){
printf("%d ",q2.data[i]);
}
//輸出桌上的牌(如果有的話)
if(poker.top>0){
printf("桌子上的牌:");
for(i = 1;i <= poker.top;i++ ){
printf("%d ",poker.data[i]);
}
}else{
printf("桌子上沒牌了。");
}
}
if(q2.head == q2.tail){
printf("甲贏得了這場撲克遊戲");
//輸出甲現在手裡的牌
printf("甲現在手中的牌是:");
/*wihle(q1.tail!=q1.head){
printf("%d ",q1.data[--tail]);
}*/
for(i = q1.head;i<q1.tail;i++){
printf("%d ",q1.data[i]);
}
//輸出桌上的牌(如果有的話)
if(poker.top>0){
printf("桌子上的牌:");
for(i = 1;i <= poker.top;i++ ){
printf("%d ",poker.data[i]);
}
}else{
printf("桌子上沒牌了。");
}
}
getchar();
getchar();
return 0;
}
第五節 連結串列
之前我們使用陣列來儲存資料,明顯它適合查詢和修改,但是在增加和刪除這倆個功能模組,陣列可就墨跡得多了。主要還是看實際需求來選擇儲存結構了。連結串列呢,它的查詢和修改和陣列比起來是有點浪費時間,但是連結串列的優點在於,刪除和增加的時候,不用像陣列那樣移動很多個元素,只需要修改一下指標就好了。 在開始之前,我們先來複習三個符號和一個函式: & :取地址符 *:乘號 :宣告一個指標變數 :間接訪問運算子 ->:結構體指標運算子,用來訪問結構體內部成員 malloc->從記憶體中申請分配指定大小的記憶體空間。 malloc的使用: 1.直接括號內寫要申請分配的位元組數。eg: malloc(4) 2.若不清楚型別的位元組大小,可間接表示。eg: malloc(sizeof(int)) 3.由於malloc函式的返回值是:void * 型別,表示未確定型別的指標。在c和c++中void *型別可以強制轉換為各種型別。一般都需要進行型別轉換。eg:p= (int *)malloc(sizeof(int)) 下面我們直接來做一個例子: 直接上程式碼啦:
//LinkedList Insert
#include<stdio.h>
struct node{
int data;
struct node *next;
};
int main(){
int n,i,x,index,sum;
struct node *p,*q,*head,*t,*ne;
printf("輸入初始化連結串列長度:");
scanf("%d",&n);
head=NULL;
printf("請輸入連結串列資料:");
//------------初始化連結串列資料------------------
for(i=0;i<n;i++){
p = (struct node *)malloc(sizeof(struct node));
scanf("%d",&p->data);
p->next=NULL;
if(head==NULL){
head=p;
}else{
q->next=p;
}
q=p;
}
//------------輸出原始連結串列-----------------------
t=head;
printf("初始化結束的連結串列為:");
printf("\n");
while(t!=NULL){
printf("%d ",t->data);
t=t->next;
}
//-------------插入資料到連結串列指定位置--------------
printf("請輸入要插入的數,以及它的位置索引:例如(6 4)");
printf("\n");
sum=2; //sum=1的話,插入的位置會後推一個。這裡還沒想透徹。估計是頭節點造成的。
scanf("%d %d",&x,&index);
t=head;
while(t!=NULL){
if(sum==index){
ne = (struct node *)malloc(sizeof(struct node));
ne->data=x;
ne->next=t->next;
t->next=ne;
break;
}
sum++;
t=t->next;
}
//--------------------輸出連結串列資料--------------
t=head;
printf("插入結束的連結串列為:");
printf("\n");
while(t!=NULL){
printf("%d ",t->data);
t=t->next;
}
getchar();
getchar();
return 0;
}
好啦,那麼第二章就練習到這裡~ 第三章《列舉》見~