顺序表和单向链表

一、顺序表

(1)基本概念

概念:顺序存储的线性表

图解:

说明:

在C语言的环境下,将数据存储到一片连续的内存钟,在C语言的环境下,可以是具名的栈数组,或者是匿名的对数组(因为可存储的空间大),存储方式不仅仅只是提供数据的存储空间,二十必须要能体现数据之间的逻辑关系,当采用顺序存储的方式来存放数据是,唯一能够用来表达数据间本身的逻辑关系,当采用顺序存储的方式来存放数据时,唯一能够用来表达数据间本身逻辑关系就是存储位置,比如队列中的两个人:

(2)顺序表的设计

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

2、初始化顺序表

3、增删查改顺序表

        // 前提:判断顺序表是否满了或者空了

        a、删:销毁顺序表、将顺序表指定位置的数据删除

        b、增:向顺序表中的表头插入一个数据

        c、查:遍历顺序表 d、改:根据位置修改顺序表中的数据

1、管理结构体设计

说明:(结构体里面的数据)

        顺序表的总容量

        顺序表的当前最末数据的下标位置

        指向顺序表内存(堆内存)的指针

图解:

示例代码:

typedef struct sq_list
{
    int capacity;   // 顺序表的容量
    int index;      // 顺序表的数据下标(最末尾数据的下标)
    int *data_p;    // 指向顺序表内存的指针
}sq_list_t, *sq_list_p;

2、初始化顺序表

说明:所谓初始化就是建立一个不包含任何元素的顺序表,设置好管理结构体中的表的总容量、末数据下标、申请号顺序表内存空间等准备工作

图解:

示例代码:

/**
 * @brief  初始化顺序表
 * @note   None
 * @param  cap_size:顺序表的容量
 * @retval 成功:返回顺序表的管理结构体的指针
 *         失败:返回NULL
*/

sq_list_p SEQUENTIAL_LIST_Init(unsigned int cap_size)
{
    //1、给顺序表管理结构体申请一个堆内存空间
    sq_list_p p = malloc(sizeof(sq_list_t));
    bzero(p, sizeof(sq_list_t));

    //2、给申请的空间进行赋值操作
    if (p != NULL)
    {
        p->capacity = cap_size;                         //顺序表的内存空间的容量(有多少个数据)
        p->index    = -1;                               //顺序表的数据下表(最末尾数据的下表) --- 一开时没有数据存入,所以是-1(0的话是第一个数据)
        p->data_p   = malloc(sizeof(int)*cap_size);     //指向顺序表内存的指针(申请一个堆空间,并让指针指向它)
        if (p->data_p == NULL)                          //判断p->data_p是否申请堆空间失败,如果失败,就将管理结构体空间释放并返回NULL
        {
            free(p);
            return NULL;
        }
    }
    else                                                //判断p是否申请堆空间失败,如果失败,就返回NULL
    {
        return NULL;
    }
    
    //3、返回管理结构体的指针
    return p;
}

3、销毁顺序表

说明:一个顺序表后面如果不再使用,应当要释放其所占用的内存空间,这被成为顺序表的销毁。

 图解: 

示例代码:

/**
 * @brief  销毁顺序表
 * @note   None
 * @param  p:顺序表管理结构体的指针(通过它来将整个顺序表全部销毁)
 * @retval None
*/

void SEQUENTIAL_LIST_UnInit(sq_list_p p)
{
    //1、如果顺序表本身就为NULL,就不必继续下面的内容
    if (p == NULL)
    {
        return;
    }

    //2、释放堆内存空间(由内到外)
    free(p->data_p);
    free(p);
}

4、判断顺序表是否为满或者空

说明:需要先判断顺序表是否满了,这样可以决定是否可以增加数据,判断其是否为空,这样可以决定是否还需要删除数据

图解: 

示例代码:

/**
 * @brief  判断顺序表数据是否满了
 * @note   None
 * @param  p:顺序表管理结构体的指针
 * @retval 顺序表数据满了:返回true
 *         顺序表数据未满:返回false
*/
bool SEQUENTIAL_LIST_IfFull(sq_list_p p)
{
    return p->index == p->capacity-1;
}
/**
 * @brief  判断顺序表数据是否空了
 * @note   None
 * @param  p:顺序表管理结构体的指针
 * @retval 顺序表数据为空:返回true
 *         顺序表数据为空:返回false
*/
bool SEQUENTIAL_LIST_IfEmpty(sq_list_p p)
{
    return p->index == -1;
}

5、插入数据

说明: 当我们将数据插入到表头的时候,顺序表后面的数据依次向后退

图解: 

示例代码:

/**
 * @brief  在顺序表的表头插入一个数据(头插法)
 * @note   None
 * @param  p:        顺序表管理结构体的指针
 *         new _data:要插入的数据
 * @retval 成功:返回0
 *         失败:返回-1
*/

int SEQUENTIAL_LIST_InserData(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++;

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

6、删除数据

 说明:根据顺序表的位置,将其数据删除(将要删除的数据的后面的一个数据全部往前移动一个位置)

 图解:

 示例代码:

/**
 * @brief  将顺序表指定的位置的数据删除
 * @note   None
 * @param  p:       顺序表管理结构体的指针
 *         data_pos:要删除的顺序表数据的位置
 * @retval 成功:返回0
 *         失败:返回-1
*/

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;
    }

    //2、根据要删除的数据的位置,将其后面的数据全部往前挪动一个位置即可
    for (int i = data_pos; i <= p->index; i++)
    {
        p->data_p[i] = p->data_p[i+1];
    }
    
    //3、将下标index-1
    p->index--;

    //4、成功删除数据,返回0
    return 0;
}

7、遍历数据

 说明:获取顺序表里面现在的存有的数据

 图解:

 示例代码:

/**
 * @brief  查看顺序表
 * @note   None
 * @param  p:       顺序表管理结构体的指针
 * @retval None
*/

void SEQUENTIAL_LIST_ShowList(sq_list_p p)
{
    printf("====================顺序里面的数据===================\n");
    for (int i = 0; i <= p->index; i++)
    {
        printf("顺序表里面的内存的数据data_p[%d] == %d\n", i, p->data_p[i]);
    }
    printf("====================================================\n");
}

8、修改数据

 说明:根据位置来修改顺序中的数据

 图解:

 示例代码:

/**
 * @brief  修改顺序表中的数据
 * @note   根据数据的位置来修改数据
 * @param  p:          顺序表管理结构体的指针
 *         data_pos:   要修改的顺序表数据的位置
 *         change_data:修改后的数据
 * @retval 成功:返回0
 *         失败:返回-1
*/

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;
}

(3)顺序表的使用

/**
  ******************************************************************************
  * @file    main.c
  * @author  Yi_An忆安
  * @version V0.0.1
  * @date    2025.09.1
  * @brief   使用顺序表实现数据的增删查改功能
  *          环境:ubuntu22.04
  *          编译:make
  *          执行:make run
  *          说明:make help
  * 
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  微信公众号:    梦入长安
  *  有疑问或者建议:15778632984@163.com
  * 
  ******************************************************************************
**/


#include "../Include/sequential_list.h"

int main(int argc, char const *argv[])
{
  //1、定义一个栈数组buf(数据再堆内存中)
  int buf[128] = {0};
  
  //2、定义一个指针buf_p指向堆空间的内存
  char *buf_p = malloc(128);

  //3、初始化顺序表
  sq_list_p p = SEQUENTIAL_LIST_Init(128);
  if (p == NULL)
  {
    printf("初始化顺序表失败!\n");
    return -1;
  }
  
  //4、选择功能(增、删、查、改)
  int select          = 0;
  int add_new_data    = 0;
  int del_data_pos    = 0;
  int change_data_pos = 0;
  int change_data     = 0;


  while (1)
  {
    //(1)显示整个顺序表的数据
    SEQUENTIAL_LIST_ShowList(p);

    //(2)、显示功能选项
    printf("请选择需要使用的功能:\n");
    printf("1、增加数据\n");
    printf("2、删除数据\n");
    printf("3、修改数据\n");
    printf("4、退出\n");


    //(3)选择需要的功能
    scanf("%d", &select);
    while(getchar()!='\n');

    switch (select)
    {
      case 1:    
          printf("请输入要插入的数据");
          scanf("%d", &add_new_data);
          while (getchar() != '\n');
          //添加数据到顺序表表头中
          SEQUENTIAL_LIST_InserData(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);C
          printf("系统已退出!\n");
          goto exit_sys_label;
          break;
    }

  }
exit_sys_label:
  return 0;
}

 注意:本代码的编译和执行需要处在以下工程文件当中,否则只需要正常使用gcc命令执行即可

二、单向链表(单链表)

(1)基本概念

概念:链式存储的线性表,简称链表(线性关系+链式存储)

图解:

说明:既然顺序存储中的数据因为挤在一起而导致需要成片移动,那种很容易想到的解决方案是将数据离散的存储在不同的内存块中,然后用指针将它们串起来,这种朴素的思路所形成的链表式线性表,就是所谓链表(单向链表(单向循环链表)、双向链表(双向循环链表))

(2)单向链表的设计

1、节点设计

2、初始化空单向链表(初始化头节点)

3、初始化数据节点

4、增删查改单向链表 // 前提:判断链表是否为空

        a、增:头插法、尾插法

        b、查:遍历链表

        c、删:删除链表中的某个数据、销毁整个链表

        d、改:修改链表中的某个数据

1、节点设计

说明:单向链表的节点非常简单,节点除了要保存的用户数据(数据域)之外(这里以整型数据为例),只需要增加一个指向本类节点的指针(指针域)即可

示例代码:

typedef struct node
{
    // 数据域
    int data;               // 数据可以是任意数据

    // 指针域
    struct node* next_p;    // 指向相邻的下一个节点的指针

}node_t, *node_p;

2、初始化空单向链表(初始化头节点)

概念:首先,链表有两种常见的形式,一种是带有头节点的,一种是不带头节点的,所谓的头节点是不存放有效数据的节点,仅仅用来方便我们操作

图解1:

注意:

        头节点的head_node是可选的,为了方便某些操作,建议大家有头节点好一点

        头节点的指针:head_node的next_p

说明:

         由于头节点不存放有效数据,因此如果空链表中带有头节点,那么头指针(hear_node)的

         next_p将永远不会变,这会给以后的链表操作带来些许便捷 

图解2:

示例代码:

/**
 * @brief  初始化空单向链表(初始化头节点)
 * @note   None
 * @param  None
 * @retval 成功:返回指向这个头节点的指针
 *         失败:返回NULL
*/
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;
    }

    // 3、成功返回头节点
    return p;
}

3、初始化数据节点

说明:这是一个有数据的节点

图解:

 示例代码:

/**
 * @brief  初始化数据节点
 * @note   None
 * @param  data:数据节点中的数据
 * @retval 成功:返回指向这个数据节点的指针
 *         失败:返回NULL
*/

node_p LINK_LIST_InitDateNode(int data)
{
    // 1、给数据节点申请一个内存空间(堆内存)
    node_p p = malloc(sizeof(node_t));
    bezero(p, sizeof(node_t));

    // 2、将数据节点的next_p指针指向NULL,并且将传进来的数据对此节点的数据进行赋值
    if (p != NULL)
    {
        // 数据域
        p->data = data;     //数据赋值

        // 指针域
        p->next_p = NULL;   //防止指针乱指C
    }
    else
    {
        return NULL;
    }

    // 3、成功返回数据节点
    return p;
}

4、插入数据

说明:将数据节点插入到链表中,一种头插法(链表中的头节点的后面插进去),一种尾插法(链表中的最后一个节点后面插入进去)

头插法图解:

 头插法示例代码:

/**
 * @brief  插入数据(头插法)
 * @note   None
 * @param  head_node:头节点    
 *         new_node: 要插入的数据节点
 * @retval None
*/

void LINK_LIST_HeadInsertDataNode(node_p head_node, node_p new_node)
{
    //1、先让new_node里面的next_p指向data_node(head_node->next_p)
    new_node->next_p = head_node->next_p;

    //2、再让head_node里面的next_p指向new_node
    head_node->next_p =new_node;
}

 尾插法图解:

 尾插法示例代码:

/**
 * @brief  插入数据(尾插法)
 * @note   None
 * @param  head_node:头节点    
 *         new_node: 要插入的数据节点
 * @retval None
*/

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;
    }
    
}

5、判断链表是否为空

说明:如果头节点的next_p指向NULL,那么就可以证明这个链表是空的

图解:

示例代码:

/**
 * @brief  判断链表是否为空
 * @note   None
 * @param  head_node:头节点    
 * @retval 如果链表为空:返回true
 *         如果链表非空:返回false
*/
bool LINK_LIST_IfEmpty(node_p head_node)
{
    return head_node->next_p == NULL;
}

6、遍历链表

 说明:遍历整个链表,并逐个打印里面的数据

图解:

示例代码:

/**
 * @brief  遍历整个链表并打印出里面的数据
 * @note   None
 * @param  head_node:头节点    
 * @retval 成功:返回0
 *         失败:返回-1
*/
int LINK_LIST_ShowListData(node_p head_node)
{
    // 1、判断链表是否为空,是的话,返回-1
    if (LINK_LIST_IfEmpty(head_node))
        return -1;

    // 2、遍历整个链表,并逐个打印里面的数据
    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++;
    }
    printf("===========================================================\n");

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

7、删除数据

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

图解:

示例代码:

/**
 * @brief  删除数据
 * @note   根据数据的值找到链表中的节点,并将其删除
 * @param  head_node:头节点  
 *         del_data: 要删除的数据   
 * @retval 成功:返回0
 *         失败:返回-1
*/
int LINK_LIST_DelDataNode(node_p head_node, int del_data)
{
    // 1、判断链表是否为空,是的话,返回-1
    if (LINK_LIST_IfEmpty(head_node))
        return -1;

    // 2、移动到要删除的节点数据那里去
    node_p tmp_p     = NULL;
    node_p last_node = NULL;
    node_p del_node  = NULL;
    node_p next_node = NULL;
    
    // a、从头到尾遍历一遍,找到要删除的数据
    for (tmp_p = head_node; tmp_p->next_p!=NULL; tmp_p=tmp_p->next_p)
    {
        // b、判断要删除的数据
        if ( (tmp_p->next_p->data) == del_data)
        {
            // c、保存删除数据的上一个节点和下一个节点、及删除节点
            last_node = tmp_p;
            del_node  = tmp_p->next_p;
            next_node = del_node->next_p; 
            break;
        }
    }

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

    // 4、释放要删除节点的资源
    free(del_node);
    
    // 5、成功返回0
    return 0;

}

8、销毁整个链表

 说明:从头结点开始,将其后面的数据节点一个个删除,最后再删除头节点本身

图解:

示例代码:

/**
 * @brief  销毁链表
 * @note   None
 * @param  head_node:头节点    
 * @retval None
*/
void LINK_LIST_UnInit(node_p head_node)
{
    // 1、如果链表为空,那么直接释放头节点空间即可
    if (LINK_LIST_IfEmpty(head_node))
    {
        free(head_node);
        return;
    }

    // 2、
    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);
    }

    // 3、释放头节点
    free(head_node);  
}

9、销毁链表

 说明:通过数据值,找到链表中的节点,并修改里面的数据

图解:

示例代码:

/**
 * @brief  修改数据
 * @note   根据数据的值来找到链表中的节点,对里面的数据进行修改
 * @param  head_node:  头节点  
 *         data:       要修改的数据
 *         change_data:修改的数据
 * @retval 成功:返回0
 *         失败:返回-1
*/
int LINK_LIST_ChangeNodeData(node_p head_node, int data, int change_data)
{
    // 1、如果链表为空,那么直接释放头节点空间即可
    if (LINK_LIST_IfEmpty(head_node))
        return -1;
    
    // 2、遍历整个链表,找到数据的节点,并修该节点的相应的数据
    node_p tmp_p = NULL;

    // a、遍历所有可能
    for (tmp_p = head_node->next_p; tmp_p!=NULL; tmp_p=tmp_p->next_p)
    {
        // b、找到要修改的数据
        if ((tmp_p->data) == data)      // 根据学号
        {
            tmp_p->data = change_data;  // 修改这个学生的其它信息
            break;
        }   
    }

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

(3)单向链表的使用

/**
  ******************************************************************************
  * @file    main.c
  * @author  Yi_An忆安
  * @version V0.0.1
  * @date    2025.09.5
  * @brief   使用单向链表实现数据的增删查改功能
  *          环境:ubuntu22.04
  *          编译:make
  *          执行:make run
  *          说明:make help
  * 
  ******************************************************************************
  * @attention
  *
  *  本文档只供学习使用,不得商用,违者必究
  * 
  *  微信公众号:    梦入长安
  *  有疑问或者建议:15778632984@163.com
  * 
  ******************************************************************************
**/

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


    // (2)、选择功能(增删改查、退出(销毁链表))
    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)
    {
        // 1、显示整个链表的数据
        LINK_LIST_ShowListData(head_node);

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

        // 3、选择要做的功能
        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");

                // 通过学号(find_data),修改学生的其它信息(changge_data)
                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;
}

注意:本代码的编译和执行需要处在以下工程文件当中,否则只需要使用gcc命令执行即可

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值