1. 程式人生 > >《啊哈!演算法》閱讀筆記-----第二章《棧、佇列、連結串列》

《啊哈!演算法》閱讀筆記-----第二章《棧、佇列、連結串列》

緊接著上一章的閱讀,現在我來自習室開始了第二章(今天下雨了天氣很涼爽,太適合學習啦)。那麼就一起開始吧~

第二章----棧、佇列、連結串列

第一節----解密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;
} 

好啦,那麼第二章就練習到這裡~ 第三章《列舉》見~