数据结构之栈(Stack)与队列(Queue)
(本文为个人学习数据结构课程和三年磨一剑的<<大话数据结构>> 后的笔记,如有侵权,请直接联系我,立即删除)(杯具了,昨天写的保存的时候,着急了没看,早上来看没有发布成果,只有从头再写一遍了)
一.栈(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),
即农夫和其他东西都安全运过了北岸。解决了农夫过河问题.