iOS--栈和队列

栈的定义

栈(Stack)是一种数据结构,它按照后进先出(LIFO,Last In First Out)的原则进行操作。栈可以看作是一种特殊的线性表,只能在表的一端进行插入和删除操作,这一端称为栈顶,另一端称为栈底。

栈的定义包括以下要素:
数据元素:栈中存储的数据元素可以是任意类型的对象,可以是基本数据类型、结构体、类等。
栈顶指针:栈顶指针指向栈顶元素的位置。当栈为空时,栈顶指针通常指向一个特定的空值。
压栈(Push)操作:将一个新的元素添加到栈顶,栈顶指针向上移动。
弹栈(Pop)操作:从栈顶移除一个元素,栈顶指针向下移动。
栈空判断:判断栈是否为空,即栈中是否没有元素。
栈满判断:在使用静态数组实现栈时,判断栈是否已满,即栈中元素数量是否达到了数组的容量上限。

栈的本质上认仍然是一个线性表,只是比较特殊,它满足线性表的所有性质,但被施加限制,只允许在表尾进行插入和删除操作(这里的表尾指的是栈顶);
简而言之,栈是一个先进先出,栈底不变,只允许在栈顶进行插入和删除的线性表 ;
栈分为两种: 顺序栈和链栈

顺序栈

顺序栈是一种以数组实现栈的一种方式,与用单链表形式实现栈的链栈不同,顺序栈往往占用较少的空间(因为链栈需要申请很多个指针地址),但也伴随着空间不足造成越界的危险 ;
定义:

typedef struct {
    int stack[100] ;
    int top ;
} Sqstack;

初始化:

Sqstack* stack = (Sqstack*) malloc(sizeof(Sqstack)) ;
    stack->top = -1 ;//初始化

压栈:

int push (Sqstack* s, int e) {
    if (s->top >= 99) {
        printf("栈满,push失败") ;
        return 0;
    } else {
        s->stack[++s->top] = e ;
        return 1;
    }
}

弹栈:

int pop (Sqstack* s, int *e) {
    if (s->top == -1) {
        printf("栈空, pop失败") ;
        return 0;
    } else {
        *e = s->stack[s->top] ;
        s->top-- ;
        return 1;
    }
}

在压栈和弹栈中,顺序表要注意最大限制,这就涉及到了栈的溢出和下溢 ;

栈的溢出(Stack Overflow):当尝试将元素压入栈时,如果栈已满,则会发生栈溢出。这可能会导致程序崩溃或出现不可预料的行为。因此,在压栈操作之前,应该先检查栈是否已满,以避免溢出。
栈的下溢(Stack Underflow):当尝试从栈中弹出元素时,如果栈为空,则会发生栈下溢。这意味着没有元素可供弹出,可能会导致程序出现错误或不正确的结果。在弹栈操作之前,应该先检查栈是否为空,以避免下溢。

所以顺序栈一定要注意判空和判满

两个栈共享一个空间

对于一个数组构成的栈,并不总是能刚好将数组填满,这样就会造成空间的浪费,那么如何提高空间的利用率,我们采用一种类似合租的方法,一个栈在前,一个栈在后,栈顶往中间移动,栈顶相遇即栈满,(这里只讲了两个栈共享,但实际上可以多个栈共享) ;
定义:

typedef struct {
    int stack01[100] ;
    int top01 ;
} Sqstack01;

typedef struct {
    int stack02[100] ;
    int top02 ;
} Sqstack02;

初始化:

Sqstack01* stack01 = (Sqstack01*) malloc(sizeof(Sqstack01)) ;
    stack01->top01 = -1 ;//初始化
    Sqstack02* stack02 = (Sqstack02*) malloc(sizeof(Sqstack02)) ;
    stack02->top02 = 100 ;//初始化

压栈:

int push01 (Sqstack01* s, Sqstack02* q, int e) {
    if (s->top01 + 1 == q->top02) {
        printf("栈满,push失败") ;
        return 0;
    } else {
        s->stack01[++s->top01] = e ;
        return 1;
    }
}
//push02类似

弹栈:

int pop01 (Sqstack01* s, Sqstack02* q, int *e) {
    if (s->top01 == -1) {
        printf("栈空, pop失败") ;
        return 0;
    } else {
        *e = s->stack01[s->top01] ;
        s->top01-- ;
        return 1;
    }
}
//pop02类似

链栈

链栈由单链表组成,元素通过结点压入栈中;
这里用一个写过的题目展示
在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Node{
    char data ;
    struct Node* next ;
} StackNode;

typedef struct LNode {
    StackNode* top ;
    int count ;
} StackList;

void Push (StackList* L, char e) {
    StackNode* s = (StackNode*) malloc(sizeof(StackNode)) ;
    s->data = e ;
    s->next = L->top ;
    L->top = s ;
    L->count++ ;
}

char Pop (StackList* L) { //没有判空栈
    if (L->count == 0) {
        return 0;
    } else {
        char e ;
        e = L->top->data ;
        L->top = L->top->next ;
        L->count-- ;
        return  e;
    }
}
int main(int argc, const char * argv[]) {
    StackList* L = (StackList*) malloc(sizeof(StackList)) ;//创建链栈
    L->count = 0 ;
    L->top = NULL ;//初始化
    
    char str[1000] ;
    scanf("%s",str) ;
    int len = (int)strlen(str) ;
    for (int i =0 ; i < len / 2; i++) {
        Push(L, str[i]) ;
    }
    for (int i = len / 2; i > 0; i--) {
        if (L->top->data == str[len - i]) {
            Pop(L) ;
        }
    }
    if (L->top == NULL) {
        printf("huiwen") ;
    } else{
        printf("no") ;
    }
    
    
  
    
   
    return 0;
}

链栈需要额外的指针空间来维护节点之间的连接关系,相对于顺序栈而言,占用更多的内存空间。

由于链栈使用链表实现,访问任意位置的元素需要遍历链表,因此随机访问性能较低,时间复杂度为O(n),其中n为链表的长度。

链栈的优点:
动态扩展:链栈的大小可以根据需要动态扩展,不受固定容量的限制。
灵活性:链栈可以处理任意大小的数据,不需要预先定义栈的最大容量。
内存管理:链栈使用动态内存分配,可以更灵活地管理内存,避免内存浪费。

队列

定义

队列(Queue)是一种常见的线性数据结构,遵循先进先出(First-In-First-Out,FIFO)的原则。队列中的元素在尾部插入,从头部移除。类比现实生活中排队等候的场景,最先进入队列的元素首先被处理或移出队列。

循环队列

对于顺序队列,我们需要在队头和队尾分别设置front和rear,以实现对头插入和队尾删除,但在空间有限的数组中,在删除时,为了减小时间复杂度,我们不会让所有元素前一,这也造成了有时队尾到头了,队头前却还有剩余空间,对于这种问题,我们采用循环队列的思想来解决;
循环队列中的队尾可以循环到数组的开头,形成一个环形结构。
每当队尾到头是,判断(rear+1)% size == front 来判断空间已满
(rear - front+size)%size获取长度 ;

在这里插入图片描述
(这里用的顺序队列)
定义:

typedef struct {
    int data[20] ;
    int front ;
    int rear ;
} Sq;

计算队列长度:

int queueLength (Sq* s) {
    return (s->rear - s->front +20) % 20;
}

入队:

int enqueue (Sq* s, int e) {
    if ((s->rear + 1) % 20 == s->front) {
        printf("队列已满") ;
        return 0;
    } else {
        s->data[s->rear] = e ;
        s->rear = (s->rear + 1) % 20 ;
        return 1;
    }
}

出队:

int dequeue (Sq* s, int* e) {
    if (s->rear == s->front) {
        printf("队列空") ;
        return 0 ;
    } else {
        *e = s->data[s->front] ;
        s->front = (s->front + 1) % 20 ;
        return 1;
    }
}

初始化:

  Sq* s = (Sq*) malloc(sizeof(Sq)) ;
    s->front  = 0 ;
    s->rear = 0 ;

循环队列的优点是充分利用了数组空间,避免了普通队列因出队操作导致的空间浪费。同时,循环队列的入队和出队操作时间复杂度都是O(1)。

循环队列是一种高效利用空间的队列实现方式,适用于需要频繁进行入队和出队操作的场景。

除了上面这种循环队列的方法,我们也可以设置flag来判断rear是在front前面还是后面,来判断队列满还是空 ;这种方法相对复杂一点,但与上面的方法相比,上面的方法会浪费一个元素的空间 ;

(链表实现的队列应该是差不多的,循环链表的话会更加简明) ;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值