浅谈【数据结构】栈和队列之栈

目录

1、栈和队列

1.1栈思想

1.2、栈存储结构分为两类

1.2.1顺序栈

1.2.2链式栈


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、栈和队列

栈和队列并不是一个什么具体的东西,是一种思想,建立在线性表上的一种思想。

1.1栈思想

思想:先进后出,后进先出

例如:

  • 弹夹
    • 压子弹:在弹夹的上方进来
    • 发射子弹:在弹夹上方出去

1.2、栈存储结构分为两类

1.2.1顺序栈

  • 数组+整型数据:结构体(类)
  • 特点:栈中元素的地址是连续的。且栈空间大小是固定的。

struct seqStack {

        int top; // 指向栈顶元素的下标。栈顶指针

        ElementType stack[长度]; // 栈的空间(栈元素存放的空间)

};

注意:顺序栈在取元素的时候其实没有删除元素的,只是把下标移动了,在下一次增加元素就会覆盖原 来的元素。

示例代码:

#include <iostream>

// 栈内元素的类型
#define ElementType int

// 出栈错误
#define STACKERR 0xffffffff

// 定义栈顶下标宏
#define STACKBUTTOM 0

// 不建议大家把结构体作为类来使用,虽然可以,但是容易混淆类和结构体概念
// 声明顺序栈的类型
typedef struct SeqStack
{
    int top; // 顺序栈的栈顶指针(下标)

    // 想让栈空间可以在创建的时候去指定,设计一个成员变量来记录最大长度
    int stack_max_length;

    ElementType stack[0];
}SeqStack_t;
/*
    完成对于栈的:
        创建栈
        入栈
        出栈
        销毁栈
        检查栈是否已满
        打印栈内元素
*/
/*
    第一种方式创建;
        // 局部的栈,销毁随作用域的消失而自动销毁。但是需要结合操作栈的API来使用
        SeqStack_t stack; 
    第二种方式创建:
        动态内存分配
*/
/*
    @brief 创建一个顺序栈
    @param maxLength 顺序栈的最大容量
    @return 成功返回栈的首地址,失败返回nullptr
*/
SeqStack_t *createNewSeqStack(int maxLength)
{
    // 为了代码好看,先求出需要空间大小
    int spaceLength = sizeof(SeqStack_t)+maxLength*sizeof(ElementType);

    // 申请一个spaceLength大小空间给到newStack,包含了栈元素空间
    SeqStack_t *newStack = (SeqStack_t*)new unsigned char[spaceLength];

    // 初始化
    // 第一种:top指向待插入元素
    newStack->top = STACKBUTTOM;
    newStack->stack_max_length = maxLength;

    /*
        第二种:top指向栈顶元素
        newStack->top = -1;
    */

    return newStack;
}

/*
    @brief 检查栈是否存满了
    @param stack 需要检查的stack的指针
    @return 未满返回false,已满返回true
*/
bool stackIsFull(SeqStack_t *stack)
{
    // 第一种:top指向待插入元素的位置
    if(stack->top == stack->stack_max_length) // top和最大数量相等时候,其实下标已经超出范围了
        return true;
    return false;

    /*
        第二种:top指向栈元素的位置
        if(stack->top == stack->stack_max_length-1)// top等于max-1原因必须要指向一个能访问的位置
            return true;
        return false;
    */
}

/*
    @brief 判断一个栈是否为空
    @param stack需要判断的栈指针
    @return 已空返回true,未空返回false
*/
bool stackIsEmpty(SeqStack_t *stack)
{
    // 第一种:top指向待插入元素的位置
    if(stack->top == STACKBUTTOM) // top和最大数量相等时候,其实下标已经超出范围了
        return true;
    return false;

    /*
        第二种:top指向栈元素的位置
        if(stack->top == -1)// top等于-1原因为0下标是需要存放数据
            return true;
        return false;
    */
}

/*
    @brief 对一个指定的栈进行入栈操作
    @param 需要入栈元素的栈指针
    @param 需要入栈的元素
    @return 成功返回true,失败返回false
*/
bool pushData(SeqStack_t*stack,ElementType data)
{
    // 判断栈是否已满,返回false
    if(stackIsFull(stack))
        return false;

    // 栈没满
    // 第一种:top指向待插入元素的位置
    stack->stack[stack->top] = data;
    stack->top++;
    return true;
    /*
        第二种:top指向栈元素的位置
        stack->top++;
        stack->stack[stack->top] = data;
        return true;
    */
}

/*
    @brief 对一个指定的栈进行元素出栈
    @param stack需要出栈的栈指针
    @return 成功返回出栈元素,失败返回错误码:STACKERR
*/
ElementType popData(SeqStack_t *stack)
{
    // 判断栈是否空
    if(stackIsEmpty(stack))
        return STACKERR;
    // 返回出栈元素
    // 第一种:top指向待插入元素
    stack->top--;
    ElementType data = stack->stack[stack->top];
    return data;
}

int main()
{
    // 创建一个栈
    // SeqStack_t s; // 这种方式栈结构体内不能存在柔性数组
    SeqStack_t * stack = createNewSeqStack(10);

    // 入栈
    pushData(stack,10);
    pushData(stack,1);
    pushData(stack,7);

    // 出栈
    ElementType data = 0;
    while(data != STACKERR)
    {
        data = popData(stack);
        std::cout << data << std::endl;
    }

    // 销毁栈
    delete stack;
    return 0;


    return 0;
}

1.2.2链式栈

  • 由一个单链表表示
  • 特点:只能在表头操作(插入和删除)(插/取)。空间大小可以不固定,且地址不连续。
    • 表头:以单链表为例,那么表头就是首结点(链表的第一个结点)。

示例代码:

#include <iostream>

/*
    外部声明,不会开辟空间,在这个文件里面声明这个标识符
*/
extern struct node *addNodeHead(struct node *list,int data);

typedef struct node {
    int data;
    struct node *next;
}ListStack;


#define STACKERR 0xefffffff

/*
    @brief 入栈
    @param stack 需要入栈的栈指针
    @param data 需要入栈的数据
    @return 成功返回true,失败返回false
*/
bool pushData(ListStack **stack,int data)
{
    *stack = addNodeHead(*stack,data);
    return true;
}


/*
    @brief 出栈
    @param stack 需要出栈的栈指针
    @return 成功返回出栈元素,失败返回STACKERR错误

    
    用二级指针的原因:
        1、首先出栈:首结点是会改变的吧,相当于单链表的首地址改变了。
        2、需要更新链表指针对于首结点的指向
            问题:返回值可以修改链表的指针指向的,但是此时的返回值被出栈元素占用了。
            解决:只能通过参数来解决修改指向操作
        3、在形参中,同级(同类型)是无法让形参改变实参的
            解决:只能通过指针/引用的方式来进行让所谓的形参去改变实参
            这是因为stack本身就是一个一级指针,你再搞个一级指针(同级:同级(同类型)是无法让形参改变实参的)
            所以我们对stack进行取地址,那么拿到的类型就是一个二级指针(非同级(非同类型))。

*/
int popData(ListStack **stack)
{
    // 判断栈是否为空
    if(!(*stack))
        return STACKERR;
    
    // 出栈

    // 取出数据
    int data = (*stack)->data;

    // 断开结点
    ListStack *node_ptr = *stack;

    // 更新栈顶指针
    *stack = (*stack)->next;

    // 释放出栈的结点
    delete  node_ptr;

    return data;
}

bool destoryListStack(ListStack **stack)
{
    if(!*stack)
        return false;
    

    std::cout << "开始销毁:" <<std::endl;
    // 销毁
    while(*stack) // 不停的出栈,直到为空
        std::cout << popData(stack) << std::endl;

    return true;
}

int main()
{
    // 创建一个空栈
    ListStack *stack = nullptr;

    // 入栈
    pushData(&stack,10);
    pushData(&stack,9);
    pushData(&stack,8);
    pushData(&stack,7);

    // 出栈
    int data = popData(&stack);
    std::cout << data << std::endl;
    data = popData(&stack);
    std::cout << data << std::endl;
    data = popData(&stack);
    std::cout << data << std::endl;


    // 销毁
    destoryListStack(&stack);
    return 0;
}

/*
    无头结点的单链表
*/



#include <iostream>

/*
    结点类型
*/
struct node
{
    // 用来存储数据的空间(成员)称为:数据域
    int data;       

    // 用来保存其他结点的地址(关系)称为:指针域
    struct node *next;
};


/*
    @brief: 尾部插入
    @param: list 需要增加新数据的链表指针
    @param: data 是需要存入链表的数据
    @return : 链表首地址
*/
struct node* addNodeTail(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 搞一个临时指针,来指向首结点
    struct node *node_ptr = list;

    // 找尾结点
    while(node_ptr->next)node_ptr = node_ptr->next;
    
    // 到这个位置 node_ptr 此时指向的结点是尾结点
    // 就可以把newNode作为尾结点的后继结点添加到链表里面去了
    node_ptr->next = newNode;

    return list;
}

/*
    @brief : 中间插入法
    @param : list 需要增加数据的链表首地址
    @param : data 需要增加到链表的数据
    @return : 链表首地址
*/
struct node *addNodeMid(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
    newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

    // 循环比较数据 大到小  小到大
    // struct node *node_previce = nullptr;
    // struct node *node_current = list;

    // 第一种方法两个指针
    // while(node_current)
    // {
    //     if(node_current->data > data)
    //         break; // 找到插入位置了
        
    //     // 把两个指针往后移
    //     node_previce = node_current;
    //     node_current = node_current->next;
    // }

    // // 进行插入
    // if(node_previce == nullptr) // 说明需要作为首结点插入(头插入)
    // {
    //     // 头插代码
    // }
    // else
    // {
    //     // 直接插入到node_previce的后面
    //     newNode->next = node_previce->next; // 要成为node_previce下一个结点的前驱结点
    //     node_previce->next = newNode; // 然后让newNode成为node_previce的后继结点
    // }

    // 第二种方法提前判断
    struct node *node_ptr = list;
    // 如果第一个结点就比data大说明要插入到最前面(头插法)
    if(node_ptr->data > data)
    {
        // 头插法
        return list;
    } 
    while(node_ptr&&node_ptr->next)
    {
        std::cout << node_ptr->data << std::endl;
        
        if(node_ptr->data < data && node_ptr->next->data >= data) // 大于前一个小于后一个
        {
            // 将新结点的next指向当前结点的后继结点node_ptr->next
            newNode->next = node_ptr->next;

            // 让newNode成为node_ptr的后继结点
            node_ptr->next = newNode;
            return list; // 退出增加
        }
        
        // 把指针往后移
        node_ptr = node_ptr->next;
    }

    // 严谨判断一下node_ptr->next是不是为空
    if(node_ptr->next == nullptr) // 尾插法
    {
        node_ptr->next = newNode;
    }

    return list;
}

/*
    @brief 头部插入法
    @parma list 需要增加数据的链表首地址
    @param data 需要增加到链表的数据
    @return 链表首地址
*/
struct node *addNodeHead(struct node *list,int data)
{
    struct node *newNode = new struct node;
    newNode->data = data;
    newNode->next = list;
    return newNode;
}


/*
    @brief:创建一个新链表
    @return:返回新链表的首结点的地址
*/
struct node *createNewList()
{
    // 新链表的首结点指针
    struct node *newList = nullptr;

    // 循环通过数据不断的去增加新结点到链表中
    while(1)
    {
        int data = -1;
        // 通过键盘获取数据
        std::cin >> data;

        // 判断退出条件
        if(data == -1)
            break;

        // 做第一次判断:链表中有没有结点
        if(newList == nullptr)
        {
            struct node *newNode = new struct node;
            newNode->data = data; // 将通过键盘获取到的数据存入结构体的数据域中
            newNode->next = nullptr; // 因为它是一个新结点,暂时是没有后继结点

            // 如果newList是nullptr说明该链表里面为空,当前的新节点就是首届点
            newList = newNode;
            continue;
        }

        // 通过中间插入法增加结点
        newList = addNodeMid(newList,data);

        // 通过尾插法增加结点
        // newList = addNodeTail(newList,data);
    }
    return newList;
}



// 打印链表(遍历方法)
void printList(struct node *list)
{
    // 判断是不是空链表
    if(list == nullptr)
    {
        std::cout << "链表为空" << std::endl;
        return;
    }

    std::cout << "List( ";
    // 如果不为空打印链表元素
    while(list) // 只要list不为空就一直循环
    {
        // list本来就可以表示首结点
        std::cout << list->data << " ";

        // 让list移动到下一个结点
        list = list->next;
    }
    std::cout << ")" << std::endl;
}

// int main()
// {
//     // 不在栈空间里面申请结点空间
//     // 创建一个链表
//     struct node *newList = createNewList();

//     // 打印链表元素
//     printList(newList); // 传入的值是newList存储的地址,并非newList自己的地址
//     return 0;
// }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值