(本文為個人學習數據結構課程和三年磨一劍的<<大話數據結構>> 后的筆記,如有侵權,請直接聯系我,立即刪除)(杯具了,昨天寫的保存的時候,着急了沒看,早上來看沒有發布成果,只有從頭再寫一遍了)
一.棧(Stack)
1.定義:僅在表尾進行插入和刪除操作的線性表
2.棧的抽象數據類型:
ADT 棧(Stack)
Data
同線性表.元素具有相同的類型,相鄰元素具有前驅和后繼關系.
Operation
InitStack(*S): 初始化操作,建立一個空的棧.
DestoryStack(*S): 若棧存在,則銷毀它.
StackEmpty(S): 若線棧為空,返回true,否則返回false.
CleaStack(*L): 將棧清空.
GetTop(L,i,*e): 若棧不為空,用e返回棧頂元素.
Push(*S,e): 若棧存在,置插入新元素e到棧S中並成為棧頂元素.
Pop(*S,*e): 若棧不為空,刪除棧的棧頂元素,並用e返回其值.
StackLentgth(L) : 返回棧S的元素個數.
3.棧的順序存儲結構
#include<stdio.h> #include<malloc.h> #define MAXSIZE 100 /*存儲空間初始分配量*/ #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 typedef int SElemType, Status; /*ElemType 類型根據實際情況而定,這里假設為int*/ /*Status 是函數的類型,其值是函數結果的狀態碼,如OK等*/ /*棧的順序存儲結構*/ typedef struct SqStack { SElemType data[MAXSIZE]; /*存儲棧的長度*/ int top; /*用於棧頂元素*/ }SqStack; /*初始化棧*/ void StackInit(SqStack *S) { S->top = -1; } /*進棧操作*/ Status Push(SqStack *S, SElemType e) { if(S->top == MAXSIZE -1 )/*棧滿*/ { return ERROR; } S->data[++S->top] = e; printf(" %d ", e); return OK; } /*出棧操作*/ Status Pop(SqStack *S) { int e; if(S->top == -1 )/*棧空*/ { return ERROR; } e = S->data[S->top--]; printf("\n出棧元素值為:%d", e); return OK; } int main() { SqStack *S; StackInit(S); printf("\n初始化后,棧頂元素值為:%d", S->top); printf("\n進棧元素值為:"); Push(S, 1); Push(S, 2); Push(S, 6); printf("\n進棧后,棧頂元素值為:%d", S->data[S->top]); Pop(S); printf("\n出棧后,棧頂元素值為:%d", S->data[S->top]); }
運行結果:
4.棧的鏈式存儲結構
#include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 typedef int SElemType, Status; /*ElemType 類型根據實際情況而定,這里假設為int*/ /*Status 是函數的類型,其值是函數結果的狀態碼,如OK等*/ /*線性表的單鏈表存儲結構*/ typedef struct StackNode { SElemType data; /*結點類型定義*/ struct StackNode *next; /*結點的指針域*/ }StackNode, *LinkStackPtr; /*棧的鏈式棧存儲結構*/ typedef struct LinkStack { int count; /*存儲棧的長度*/ LinkStackPtr top; /*用於棧頂元素*/ }LinkStack; /*初始化棧*/ void StackInit(LinkStack *S) { S->count = 0; S->top = NULL; } /*進棧操作*/ Status Push(LinkStack *S, SElemType e) { LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode)); s->data = e; s->next = S->top; S->top = s; S->count++; printf(" %d ", e); return OK; } /*出棧操作*/ Status Pop(LinkStack *S) { LinkStackPtr p; if( S->count == -1 )/*棧空*/ { return ERROR; } int e; e = S->top->data; p= S->top; S->top = S->top->next; free(p); S->count--; printf("\n出棧元素值為:%d", e); return OK; } int main() { int i = 0; LinkStack *S; StackInit(S); printf("\n初始化后,棧頂元素值為:%d", S->top); printf("\n進棧元素值為:"); while(i<=10) { Push(S, i); i++; } printf("\n進棧后,棧頂元素值為:%d,棧長度為:%d", S->top->data,S->count); Pop(S); printf("\n出棧后,棧頂元素值為:%d,棧長度為:%d", S->top->data,S->count); }
運行結果:
5.棧的應用
1).遞歸
菲波那契(Fibonacci)數列
0 , 當n =0
F(n) = 1 , 當n=1
F(n-1)+F(n-2) , 當n>1
迭代:
#include<stdio.h> int main() { int i,j=0; int a[40]; a[0] = 0; a[1] = 1; printf("%d ", a[0]); printf("%d ", a[1]); for(i = 2; i < 40; i++) { a[i] = a[i-1] + a[i-2]; j++; if(j%10==0) printf("\n"); printf("%d ", a[i]); } return 0; }
遞歸:
#include<stdio.h> int Fbi() { if( n < 2) { return n==0? 0 : 1; } return Fbi(i-1)+Fbi(i-2); } int main() { int i; for(i = 40; i < 40; i++) { printf("%d ", Fbi(i)); } return 0; }
通過分析遞歸時的執行的退回順序是它前行順序的逆序
2).四則運算表達式求值
中綴表達式(標准四則運算表達式),即"9+ (3-1) X3+10/2"叫作中綴表達式.
中綴表達式轉后綴表達式:
規則: 從左到右遍歷表達式的每個數字和符號,遇到數字就輸出,即成為后綴表達式的一部分,遇到符號,就先判斷其與棧頂符號的優先級,是右括號或優先級低的於
棧頂符號(乘除優先加減)則棧頂依次出棧並輸出,並將當前符號進棧,一直到輸出后綴表達式為止.
中綴表達式:"9+ (3-1) X3+10/2"
步驟:
1.初始化一個空棧,用來對符號進出使用
2.第一個字符是數字9,將9輸出,后面是"+",進棧.
3.第三個字符"(",因其是左括號,還未匹配,故進棧,后面是數字3,輸出3,總表達式為93,接着是"-",進棧.
4.接下來是數字1,輸出,總表達式為:931,后面是符號")",此時,匹配此前的"(",所以棧頂元素依次出棧,並輸出,直到"("出棧為止.輸出"-",此時總表達式為:931-
5.接下來是"*",優先級高於"+","*"進棧,接着是數字3,輸出,此時從表達式為931-3.
6.之后是"+",比此時棧頂符號為"*"的優先級低,棧中元素出棧並輸出(沒有比"+"號更低的優先級,所以全部出棧),總輸出表達式為:931-3*-.然后激昂當前"+"進棧
7.緊接着是10,輸出,后是符號"/",優先級高於"+",進棧,最后一個是數字2輸出.此時總表達式為931-3*+10 2
8.已經到了最后,棧中符號依次出棧並輸出,最終輸出表達式為:"9 3 1 - 3 * + 10 2 / 4"
后綴表達式(逆波蘭表示):一種不需要括號的后綴表達式,上中綴表達式轉化為后綴表達式為:"931-3*+102/+"
后綴表達式的運算:
規則:從左到右遍歷表達式的每個數字和符號,遇到數字就進棧,遇到符號,就將處於棧頂的2個數字出棧,進行運算,運算結果進棧,
一直到最終獲得結果.
后綴表達式:"931-3*+102/+"
步驟:
1.初始化一個空棧,用來對要運算數字進出使用.
2.后綴表達式中前三個都是數字,所以9,3,1進棧,此時棧中元素從上到下依次為1,3,9
3.接下來是"-",所以將棧中的1出棧作為減數,3出棧作為被除數,運算 "3-1=2", 將2進棧,再將后面一位數字3進棧,此時棧中元素從上到下依次為3,2,9
4.后面是"*",也就意味着棧中3和2出棧,2和3相乘,得到6,將6進棧,此時棧中元素從上到下依次為6,9
5.下面是"+",所以棧中6和9出棧,6+9=15,將15進棧,此時棧中只有一個元素15
6.接着就是10,2,依次進棧,此時棧中元素從上到下依次為2,10,15
7.接下來是符號"/",將2,10出棧,2為除數,10為被除數,10/2=5,將5進棧,此時棧中元素從上到下依次為5,15
8.最后一個符號"+",棧中最后2個元素出棧,相加得到20,為最終結果
二.隊列(Queue)
1.定義:只允許在一端(隊尾)進行插入操作,而在另一端(對頭)進行刪除操作.
2.隊列的抽象數據類型:
ADT 隊列(Queue)
Data
同線性表.元素具有相同的類型,相鄰元素具有前驅和后繼關系.
Operation
InitQueue(*Q): 初始化操作,建立一個空的隊列Q.
DestoryQueue(*Q): 若隊列存在,則銷毀它.
CleaQueue(*Q): 將隊列清空.
QueueEmpty(Q): 若線性表為空,返回true,否則返回false.
GetHead(Q,*e): 若隊列存在且非空,用e返回隊列Q的對頭元素.
EnQueue(*Q,e): 若隊列存在,新元素e到隊列Q中並成為對尾元素.
DeQueue(*Q,*e): 若隊列存在,刪除隊列中的頭元素,並用e返回其值.
QueueLentgth(L) : 返回隊列的元素個數.
3.隊列的順序存儲結構
A. 1 B.2 C.3 4.D
循環隊列:
#include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 #define MAXSIZE 100 typedef int QElemType, Status; /*ElemType 類型根據實際情況而定,這里假設為int*/ /*Status 是函數的類型,其值是函數結果的狀態碼,如OK等*/ /*線性表的單鏈表存儲結構*/ typedef struct SqQueue { QElemType data[MAXSIZE]; /*結點類型定義*/ int front; /*頭指針*/ int rear; /*尾指針,若隊列不空,指向隊列尾元素的下一個位置*/ }SqQueue; /*初始化隊列*/ Status QueueInit(SqQueue *Q) { Q->front = 0; Q->rear = 0; printf("隊列初始化完畢"); return OK; } /*返回隊列長度*/ Status QueueLength(SqQueue *Q) { return (Q->rear - Q->front + MAXSIZE)%MAXSIZE; } /*入隊操作*/ Status EnQueue(SqQueue *Q, QElemType e) { if((Q->rear+1)%MAXSIZE == Q->front)/* 判斷隊列是否已滿*/ return ERROR; Q->data[Q->rear] = e; Q->rear = (Q->rear+1)%MAXSIZE; printf(" %d ", e); return OK; } /*出隊操作*/ Status DeQueue(SqQueue *Q) { printf("\n出隊前隊頭指針下標值為:%d,出隊前隊尾指針下標值為:%d", Q->front,Q->rear); printf("\n出隊元素值為:%d", Q->data[Q->front]); if( Q->front == Q->rear )/*隊空*/ return ERROR; int e; printf("\n出隊元素值為:%d", Q->data[Q->front]); e = Q->data[Q->front]; Q->front = (Q->front+1)%MAXSIZE; return OK; } int main() { SqQueue *Q; QueueInit(Q); printf("\n初始隊列后,隊列長度為:%d", QueueLength(Q)); printf("\n入隊元素值為:"); EnQueue(Q, 1); EnQueue(Q, 2); EnQueue(Q, 3); EnQueue(Q, 4); printf("\n入隊后,隊列長度為:%d", QueueLength(Q)); DeQueue(Q); printf("\n出隊后,隊列長度為:%d", QueueLength(Q)); }
運行結果:
4.隊列的鏈式存儲結構
#include<stdio.h> #include<malloc.h> #define OK 1 #define ERROR 0 #define TURE 1 #define FALSE 0 #define MAXSIZE 100 typedef int QElemType, Status; /*ElemType 類型根據實際情況而定,這里假設為int*/ /*Status 是函數的類型,其值是函數結果的狀態碼,如OK等*/ /*線性表的單鏈表存儲結構*/ typedef struct QNode { QElemType data; /*結點類型定義*/ struct QNode *next; }QNode, *QueuePtr; typedef struct { QueuePtr front, rear; /*隊頭隊尾指針*/ }LinkQueue; /*初始化隊列*/ Status QueueInit(LinkQueue *Q) { Q->front = (QueuePtr)malloc(sizeof(QNode)); //頭結點 Q->front->next = NULL; Q->rear = Q->front; printf("鏈隊列初始化完畢"); return OK; } /*返回隊列長度*/ Status QueueLength(LinkQueue *Q) { int l=0; QueuePtr p= Q->front->next; while(p!=NULL){ l++; p=p->next; } return l; } /*入隊操作*/ Status EnQueue(LinkQueue *Q, QElemType e) { QueuePtr s = (QueuePtr)malloc(sizeof(QNode)); if(!s) /*分配內存失敗*/ return ERROR; s->data = e; s->next = NULL; if(Q->front->next==NULL) Q->front->next = s; else Q->rear->next = s; Q->rear = s; printf(" %d ", e); return OK; } /*出隊操作*/ Status DeQueue(LinkQueue *Q) { if( Q->front == Q->rear )/*隊空*/ return ERROR; int e; QueuePtr p; p = Q->front->next; e = p->data; Q->front->next = p->next; if(Q->rear == p) /*只有一個結點*/ Q->rear = Q->front; free(p); printf("\n出隊元素值為:%d",e); return OK; } int main() { LinkQueue *Q; QueueInit(Q); printf("\n初始隊列后,隊列長度為:%d", QueueLength(Q)); printf("\n入隊元素值為:"); EnQueue(Q, 1); EnQueue(Q, 2); EnQueue(Q, 3); EnQueue(Q, 4); printf("\n入隊后,隊列長度為:%d", QueueLength(Q)); DeQueue(Q); printf("\n出隊后,隊列長度為:%d", QueueLength(Q)); }
運行結果:
農夫過河問題
一個農夫帶着一只狼、一只羊和一棵白菜,身處河的南岸。他要把這些東西全部運到北岸。問題是他只有一條船,船小到只能容下他和一件物品,當然,船只有農夫能撐。另外,因為狼能吃羊,而羊愛吃白菜,所以農夫不能留下羊和狼或者羊和白菜單獨在河的一邊,自己離開。好在狼屬於食肉動物,它不吃白菜。請問農夫該采取什么方案,才能將所有的東西安全運過河呢?
一、算法與數據結構的設計:
要模擬農夫過河的問題,用四位二進制數順序分別表示農夫、狼、白菜和羊的位置。用0表示農夫和或者某種東西在河的南岸,1表示在河的北岸。則問題的初始狀態是整數(其二進制表示為0000);而問題的終結狀態是整數15(其二進制表示為1111)。
用整數location表示用上述方法描述的狀態,函數返回值為真(1)表示所考察的人或物在河的北岸,否則在南岸。
二、算法的精化:
用一個整數隊列moveTo,把搜索過程中每一步所有可能達到的狀態都保存起來。隊列中每一個元素表示一個可以安全到達的中間狀態。另外,用一個整數數組route,用於記錄已被訪問過的各個狀態,以及已被發現的能夠到達這些狀態的前驅狀態。route數組只需要使用16個元素。Route的每一個元素初始化值均設置為-1,每當在隊列加入一個新狀態時,就把route中以該狀態坐下標的元素的值改為達到這個狀態的前一狀態的下標值。所以,數組的第i個元素,不僅記錄狀態i是否已被訪問過,同時對已被訪問過的狀態,還保存了這個狀態的前驅狀態下標。算法結束后,可以利用route數組元素的值生成一個正確的狀態路徑。
#include <stdio.h> #include <stdlib.h> typedef int DataType; //順序隊列:類型和界面函數聲明 struct SeqQueue { // 順序隊列類型定義 int MAXNUM; // 隊列中最大元素個數 int f, r; DataType *q; }; typedef struct SeqQueue *PSeqQueue; // 順序隊列類型的指針類型 PSeqQueue createEmptyQueue_seq(int m) { //創建一個空隊列 PSeqQueue queue = (PSeqQueue)malloc(sizeof(struct SeqQueue)); if (queue != NULL) { queue->q = (DataType*)malloc(sizeof(DataType) *m); if (queue->q) { queue->MAXNUM = m; queue->f = 0; queue->r = 0; return (queue); } else free(queue); } printf("Out of space!!\n"); // 存儲分配失敗 return NULL; } int isEmptyQueue_seq(PSeqQueue queue) { //判斷隊列是否為空 return (queue->f == queue->r); } void enQueue_seq(PSeqQueue queue, DataType x) // 在隊尾插入元素x { if ((queue->r + 1) % queue->MAXNUM == queue->f) printf("Full queue.\n"); else { queue->q[queue->r] = x; queue->r = (queue->r + 1) % queue->MAXNUM; } } void deQueue_seq(PSeqQueue queue) // 刪除隊列頭部元素 { if (queue->f == queue->r) printf("Empty Queue.\n"); else queue->f = (queue->f + 1) % queue->MAXNUM; } DataType frontQueue_seq(PSeqQueue queue) { if (queue->f == queue->r) printf("Empty Queue.\n"); else return (queue->q[queue->f]); } //個體狀態判斷函數 int farmer(int location) { //判斷農夫的位置 return (0 != (location &0x08)); } int wolf(int location) { //判斷狼的位置 return (0 != (location &0x04)); } int cabbage(int location) { //判斷白菜的位置 return (0 != (location &0x02)); } int goat(int location) { //判斷羊的位置 return (0 != (location &0x01)); } //安全狀態的判斷函數 int safe(int location) { // 若狀態安全則返回true if ((goat(location) == cabbage(location)) && (goat(location) != farmer (location))) return (0); // 羊吃白菜 if ((goat(location) == wolf(location)) && (goat(location) != farmer (location))) return (0); // 狼吃羊 return (1); // 其他狀態是安全的 } main() { int i, movers, location, newlocation; int route[16]; //用於記錄已考慮的狀態路徑 PSeqQueue moveTo; //用於記錄可以安全到達的中間狀態 moveTo = createEmptyQueue_seq(20); //創建空隊列 enQueue_seq(moveTo, 0x00); //初始狀態進隊列 for (i = 0; i < 16; i++) route[i] = - 1; //准備數組route初值 route[0] = 0; while (!isEmptyQueue_seq(moveTo) && (route[15] == - 1)) { location = frontQueue_seq(moveTo); //取隊頭狀態為當前狀態 deQueue_seq(moveTo); for (movers = 1; movers <= 8; movers <<= 1) //考慮各種物品移動 if ((0 != (location &0x08)) == (0 != (location &movers))) //農夫與移動的物品在同一側 { newlocation = location ^ (0x08 | movers); //計算新狀態 if (safe(newlocation) && (route[newlocation] == - 1)) //新狀態安全且未處理 { route[newlocation] = location; //記錄新狀態的前驅 enQueue_seq(moveTo, newlocation); //新狀態入隊 } } } // 打印出路徑 if (route[15] != - 1) //到達最終狀態 { printf("The reverse path is : \n"); for (location = 15; location >= 0; location = route[location]) { printf("The location is : %d\n", location); if (location == 0) exit(0); } } else printf("No solution.\n"); //問題無解 }
程序運行結果如下: The reverse path is :
The location is : 15
The location is : 6
The location is : 14
The location is : 2
The location is : 11
The location is : 1
The location is : 9
The location is : 0
結果分析:從初始狀態0到最后狀態15的動作序列為:
1.初始全部在南岸(0000),
2農夫把羊帶到北岸(1001),
3.農夫獨自回到南岸(0001),
4.農夫把白菜帶到北岸(1011),
5.農夫帶着羊回到南岸(0010),
6.農夫把狼帶到北岸(1110),
7.農夫獨自回到南岸(0110),
8.農夫把羊帶到北岸(1111)
程序運行最后location結果為15(二進制為1111),
即農夫和其他東西都安全運過了北岸。解決了農夫過河問題.