数据结构与算法-2
10.21–11.8笔记
一、栈
(1)定义:栈(stack)是限定仅在表尾进行插入或删除操作的线性表。它只能通过访问它的一端来实现数据存储和检索的一种显性数据结构。换句话说,栈的修改是按先进后出的原则操作的。因此,栈又称为先进后出(FILO,或后进先出)的线性表。在栈中,进行插入和删除操作的一端称为栈顶(Top),相应地,另一端称为栈底(Bottom)。不含数据元素的栈称为空栈。
(2)基本运算
a.初始化栈initStack(S):创建一个空栈。
b.判栈空isEmpty(S):当栈S为空栈时返回“真“,否则返回”假“。
c.入栈push(S,x):将元素x加入栈顶,并更新栈顶指针。即:元素x先进去,top指针上移;
d.出栈(pop(S)):将栈顶元素从栈中删除,并更新栈顶指针。即:top指针下移的一瞬间元素出栈;
e.读栈顶元素top(S):返回栈顶元素的值,但不修改栈顶指针。
1.栈的顺序存储结构
栈的顺序存储是指用一组地址连续的存储单元作为栈的存储空间,同时设置指针top指示栈顶元素的位置。采用顺序存储结构的栈也称为顺序栈。在顺序存储方式下,需要预先定义或申请栈的存储空间,也就是说栈的空间的容量是有限的。因此在顺序栈中,当一个元素入栈时,需要判断是否栈满,若栈满(即栈空间中没有空闲单元),则元素入栈会发生上溢现象。
2.代码实现
注:判断栈满条件:S.top-S.base>=S.stacksize;
判断空栈条件:S.top==S.base;
栈中的元素个数:top-base;
//举例 元素abc入栈 输出栈顶元素;元素c出栈 输出出栈的元素
#define STACKINCREMENT 10 //存储分配增量
#define ERROR
#include<stdio.h>
typedef struct{
char *base; //在栈构造之前和销毁之后,base的值为NULL
char *top; //栈顶指针
int stacksize; //当前已分配的存储空间,以元素为单位
}Stack; //Stack为栈的类型
Stack stack; //定义了一个栈 stack
void initstack(){
stack.base=(char *)malloc(100*sizeof(char ));
if(!stack.base) return ERROR; //存储分配失败
stack.top=stack.base;
stack.stacksize=100;
} //栈的初始化
void push(char e){ //插入e为新的栈顶元素
if(stack.top-stack.base>=stack.stacksize){ //如果栈满
stack.base=(char *)realloc(stack.base,(stack.stacksize+STACKINCREMENT)*sizeof(char ));
if(!stack.base) return ERROR; //存储分配失败
stack.top=stack.base+stack.stacksize;
stack.stacksize+= STACKINCREMENT;
}
*stack.top=e; //用e返回stack的栈顶元素
stack.top++; //top指针上移
}
char pop(){
char e;
if(stack.top==stack.base) return ERROR; //如果stack为空栈
stack.top--;
e=*stack.top; //e为出栈元素
return e;
}
main(){
char e;
initstack();
push('a');
push('b');
push('c');
printf("%c",*(stack.top-1));
e=pop();
printf("%c",e);
}
3.运行结果
2.栈的链式存储结构
为了克服顺序存储的栈可能存在上溢的不足,可以用链表存储栈中的元素。用链表作为存储的栈也称为链栈。由于栈中元素的插入和删除仅在栈顶一端进行,因此不必另外设置头指针,链表的头指针就是栈顶指针。
二、队列
(1)定义:队列是一种先进先出(FIFO)的线性表,只允许在队列的一端插入元素,而在另一端删除元素。在队列中,允许插入元素的一端称为队尾(Rear),允许删除元素的一端称为队头(Front)。
(2)基本运算:
a.初始化队列initQueue(Q):创建一个空的队列Q。
b.判队空isEmpty(Q):当队列为空时返回“真”值,否则返回“假”值。
c.入队enQueue(Q,x):当元素x加入队列Q的队尾,并更新队尾指针。
d.出队deQueue(Q):将队头元素从队列Q中删除,并更新队头指针。
e.读队头元素frontQueue(Q):返回队头元素的值,但不更新队头指针。
1.队列的顺序存储结构
队列的顺序存储是指利用一组地址连续的存储单元存放队列中的元素。由于队中元素的插入和删除限定在两端进行,因此设置对头指针和队尾指针,分别指示出当前的的队首元素和队尾元素。
在顺序队列中,为了降低运算的复杂度,元素入队时只修改队尾指针,元素出队时值修改对头指针。由于顺序队列的存储空间容量是提前设定的,所以队尾指针会有一个上限值,当队尾指针达到该上限时,就不能只通过修改队尾指针来实现新元素的入队操作了。若将顺序队列假象称一个环状结构(通过整除取余运算实现),则可维持入队、出队操作运算的简单性,称为循环队列。
设循环队列Q的容量为MAXSIZE,初始时队列为空,且Q.rear和Q.front都等于0;元素入队时修改队尾指针,即令Q.rear=(Q.rear+1)%MAXSIZE;元素出队时修改队头指针,即令Q.front=(Q.front+1)%MAXSIZE。
根据队列操作的定义,当出队操作导致队列变为空时,就有Q.rear=Q.front;若入队列操作导致队满,则也有Q.rear=Q.front。在队列空和队列满的情况下,循环队列的队头、队尾指针指向的位置是相同的,此时仅根据Q.rear和Q.front之间的关系无法判定队列的状态。为了区别队空和队满的情况,可采用两种处理方式:1.设置一个标志域,以区别头、尾指针的值相同时队列是空还是满;2.牺牲一个元素空间,约定以“队列的尾指针所指位置的下一个位置是头指针”表示队列满,而头尾指针的值相同是表示队列空。
//循环队列的顺序存储结构代码:
typedef struct {
type data[Max];
int front;
int rear;
}SqQueue;
//循环队列的初始化:
struct InitQueue(SqQueue *Q)
{
Q->front=0;
Q->rear=0;
return OK;
}
//求循环队列的长度的函数:
int QueueLength(SqQueue *Q)
{
return (Q.rear - Q.front+Max)%Max;
}
//队列的入队函数:
Status EnQueue(SqQueue *Q , type *e)
{
if((Q->rear+1)%Max==Q->next)
return ERROR; // 判断队列是否满了
Q->data[Q->rear]=e; // 把e赋给rear
Q->rear=(Q->rear+1)%Max; // rear++
return OK;
}
//队列的出队函数:
Status EnQueue(SqQueue *Q , type *e)
{
if(Q->front==Q->rear) //判断队列是否为空
return ERROR;
*e=Q->data[Q->front]; // 把队头元素赋给e
Q->front=(Q->front+1)%Max; // front++;
return OK;
}
2.队列的链式存储结构
队列的链式存储也称为链队列。为了便于操作,可给链队列添加一个头结点,并令头指针指向头结点,如下图所示。在这种情况下,队列为空的判定条件是头指针和尾指针相同,且均指向头结点。
入队操作:
- 构建一个结点;(①分配空间;②处理数据域data;③处理指针域next;)
- 改变两个指针让元素入队;(①先进入链表改变rear->next;②认可它是新的队尾rear;)
出队操作:
- 只改变一个指针(font->next=front->next->next);
3.代码实现
//举例 元素abc入队 输出队尾元素;元素a出队 输出出队的元素
#define NULL-1
#include<stdio.h>
//定义结点类型
typedef struct qnode{
char data;
struct qnode *next;
}qnode,*queueptr;
//定义队列类型
typedef struct{
queueptr front;
queueptr rear;
}Linkqueue;
Linkqueue Q;//Q此时不存在
//初始化队列
void initqueue(){
Q.front=(qnode*)malloc(sizeof(qnode));
Q.front->next=NULL;
Q.rear=Q.front;
}
//入队
void enqueue(char e){
queueptr s;
s=(qnode*)malloc(sizeof(qnode));
s->data=e;
s->next=NULL;
Q.rear->next=s;
Q.rear=s;
}
//出队
char delqueue(){
char e;
queueptr p;
if(Q.front!=Q.rear){
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p) Q.front=Q.rear;
free(p);
}
return e;
}
main(){
char e;
initqueue();
enqueue('a');
enqueue('b');
enqueue('c');
printf("%5c",Q.rear->data);
e=delqueue();
printf("\n");
printf("%5c",e);
}
4.运行结果
三、利用栈和队列的属性判断回文序列
回文序列:指正读和反读都相同的字符序列。如“abba”、“abccba”、12321、123321是“回文”,“abcde”和“ababab”则不是“回文”。
代码如下:
#define NULL-1
#define ERROR
#include<stdio.h>
typedef struct qnode{
char data;
struct qnode *next;
}qnode,*queueptr;
typedef struct{
queueptr front;
queueptr rear;
}Linkqueue;
typedef struct{
char *base;
char *top;
int stacksize;
}Stack;
Linkqueue Q;
Stack stack;
void initqueue(){
Q.front=(qnode*)malloc(sizeof(qnode));
Q.front->next=NULL;
Q.rear=Q.front;
}
void enqueue(char e){
queueptr s;
s=(qnode*)malloc(sizeof(qnode));
s->data=e;
s->next=NULL;
Q.rear->next=s;
Q.rear=s;
}
char delqueue(){
char e;
queueptr p;
if(Q.front!=Q.rear){
p=Q.front->next;
e=p->data;
Q.front->next=p->next;
if(Q.rear==p) Q.front=Q.rear;
free(p);
}
return e;
}
void initstack(){
stack.base=(char *)malloc(100*sizeof(char ));
stack.top=stack.base;
stack.stacksize=100;
}
void push(char e){
*stack.top=e;
stack.top++;
}
char pop(){
char e;
stack.top--;
e=*stack.top;
return e;
}
main(){
char c;
initqueue();
initstack();
printf("请输入一串字符:\n");
while((c=getchar())!='@'){
enqueue(c);
push(c);
}
if(Q.front!=Q.rear){
if(delqueue()==pop()){
printf("此为回文\n");
}else{
printf("不是回文\n");
}
}
}
四、简述栈和队列这两种数据类型的相同点和差异处
相同点:队列和栈都是线性结构的线性表。
异同点:栈是在栈顶进行插入或删除元素的操作,是一种先进后出的线性表;队列是在队尾进行插入元素,在队头进行删除元素操作,是一种先进先出的线性表。
代码编译器:codeblocks。