数据结构——(3)、栈和队列

一、栈

1、定义

栈是只允许在一端进行插入或删除的线性表。首先栈是一种线性表。栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。因此,对栈来说,表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom )。不含元素的空表称为空栈。

2、栈的特征

主要特点是“后进先出”,即后进栈的元素先处理。因此栈又称为后进先出(last in first out, LIFO)表。

3、栈的常见操作

  • InitStack(&S):初始化一个空栈S。

  • StackEmpty(S):判断一个栈是否为空,若栈为空则返回true,否则返回false。

  • Push(&S, x):进栈(栈的插入操作),若栈S未满,则将x加入使之成为新栈顶。

  • Pop(&S, &x):出栈(栈的删除操作),若栈S非空,则弹出栈顶元素,并用x返回。

  • GetTop(S, &x):读栈顶元素,若栈S非空,则用x返回栈顶元素。

  • DestroyStack(&S):栈销毁,并释放S占用的存储空间(“&”表示引用调用)。

4、栈的抽象数据类型

public interface IStack<E>{     
  E push();        
  //入栈     
  E pop();        
  //出栈     
  E peek();        
  //取栈顶元素     
  int size();     
  //返回栈中元素的个数     
  boolean empty(); 
  //判断栈是否为空 
}

二、栈的顺序存储结构

1、栈的顺序存储

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。

2、顺序栈的基本算法

(1)、InitStack(&S) 初始化栈

初始化顺序栈就是创建一个空栈,即调用 SeqStack<E>的构造函数,在构造函数中执行下面的步骤:

  • 初始化 maxsize 为实际值。

  • 为数组申请可以存储 maxsize 个数据元素的存储空间,数据元素的类型由实际应用而定。

  • 初始化 top 为的值为一1。

void InitStack(SqStack *S){     S->top = -1;    //初始化栈顶指针 }

(2)、Push(&S, x) 入栈

假设顺序栈顶端元素的索引保存在变量 top 中,栈中已有 top+1(0≤(top+1)≤maxsize-1)个数据元素,push操作是将一个给定的数据元素保存在堆栈的最顶端,执行过程如图所示:

代码:

/*插入元素e为新的栈顶元素*/
Status Push(SqStack *S, ElemType e){
    //满栈
    if(S->top == MAXSIZE-1){
        return ERROR;
    }
    S->top++;   //栈顶指针增加一
    S->data[S->top] = e;    //将新插入元素赋值给栈顶空间
    return OK;
}

(3)、Pop(&S, &x) 出栈

出栈操作就是从堆栈的顶部取出数据。要进行出栈操作,需要执行以下的步骤:

  • 检查堆栈中是否含有元素,如果无,返回 null;否则执行下面的步骤。

  • 获取索引 top 中的元素。

  • 将索引 top 的值减 1。

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack *S, ElemType *e){
    if(S->top == -1){
        return ERROR;
    }
    *e = S->data[S->top];   //将要删除的栈顶元素赋值给e
    S->top--;   //栈顶指针减一
    return OK;
}

(4)、GetTop(S, &x) 取栈顶元素

取栈顶元素操作与出栈操作相似,只是取栈顶元素操作不改变原有堆栈,不删除取出的元素。

  • 检查堆栈中是否含有元素,如果无,返回 null;否则执行下面的步骤:

  • 获取索引 top 中的元素。

/*读栈顶元素*/
Status GetTop(SqStack S, ElemType *e){
    if(S->top == -1){   //栈空
        return ERROR;
    }
    *e = S->data[S->top];   //记录栈顶元素
    return OK;
}

(5)、StackEmpty(S) 判栈空

判断一个栈是否为空,若栈为空则返回true,否则返回false。

  • 当top指向栈顶元素存储的下一个存储单元的位置时,判空条件为top==0。

  • 当top指向栈顶元素的存储位置时,判空条件为top==-1。

bool StackEmpty(SqStack S){
    if(S.top == -1){    
        return true;    //栈空
    }else{  
        return false;   //不空
    }
}

3、顺序栈的上溢和下溢,以及防止溢出

上溢:栈满时在做进栈运算必定产生空间溢出

下溢:栈空时在做退栈运算也将产生溢出

防止溢出:将多个栈分配在同一个顺序存储空间内,既让多个共享存储空间,则可以相互进行调节,既节约了空间,又可降低发生溢出的频率。

4、顺序栈的Java实现

import java.lang.reflect.Array;
public class SeqStack<E> implements IStack<E> {
    private int maxsize; // 顺序栈的容量
    private E[] data; // 数组,用于存储顺序栈中的数据元素
    private int top; // 指示顺序栈的栈顶
    // 初始化栈
    @SuppressWarnings("unchecked")
    public SeqStack(Class<E> type, int size) {
        data = (E[]) Array.newInstance(type, size);    //使用Array.newInstance()创建动态数组
        size = 0;
        maxsize = size;
        top = -1;
    }
    // 入栈操作
    public E push(E item) {
        if (!isFull()) {
            data[++top] = item;
            return item;
        }
        else
            return null;
    }
    // 出栈操作
    public E pop() {
        E item = null;
        if (!empty()) {
            item = data[top--];
        }
        return item;
    }
    // 获取栈顶数据元素
    public E peek() {
        E item = null;
        if (!empty()) {
            item = data[top];
        }
        return item;
    }
    //求栈的长度
    public int size() {
        return top+1;
    }
    // 判断顺序栈是否为空
    public boolean empty() {
        if (top == -1) {
            return true;
        } else {
            return false;
        }
    }
    // 判断顺序栈是否为满
    public boolean isFull() {
        if (top == maxsize - 1) {
            return true;
        } else {
            return false;
        }
    }
}

//主方法
public class Test {
    public static void main(String[] args) {
        int[] data = {23,56,89,17};
        IStack<Integer> stack = new SeqStack<Integer>(Integer.class,50);
        //入栈操作
        for(int i = 0;i<data.length;i++) {
            stack.push(data[i]);
            System.out.println(data[i]+" 入栈");
        }
        int size = stack.size();
        //出栈
        for(int i = 0 ; i<size ; i++){
            System.out.println(stack.pop()+" 出栈");
        }
    }
}

三、栈的链式存储结构

1、链栈

采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头节点,Lhead指向栈顶元素

对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。

2、栈的链式存储结构

/*栈的链式存储结构*/
/*构造节点*/
typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
}StackNode, *LinkStackPrt;
/*构造链栈*/
typedef struct LinkStack{
    LinkStackPrt top;
    int count;
}LinkStack;

3、链栈的基本算法

(1)、链栈的进栈

将元素 1、2、3、4 依次入栈,等价于将各元素采用头插法依次添加到链表中,每个数据元素的添加过程如下:

初始化:head——>NULL

添加元素1:head——>1——>NULL

添加元素2:head——>2——>1——>NULL

添加元素3:head——>3——>2——>1——>NULL

添加元素4:head——>4——>3——>2——>1——>NULL

//链表中的节点结构
typedef struct lineStack{
    int data;
    struct lineStack * next;
}lineStack;
//stack为当前的链栈,a表示入栈元素
lineStack* push(lineStack * stack,int a){
    //创建存储新元素的节点
    lineStack * line=(lineStack*)malloc(sizeof(lineStack));
    line->data=a;
    //新节点与头节点建立逻辑关系
    line->next=stack;
    //更新头指针的指向
    stack=line;
    return stack;
}

(2)、链栈的出栈

初始化:head——>4——>3——>2——>1——>NULL

元素4出栈:head——>3——>2——>1——>NULL

元素3出栈:head——>2——>1——>NULL

元素2出栈:head——>1——>NULL

元素1出栈:head————>NULL

//栈顶元素出链栈的实现函数
lineStack * pop(lineStack * stack){
    if (stack) {
        //声明一个新指针指向栈顶节点
        lineStack * p=stack;
        //更新头指针
        stack=stack->next;
        printf("出栈元素:%d ",p->data);
        if (stack) {
            printf("新栈顶元素:%d\n",stack->data);
        }else{
            printf("栈已空\n");
        }
        free(p);
    }else{
        printf("栈内没有元素");
        return stack;
    }
    return stack;
}

四、队列  

1、定义

队列(queue)是一种特殊的线性表,只允许在表的一端进行插入操作而在另一端进行删除操作的线性表。进行插入操作的端称为队尾(rear),进行删除操作的端称为队头(front)。队列中没有数据元素时称为空队列(empty queue)。

  • 队头(Front):允许删除的一端,又称队首。

  • 队尾(Rear):允许插入的一端。

  • 空队列:不包含任何元素的空表。

2、队列特征

队列的操作是按照先进先出(first in first out)或后进后出( last in last out)的原则进行的 ,因此,队列又称为 FIFO 表或 LILO 表。

3、队列的常见基本操作

  • InitQueue(&Q):初始化队列,构造一个空队列Q。

  • QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。

  • EnQueue(&Q, x):入队,若队列Q未满,将x加入,使之成为新的队尾。

  • DeQueue(&Q, &x):出队,若队列Q非空,删除队头元素,并用x返回。

  • GetHead(Q, &x):读队头元素,若队列Q非空,则将队头元素赋值给x。

五、队列的顺序存储结构

1、顺序队列

队列的顺序存储类型:

#define MAXSIZE 50;	//定义队列中元素的最大个数
typedef struct{
	ElemType data[MAXSIZE];	//存放队列元素
	int front,rear;
}SqQueue;

初始状态(队空条件):Q->front == Q->rear == 0。 

进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。 

出队操作:队不空时,先取队头元素值,再将队头指针加1。

(1)、初始化顺序队列 InitQueue(&Q)

初始化顺序队列就是创建一个空队列,即调用 SeqQueue<E>的构造函数,在构造函数中执行下面的步骤:

  • 初始化 maxsize 为实际值。

  • 为数组申请可以存储 maxsize 个数据元素的存储空间,数据元素的类型由实际应用而定。

  • 将队头和队尾指示变量都置为-1。

(2)、入队操作 EnQueue(&Q, x)

入队操作是将一个给定的元素保存在队列的尾部,同时修改队头和队尾指示变量 front和 rear 的值。要执行人队操作,需要执行下面的步骤:

  • 判断队列是否是满,如果是,返回 false。

  • 设置 rear 的值为(rear + 1)% maxsize,使 rear 指向要插入元素的位置。

  • 设置数组索引为 rear 的位置的值为入队元素的值。

(3)、出队操作 DeQueue(&Q, &x)

出队操作是指在队列不为空的情况下从队列的前端删除元素,使队头指示器 front 加1。要执行出队操作,需要执行下面的步骤:

    (1)、检查队列是否为空。如果为空,返回 null

    (2)、设置 front 的值为(front + 1)% maxsize,使 front 指向要删除元素的位置。

    (3)、返回队头指示器 front 所在位置的元素。

(4)、取队头元素 GetHead(Q, &x)

取队头元素操作与出队操作相似,只是取队头元素操作不改变原有队列,不删除取出的队头元素。要执行取队头操作,需要检查队列是否为空,如果为空,返回 null;否则,返回队头指示器 front 所在位置的元素。

(5)、求队列的长度 size()

循环顺序队列的长度取决于队尾指示器 rear 和队头指示器 front。一般情况下,rear 大于 front,因为入队的元素肯定比出队的元素多。特殊的情况是 rear 到达数组的上限之后又从数组的低端开始,此时 rear 是小于 front 的,所以 rear 的大小要加上 maxsize。循环顺序队列的长度应该是(rear-front+maxsize)%maxsize。

(6)、队列是否为满 isFull()

a、牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是种较为普遍的做法,约定以“队头指针在队尾指针的下一位置作为队满的标志“

  • 队满条件: (Q->rear + 1)%Maxsize == Q->front

  • 队空条件仍: Q->front == Q->rear

  • 队列中元素的个数: (Q->rear - Q ->front + Maxsize)% Maxsize

b、类型中增设表示元素个数的数据成员。这样,队空的条件为 Q->size == O ;队满的条件为 Q->size == Maxsize 。这两种情况都有 Q->front == Q->rear

c、类型中增设tag 数据成员,以区分是队满还是队空。tag 等于0时,若因删除导致 Q->front == Q->rear ,则为队空;tag 等于 1 时,若因插入导致 Q ->front == Q->rear ,则为队满。

2、顺序队列Java表示

//接口类
public interface IQueue<E> {
    boolean enqueue(E item);
    E deueue();
    E peek();
    int size();
    boolean isEmpty();
    boolean isFull();
}

import java.lang.reflect.Array;
public class SeqQueue<E> implements IQueue<E>{
    private int maxsize;        //队列容量
    private E[] data;       //存储循环队列中的数据元素
    private int front;      //指示最近一个已经离开队列的元素所占的位置
    private int rear;       //指示最近一个已经入队列的元素的位置
    @SuppressWarnings("unchecked")
    public SeqQueue(Class<E> type, int size){
        data = (E[]) Array.newInstance(type,size);  //使用Array.newInstance()创建动态数组
        maxsize = size;
        front = rear = -1;
    }
    //入队列操作
    @Override
    public boolean enqueue(E item) {
        if(!isFull()) {
            rear = (rear + 1) % maxsize;
            data[rear] = item;
            return true;
        }else
            return false;
    }
    //出队列操作
    @Override
    public E deueue() {
        if(!isEmpty()) {
            front = (front + 1) % maxsize;
            return data[front];
        }else
            return null;
    }
    //取队头元素
    @Override
    public E peek() {
        if(!isEmpty()){
            return data[(front+1) % maxsize];
        }else
            return null;
    }
    //求队列长度
    @Override
    public int size() {
        return (rear-front+maxsize) % maxsize;
    }
    //判断循环顺序队列是否为空
    @Override
    public boolean isEmpty() {
        if(front == rear){
            return true;
        }else
        return false;
    }
    //判断循环队列是否为满
    @Override
    public boolean isFull() {
        if ((front == -1 && rear == maxsize - 1) || (rear + 1) % maxsize == front) {
            return true;
        } else {
            return false;
        }
    }
}

//主方法
public class Test {
    public static void main(String[] args) {
        int data[] = {45,89,78,56,15,29,36};
        //循环队列长度比实际长度大1
        IQueue<Integer> queue = new SeqQueue<Integer>(Integer.class,data.length+1);
        //入队操作
        for(int i = 0;i<data.length;i++){
            queue.enqueue(data[i]);
            System.out.println(data[i]+" 入队");
        }
        int size = queue.size();
        //出队操作
        for(int i= 0;i< size;i++){
            System.out.println(queue.deueue()+" 出队");
        }
    }
}

3、循环队列

头尾相接的顺序存储结构称为循环队列。当队首指针Q->front = MAXSIZE-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。

  • 初始时:Q->front = Q->rear=0。

  • 队首指针进1:Q->front = (Q->front + 1) % MAXSIZE。

  • 队尾指针进1:Q->rear = (Q->rear + 1) % MAXSIZE。

  • 队列长度:(Q->rear - Q->front + MAXSIZE) % MAXSIZE。

3、循环队列常见基本算法

(1)、循环队列初始化

/*初始化一个空队列Q*/
Status InitQueue(SqQueue *Q){
    Q->front = 0;
    Q->rear = 0;
    return OK;
}

(2)、循环队列判空

/*判队空*/
bool isEmpty(SqQueue Q){
    if(Q.rear == Q.front){
        return true;
    }else{
        return false;
    }
}

(3)、求循环队列长度

/*返回Q的元素个数,也就是队列的当前长度*/
int QueueLength(SqQueue Q){
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

(4)、循环队列入队

/*若队列未满,则插入元素e为Q新的队尾元素*/
Status EnQueue(SqQueue *Q, ElemType e){
    if((Q->real + 1) % MAXSIZE == Q->front){
        return ERROR;   //队满
    }
    Q->data[Q->rear] = e;   //将元素e赋值给队尾
    Q->rear = (Q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
    return OK;
}

(5)、循环队列出队

/*若队列不空,则删除Q中队头元素,用e返回其值*/
Status DeQueue(SqQueue *Q, ElemType *e){
    if(isEmpty(Q)){
        return REEOR;   //队列空的判断
    }
    *e = Q->data[Q->front]; //将队头元素赋值给e
    Q->front = (Q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
}

六、队列的链式存储结构

1、链队列

队列的链式存储结构表示为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已。

空队列时,front和real都指向头结点。

2、链队列常见算法

(1)、链队列存储类型

/*链式队列结点*/
typedef struct {
	ElemType data;
	struct LinkNode *next;
}LinkNode;
/*链式队列*/
typedef struct{
	LinkNode *front, *rear;	//队列的队头和队尾指针
}LinkQueue;

(2)、链队列初始化

代码:

void InitQueue(LinkQueue *Q){
	Q->front = Q->rear = (LinkNode)malloc(sizeof(LinkNode));	//建立头结点
	Q->front->next = NULL;	//初始为空
}

(3)、链队列入队

Status EnQueue(LinkQueue *Q, ElemType e){
	LinkNode s = (LinkNode)malloc(sizeof(LinkNode));
	s->data = e;
	s->next = NULL;
	Q->rear->next = s;	//把拥有元素e新结点s赋值给原队尾结点的后继
	Q->rear = s;	//把当前的s设置为新的队尾结点
	return OK;
}

(4)、链队列出队

/*若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR*/
Status DeQueue(LinkQueue *Q, Elemtype *e){
	LinkNode p;
	if(Q->front == Q-<rear){
		return ERROR;
	}
	p = Q->front->next;	//将欲删除的队头结点暂存给p
	*e = p->data;	//将欲删除的队头结点的值赋值给e
	Q->front->next = p->next;	//将原队头结点的后继赋值给头结点后继
	//若删除的队头是队尾,则删除后将rear指向头结点
	if(Q->rear == p){	
		Q->rear = Q->front;
	}
	free(p);
	return OK;
}

猜你喜欢

转载自blog.csdn.net/qq_41819893/article/details/121323939