数据结构:栈和队列

目录

一,栈的基本概念

1,栈的定义

 2,栈的常见操作

 3,栈的插入与删除 

4,进栈出栈变化形式

 5,栈的顺序存储结构

 6,两栈的共享空间

   

 7.栈的链式存储结构

二,栈的应用——递归

队列

二,队列的基本操作

1,队列的定义

 2,队列的常见基本操作:

3,循环队列

4,队列的链式存储结构


一,栈的基本概念

1,栈的定义

      栈(stack)是限定仅在表尾进行插入和删除操作的线性表,又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
      允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)。

      不含任何数据元素的栈称为空栈

 2,栈的常见操作

      InitStack(*s):初始化操作,建立一个空栈S。
      DestroyStack(*s):若栈存在,则销毁它。
      ClearStack(*s):将栈清空。
      StackEmpty(S):若栈为空,返回true,否则返回false。

      GetTop(S,*e):若栈存在且非空,用e返回S的栈顶元素。

      Push(*S,e):若栈s存在,插入新元素e到栈S中并成为栈顶元素。

      Pop(*S,*e):删除栈S中栈顶元素,并用e返回其值。
      StackLength(S):返回栈S的元素个数。

 3,栈的插入与删除 

       栈的插入操作,叫作进栈,也称压栈、入栈。

       栈的删除操作,叫作出栈,也有的叫作弹栈。

4,进栈出栈变化形式

     最先进栈的元素,不一定最后出栈。栈对线性表的插入和删除的位置进行了限制,并没有对元素进出的时间进行限制,也就是说,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。

     举例来说,如果我们现在是有3个整型数字元素1、2、3依次进栈,会有哪些出栈次序呢?
     第一种:1、2、3进,再3、2、1出。这是最简单的最好理解的一种,出栈次序为321。
     第二种:1进,1出,2进,2出,3进,3出。也就是进一个就出一个,出栈次序为123。
     第三种:1进,2进,2出,1出,3进,3出。出栈次序为213。 
     第四种:1进,1出,2进,3进,3出,2出。出栈次序为132。
     第五种:1进,2进,2出,3进,3出,1出。出栈次序为231。

 5,栈的顺序存储结构

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

      若存储栈的长度为StackSize=5,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定位top等于-1。栈的普通情况,空栈,栈满如图所示。

     顺序栈的进栈操作:

顺序栈的基本操作代码:

#include <stdio.h>

#define MAXSIZE 50
typedef int E;
typedef struct Stack{                //栈的结构定义
    E data[MAXSIZE];
    E top;
}SqTack, *ArrayStack;
void InitStack(ArrayStack s) {       //栈的初始化
    s->top = -1;
}
void Push(ArrayStack s, E element) { //进栈
    if (s->top == MAXSIZE - 1) {
        return;
    }
    s->data[++s->top] = element;
}
void PrintStack(ArrayStack s) {      //栈的打印
    while(s->top != -1) {
        printf("%d ", s->data[s->top--]);
    }
}
void Pop(ArrayStack s, E* element) { //出栈
    if (s->top == -1) {
        return;
    }
    *element = s->data[s->top--];
}
int main(int argc, const char * argv[]) {
    SqTack S;
    InitStack(&S);
    for (int i = 0; i < 50; i++) {
        Push(&S, i);
    }
    int n;
    Pop(&S, &n);
    PrintStack(&S);
    printf("%d ", n);
    return 0;
}

 6,两栈的共享空间

       共享栈的概念

      利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示

     

    栈满 :两个指针相差为1时,即top1 + 1 = top2。 

    共享栈的基本操作代码:

#include <stdio.h>
#define MAXSIZE 50
typedef int E;
/*共享栈的空间结构*/
typedef struct Stack{
    E data[MAXSIZE];
    E top1;
    E top2;
}SqStack, *ArrayStack;
void InitStack(ArrayStack s) {
    s->top1 = -1;
    s->top2 = MAXSIZE;
}
void Push(ArrayStack s, E element, E StackNumber) {  //进栈
    if (s->top1 + 1 == s->top2) {                    //栈满
        return;
    }
    if (StackNumber == 1) {
        s->data[++s->top1] = element;
    } else if (StackNumber == -1) {
        s->data[--s->top2] = element;
    }
}
void PrintfStack(ArrayStack s) {                     //共享栈的打印
    while (s->top1 != -1) {
        printf("%d ", s->data[s->top1--]);
    }
    while (s->top2 != MAXSIZE) {
        printf("%d ", s->data[s->top2++]);
    }
                              
}
void Pop(ArrayStack s, E* element, E StackNumber) {  //出栈
    if (StackNumber == 1) {
        if (s->top1 == -1) {
            return;
        }
        *element = s->data[s->top1--];
    } else if (StackNumber == 2) {
        if (s->top2 == MAXSIZE) {
            return;
        }
        *element = s->data[s->top2++];
    }
}
int main(int argc, const char * argv[]) {
    SqStack S;
    InitStack(&S);
    int k = 1;
    for (int i = 0; i < MAXSIZE; i++) {
        Push(&S, i, k);
        k = -k;
    }
    int n;
    Pop(&S, &n, 1);
    printf("%d \n", n);
    Pop(&S, &n, 2);
    printf("%d \n", n);
    PrintfStack(&S);
    return 0;
}

 7.栈的链式存储结构

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

 链栈的进栈

     假设元素值为e的新节点是s,top为栈顶指针。

链栈的出栈

   假设变量p用来储存要删除的栈顶节点,将栈顶指针下移一位,最后释放p。

 链栈的基本操作代码:

#include<stdio.h>
#include<stdlib.h>
typedef  int E;
/*链栈的定义结构*/
typedef  struct StackNode {
    E date;
    struct StackNode* next;
}StackNode,*LinkStackPtr;
typedef struct Stack {
    LinkStackPtr top;
    E count;
} SqStack, *LinkStack;
void InitStack(LinkStack s) {                                 //初始化
    s->count = 0;
}
void Push(LinkStack S, E element) {                           //进栈
    LinkStackPtr s = (LinkStackPtr)malloc(sizeof(StackNode));
    s->date = element;
    s->next = S->top;
    S->top = s;
    S->count++;
}
void Pop(LinkStack S, E* element) {                           //出栈
    LinkStackPtr p;
    if (S->count == 0) {
        return;
    }
    *element = S->top->date;
    p = S->top;
    S->top = S->top->next;
    free(p);
    S->count--;
}
void PrintfListStack(LinkStack S) {                           //打印
    while (S->top) {
        printf("%d ", S->top->date);
        S->top = S->top->next;
    }
}
int main(int argc, const char * argv[]) {
    SqStack S;
    InitStack(&S);
    int len;
    scanf("%d", &len);
    for (int i = 0; i < 50; i++) {
        Push(&S, i+1);
    }
    int n;
    Pop(&S, &n);
    printf("%d \n", n);
    PrintfListStack(&S);
    return 0;
}

二,栈的应用——递归

        若在一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的,简称递归

 例子 斐波那契额数列:

/*斐波那契数列的实现*/
int Fib(int n){
    if(n == 0){
        return 0;   //边界条件
    }else if(n == 1){
        return 1; //边界条件
    }else{
        return Fib(n-1) + Fib(n-2); //递归表达式
    }
}

 

          那么我们讲了这么多递归的内容,和栈有什么关系呢?

      这得从计算机系统的内部说起。前面我们已经看到递归是如何执行它的前行和退回阶段的。递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。
     这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈这样的数据结构,因此,编译器使用栈实现递归就没什么好惊讶的了。
简单的说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
    当然,对于现在的高级语言,这样的递归问题是不需要用户来管理这个栈的,一切都由系统代劳了。

队列

二,队列的基本操作

1,队列的定义

       队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
       队列是一种先进先出(First In First Out)的线性表,简称FIFO。

      允许插入的一端称为队尾,允许删除的一端称为队头

 2,队列的常见基本操作:

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

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

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

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

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

3,循环队列

        上图的rear 可以改为指向下标为0的位置,这样就不会造成指针指向不明的问题了,后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列

队列空:front == rear == 0;

队列满:

       由于rear可能比front大,也可能比front小,所以尽管它们只相差一个位置时就是满的情况,但也可能是相差整整一圈。所以若队列的最大尺寸为 QueueSize,那么队列满的条件是(rear+1) %QueueSize == front 

队列长度:

      当rear>front时,此时队列的长度为rear-front。但当rear<front时,队列长度分为两段,一段是QueueSize-front,另一段是0+ rear,加在一起,队列长度为rear一front+QueueSize。因此通用的计算队列长度公式为:(rear—front + QueueSize)%QueueSize

循环队列基础操作代码:

#include <stdio.h>
#define  MAXSIZE 10
typedef int E;
/*循环队列的基本结构*/
typedef struct Queue {
    E data[MAXSIZE];
    int front;
    int rear;
}SqQueue, *ArrayStack;
void InitQueue(ArrayStack Q) {                   //初始化
    Q->front = 0;
    Q->rear = 0;
}

void EnQueue(ArrayStack Q, E element) {          //入队
    if ((Q->rear + 1) % MAXSIZE == Q->front) {   
        return;
    }
    Q->data[Q->rear] = element;
    Q->rear = (Q->rear + 1) % MAXSIZE;           //rear指针后移一位,若到最后则转到数组头部
}
void DeQueue(ArrayStack Q, E* element) {         //出队
    if (Q->front == Q->rear) {
        return;
    }
    *element = Q->data[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
}
int QueueLength(ArrayStack Q) {                  //求队列长度
    return (Q->rear - Q->front + MAXSIZE) % MAXSIZE;
}
void PrintfQueue(ArrayStack Q) {                 //打印队列
    if (Q->front <= Q->rear) {
        for (int i = Q->front; i < Q->rear; i++) {
            printf("%d ", Q->data[i]);
        }
    } else {
        for (int i = Q->front; i < MAXSIZE; i++) {
            printf("%d ", Q->data[i]);
        }
        for (int i = 0; i < Q->rear; i++) {
            printf("%d ", Q->data[i]);
        }
    }
}
int main(int argc, const char * argv[]) {
    SqQueue q;
    InitQueue(&q);
    for (int i = 0; i < MAXSIZE - 1; i++) {
        EnQueue(&q, i + 1);
    }
    int n;
    DeQueue(&q, &n);
    printf("%d\n", n);
    EnQueue(&q, 10);
    int length = QueueLength(&q);
    printf("%d\n", length);
    PrintfQueue(&q);
    return 0;
}

4,队列的链式存储结构

       队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已, 我们把它简称为链队列。为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。

空队列时

        front和rear都指向头结点

链队列入队操作

       入队操作时,其实就是在链表尾部插入结点

 链队列的出队操作

        出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,
若链表除头结点外只剩一个元素时,则需将rear指向头结点

链队列的基本操作代码

#include <stdio.h>
#include <stdlib.h>
typedef int E;
/*链队列的基本结构*/
typedef struct QueueNode {
    E data;
    struct QueueNode* next;
}QueueNode, *LinkQueuePtr;
typedef struct Queue {
    LinkQueuePtr front;
    LinkQueuePtr rear;
}SqQueue, *LinkQueue;
void InitQueue(LinkQueue Q) {
    LinkQueuePtr head = malloc(sizeof(QueueNode));
    Q->front = head;
    Q->rear = head;
}
void EnQueue(LinkQueue Q, E element) {                       //进队
    LinkQueuePtr s = (LinkQueuePtr)malloc(sizeof(QueueNode));
    s->data = element;
    s->next = NULL;
    Q->rear->next = s;
    Q->rear = s;
}
void DeQueue(LinkQueue Q, E* element) {                      //出队
    LinkQueuePtr p;
    if (Q->front == Q->rear) {
        return;
    }
    p = Q->front->next;
    *element = p->data;
    Q->front->next = Q->front->next->next;
    if (Q->rear == p) {//要删除的节点是尾节点
        Q->rear = Q->front;
    }
    free(p);
}
void PrintfQueue(LinkQueue Q) {                             //打印队列
    LinkQueuePtr p = Q->front->next;
    while (p) {
        printf("%d ", p->data);
        p = p->next;
    }
}
int main(int argc, const char * argv[]) {
    SqQueue q;
    InitQueue(&q);
    for (int i = 0; i < 50; i++) {
        EnQueue(&q, i + 1);
    }
    int n;
    DeQueue(&q, &n);
    printf("%d ", n);
    PrintfQueue(&q);
    return 0;
}


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值