1.栈:栈和队列是两种特殊的线性表,他们的逻辑结构和线性表相同,但是运算规则有限制;
1.1.栈的定义及运算:
1.定义:栈是仅限在一端进行操作的线性表。对栈而言,允许进行插入和删除的一端称为栈顶,固定不变的一端称为栈底;
2.栈中的元素按照“后进先出”的规则进行。当栈中没有任何元素时称为栈空,当栈的存储空间被用完时,称为栈满;
3.对栈的基本操作:
栈初始化:init_Stack(s);生成一个空栈;
判断栈空:empty_Stack(s);判断栈是否为空栈,若是返回1,不是返回0;
入栈:push_Stack(s,x);在栈的顶部插入一个元素x,使x成为新的栈顶元素;
出栈:pop_Stack(s,x);在栈非空的情况下,将栈顶元素从栈中移除,并由x返回栈顶元素的值;
读栈顶元素:top_Stack(s,x);在栈非空的情况下,将栈顶元素读入x中。
1.2.顺序栈的运算和实现:
1.顺序栈是栈的顺序存储结构,利用一组连续的内存地址存放栈底到栈顶的元素;
2.定义格式:
typedef struct { datatype data[MAXSIZE];//栈中元素的存储空间 int top;//栈顶 }SeqStack;
top指针表示栈顶元素在栈中的位置,栈底的下标为0,故栈空时top=-1;
3.创建栈:
void init_SeqStack(SeqStack **s){ *s=(SeqStack *)malloc(sizeof(SeqStack)); (*s)->top=-1; }
4.判断是否栈空
int empty_SeqStack(SeqStack *s){ if(s->top==-1){ return 1; }else{ return 0; } }
5.入栈:
void push_SeqStack(SeqStack *s,char c){ if(s->top==MAXSIZE-1){ printf("Stack is full!!"); }else{ s->top++; s->data[s->top]=c; } }
6.出栈:
void Pop_SeqStack(SeqStack *s,char *c){ if(s->top==-1){ printf("Stack is empty!!!"); }else{ *c=s->data[s->top]; s->top--; } }
7.取栈顶元素:
void top_SeqStack(SeqStack *s,char *c){ if(s->top==-1){ printf("Stack is empty!!!"); }else{ *c=s->data[s->top]; } }
1.3.多个顺序栈共享连续空间:
1.利用栈底相对位置不变这一特点,两个顺序栈可以共享一个一维数组空间来互补缺余,实现方法是将两个栈的栈底分别设在一维数组空间的两端,并各自向中间延伸。
2.至于多个栈共享连续空间的问题比较复杂,除了需要设置栈顶指针外,还要设置栈底指针;
1.4.链栈:
1.1.4.定义:
1.栈的链式存储结构,称为链栈;相比顺序栈,链栈可以动态分配存储空间;
2.由于栈的操作都是在栈顶,因此不必设置头结点,头指针即为栈顶指针;
3.定义格式:和单链表相同:
typedef struct node{ char data; struct node *next; }StackNode;
1.4.2.运算实现:
1.置空栈:
void init_StackNode(StackNode **s){ *s=NULL; }
2.判断栈:
int empty_StackNode(StackNode *s){ if(s==NULL) return 1; else return 0; }
3.入栈:
void push_StackNode(StackNode **top,char c){ StackNode *p; p=(StackNode *)malloc(sizeof(StackNode)); p->data=c; p->next=*top; *top=p; }
4.出栈:
void pop_StackNode(StackNode **top,char *c){ StackNode *p; if(*top==NULL){ puts("stack is empty"); }else{ p=*top; *c=(*top)->data; *top=*top->next; free(p); } }
2.栈和递归:
2.1.递归:
1.概念:一个对象的组成包含本身的现象称之为递归;
2.在计算机中,通过使用栈来存放调用函数中的数据参数及其地址,尤其是递归函数;
3.递归算法设计程序时要满足的条件:
.1.能将一个复杂的问题转变成一个简单的规模小的问题,而新问题与原问题的解法相同或类同,不同的仅仅是所处理的对象,而处理对象的变化时有规律的。
.2.必须有一个明确的递归出口;
2.2.递归调用过程分析:
1.使用动态图的方法阿里对执行的程序进行描述,记录主函数和被吊喊叔的调用,运行,撤销各个阶段的状态,以及程序运行期间所有变量和函数形参的变化过程。规则如下:
.1.动态图纵向描述主函数和其他函数的调用关系,横向由左至右按时间顺序记录主函数和其他函数中各变量及形参值的变化;
.2.函数的形参看做是带初值的局部变量。
.3.主函数、其他函数之间按运行的调用关系自上而下分层,各层说明的变量包括形参都依次列于该层首列,各值的变化情况按时间顺序记录在与该变量对应的同一行上;
2.递归的调用关系如同栈一样,先进后出。
3.采用递归算法得到的程序结构简单,但执行效率较低,需要更多的存储空间;
3.队列
3.1.定义及其运算:
1.同栈一样,队列也是一种受限制的线性表,队列只能在一端进行插入,同时在另一端进行删除,即操作遵循“先进先出”的规则;
2.只能插入的一端称为队尾(rear),只能删除的一端称为队头(front);
3.队列和栈的关系:两个栈能组成一个队列;
4.基本操作:
队列初始化:init_seqQueue(&q),生成一个空队列q;
判断空队列:empty_seqQueue(q),判断是否是一个空队列,若是返回1,否则返回0;
入队操作:in_SeqQueue(q,x);将x插入队尾;
出队操作:out_SeqQueue(q,x);将队头元素删除并由x返回;
读队头元素:front_SeqQueue(q,x);将队头元素由x返回;
3.2.队列的存储结构和运算的实现:
3.2.1.顺序队列:队列的顺序存储结构,利用一组连续的存储单元来存储队列元素;
1.定义格式:
typedef struct{ int data[MAXSIZE]; int rear,front;//队尾指针和队头指针 }SeqQueue;
2.规则:
若:SeqQueue *q=(SeqQueue *)malloc(sizeof(SeqQueue));
此时该队列的数据区为:q->data[0]~q->data[MaxSize-1];
通常设队头指针q->front指向队头元素的前一个位置,注意,是前一个位置,队尾指针指向队尾元素,则有:
.1.队空:q->rear=q->front;
.2.队满:q->rear=MaxSize;
.3.队中某个元素:q-rear-q->front;
在不考虑溢出的情况下,入队可使尾指针加1,出队可使头指针加1,但这样会出现“假溢出”,浪费空间;解决假溢出的方法是将队列改为循环队列;
3.2.2.循环队列:将顺序队列进行首尾相接,组成一个圆环;
1.虽然循环队列可以解决假溢出的问题,但此时,判断队空和队满都会变为:q->rear==q-front;这是极其不方便的,因此,为了解决该问题,采取的方法是损失一个数据元素的存储空间,将对满条件改为 (q->rear+1)%MaxSize==q->front;而队空条件保持不变:q-rear==q->front;
循环队列的元素个数为:(q->rear-q->front+MaxSize)%MaxSize;
因为首尾相接,则入队操作为:q->rear=(q->rear+1)%MaxSize;q->data[q->rear]=x;
出队操作为:q->front=(q->front+1)%MaxSize;x=q->data[q->front];
2.置空队:
void init_SeqQ(SeqQueue **q){ *q=(SeqQueue *)malloc(sizeof(SeqQueue)); *q->rear=0; *q->front=0; }
3.入队(需判断是否队满):
void in_SeqQ(SeqQueue *q,int x){ if((q->rear+1)%MAXSIZE==q->front){ puts("Queue is full"); }else{ q->rear=(q->rear+1)%MAXSIZE; q->data[q->rear]=x; } }
4.出队(需判断是否对为空):
void out_SeqQ(SeqQueue **q,int *x){ if(*q->rear==*q->front){ puts("Queue is empty!!!"); }else{ *q->front=(*q->front+1)%MAXSIZE; *x=*q->data[*q->front]; } }
5.判断队列是否为空:
int empty_SeqQ(SeqQueue *q){ if(q->rear==q->front) return 1; else return 0; }
3.2.3.链队列:队列的链式存储结构,链队列也需要标识队尾和队头的指针;
1.定义格式:
typedef struct node{ char data; struct node *next; }QNode; //链队列结点的类型 typedef struct{ QNode *rear,*front; }LQNode; //链队列类型
2.创建一个空队列:
void init_LQueue(LQNode **q){ QNode *p; p=(LQNode *)malloc(sizeof(QNode)); *q=(LQNode *)malloc(sizeof(LQNode)); p->next=NULL; *q->rear=p; *q->front=p; }
3.入队:
void in_LQueue(LQNode *q,char c){ QNode *p; p=(QNode *)malloc(sizeof(QNode)); p->data=c; p=NULL; q->rear->next=p; q->rear=p; }
4.判断队列是否为空:
int empty_LQueue(LQNode *q){ if(q->rear==q->front) return 1; else return 0; }
5.出队:
void out_LQueue(LQNode *q,char *c){ QNode *p; if(q->rear==q->front){ puts("Link Queue is empty"); }else{ p=(QNode *)malloc(sizeof(QNode)); p=q->front->next; q->front->next=p->next; *c=p->data; free(p); if(q->front->next==NULL){ //出队后若为空,则置为空队列 q->rear=q->front; } } }
4.总结:
1.栈和线性表的关系:
个人认为,统的来说,栈是特殊的线性表,因为栈只能允许在一端进行操作;而个体来说,线性表和栈又是同级关系,线性表可以由数组或者指针组成,同样地,栈也可以由数组或者指针组成;
对于顺序存储的线性表和栈,相当于“指针”的下标意义改变,前者指表长,后者指指向栈顶元素的下标;对于链式存储的线性表和栈,前者需要设置头结点和头指针,后者由于都是在一端进行操作,因此不需要设置头结点,头指针即是栈顶指针;
2.链式存储的队列为什么要分别用两个数据类型?
从形式上讲,队列是特殊的线性结构,可以用像定义栈那样,使用一个结构体,但是,这样一来,和栈就没什么区别了,因此,将队列的头指针和尾指针放入一个结构体中,并分别指向队列结点的首尾。
3.栈的输出准则:在输出序列中任意元素的后面不能出现比该元素小并且是升序(均是对元素的序号而言)的两个元素;