1.1
栈的定义
栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。如下所示:
结论:后进先出(Last In First Out),简称为LIFO线性表。
栈的基本运算有六种:
构造空栈:InitStack(S)、
判栈空: StackEmpty(S)、
判栈满: StackFull(S)、
进栈: Push(S,x)、可形象地理解为压入,这时栈中会多一个元素
退栈: Pop(S) 、 可形象地理解为弹出,弹出后栈中就无此元素了。
取栈顶元素:StackTop(S),不同与弹出,只是使用栈顶元素的值,该元素仍在栈顶不会改变。
由于栈也是线性表,因此线性表的存储结构对栈也适用,通常栈有顺序栈和链栈两种存储结构,这两种存储结构的不同,则使得实现栈的基本运算的算法也有所不同。
我们要了解的是,在顺序栈中有"上溢"和"下溢"的概念。顺序栈好比一个盒子,我们在里头放了一叠书,当我们要用书的话只能从第一本开始拿(你会把盒子翻过来吗?真聪明^^),那么当我们把书本放到这个栈中超过盒子的顶部时就放不下了(叠上去的不算,哼哼),这时就是"上溢","上溢"也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,我们再去拿,看看没书,把盒子拎起来看看盒底,还是没有,这就是"下溢"。"下溢"本身可以表示栈为空栈,因此可以用它来作为控制转移的条件。
链栈则没有上溢的限制,它就象是一条一头固定的链子,可以在活动的一头自由地增加链环(结点)而不会溢出,链栈不需要在头部附加头结点,因为栈都是在头部进行操作的,如果加了头结点,等于要在头结点之后的结点进行操作,反而使算法更复杂,所以只要有链表的头指针就可以了。
1.2 栈的顺序存储
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
#define FALSE 0
#define TRUE 1
#define OVERFLOW_ERROR -1
#define ERROR 0
typedef int SElemType;
typedef int Status;
typedef struct
{
SElemType *base;//尾巴
SElemType *top;//头
int stacksize;//长度
} Sq_Stack;
//初始化
Status Init_stack(Sq_Stack &sq_stack)
{
sq_stack.base = new SElemType[MAXSIZE];
if(!sq_stack.base)
{
exit(OVERFLOW_ERROR);
}
else
{
sq_stack.top = sq_stack.base;
sq_stack.stacksize = MAXSIZE;
}
return TRUE;
}
//入栈
Status Push_stack(Sq_Stack &sq_stack, SElemType e)
{
if(sq_stack.top - sq_stack.base == sq_stack.stacksize)
{
return ERROR;
}
else
{
(*sq_stack.top) = e;//数赋值
sq_stack.top = sq_stack.top + 1;//指针加一t
return TRUE;
}
}
//出栈就是删除栈顶元素
Status Pop_stack(Sq_Stack &sq_stack, SElemType &e)
{
if(sq_stack.top == sq_stack.base)
{
//空栈
return ERROR;
}
else
{
sq_stack.top = sq_stack.top - 1;
e = (*sq_stack.top);
return TRUE;
}
}
//取栈顶元素
Status GetTop_stack(Sq_Stack &sq_stack, SElemType &e)
{
if(sq_stack.top != sq_stack.base)
{
sq_stack.top = sq_stack.top-1;
e = *sq_stack.top;
sq_stack.top = sq_stack.top + 1;
}
}
int main(void)
{
Sq_Stack sq_stack;
if(Init_stack( sq_stack))
{
printf("初始化成功!|\n");
}
else
{
printf("初始化失败:\n");
}
SElemType ElemType;
printf("请输入你要插入的元素:\n");
while(scanf("%d", &ElemType) && ElemType != 0)
{
printf("请输入你要插入的元素:\n");
Push_stack( sq_stack, ElemType);
}
int flag = 1;
while(flag)
{
if(GetTop_stack(sq_stack, ElemType))
{
printf("'\t%d\n", ElemType);
flag = 0;
}
}
flag = 1;
while(flag)
{
if(Pop_stack(sq_stack, ElemType))
{
printf("\t%d", ElemType);
}
else
{
flag = 0;
}
}
}
结论:由于栈的插入和删除操作具有它的特殊性,所以用顺序存储结构表示的栈并不存在插入删除数据元素时需要移动的问题,但栈容量难以扩充的弱点仍就没有摆脱。
1.3 栈的链式存储
若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称作"链栈"。链栈通常用一个无头结点的单链表表示。如图所示:
栈的操作是线性表操作的特例。
///链栈
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define ERROR 0
#define OVERFLOW_ERROR -1
typedef char SElemType;
typedef int Status;
typedef struct StackNode
{
SElemType data;
struct StackNode *next;
} StackNode, *Stack_Linklist;
//链栈初始化 由于栈的操作是在栈顶插入与删除的,所以没有必要向链表那样加上头结点
Status Init_Stack_Linklist(Stack_Linklist &stack_linklist)
{
stack_linklist = NULL;
return TRUE;
}
//入栈,不需要判断因为是动态分配内存, 类似于头插法
Status Push_Stack_Linklist(Stack_Linklist &stack_linklist, SElemType ElemType)
{
Stack_Linklist stack_Node;
stack_Node = (StackNode *)malloc(sizeof(StackNode));
if(!stack_Node)
{
exit(OVERFLOW_ERROR);
}
else
{
stack_Node->data = ElemType;
stack_Node->next = stack_linklist;
stack_linklist = stack_Node;
return TRUE;
}
}
//链表的出栈
Status Pop_Stack_Linklist(Stack_Linklist &stack_linklist, SElemType &ElemType)
{
Stack_Linklist p_Temp;
if(stack_linklist == NULL)
{
return FALSE;
}
else
{
ElemType = stack_linklist->data;
p_Temp = stack_linklist;
stack_linklist = stack_linklist->next;
free(p_Temp);
return TRUE;
}
}
//取栈顶元素
Status GetTop_Stack_Linklist(Stack_Linklist &stack_linklist, SElemType &ElemType)
{
if(stack_linklist == NULL)
{
return FALSE;
}
else
{
ElemType = stack_linklist->data;
return TRUE;
}
}
//栈在递归中的具体应用
long Fact(int n)
{
if(n == 1)
{
return 1;
}
else
{
return n*Fact(n-1);
}
}
//斐波那契
long Fib(int n)
{
if(n == 1 || n == 2)
{
return 1;
}
else
{
return Fib(n-1) + Fib(n-2);
}
}
//主函数
int main(void)
{
Stack_Linklist stack_linklist;
if(Init_Stack_Linklist(stack_linklist))
{
printf("初始化成功:\n");
}
else
{
printf("初始化失败:\n");
}
SElemType ElemType;
printf("请输入元素:\n");
scanf("%c", &ElemType) ;
getchar();
while(ElemType != '0')
{
Push_Stack_Linklist(stack_linklist, ElemType);
printf("请输入元素:\n");
scanf("%c", &ElemType) ;
getchar();
}
printf("栈顶元素为:");
GetTop_Stack_Linklist(stack_linklist, ElemType);
printf("%c\n", ElemType);
printf("输出栈中的所有元素:\n");
int flag = 1;
while(flag)
{
if(Pop_Stack_Linklist(stack_linklist, ElemType))
{
printf("\t%c", ElemType);
}
else
{
flag = 0;
}
}
printf("\n");
printf("\t\thello world\n");
printf("===============《递归》===============\n");
printf("%d\n", Fact(5));
printf("%d", Fib(10));
return 0;
}
1.4 栈的应用
1) 数制转换
2)语法词法分析
3)表达式求值等
1.5 栈的递归和实现
汉诺塔的问题:
解决:
1)如果有一个盘子,直接从X移到Z即可。
2)如果有n个盘子要从X移到Z,Y作为辅助。问题可以转化为,先将上面n-1个从X移动到Y,Z作为辅助,然后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X作为辅助。
#include "stdafx.h"
#include <stdio.h>
#include "stdlib.h"
#include <iostream>
using namespace std;
//宏定义
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define STACKEMPTY -3
#define LT(a,b) ((a)<(b))
#define N = 100
typedef int Status;
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
typedef struct stack{
LinkList top;
} STACK;
/************************************************************************/
/* 接口:
*/
/************************************************************************/
void InitStack(STACK &S);
void Push(STACK &S,ElemType e);
void Pop(STACK &S, ElemType *e);
ElemType GetTop(STACK S,ElemType *e);
int StackEmpty(STACK S);
/************************************************************************/
/*
*/
/************************************************************************/
void InitStack(STACK &S)
{
S.top=NULL;
}
/************************************************************************/
/* 入栈
*/
/************************************************************************/
void Push(STACK &S,ElemType e)
{
LinkList p;
p = (LinkList )malloc(sizeof(LNode));
if (!p) exit(OVERFLOW);
p->data = e;
p->next = S.top;
S.top = p;
}
/************************************************************************/
/* 出栈
*/
/************************************************************************/
void Pop(STACK &S, ElemType *e)
{
LinkList p;
if(StackEmpty(S)) exit(STACKEMPTY);
*e = S.top->data;
p = S.top;
S.top = p->next;
free(p);
}
/************************************************************************/
/* 获取栈顶元素内容
*/
/************************************************************************/
ElemType GetTop(STACK S, ElemType *e)
{
if(StackEmpty(S)) exit(STACKEMPTY);
*e = S.top->data;
}
void printStack(STACK S){
LinkList p;
p = S.top;
printf("栈: ");
while (p) {
printf("%d ", p->data);
p = p->next;
}
}
/************************************************************************/
/* 如果有一个盘子,直接从X移到Z即可。
如果有n个盘子要从X移到Z,Y作为辅助。问题可以转化为,先将上面n-1个从X移动到Y,Z作为辅助,然后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X作为辅助。
*/
/************************************************************************/
void move(STACK &Sa,STACK &Sb)
{
ElemType e;
Pop(Sa,&e);
Push(Sb, e);
}
void hanoi(int n,STACK &X,STACK &Y,STACK &Z)
{
if(n==1) return move(X, Z); //将圆盘1号直接移到z
hanoi(n-1,X,Z,Y); //将x上的1大n-1圆盘移到y,z做辅助塔
move(X, Z); //将编号为n的圆盘移z
hanoi(n-1,Y,X,Z); //将y上的1大n-1圆盘移到z,x做辅助塔
}
/************************************************************************/
/* 判断栈S是否空
*/
/************************************************************************/
int StackEmpty(STACK S)
{
if(S.top==NULL) return TRUE;
return FALSE;
}
void main()
{
STACK Sx, Sy,Sz;
InitStack( Sx);
InitStack( Sy);
InitStack( Sz);
int i, n = 10;
for (i = 10 ; i>=1 ;i--) {
Push(Sx, i);
}
printStack(Sx);
hanoi(n, Sx,Sy,Sz);
printStack(Sz);
}
1.队列
1.1 队列定义
队列(Queue)也是一种运算受限的线性表,它的运算限制与栈不同,是两头都有限制,插入只能在表的一端进行(只进不出),而删除只能在表的另一端进行(只出不进),允许删除的一端称为队尾(rear),允许插入的一端称为队头 (Front)
,队列的操作原则是先进先出的,所以队列又称作FIFO表(First In First Out)
队列的基本运算也有六种:
置空队 :InitQueue(Q)
判队空: QueueEmpty(Q)
判队满: QueueFull(Q)
入队 : EnQueue(Q,x)
出队 : DeQueue(Q)
取队头元素: QueueFront(Q),不同与出队,队头元素仍然保留。
队列也有顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队。
对于顺序队列,我们要理解"假上溢"的现象。
我们现实中的队列比如人群排队买票,队伍中的人是可以一边进去从另一头出来的,除非地方不够,总不会有"溢出"的现象,相似地,当队列中元素完全充满这个向量空间时,再入队自然就会上溢,如果队列中已没有元素,那么再要出队也会下溢。
那么"假上溢"就是怎么回事呢?
因为在这里,我们的队列是存储在一个向量空间里,在这一段连续的存储空间中,由一个队列头指针和一个尾指针表示这个队列,当头指针和尾指针指向同一个位置时,队列为空,也就是说,队列是由两个指针中间的元素构成的。在队列中,入队和出队并不是象现实中,元素一个个地向前移动,走完了就没有了,而是指针在移动,当出队操作时,头指针向前(即向量空间的尾部)增加一个位置,入队时,尾指针向前增加一个位置,在某种情况下,比如说进一个出一个,两个指针就不停地向前移动,直到队列所在向量空间的尾部,这时再入队的话,尾指针就要跑到向量空间外面去了,仅管这时整个向量空间是空的,队列也是空的,却产生了"上溢"现象,这就是假上溢。
为了克服这种现象造成的空间浪费,我们引入循环向量的概念,就好比是把向量空间弯起来,形成一个头尾相接的环形,这样,当存于其中的队列头尾指针移到向量空间的上界(尾部)时,再加1的操作(入队或出队)就使指针指向向量的下界,也就是从头开始。这时的队列就称循环队列。
通常我们应用的大都是循环队列。由于循环的原因,光看头尾指针重叠在一起我们并不能判断队列是空的还是满的,这时就需要处理一些边界条件,以区别队列是空还是满。方法至少有三种,一种是另设一个布尔变量来判断(就是请别人看着,是空还是满由他说了算),第二种是少用一个元素空间,当入队时,先测试入队后尾指针是不是会等于头指针,如果相等就算队已满,不许入队。第三种就是用一个计数器记录队列中的元素的总数,这样就可以随时知道队列的长度了,只要队列中的元素个数等于向量空间的长度,就是队满。
2.2 队列的顺序存储
顺序存储如图:
由于是顺序存储结构的存储空间是静态分配的,所以在添加数据的时,有可能没有剩余空间的情况。
解决这种“假溢出”情况,使用循环队列。在c语言中,不能用动态分配的一维数组来实现循环队列。若使用循环队列,必须设置最大队列长度,若无法估计最大长度,就使用链式队列。
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 100
#define TRUE 1
#define FALSE 0
#define ERROR 0
#define OVERFLOW_ERROR -1
typedef int Status;
typedef int QElemType;
typedef struct SQueue
{
QElemType *base;
int front;//对头指针
int rear;//队尾指针
} Sql_Queue;
//初始化队列
Status Init_Queue(Sql_Queue &sql_queue)
{
sql_queue.base = new QElemType[MAXSIZE];
if(!sql_queue.base)
{
exit(OVERFLOW_ERROR);
}
else
{
sql_queue.front = sql_queue.rear = 0;
return TRUE;
}
}
//入队列
Status EnQueue(Sql_Queue &sql_queue, QElemType &ElemType)
{
//满栈
if((sql_queue.rear + 1)%MAXSIZE == sql_queue.front)
{
return ERROR;
}
else
{
sql_queue.base[sql_queue.rear] = ElemType;
sql_queue.rear = (sql_queue.rear + 1)%MAXSIZE;
return TRUE;
}
}
//出队操作, 就是删除对头元素
Status DeQueue(Sql_Queue &sql_queue, QElemType &ElemType)
{
//空栈
if(sql_queue.rear == sql_queue.front)
{
return ERROR;
}
else
{
ElemType = sql_queue.base[sql_queue.front];
sql_queue.front = sql_queue.front + 1;
return TRUE;
}
}
//取队头元素
Status GetHeadQueue(Sql_Queue &sql_queue, QElemType &ElemType)
{
if(sql_queue.front != sql_queue.rear)
{
ElemType = sql_queue.base[sql_queue.front];
return TRUE;
}
else
{
return FALSE;
}
}
//队列的长度
Status QueueLength(Sql_Queue &sql_queue, QElemType &Length)
{
Length = (sql_queue.rear - sql_queue.front + MAXSIZE)%MAXSIZE;
return TRUE;
}
//主函数
int main(void)
{
Sql_Queue sql_queue;
int Length;
if(Init_Queue(sql_queue))
{
printf("初始化成功:\n");
}
else
{
printf("初始化失败:\n");
}
QElemType ElemType;
printf("请输入元素:\n");
scanf("%d", &ElemType);
while(ElemType != 0)
{
EnQueue( sql_queue, ElemType);
printf("请输入元素:\n");
scanf("%d", &ElemType);
}
GetHeadQueue(sql_queue, ElemType);
printf("队头元素:%d\n", ElemType);
QueueLength(sql_queue, Length);
printf("队列的长度:%d\n", Length);
int flag = 1;
while(flag)
{
if(DeQueue(sql_queue, ElemType))
{
printf("\t%d", ElemType);
}
else
{
flag = 0;
}
}
printf("\n");
return 0;
}
2.3 链式队列:
/链队---一个链队显然需要两个分别指向队头和队尾的指针才能唯一确定,在这里和单链表差不多,都需要头节点
#include <stdio.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
#define ERROR 0
#define OVERFLOW_ERROR -1
typedef int Status;
typedef int QElemType;
//结点
typedef struct QueueNode
{
QElemType data;
struct QueueNode *next;
} QueueNode, *Queue_Linklist;
//首尾指针
typedef struct
{
Queue_Linklist front;//队头
Queue_Linklist rear;//队尾
} LinkQueue;
//链队的初始化
Status Init_Queue(LinkQueue &link_queue)
{
link_queue.rear = link_queue.front = new QueueNode;
if(!link_queue.front)
{
exit(OVERFLOW_ERROR);
}
else
{
link_queue.rear->next = NULL;
link_queue.front->next = NULL;
return TRUE;
}
}
//链队的入队
Status EnQueue(LinkQueue &link_queue, QElemType &ElemType)
{
Queue_Linklist Queue_Temp;
Queue_Temp = new QueueNode;
if(!Queue_Temp)
{
exit(OVERFLOW_ERROR);
}
else
{
Queue_Temp->data = ElemType;
Queue_Temp->next = NULL;
link_queue.rear->next = Queue_Temp;
//替代
link_queue.rear = Queue_Temp;
return TRUE;
}
}
//出队,出队需要释放队头的结点
Status DeQueue(LinkQueue &link_queue, QElemType &ElemType)
{
if(link_queue.front == link_queue.rear)
{
return ERROR;
}
else
{
Queue_Linklist Queue_Temp;
Queue_Temp = link_queue.front->next;//队列的第一个元素
ElemType = Queue_Temp->data;
//修改头指针
link_queue.front->next = Queue_Temp->next;
//删除的是最后一个元素
if(link_queue.rear == Queue_Temp)
{
link_queue.rear = link_queue.front;//首尾相接
}
//释放元素
free(Queue_Temp);
return TRUE;
}
}
//取队头元素
Status GetHeadQueue(LinkQueue &link_queue, QElemType &ElemType)
{
if(link_queue.front != link_queue.rear)
{
ElemType = link_queue.front->next->data;
return TRUE;
}
}
//主函数
int main(void)
{
LinkQueue link_queue;
if(Init_Queue(link_queue))
{
printf("初始化成功:\n");
}
else
{
printf("初始化失败:\n");
}
QElemType ElemType;
printf("请输入元素:\n");
scanf("%d", &ElemType);
while(ElemType != 0)
{
EnQueue( link_queue, ElemType);
printf("请输入元素:\n");
scanf("%d", &ElemType);
}
GetHeadQueue(link_queue, ElemType);
printf("队头元素:%d\n", ElemType);
int flag = 1;
while(flag)
{
if(DeQueue(link_queue, ElemType))
{
printf("\t%d", ElemType);
}
else
{
flag = 0;
}
}
printf("\n");
return 0;
}
应用:杨辉三角形
#include <stdio.h>
#include <stdlib.h>
#define OK 1
#define ERROR 0
//队列数据类型的定义
typedef int QElemType;
//链队结点定义
typedef struct QNode
{
QElemType data;
struct QNode *next;
} QNode, *QueueNode;
//链队定义
typedef struct LQueue
{
QueueNode front;
QueueNode rear;
} LQueue,*LinkQueue;
//构造一个空队列
Status InitQueue(LinkQueue Q)
{
Q->front=Q->rear=(QueueNode)malloc(sizeof(QNode));
if(!Q->front)
{
printf("构造空队列失败,系统内存不足!\n");
return ERROR;
}
Q->front->next=NULL;
return OK;
}
//销毁一个队列
Status DestroyQueue(LinkQueue Q)
{
while(Q->front)
{
Q->rear=Q->front->next;
free(Q->front);
Q->front=Q->rear;
}
free(Q);
printf("成功销毁该队列!\n");
return OK;
}
//插入元素e为Q的新的队尾元素
Status EnQueue(LinkQueue Q,QElemType e)
{
QueueNode p=(QueueNode)malloc(sizeof(QNode));
if(!p)
{
printf("插入元素失败,系统内存不足!\n");
return ERROR;
}
p->data=e;
p->next=NULL;
Q->rear->next=p;
Q->rear=p;
return OK;
}
//若队列不空,则删除Q的队头元素,并用e返回其值,并返回OK,否则返回ERROR
Status DeQueue(LinkQueue Q,QElemType *e)
{
QueueNode p;
if(Q->front==Q->rear)
{
printf("删除元素失败,队列已空!\n");
return ERROR;
}
p=Q->front->next;
*e=p->data;
Q->front->next=p->next;
if(Q->rear==p)
{
Q->rear=Q->front;
}
free(p);
return OK;
}
//初始化杨辉三角
Status InitYangHui(LinkQueue Q)
{
EnQueue(Q,0);
EnQueue(Q,1);
EnQueue(Q,0);
return OK;
}
//打印杨辉三角
Status PrintYangHui(LinkQueue Q,int n)
{
int i,j,e;
//单独打印第一行
for(i=1; i<=n-1; i++) //对显示的数据进行排版
printf("\t");
printf("%d\n",Q->front->next->next->data);//第二个元素的数据域
//从第二行开始打印
for(i=2; i<=n; i++)
{
for(j=1; j<=n-i; j++) //对显示的数据进行排版
printf("\t");
do
{
DeQueue(Q,&e);
e=e+Q->front->next->data;
EnQueue(Q,e);
printf("%d\t\t",e);
}
while(Q->front->next->data!=0); //当第二次取队头元素为0时,结束循环
printf("\n"); //每次打印一行过后,换行
EnQueue(Q,0); //每次循环后,在队尾增加一个0,以保证下次循环的进行
}
return OK;
}
int main()
{
int n;
LinkQueue Q=(LinkQueue)malloc(sizeof(LQueue));
InitQueue(Q);
InitYangHui(Q);
printf("请输入杨辉三角的层数n:\n");
scanf("%d",&n);
PrintYangHui(Q,n);
DestroyQueue(Q); //释放内存,防止内存泄漏
return 0;
}