顺序表与单链表:核心原理与实战应用

一、顺序表

(1)、基本概念

  • 概念:顺序存储的线性表(线性关系+顺序存储)
  • 图解:
  • 说明:就是将数据存储到一片连续的内存中,在C语言的环境下,可以是具名的栈数组,或者是匿名的堆数组(因为可存储的空间大),存储方式不仅仅只是提供数据的存储空间,而是必须要能体现数据之间的逻辑关系,当采用顺序存储的方式来存放数据 时,唯一能够用来表达数据间本身的逻辑关系就是存储位置(简单来说,顺序表是一种用连续的内存空间来存储数据元素的线性数据结构,它的逻辑顺序和物理顺序是完全一致的)。比如队列中的两个人:

(2)、顺序表的核心特点

1、优点:

  • 随机访问能力强 :这是它最大的优点。通过下标(索引)可以直接访问任何一个元素,时间复杂度是 O(1),速度非常快。

  • 存储密度高:因为它只存储数据元素本身,不需要像链表那样额外存储指针地址,所以空间利用率高。

2、缺点:

  • 插入和删除效率低 :这是它最大的缺点。假设要在第i个位置插入一个新元素,那么从第i个位置开始的所有元素都必须向后移动一位 ,腾出空间。删除则需要向前移动一位来填补空位。平均时间复杂度为 O(n)

  • 容量固定,扩容麻烦 :创建时需要预先指定大小。如果空间不够了,就需要申请一个更大的新空间,然后把所有数据逐个复制过去,非常耗时。

(3)、顺序表的设计

cpp 复制代码
1、顺序表的管理结构体设计
2、初始化顺序表
3、判断顺序表是否满了
4、判断顺序表是否为空了
5、增删查改顺序表
    a、删:销毁顺序表、将顺序表指定位置的数据删除
    b、增:向顺序表中的表头插入一个数据
    c、查:遍历顺序表
    d、改:根据位置修改顺序表中的数据

1、顺序表的管理结构体设计

  • 说明(结构体里面的数据):
    • 顺序表的总容量
    • 顺序表的当前最末数据的下标位置
    • 指向顺序表内存(堆内存)的指针
  • 图解
  • 示例代码
cpp 复制代码
typedef struct sq_list
{
    int capacity;   // 容量
    int index;      // 下标
    int *data_p;    // 指针
}sq_list_t, *sq_list_p;

2、初始化顺序表

  • 说明:所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中的表的总容量、末数据小标、申请好顺序表内存空间等系列准备工作
  • 图解:
  • 示例代码
cpp 复制代码
sq_list_p SEQUENTIAL_LIST_Init(unsigned int cap_size)
{
    sq_list_p p = malloc(sizeof(sq_list_t));
    bzero(p, sizeof(sq_list_t));

    if ( p!= NULL)
    {
        p->capacity = cap_size;                        
        p->index    = -1;                             
        p->data_p   = malloc(sizeof(int)*cap_size);     
        if (p->data_p == NULL)                         
        {
            free(p);
            return NULL;
        }
    }
    else                                             
        return NULL;
    }

    // 3、返回管理结构体的指针
    return p;
}

3、销毁顺序表

  • 说明:顺序表后面不再使用了,应当要释放其所占用的内存空间,这被称为顺序表的销毁
  • 图解:
  • 示例代码:
cpp 复制代码
void SEQUENTIAL_LIST_UnInit(sq_list_p p)
{
    if (p == NULL)
        return;

    free(p->data_p);
    free(p);
}

4、判断顺序表是否满了

  • 说明:需要先判断顺序表是否满了,这样可以决定是否还可以增加数据。
  • 图解:
  • 示例代码
cpp 复制代码
bool SEQUENTIAL_LIST_IfFull(sq_list_p p)
{
    return p->index == p->capacity-1;
}

5、判断顺序表是否为空了

  • 说明:判断其是否空了,这样可以决定是否还需要删除数据。
  • 图解
  • 示例代码
cpp 复制代码
bool SEQUENTIAL_LIST_IfEmpty(sq_list_p p)
{
    return p->index == -1;
}

6、增加顺序表数据

  • 说明:当我们将数据插入到表头的时候,顺序表后面的数据依次向后退
  • 图解
  • 示例代码
cpp 复制代码
int SEQUENTIAL_LIST_InsertData(sq_list_p p, int new_data)
{
    // 1、判断顺序表数据是否满了,满了返回-1
    if (SEQUENTIAL_LIST_IfFull(p))
        return -1;
    
    // 2、将原有的数据全部往后挪一个位置,再将数据插入到表头中
    for (int i = p->index; i>=0; i--)
    {
        p->data_p[i+1] = p->data_p[i];
    }
    
    // 3、将数据插入到表头
    p->data_p[0] = new_data;

    // 4、顺序表的数据下标index+1
    p->index++;

    return 0;
}

7、删除数据

  • 说明:根据顺序表的位置,将其数据删除(将要删除的数据的后面的数据全部往前挪一个位置)
  • 图解:
  • 示例代码
cpp 复制代码
int SEQUENTIAL_LIST_DelPosData(sq_list_p p, unsigned int data_pos)
{
    // 1、如果顺序表里面没有数据,并且没有这个位置的数据,就返回-1
    if ( (SEQUENTIAL_LIST_IfEmpty(p)) || (data_pos>(p->index)) ) 
        return -1;

    for (int i = data_pos; i<=p->index; i++)
    {
        p->data_p[i] = p->data_p[i+1];
    }

    p->index--;

    return 0; 
}

8、修改数据

  • 说明:根据位置来修改顺序表中的数据
  • 图解:
  • 示例代码
cpp 复制代码
int SEQUENTIAL_LIST_ChangeData(sq_list_p p, int data_pos, int change_data)
{
    // 1、判断顺序表是否为空
    if ( (SEQUENTIAL_LIST_IfEmpty(p)) || (data_pos>(p->index)))
        return -1;

    // 2、根据位置,修改数据
    p->data_p[data_pos] = change_data;

    // 3、成功返回0
    return 0;    
}

9、遍历数据

  • 说明:获取顺序表里面现在的存有的数据
  • 图解:
  • 示例代码
cpp 复制代码
void SEQUENTIAL_LIST_ShowList(sq_list_p p)
{
    printf("==================顺序表里面的数据====================\n\n");
    for (int i = 0; i<=p->index; i++)
       printf("顺序表里面的内存的数据data_p[%d] == %d\n", i, p->data_p[i]);


}

10、顺序表的使用

cpp 复制代码
/**
  ******************************************************************************
  * @file    main.c
  * @author  MChine慕青
  * @version V0.0.1
  * @date    2025.09.04
  * @brief   使用顺序表实现数据的增删查改功能
  *          环境:ubuntu16.04
  *          编译:gcc main.c sequential_list.c
  *          执行:./a.out
  * 
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */
#include "sequential_list.h"

// 主函数
int main(int argc, char const *argv[])
{
    // (1)、栈数组(数据在栈内存中)
    int buf[128] = {0};
    
    // (2)、堆数组(数据在堆内存中)
    char *buf_p = malloc(128);
    
    // (1)、初始化顺序表
    sq_list_p p = SEQUENTIAL_LIST_Init(128);
    if ( p == NULL)
    {
        printf("初始化顺序表失败!\n");
        return -1;
    }
    
    // (2)、选择功能(增删查改、退出(销毁顺序表))
    int select            = 0;
    int add_new_data      = 0;
    int del_data_pos      = 0;
    int change_data_pos   = 0;
    int change_data       = 0;

    while (1)
    {
        //遍历
        SEQUENTIAL_LIST_ShowList(p);

        printf("请选择一下功能:\n");
        printf("1、增加数据\n");
        printf("2、删除数据\n");
        printf("3、修改数据\n");
        printf("4、退出\n");

        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                // 提示
                printf("请输入要插入的数据:\n");
                scanf("%d", &add_new_data);
                while(getchar()!='\n');

                // 添加数据到顺序表表头中
                SEQUENTIAL_LIST_InsertData(p, add_new_data);
                break;
            
            case 2:
                // 提示
                printf("请输入要删除的数据的位置:\n");
                scanf("%d", &del_data_pos);
                while(getchar()!='\n');
                
                // 根据位置删除顺序表中的数据
                SEQUENTIAL_LIST_DelPosData(p, del_data_pos);
                break;

            case 3:
                // 提示
                printf("请输入要修改的数据的位置和数据:\n");
                scanf("%d,%d", &change_data_pos, &change_data);
                while(getchar()!='\n');

                // 修改顺序表中的数据
                SEQUENTIAL_LIST_ChangeData(p, change_data_pos, change_data);
                break;
                
            case 4:
                // 销毁顺序表
                SEQUENTIAL_LIST_UnInit(p);
                
                // 提示
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;
        }
    }
exit_sys_label:
    return 0;
}

二、单向链表(单链表)

(1)、基本概念:

单链表是一种线性数据结构 ,它不像数组那样在内存中占据连续的空间,而是通过"指针"将一组零散的内存块串联起来使用。

  • 图解

(2)、特点:

  • 非连续存储:节点在内存中可以是分散的,靠指针连接。这使得增加和删除节点非常高效,只需要修改指针指向即可,不需要移动大量数据。

  • 顺序访问 :你不能 像数组那样直接跳到第5个节点。你必须从头节点(Head) 开始,一个一个地通过 Next 指针往后找(这被称为"顺序访问")。查找效率相对较低。

  • 方向性:是"单"向的,只能从头到尾,不能从尾到头。

(3)、单向链表的设计

cpp 复制代码
1、节点设计
2、初始化空单向链表(初始化头节点)
3、初始化数据节点
4、判断链表是否为空
5、增删查改单向链表
    a、增:头插法、尾插法
    b、查:遍历链表
    c、删:删除链表中的某个数据、销毁整个链表
    d、改:修改链表中的某个数据
1、节点设计
  • 说明:单向链表的节点只需要包括数据域、指针域
  • 图解:
  • 示例代码
cpp 复制代码
typedef struct node
{
    // 数据域
    int data;              

    // 指针域
    struct node* next_p;    

}node_t, *node_p;
2、初始化空单向链表(初始化头节点)
  • 概念:首先链表有两种常见的形式,一种是带有所谓的头节点的(推荐),一种是不带头节点的,所谓的头节点是不存放有效数据的节点,仅仅用来方便操作
  • 图解
  • 说明:
    • 由于头节点不存放有效数据的,因此如果空链表中带有头节点,那么头指针(head_node的next_p)将永远不变,这会给以后的链表操作带来些许便捷
  • 图解:初始化头节点
  • 示例代码:
cpp 复制代码
node_p LINK_LIST_InitHeadNode(void)
{
    // 1、给头节点申请一个内存空间(堆内存)
    node_p p = malloc(sizeof(node_t));
    bzero(p, sizeof(node_t));

    // 2、将头节点的next_p指针指向NULL
    if ( p!= NULL)
    {
        // 指针域
        p->next_p = NULL;   // 防止指针乱指
    }
    else
    {
        return NULL;
    }
    
    return p;
}
3、初始化数据节点
  • 说明:初始化有数据的节点
  • 图解
  • 示例代码
cpp 复制代码
node_p LINK_LIST_InitDataNode(int data)
{
    // 1、给数据节点申请一个内存空间(堆内存)
    node_p p = malloc(sizeof(node_t));
    bzero(p, sizeof(node_t));

    if ( p != NULL)
    {
        // 数据域
        p->data   = data;   

        // 指针域
        p->next_p = NULL;   
    }
    else
    {
        return NULL;
    }

    return p;
    
}
4、判断链表是否为空
  • 说明:头节点的next_p指向的是NULL
  • 图解
  • 示例代码
cpp 复制代码
bool LINK_LIST_IfEmpty(node_p head_node)
{
    return head_node->next_p == NULL;
}
5、插入数据
a、头插法
  • 说明:链表中的头节点的后面插进去
  • 图解
  • 示例代码:
cpp 复制代码
void LINK_LIST_InsertHeadDataNode(node_p head_node, node_p new_node)
{
    // 1、先让new_node里面的next_p指向data_node
    new_node->next_p = head_node->next_p;

    // 2、再让head_node里面的next_p指向new_node
    head_node->next_p = new_node;
}
b、尾插法
  • 说明:链表中的最后一个节点后面插入进去
  • 图解
  • 示例代码
cpp 复制代码
void LINK_LIST_TailInsertDataNode(node_p head_node, node_p new_node)
{
    // 1、设置一个中间指针,将指针指向单向链表的末尾节点
    node_p tmp_p = NULL;
    for (tmp_p = head_node; tmp_p->next_p != NULL; tmp_p = tmp_p->next_p);

    // 2、让tmp_p的next_p指向新节点
    tmp_p->next_p = new_node;

    // 3、再让new_node的next_p指向NULL
    new_node->next_p = NULL;
}
6、删除数据

说明:根据数据来删除链表中的节点

图解:

  • 示例代码
cpp 复制代码
int LINK_LIST_DelDataNode(node_p head_node, int del_data)
{
    // 1、判断链表是否为空,是的话,返回-1
    if (LINK_LIST_IfEmpty(head_node))
        return -1;

    node_p tmp_p     = NULL;
    node_p last_node = NULL;
    node_p del_node  = NULL;
    node_p next_node = NULL;
    
    for (tmp_p = head_node; tmp_p->next_p!=NULL; tmp_p=tmp_p->next_p)
    {
        // 判断要删除的数据
        if ( (tmp_p->next_p->data) == del_data)
        {
            last_node = tmp_p;
            del_node  = tmp_p->next_p;
            next_node = del_node->next_p; 
            break;
        }
    }

    // 绕过原链表要删除的节点
    last_node->next_p = next_node;
    del_node->next_p  = NULL;

    free(del_node);
    
    return 0;

}
7、修改数据
  • 说明:通过数据值,找到链表中的节点,并修改里面的数据
  • 图解:
  • 示例代码
cpp 复制代码
int LINK_LIST_ChangeNodeData(node_p head_node, int data, int change_data)
{
    // 如果链表为空,那么直接释放头节点空间即可
    if (LINK_LIST_IfEmpty(head_node))
        return -1;
    
    node_p tmp_p = NULL;

    // 遍历所有可能
    for (tmp_p = head_node->next_p; tmp_p!=NULL; tmp_p=tmp_p->next_p)
    {
        // 找到要修改的数据
        if ((tmp_p->data) == data)      
        {
            tmp_p->data = change_data;  
            break;
        }   
    }

    return 0;
}
8、遍历数据
  • 说明:遍历整个链表,并逐个打印里面的数据
  • 图解:
  • 示例代码:
cpp 复制代码
int LINK_LIST_ShowListData(node_p head_node)
{
    // 判断链表是否为空
    if (LINK_LIST_IfEmpty(head_node))
        return -1;

    node_p tmp_p = NULL;
    int    i     = 0;

    printf("=====================链表中的数据========================\n");
    for ( tmp_p = head_node->next_p; tmp_p!=NULL; tmp_p=tmp_p->next_p)
    {
        printf("链表中的第%d的节点, 数据为: %d\n", i, tmp_p->data);
        i++;
    }
  
    return 0;
}
9、销毁整个链表
  • 说明
  • 图解:
  • 示例代码
cpp 复制代码
void LINK_LIST_UnInit(node_p head_node)
{
    // 若是链表为空,就直接释放头节点空间
    if (LINK_LIST_IfEmpty(head_node))
    {
        free(head_node);
        return;
    }

    node_p tmp_p      = NULL;               // 遍历
    node_p last_node  = head_node;
    node_p del_node   = head_node->next_p;
    node_p next_node  = del_node->next_p;

    int num = 0;
    // 如果链表只有一个数据节点
    if (next_node == NULL)
    {
        last_node->next_p = next_node;
        free(del_node);
    }
    else
    {
        for (tmp_p = head_node; tmp_p->next_p!=NULL; tmp_p=tmp_p->next_p)
        {
            // a、删除节点并释放其内存
            last_node->next_p = next_node;
            free(del_node);
            printf("num == %d\n", num);
            num++;

            // b、轮回继续
            del_node  = next_node;
            next_node = next_node->next_p;
        }
        
        // 少删除了一个节点数据
        last_node->next_p = next_node;
        free(del_node);
    }

    free(head_node);  
}

(4)、单向链表的使用

cpp 复制代码
/**
  ******************************************************************************
  * @file    main.c
  * @author  MChine慕青
  * @version V0.0.1
  * @date    2025.09.05
  * @brief   使用单向链表实现数据的增删查改功能
  *          环境:ubuntu22.04
  *          编译:gcc main.c link_list.c
  *          执行:./a.out
  * 
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  有疑问或者建议:3211735057@qq.com
  * 
  ******************************************************************************
  */

#include "link_list.h"
int main(int argc, char const *argv[])
{
    // 初始化头节点
    node_p head_node = LINK_LIST_InitHeadNode();
    if (head_node == NULL)
    {
        printf("初始化空链表(头节点)失败!\n");
        return -1;
    }


    // 选择功能
    node_p new_node = NULL;
    int select      = 0;
    int new_data    = 0;
    int del_data    = 0;
    int find_data   = 0;   
    int change_data = 0;   

    while (1)
    {
        // 遍历
        LINK_LIST_ShowListData(head_node);

        printf("请选择以下功能:\n");
        printf("1、插入数据(头插法)\n");
        printf("2、插入数据(尾插法)\n");
        printf("3、删除数据(根据数据值)\n");
        printf("4、修改数据(根据数据值)\n");
        printf("5、退出(销毁链表)\n");

        scanf("%d", &select);
        while(getchar()!='\n');

        switch (select)
        {
            case 1:
                printf("请输入要添加的数据(整数)(头插法)\n");
                
                // 输入要插入的数据
                scanf("%d", &new_data);
                while(getchar()!='\n');

                new_node = LINK_LIST_InitDataNode(new_data);

                // 头插
                LINK_LIST_HeadInsertDataNode(head_node, new_node);

                break;

            case 2:
                printf("请输入要添加的数据(整数)(尾插法)\n");

                scanf("%d", &new_data);
                while(getchar()!='\n');

                new_node = LINK_LIST_InitDataNode(new_data);

                // 尾插法
                LINK_LIST_TailInsertDataNode(head_node, new_node);
                break;

           case 3:
                printf("请输入要删除的数据(整数)(根据数据值)\n");

                scanf("%d", &del_data);
                while(getchar()!='\n');

                // 从头到尾删除
                LINK_LIST_DelDataNode(head_node, del_data);
                break;

            case 4:
                printf("请输入要修改的数据(整数)(根据数据值)\n");

                scanf("%d,%d", &find_data, &change_data);
                while(getchar()!='\n');

                LINK_LIST_ChangeNodeData(head_node, find_data, change_data);
                break;

           case 5:
                 LINK_LIST_UnInit(head_node);
                printf("系统已退出!\n");
                goto exit_sys_label;
                break;

        }
    }
        
exit_sys_label:
    return 0;
}

三、顺序表与单链表的比较总结

顺序表和单链表是数据结构中最基础的两种线性表实现方式,各自具有独特的优缺点和适用场景。

顺序表通过连续内存存储数据,支持随机访问且缓存命中率高,适合频繁查询或元素数量固定的场景。单链表通过节点动态分配内存,插入删除效率高且无需预分配空间,适合动态数据操作或内存受限的环境。

实际开发中,需根据具体需求选择合适的数据结构。若注重查询性能且数据规模稳定,顺序表更优;若需要频繁增删或数据规模变化大,单链表更合适。理解二者的核心原理和性能差异,能帮助开发者优化算法设计,提升程序效率。

掌握这两种结构的实现与应用,是进一步学习栈、队列、树等高级数据结构的重要基础。

相关推荐
qq_195551692 小时前
代码随想录70期day7
java·开发语言
虎头金猫3 小时前
如何在Linux上使用Docker在本地部署开源PDF工具Stirling PDF:StirlingPDF+cpolar让专业操作像在线文档一样简单
linux·运维·ubuntu·docker·pdf·开源·centos
塔中妖3 小时前
【华为OD】查找接口成功率最优时间段
算法·链表·华为od
塔中妖3 小时前
【华为OD】最大子矩阵和
算法·华为od·矩阵
努力学习的小廉4 小时前
深入了解linux系统—— 线程同步
linux·服务器·数据库·算法
数据爬坡ing4 小时前
从挑西瓜到树回归:用生活智慧理解机器学习算法
数据结构·深度学习·算法·决策树·机器学习
luoganttcc4 小时前
小鹏汽车 vla 算法最新进展和模型结构细节
人工智能·算法·汽车
sinat_602035364 小时前
模块与包的导入
运维·服务器·开发语言·python
恋雨QAQ4 小时前
python函数和面向对象
开发语言·python