动态数组栈实现详解:从零构建高效数据结构

C语言数据结构基石:动态数组 vs. 单向链表,从零实现核心操作 本文介绍了动态数组和单向链表两种基础数据结构的C语言实现。动态数组通过malloc/realloc实现动态扩容,支持随机访问但插入删除效率低;单向链表通过节点指针串联数据,插入删除高效但不支持随机访问。文章详细展示了两种结构的头文件设计、核心功能实现(初始化、插入、遍历、删除等),并通过Person结构体进行测试验证。这两种结构分别代表连续存储和离散存储思想,是构建更复杂数据结构的基础,掌握它们对提升编程能力至关重要。 阅读详情

1. 栈是什么?答:像一叠盘子


想象你面前有一叠盘子,每次你只能:

放一个新盘子到最上面(压栈 / push)

拿走最上面的盘子(出栈 / pop)

看一眼最上面的盘子是什么,但不拿走(查看栈顶 / peek)

这就叫“后进先出”,英文是 LIFO(Last In First Out)。

在程序里,栈就是一个只能在一端操作的容器。我们要做的是用一个“数组”来存这些盘子,并且这个数组能自动变大,这样我们就永远不用担心盘子放不下了。

2. 为什么要“动态”?


普通的数组一旦定义好大小就不能变了。比如 int arr[10] 就只能放10个数字。如果突然来了第11个盘子,就放不下了。

动态数组的意思是:一开始我们只申请一点点内存(比如能放4个数字),等它放满了,我们再申请一个更大的内存(比如能放8个),然后把原来的4个数字搬过去,之后还可以继续变大。这样一来,你用的时候完全不需要事先知道要存多少个元素。

3. 动态数组栈需要哪些零件?


我们需要用一个结构体把三样东西捆在一起:

typedef struct {
    int *data;     // 指向存放元素的数组(用指针,方便重新分配)
    int top;       // 栈顶索引,表示最后一个元素的位置,-1 代表栈空
    int capacity;  // 当前分配的内存最多能放几个元素
} ArrayStack;

data  是个指针,我们用它指向动态分配的那块内存。

top  记录栈顶在哪里。比如栈里已有两个数,那 top = 1(索引从0开始)。

capacity 记录现在分配的空间大小,不是实际元素个数。

4. 一步一步实现各个功能

4.1 初始化
刚开始栈是空的,我们要申请一小块内存,把 top 设成 -1,capacity 设成初始大小。

#define INIT_SIZE 4   // 刚开始分配4个位置

void initStack(ArrayStack *s) {
    s->data = (int*)malloc(INIT_SIZE * sizeof(int));
    s->top = -1;
    s->capacity = INIT_SIZE;
}

这里的 malloc 就是在内存里要了一块地,能放4个 int。

4.2 判空、查大小

int isEmpty(ArrayStack *s) {
    return s->top == -1;   // 如果 top 还是 -1,说明没有盘子
}

int size(ArrayStack *s) {
    return s->top + 1;     // 因为索引从0开始,数量 = 索引 + 1
}

4.3 扩容(最关键的一步)


当栈满的时候,我们需要更大的地方。这里用 realloc 函数,它可以把之前 malloc 出来的地盘变大(实际上可能找一块新地,把你原来的东西拷贝过去,老地自动释放)。

void expand(ArrayStack *s) {
    s->capacity = s->capacity * 2;   // 容量扩成两倍
    s->data = (int*)realloc(s->data, s->capacity * sizeof(int));
}

realloc(原指针, 新大小) 返回新内存的地址。我们把它重新赋给 s->data 就行了。

4.4 压栈(push)—— 放盘子

每次放盘子之前,先看看是不是满了。满了就先扩容,然后 top 加1,把新元素放在 top 位置上。

void push(ArrayStack *s, int value) {
    if (s->top + 1 == s->capacity) {   // 真的满了
        expand(s);                     // 扩大地盘
    }
    s->top++;                    // 栈顶上移
    s->data[s->top] = value;     // 把新数字放到栈顶
}

4.5 出栈(pop)—— 拿走最上面的盘子


拿出最上面的盘子,同时 top 减1,表示盘子少了。注意检查是否为空。

int pop(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈已经空了,不能再拿!\n");
        exit(-1);   // 异常退出,或者你可以返回一个特殊值
    }
    int val = s->data[s->top];   // 拿到栈顶的值
    s->top--;                    // 栈顶向下移
    return val;
}

4.6 查看栈顶(peek)—— 只看不拿

和 pop 一样,但不移动 top。

int peek(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈是空的,没东西可看!\n");
        exit(-1);
    }
    return s->data[s->top];
}

4.7 释放内存


最后不用时,一定要把 malloc 出来的内存还回去,不然会内存泄漏。

void destroyStack(ArrayStack *s) {
    free(s->data);
    s->data = NULL;
    s->top = -1;
    s->capacity = 0;
}

5. 完整代码(可以直接跑起来)

#include <stdio.h>
#include <stdlib.h>

#define INIT_SIZE 4

typedef struct {
    int *data;
    int top;
    int capacity;
} ArrayStack;

// 初始化
void initStack(ArrayStack *s) {
    s->data = (int*)malloc(INIT_SIZE * sizeof(int));
    s->top = -1;
    s->capacity = INIT_SIZE;
}

// 判空
int isEmpty(ArrayStack *s) {
    return s->top == -1;
}

// 元素个数
int size(ArrayStack *s) {
    return s->top + 1;
}

// 扩容
void expand(ArrayStack *s) {
    s->capacity *= 2;
    s->data = (int*)realloc(s->data, s->capacity * sizeof(int));
    printf("(内部扩容:容量变为了 %d)\n", s->capacity);
}

// 压栈
void push(ArrayStack *s, int value) {
    if (s->top + 1 == s->capacity) {
        expand(s);
    }
    s->data[++(s->top)] = value;   // 等价于 top++; data[top]=value;
}

// 出栈
int pop(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈空,无法出栈\n");
        exit(1);
    }
    return s->data[(s->top)--];   // 先返回 data[top],然后 top--
}

// 查看栈顶
int peek(ArrayStack *s) {
    if (isEmpty(s)) {
        printf("栈空\n");
        exit(1);
    }
    return s->data[s->top];
}

// 打印整个栈(从底到顶)
void printStack(ArrayStack *s) {
    printf("栈内容(底->顶):");
    for (int i = 0; i <= s->top; i++) {
        printf("%d ", s->data[i]);
    }
    printf("\n");
}

// 销毁
void destroyStack(ArrayStack *s) {
    free(s->data);
    s->data = NULL;
    s->top = -1;
    s->capacity = 0;
}

// 测试一下
int main() {
    ArrayStack stack;
    initStack(&stack);

    printf("压入 10, 20, 30, 40\n");
    push(&stack, 10);
    push(&stack, 20);
    push(&stack, 30);
    push(&stack, 40);
    printStack(&stack);
    printf("当前栈顶: %d\n", peek(&stack));
    printf("元素个数: %d\n", size(&stack));

    printf("\n再压入 50(会触发扩容)\n");
    push(&stack, 50);   // 这里会看到扩容提示
    printStack(&stack);
    printf("元素个数: %d\n", size(&stack));

    printf("\n连续出栈两个:\n");
    printf("弹出: %d\n", pop(&stack));
    printf("弹出: %d\n", pop(&stack));
    printStack(&stack);

    destroyStack(&stack);
    return 0;
}

6. 复盘一下关键点


我们用 malloc 申请初始数组,用 realloc 在空间不够时自动扩大。

top 既表示最后一个元素的索引,也表示下一次压栈时要放的位置(只要先把 top+1 再赋值)。

扩容通常扩容为原来的两倍,这样可以减少频繁的重新分配。

7.用链表实现栈

除了用数组来实现以外,用链表也可以实现。

以下是用链表来实现的代码。

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 链表节点
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 栈结构(保存栈顶指针和节点数)
typedef struct {
    Node *top;
    int count;
} LinkedStack;

// 初始化
void initStack(LinkedStack *s) {
    s->top = NULL;
    s->count = 0;
}

// 判空
bool isEmpty(LinkedStack *s) {
    return s->top == NULL;
}

// 获取大小
int size(LinkedStack *s) {
    return s->count;
}

// 压栈(头插法)
void push(LinkedStack *s, int value) {
    Node *newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL) {
        printf("内存分配失败\n");
        exit(EXIT_FAILURE);
    }
    newNode->data = value;
    newNode->next = s->top;
    s->top = newNode;
    s->count++;
}

// 出栈
int pop(LinkedStack *s) {
    if (isEmpty(s)) {
        printf("栈空,无法出栈\n");
        exit(EXIT_FAILURE);
    }
    Node *temp = s->top;
    int value = temp->data;
    s->top = s->top->next;
    free(temp);
    s->count--;
    return value;
}

// 获取栈顶
int peek(LinkedStack *s) {
    if (isEmpty(s)) {
        printf("栈空\n");
        exit(EXIT_FAILURE);
    }
    return s->top->data;
}

// 打印栈(从顶到底)
void printStack(LinkedStack *s) {
    printf("栈 (顶->底): ");
    Node *cur = s->top;
    while (cur != NULL) {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

// 销毁栈
void destroyStack(LinkedStack *s) {
    while (!isEmpty(s)) {
        pop(s);
    }
}

// ---------- 测试 ----------
int main() {
    LinkedStack stack;
    initStack(&stack);

    push(&stack, 100);
    push(&stack, 200);
    push(&stack, 300);
    printStack(&stack);               // 300 200 100
    printf("栈顶: %d\n", peek(&stack));   // 300
    printf("出栈: %d\n", pop(&stack));   // 300
    printf("出栈: %d\n", pop(&stack));   // 200
    printf("大小: %d\n", size(&stack));  // 1
    push(&stack, 400);
    push(&stack, 500);
    printStack(&stack);               // 500 400 100
    destroyStack(&stack);
    return 0;
}

高级数据结构:树状数组详解(c++实现 求解动态数组区间和问题) 什么是树状数组???解决数据压缩里的累积频率(Cumulative Frequency)的计算问题,现多用于高效计算数列的前缀和, 区间和,这样的问题可以使用树状数组算法来解决。我们现在通过修改数组,往数组中某一个位置增加或减小一个值,从而导致前缀和数组会发生变化,因此我们不可以使用普通前缀和这一思路来做。 考虑一下:我们求静态数组的前缀和时,如果我们使用暴力法,使用两个for循环来枚举子区间之和,从而得到静态数组的区间和,但是这一复杂度会达到O(N^2),因此使用普通的前缀和可以将复杂度达到O(N)。但是 阅读详情

相关推荐

[Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--地图(一)

上面3章图是完成后的效果图 游戏已完成,除了英雄外,基本还原了90%的游戏内容,一共13关,20种防御塔,30+种敌人,如上图,以假乱真吧 下面从地图模块起介绍我的方法,如有更好的方法,请留言一起讨论,游戏资源下载原版游戏数据包,解压即可 推荐一款软件,TextureUnpackerRelease1.04可以分割plist形式的图片,提高效率 经过一个月学习也发现之前有很多化简为繁的错误

7172

【数据结构】栈的实现以及数组和链表的优缺点

如果数据已经在缓存中(缓存命中),那么CPU就可以直接从缓存中读取数据,而不需要从主存中读取,从而提高了程序的运行效率。由于数组的物理地址是连续的,因此数组中的数据在缓存中的命中率更高,减少了从主存中读取数据的次数,从而提高了缓存利用率。但是,链表的节点在物理存储上是不连续的,因此链表中的数据在缓存中的命中率相对较低。在访问一块数据内存空间时,要先把内存加载到缓存,在对其进行访问,而其并不是没此访问一个内存就将其加载到缓存,而是访问一块内存时将其后面的连续的内存一起加载到缓存,然后在对其进行访问。

2303_80187476的博客 4596

双传声器法测量吸声材料的吸声系数(公式推导与解释)

在双传声器法中,通过测量两个位置的声压 p1和 p2​,可以分离出入射波和反射波的声压分量。双传声器法一般在阻抗管中进行,阻抗管的一端为平面波声源,另一端放置待测材料。两个传声器放置在靠近材料的阻抗管内,用于测量管道内的声压。双传声器法是一种常用于测量材料吸声系数的实验方法,通过在管道内使用两个传声器测量声波的入射和反射,来计算出材料的吸声系数。当测试样品较少时,测试时,需要交换两个传声器的位置,目的是为了相位校准。

m0_57407372的博客 1875

【数据结构与算法】使用数组实现栈:原理、步骤与应用

栈是一种后进先出(LIFO, Last In First Out)的数据结构。栈是一种只能在一端进行插入和删除操作的线性表。允许进行插入和删除操作的一端称为栈顶(top),另一端称为栈底(bottom)。栈中没有元素时,称为空栈。栈的基本操作包括:push(入栈)、pop(出栈)、peek(查看栈顶元素)和isEmpty(判断栈是否为空)等。

倔强的石头的博客 2169

双有源桥(DAB)进阶:EPS调制策略与Matlab/Simulink仿真实践

本文深入探讨了双有源桥(DAB)的扩展移相(EPS)调制策略及其在Matlab/Simulink中的仿真实践。通过分析EPS的两种黄金调制策略——回流功率消除和电流应力优化,结合实际工程案例,展示了如何提升DAB系统的效率和性能。文章还详细介绍了Simulink模型搭建要点和关键仿真结果,为工程师提供了实用的技术参考。

weixin_33734785的博客 484

62. 如何通过增强(Enhancement) 的方式给 SAP ABAP 标准程序增添新功能

本文通过一个实际的例子,介绍了如何不通过修改 SAP 标准 ABAP 程序源代码的方式,对其的功能进行增强,即采取 ABAP 增强开发的方式进行二次开发,这是一种在实际 SAP 项目实施过程中使用非常广泛的技术。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3309

【算法与数据结构】栈的实现详解

栈的概念:栈的主要特点:先进后出(LIFO, Last In First Out)。新添加的元素都放在栈顶,取出元素时也是从栈顶取出。只允许在一端(栈顶)进行插入和删除操作。插入操作称为入栈,删除操作称为出栈。栈内元素的访问只能是顺序访问,不能随机访问。通常使用数组或链表来实现栈。栈的基本操作: 栈的结构: 使用数组实现栈时,维护一个指针指向栈顶元素的下一个位置。入栈时将元素添加到数组位置,并将加;出栈时从位置取元素,并将减。 使用链表实现栈时,链表的头结点指向栈顶元素。入栈添加新节点到头结点后面,出栈删除

a_sen的博客 2169

STM32 stack_size和heap_size大小设置的意义

在使用STM32编程时,一般情况下我们不会关注堆栈空间的大小,因为在STM32的启动文件中,已经帮我们预先设置好了堆栈空间的大小。如下图所示的启动代码中,Stack栈的大小为:0x400(1024Byte),Heap堆的大小为:0x200(512Byte)。 1、若工程中使用的局部变量较多,定义的数据长度较大时,若不调整栈的空间大小,则会导致程序出现栈溢出,程序运行结果与预期的不符或程序跑飞。这时我们就需要手动的调整栈的大小。 2、当工程中使用了malloc动态分配内存空间时,这时分配.......

zxm8513优快云博客 3万+

数据结构与算法之美总结(数组、链表、栈、队列、递归、排序及二分)

数据结构与算法之美总结(数组、链表、栈、队列、递归、排序及二分)

凡的博客 1万+

甲基化生物信息学分析及批次效应校正全流程

本文系统阐述了DNA甲基化数据分析中的批次效应问题及解决方案。甲基化数据(β值和M值)对实验批次差异高度敏感,可能导致假阳性结果。文章详细介绍了批次效应检测方法(PCA可视化、BEclear定量评分)和校正流程,包括数据预处理、参数化(ComBat-met)与非参数化(BEclear)校正方法,并提供了完整的R代码实现。重点解决了β值有界分布与统计模型假设冲突的问题,为甲基化研究的可靠性提供技术保障。

huanghm88的专栏 110

【Mode】微带传输线阻抗

微带线,英文名称为 (Microstrip) 是一种常见的平面传输线,主要用于传输微波频率信号(通常在射频和微波电路重中使用)。

u011699626的博客 244

数据结构——lesson5栈和队列详解

数据结构栈和队列完整实现

Renswc的博客 2944

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C++之常用的三类数据结构--栈、队列、数组

函数名功能描述push()向栈顶添加一个元素pop()删除栈顶元素top()返回栈顶元素的值empty()判断栈是否为空,如果为空则返回true,否则返回falsesize()返回栈中元素的个数clear()清空栈中的所有元素。函数名功能描述push()在队列尾部添加一个元素pop()删除队列的头部元素front()返回队列的头部元素back()返回队列的尾部元素empty()判断队列是否为空(元素个数为0)size()返回队列中元素的个数clear()清空栈中的所有元素。函数名功能描述。

PPdouluo的博客 2482

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

【数据结构】栈的实现

本篇主要利用数组来实现栈,对于栈的各种操作都作详细介绍,压栈,出栈以及获取栈中元素的操作都是学习栈的必备知识,快来学起来吧!!!

Extreme_wei的博客 1237

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

C语言数据结构——栈

在计算机科学中,数据结构是组织、管理和存储数据的有效方式,以便可以高效地访问和修改数据。栈是一种基本的数据结构,它遵循特定的操作顺序,即后进先出(LIFO)。由于其简单和高效的特性,在许多算法和系统功能中都有应用,如系统调用栈、表达式求值和回溯算法等。栈是一种简单而强大的数据结构,它在算法设计和系统实现中扮演着重要的角色。通过静态或动态的方式实现栈,可以在不同的应用场景中有效地利用其后进先出的特性。深入理解栈的工作原理和应用,对于学习更复杂的数据结构和算法是非常有帮助的。

wxk2227814847的博客 5561

常见的数据结构---数组、链表、栈的深入剖析

数据结构是算法的基石,是程序设计的核心基础。不同的数据结构适用于不同的场景和需求,选择合适的数据结构能显著提升程序的效率和可读性。在众多的数据结构中,数组、链表和栈是最基本、最常用的三种。它们各具特色,不仅应用广泛,也是更复杂数据结构的基础。

wallwayj的博客 1736

【数据结构】C语言实现栈(详细解读)

c--数据结构,使用C语言实现栈,详细解读

weixin_65186652的博客 1万+

线性表之数组与栈详解

数组和栈作为线性表的重要组成部分,各自具有独特的特性和应用场景。数组凭借其随机访问高效、内存连续的特点,在数据存储和算法基础操作中发挥着重要作用;栈遵循后进先出原则,在函数调用管理、表达式处理等方面表现出色。

1290

【数据结构】栈是什么?从0理解栈的数据结构

在数据结构的学习中,栈是一种非常重要的结构。无论你是否是计算机专业,栈的概念其实并不复杂。本篇文章将帮助你从零理解栈的数据结构,了解它的基本用途、实际应用以及如何在C++中实现栈操作。

XuanRan的博客 4544

数组与动态数组【C++数据结构】

本文深入剖析`std::array`与`std::vector`。`array`是编译时定长的静态数组,性能极致;`vector`是运行时可变的动态数组,灵活通用,是现代C++处理可变序列的首选。

不会C++、害怕嵌入式且不爱看动漫的小趴菜 1万+

用C语言实现动态增长的栈:从原理到代码详解

/ 动态数组存储元素int _top;// 栈顶指针(指向下一个空闲位置)// 当前容量} Stack;_a:动态数组指针,存放栈元素。_top:栈顶指针,初始为0。当插入一个元素时,_top指向下一个空闲位置。_capacity:数组的当前容量,动态调整以支持栈的扩容。动态扩容:避免固定容量的限制。高效操作:入栈、出栈均摊时间复杂度为O(1)。

2501_90200491的博客 1280

数据结构·栈和队列

在学习C语言的时候我们可以了解到,局部变量是在栈中创建的,动态开辟的空间是在堆上开辟的,用了static关键字的变量是在静态区创建的,我们在学习数据结构的时候同样会涉及到栈和堆的概念,但是此栈非彼栈,堆也是,因为这是两门不同的学科。局部变量在栈中创建等是隶属于操作系统的知识,而今天介绍的栈和队列,是隶属于数据结构的内容,他们是一种结构,比如栈,是后进先出的结构(Last in first out),即LIFO,队列是相反的,是先进先出的结构(First in First out),即FIFO。

更新各种计算机技能~ 1580

四大基础数据结构:数组、链表、队列和栈

四大基础数据结构:数组、链表、队列和栈1、时间复杂度(大O表示法)2、数组3. 链表合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 1、时间复杂度(大O表示法) 衡量时间复杂度与空间复杂度的计数表示,即 衡量算法时间长与不长、占用内存多与多

weixin_46585151的博客 3635

Zerox 视觉模型 OCR 实战:从美国护照信息页到结构化 Markdown 的字段级还原与源码原理

本篇文章以 Zerox 开源仓库中一份真实运行产物为切入点——[shared/outputs/0038.md](https://link.gitcode.com/i/6284b9415f779e6a0c9ac9e9cc86a9ed) 是视觉模型对 [shared/inputs/0038.png](https://link.gitcode.com/i/c7b3668dd1af67246f6b301f

gitblog_00920的博客 759

数据结构基础:动态数组、栈和队列

数据结构基础:动态数组、栈和队列一、 数据结构基础二、 数组1. 概念 一、 数据结构基础 数据结构研究的是数据如何在计算机中进行组织和存储,使得我们可以高效的获取数据或者修改数据。在内存世界“增删改查”。 例如: 线性结构: 数组:栈 队列:链表 哈希表…… 树结构: 二叉树:二分搜索树 AVL:红黑树、Treap、Splay 堆;Trie;线段树;K-D树 并查集;哈夫曼树…… 图结构: 邻接矩阵 邻接表 二、 数组 1. 概念 把数据码成一排进行存放 ...

Aurorat的博客 483
上一篇: 数据结构入门:顺序表的原理与应用
下一篇: 二叉树入门:从零掌握数据结构
CCCCCCCCharlie
博客等级 码龄1年 2粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值