数据结构(1)数据结构基础(单向链表)

2024-09-04 05:20

本文主要是介绍数据结构(1)数据结构基础(单向链表),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、什么是数据结构

        数据结构是一组用来保存一种或多种特定关系的数据的集合。其主要目的是组织和存储数据,以便于高效的访问和修改。在程序设计中,将大量而复杂的问题以特定的数据类型和特定的存储结构存储在内存中,并在此基础上实现某个特定的功能的操作,即:

                                程序 = 数据结构 + 算法

二、 数据与数据之间的关系

2.1 数据的逻辑结构
  • 集合:数据元素之间关系平等,常用于处理无关元素的组。
  • 线性结构:元素之间存在一对一的关系,常见的有数组、链表、队列、栈等。
  • 树型结构:元素之间存在一对多的关系,典型的如二叉树。
  • 图形结构:元素之间存在多对多的关系,形成网络状的结构。
2.2 数据的物理结构

数据的逻辑结构在计算机内存中的存储方式是其物理结构,主要包括以下几种存储方式:

  • 顺序存储
    • 利用一段连续的内存空间来保存元素。
    • 优点
      • 空间连续,访问方便(如随机访问)。
    • 缺点
      • 插入与删除操作需要移动大量元素。
      • 需要预分配内存空间。
      • 容易造成存储空间碎片。
  • 链式存储
    • 使用非连续的内存空间保存元素。
    • 优点
      • 插入和删除数据方便。
      • 不需要预分配内存。
    • 缺点
      • 访问元素的效率低。
  • 索引存储
    • 通过关键字构建索引表,通过该索引表找到数据的存储位置。
  • 散列存储(哈希存储)
    • 将数据元素的存储位置与关键码之间建立确定对应关系,以实现快速查找。

三、储备知识

  • 指针:用于存储其他变量的地址,是链式存储结构的关键。
  • 结构体:在编程中,常用结构体定义数据节点,便于管理数据。
  • 动态内存分配:为数据结构中的节点动态分配和释放内存。

四、 链式数据结构:单向链表

4.1 定义

        单向链表是一种线性数据结构,由一系列节点构成,每个节点包含数据部分和指向下一个节点的指针。它是一种灵活的存储方式,相比于数组,单向链表不需要连续的内存空间。

4.2 结构
  • 节点(Node)

    • 数据部分:存储实际的数据。
    • 指针部分:指向下一个节点的指针。
  • 链表头(Head)

    • 链表的起始节点,所有操作通常从此节点开始。
  • 尾节点(Tail)

    • 链表的最后一个节点,其指针部分为空,表示链表的结束。
4.3 主要操作
  • 插入操作

    • 在头部:创建新节点,将其指针指向当前头节点,然后更新头节点。
    • 在尾部:遍历找到最后节点,令其指向新节点,并将新节点的指针设为 null
    • 在指定位置:找到插入位置的前一节点,将新节点连接至该位置。
  • 删除操作

    • 删除头节点:更新头节点为当前头节点的下一个节点。
    • 删除尾节点:遍历找到倒数第二个节点,更新其指针为 null
    • 删除特定位置节点:找到前一节点,调整指针跳过待删除节点。
  • 查找操作

    • 从头节点开始遍历链表,逐个比较数据部分查找特定值。
4.4 优缺点
  • 优点
    • 动态存储,插入和删除方便。
  • 缺点
    • 存储开销大(每个节点额外的指针)。
    • 访问效率低(需要遍历)。

示例:

// 定义数据类型为整型  
typedef int Datatype;  // 定义链表节点结构  
typedef struct node  
{  Datatype data; // 节点存储的数据  struct node * pnext; // 指向下一个节点的指针  
} node_t;  // 定义链表结构  
typedef struct link  
{  struct node* phead; // 指向链表头节点的指针  int clen; // 链表中节点的数量  
} link_t;// 创建链表  
link_t* create_link()  
{  // 分配内存给链表结构  link_t * plink = malloc(sizeof(link_t));  if(NULL == plink )  {  perror("create link error"); // 内存分配失败  return NULL;  }  plink->phead = NULL; // 初始化头指针为空  plink->clen = 0; // 初始化链表长度为0  return plink; // 返回新创建的链表  
}  // 在链表头部插入新节点  
int push_into_link_head(link_t* plink, Datatype data)  
{  // 分配内存给新节点  node_t* pnode = malloc(sizeof(node_t));  if(NULL == pnode)  {  perror("push node error"); // 内存分配失败  return -1;  }  pnode->data = data; // 设置节点数据  pnode->pnext = plink->phead; // 新节点指向当前头节点  plink->phead = pnode; // 更新头节点为新节点  plink->clen++; // 增加链表长度  return 0; // 成功  
}  // 遍历并打印链表中的所有节点数据  
int traverse_print_link(link_t* plink)  
{  node_t* p = plink->phead; // 从头节点开始  while(p)  {  printf("%d ", p->data); // 打印节点数据  p = p->pnext; // 移动到下一个节点  }  printf("\n"); // 打印换行  return 0; // 成功  
}  // 在链表尾部插入新节点  
int push_into_link_tail(link_t * plink, Datatype data)  
{  // 分配内存给新节点  node_t * pnode = malloc(sizeof(node_t));  pnode->data = data; // 设置节点数据  pnode->pnext = NULL; // 新节点的下一个指针为空  if(plink->phead == NULL) // 如果链表为空  {  plink->phead =  pnode; // 头指针指向新节点  }  else  {  node_t* p = plink->phead; // 从头节点开始遍历  while(p->pnext) // 找到最后一个节点  {  p = p->pnext;  }  p->pnext = pnode; // 将最后一个节点的指针指向新节点  }  plink->clen++; // 增加链表长度  return 0; // 成功  
}  // 从链表头部删除节点  
int pop_into_link_head(link_t* plink)  
{  if(NULL == plink->phead) // 如果链表为空  {  return 0; // 无需删除  }  else  {  node_t *pnode = plink->phead; // 获取头节点  plink->phead = pnode->pnext; // 更新头节点为下一个节点  free(pnode); // 释放原头节点内存  }  plink->clen--; // 减少链表长度  return 0; // 成功  
}  // 从链表尾部删除节点  
int pop_into_link_tail(link_t* plink)  
{  if(NULL == plink->phead) // 如果链表为空  {  return 0; // 无需删除  }  else if(plink->phead->pnext == NULL) // 如果只有一个节点  {  pop_into_link_head(plink); // 删除头节点  }  else  {  node_t * pnode = plink->phead; // 从头节点开始遍历  while(pnode->pnext->pnext) // 找到倒数第二个节点  {  pnode = pnode->pnext;  }  free(pnode->pnext); // 释放最后一个节点内存  pnode->pnext = NULL; // 将倒数第二个节点的指针设为NULL  plink->clen--; // 减少链表长度  return 0; // 成功  }  return 0; // 成功  
}  // 在链表中查找特定数据的节点  
node_t * search(link_t* plink, Datatype data)  
{  node_t* pnode = plink->phead; // 从头节点开始  while(pnode)  {  if(pnode->data == data) // 如果找到数据  {  return pnode; // 返回该节点  }  pnode = pnode->pnext; // 移动到下一个节点  }  return NULL; // 未找到  
}  // 修改链表中指定数据的节点数据  
int change_link_data(link_t* plink, Datatype srcdata, Datatype destdata)  
{  node_t * node = search(plink, srcdata); // 查找源数据节点  node->data = destdata; // 修改节点数据  return 0; // 成功  
}  // 销毁链表,释放所有节点内存  
int destory_link(link_t* plink)  
{  while(plink->phead) // 当链表不为空  {  pop_into_link_head(plink); // 删除头节点  }  free(plink); // 释放链表结构内存  return 0; // 成功  
}  // 查找链表中的中间节点  
node_t * search_mid_node(link_t * plink)  
{  node_t * pnode = plink->phead; // 从头节点开始  if(NULL == pnode) // 如果链表为空  {  return NULL; // 返回NULL  }  if(plink->clen == 1) // 如果只有一个节点  {  return pnode; // 返回该节点  }  int i = 0;  while(pnode)  {  pnode = pnode->pnext; // 移动到下一个节点  i++;  if(i == (plink->clen) / 2) // 找到中间节点  {  break;  }  }  return pnode; // 返回中间节点  
}  // 从链表中查找倒数第number个节点  
node_t *search_countdown(link_t* plink, int number)  
{  node_t* pnode = plink->phead; // 从头节点开始  if(NULL == pnode) // 如果链表为空  {  return NULL; // 返回NULL  }  if(plink->clen == 1) // 如果只有一个节点  {  if(number == 1)  {  return pnode; // 返回该节点  }  return NULL; // 返回NULL  }  int i = 0;  while(pnode)  {  pnode = pnode->pnext; // 移动到下一个节点  i++;  if(i == (plink->clen) - number) // 找到倒数第number个节点  {  break;  }  }  return pnode; // 返回该节点  
}  // 删除指定位置的节点  
int pop_appointed_node(link_t* plink, int appointed)   
{  if (plink->phead == NULL) // 如果链表为空  {  return -1; // 返回错误  }  if (appointed < 1 || appointed > plink->clen) // 如果指定位置不合法  {  return -1;   }  node_t* pnode = plink->phead;    if (appointed == 1) // 如果删除的是头节点  {  pop_into_link_head(plink); // 删除头节点  return 0;  }  for (int i = 1; i < appointed - 1; i++) // 遍历到指定位置的前一个节点  {  pnode = pnode->pnext;   }  node_t* temp = pnode->pnext; // 获取要删除的节点  if (temp == NULL) // 如果要删除的节点不存在  {  return -1;  }  pnode->pnext = temp->pnext; // 将前一个节点的指针指向要删除节点的下一个节点  free(temp); // 释放要删除节点的内存  plink->clen--; // 减少链表长度  return 0; // 成功  
}

这篇关于数据结构(1)数据结构基础(单向链表)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1135105

相关文章

从基础到进阶详解Pandas时间数据处理指南

《从基础到进阶详解Pandas时间数据处理指南》Pandas构建了完整的时间数据处理生态,核心由四个基础类构成,Timestamp,DatetimeIndex,Period和Timedelta,下面我... 目录1. 时间数据类型与基础操作1.1 核心时间对象体系1.2 时间数据生成技巧2. 时间索引与数据

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

安装centos8设置基础软件仓库时出错的解决方案

《安装centos8设置基础软件仓库时出错的解决方案》:本文主要介绍安装centos8设置基础软件仓库时出错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录安装Centos8设置基础软件仓库时出错版本 8版本 8.2.200android4版本 javas

Linux基础命令@grep、wc、管道符的使用详解

《Linux基础命令@grep、wc、管道符的使用详解》:本文主要介绍Linux基础命令@grep、wc、管道符的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录grep概念语法作用演示一演示二演示三,带选项 -nwc概念语法作用wc,不带选项-c,统计字节数-

python操作redis基础

《python操作redis基础》Redis(RemoteDictionaryServer)是一个开源的、基于内存的键值对(Key-Value)存储系统,它通常用作数据库、缓存和消息代理,这篇文章... 目录1. Redis 简介2. 前提条件3. 安装 python Redis 客户端库4. 连接到 Re

SpringBoot基础框架详解

《SpringBoot基础框架详解》SpringBoot开发目的是为了简化Spring应用的创建、运行、调试和部署等,使用SpringBoot可以不用或者只需要很少的Spring配置就可以让企业项目快... 目录SpringBoot基础 – 框架介绍1.SpringBoot介绍1.1 概述1.2 核心功能2

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin

MySQL复合查询从基础到多表关联与高级技巧全解析

《MySQL复合查询从基础到多表关联与高级技巧全解析》本文主要讲解了在MySQL中的复合查询,下面是关于本文章所需要数据的建表语句,感兴趣的朋友跟随小编一起看看吧... 目录前言:1.基本查询回顾:1.1.查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J1.2.按照部门

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键