逻辑结构上一个挨一个的数据,在实际存储时,并没有像顺序表那样也相互紧挨着。恰恰相反,数据随机分布在内存中的各个位置,这种存储结构称为 线性表的链式存储 。

由于分散存储,为了能够体现出数据元素之间的逻辑关系,每个数据元素在存储的同时,要配备一个指针,用于指向它的直接后继元素,即每一个数据元素都指向下一个数据元素(最后一个指向 NULL(空) )。


图1 链式存储存放数据

如图1所示,当每一个数据元素都和它下一个数据元素用指针链接在一起时,就形成了一个链,这个链子的头就位于第一个数据元素,这样的存储方式就是链式存储。

线性表的链式存储结构生成的表,称作“链表”。

链表中数据元素的构成

每个元素本身由两部分组成:

  1. 本身的信息,称为“数据域”;
  2. 指向直接后继的指针,称为“指针域”。

图2 结点的构成

这两部分信息组成数据元素的存储结构,称之为 “结点” 。n个结点通过指针域相互链接,组成一个链表。


图3 含有n个结点的链表
 

图 3 中,由于每个结点中只包含一个指针域,生成的链表又被称为  线性链表  或  单链表 。

链表中存放的不是基本数据类型,需要用结构体实现自定义:

typedef struct Link{char elem;//代表数据域struct Link * next;//代表指针域,指向直接后继元素
}link;

头结点、头指针和首元结点

头结点: 有时,在链表的第一个结点之前会额外增设一个结点,结点的数据域一般不存放数据(有些情况下也可以存放链表的长度等信息),此结点被称为头结点。

若头结点的指针域为空(NULL),表明链表是空表。头结点对于链表来说,不是必须的,在处理某些问题时,给链表添加头结点会使问题变得简单。

首元结点: 链表中第一个元素所在的结点,它是头结点后边的第一个结点。

头指针: 永远指向链表中第一个结点的位置(如果链表有头结点,头指针指向头结点;否则,头指针指向首元结点)。

头结点和头指针的区别:头指针是一个指针,头指针指向链表的头结点或者首元结点;头结点是一个实际存在的结点,它包含有数据域和指针域。两者在程序中的直接体现就是:头指针只声明而没有分配存储空间,头结点进行了声明并分配了一个结点的实际物理内存。
图 4 头结点、头指针和首元结点

单链表中可以没有头结点,但是不能没有头指针!

链表的创建和遍历

万事开头难,初始化链表首先要做的就是创建链表的头结点或者首元结点。创建的同时,要保证有一个指针永远指向的是链表的表头,这样做不至于丢失链表。

例如创建一个链表(1,2,3,4):

link * initLink(){link * p=(link*)malloc(sizeof(link));//创建一个头结点link * temp=p;//声明一个指针指向头结点,用于遍历链表//生成链表for (int i=1; i<5; i++) {link *a=(link*)malloc(sizeof(link));a->elem=i;a->next=NULL;temp->next=a;temp=temp->next;}return p;
}

链表中查找某结点

一般情况下,链表只能通过头结点或者头指针进行访问,所以实现查找某结点最常用的方法就是对链表中的结点进行逐个遍历。

实现代码:

int selectElem(link * p,int elem){link * t=p;int i=1;while (t->next) {t=t->next;if (t->elem==elem) {return i;}i++;}return -1;
}

链表中更改某结点的数据域

链表中修改结点的数据域,通过遍历的方法找到该结点,然后直接更改数据域的值。

实现代码:

//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
link *amendElem(link * p,int add,int newElem){link * temp=p;temp=temp->next;//在遍历之前,temp指向首元结点//遍历到被删除结点for (int i=1; i<add; i++) {temp=temp->next;}temp->elem=newElem;return p;
}

向链表中插入结点

链表中插入头结点,根据插入位置的不同,分为3种:
  1. 插入到链表的首部,也就是头结点和首元结点中间;
  2. 插入到链表中间的某个位置;
  3. 插入到链表最末端;

图 5 链表中插入结点5

虽然插入位置有区别,都使用相同的插入手法。分为两步,如图 5 所示:

  • 将新结点的next指针指向插入位置后的结点;
  • 将插入位置前的结点的next指针指向插入结点;

提示: 在做插入操作时,首先要找到插入位置的上一个结点,图4中,也就是找到结点 1,相应的结点 2 可通过结点 1 的 next 指针表示,这样,先进行步骤 1,后进行步骤 2,实现过程中不需要添加其他辅助指针。

实现代码:

link * insertElem(link * p,int elem,int add){link * temp=p;//创建临时结点temp//首先找到要插入位置的上一个结点for (int i=1; i<add; i++) {if (temp==NULL) {printf("插入位置无效\n");return p;}temp=temp->next;}    //创建插入结点clink * c=(link*)malloc(sizeof(link));c->elem=elem;//向链表中插入结点c->next=temp->next;temp->next=c;return  p;
}

注意:首先要保证插入位置的可行性,例如图 5 中,原本只有 5 个结点,插入位置可选择的范围为:1-6,如果超过6,本身不具备任何意义,程序提示插入位置无效。

从链表中删除节点

当需要从链表中删除某个结点时,需要进行两步操作:

  • 将结点从链表中摘下来;
  • 手动释放掉结点,回收被结点占用的内存空间;
使用malloc函数申请的空间,一定要注意手动free掉。否则在程序运行的整个过程中,申请的内存空间不会自己释放(只有当整个程序运行完了以后,这块内存才会被回收),造成内存泄漏,别把它当成是小问题。

实现代码:

link * delElem(link * p,int add){link * temp=p;//temp指向被删除结点的上一个结点for (int i=1; i<add; i++) {temp=temp->next;}link * del=temp->next;//单独设置一个指针指向被删除结点,以防丢失temp->next=temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域free(del);//手动释放该结点,防止内存泄漏return p;
}

完整代码

#include <stdio.h>
#include <stdlib.h>typedef struct Link{int  elem;struct Link *next;
}link;
link * initLink();
//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置
link * insertElem(link * p,int elem,int add);
//删除结点的函数,p代表操作链表,add代表删除节点的位置
link * delElem(link * p,int add);
//查找结点的函数,elem为目标结点的数据域的值
int selectElem(link * p,int elem);
//更新结点的函数,newElem为新的数据域的值
link *amendElem(link * p,int add,int newElem);
void display(link *p);int main() {//初始化链表(1,2,3,4)printf("初始化链表为:\n");link *p=initLink();display(p);printf("在第4的位置插入元素5:\n");p=insertElem(p, 5, 4);display(p);printf("删除元素3:\n");p=delElem(p, 3);display(p);printf("查找元素2的位置为:\n");int address=selectElem(p, 2);if (address==-1) {printf("没有该元素");}else{printf("元素2的位置为:%d\n",address);}printf("更改第3的位置的数据为7:\n");p=amendElem(p, 3, 7);display(p);return 0;
}link * initLink(){link * p=(link*)malloc(sizeof(link));//创建一个头结点link * temp=p;//声明一个指针指向头结点,用于遍历链表//生成链表for (int i=1; i<5; i++) {link *a=(link*)malloc(sizeof(link));a->elem=i;a->next=NULL;temp->next=a;temp=temp->next;}return p;
}
link * insertElem(link * p,int elem,int add){link * temp=p;//创建临时结点temp//首先找到要插入位置的上一个结点for (int i=1; i<add; i++) {if (temp==NULL) {printf("插入位置无效\n");return p;}temp=temp->next;}//创建插入结点clink * c=(link*)malloc(sizeof(link));c->elem=elem;//向链表中插入结点c->next=temp->next;temp->next=c;return  p;
}link * delElem(link * p,int add){link * temp=p;//遍历到被删除结点的上一个结点for (int i=1; i<add; i++) {temp=temp->next;}link * del=temp->next;//单独设置一个指针指向被删除结点,以防丢失temp->next=temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域free(del);//手动释放该结点,防止内存泄漏return p;
}
int selectElem(link * p,int elem){link * t=p;int i=1;while (t->next) {t=t->next;if (t->elem==elem) {return i;}i++;}return -1;
}
link *amendElem(link * p,int add,int newElem){link * temp=p;temp=temp->next;//tamp指向首元结点//temp指向被删除结点for (int i=1; i<add; i++) {temp=temp->next;}temp->elem=newElem;return p;
}
void display(link *p){link* temp=p;//将temp指针重新指向头结点//只要temp指针指向的结点的next不是Null,就执行输出语句。while (temp->next) {temp=temp->next;printf("%d",temp->elem);}printf("\n");
}

运行结果:

初始化链表为:
1234
在第4的位置插入元素5:
12354
删除元素3:
1254
查找元素2的位置为:
元素2的位置为:2
更改第3的位置的数据为7:
1274

总结

线性表的链式存储相比于顺序存储,有两大优势:

  1. 链式存储的数据元素在物理结构没有限制,当内存空间中没有足够大的连续的内存空间供顺序表使用时,可能使用链表能解决问题。(链表每次申请的都是单个数据元素的存储空间,可以利用上一些内存碎片)
  2. 链表中结点之间采用指针进行链接,当对链表中的数据元素实行插入或者删除操作时,只需要改变指针的指向,无需像顺序表那样移动插入或删除位置的后续元素,简单快捷。

链表和顺序表相比,不足之处在于,当做遍历操作时,由于链表中结点的物理位置不相邻,使得计算机查找起来相比较顺序表,速度要慢。

以上有参考https://www.2cto.com/kf/201803/731494.html

线性表:3.链表,单链表详解与C语言实现相关推荐

  1. 线性表的应用 —— 单链表

    线性表的应用 -- 单链表 链表是线性表的链式存储方式,逻辑上相邻的数据在计算机内的存储位置不一定相邻,可以给每个元素都附加一个指针域,指向下一个元素的存储位置. 像这样: 从图中可以看出,每个节点都 ...

  2. C语言线性表之循环单链表

    #include<stdio.h> #include<stdlib.h>int typeOfLinkList;typedef struct LNode{int data;str ...

  3. 数据结构(C语言版) 第二章 线性表 知识梳理+作业习题详解

    目录 一.线性表顺序存储结构(顺序表) 0.线性表的基本概念 1.样例引入:多项式相加 二.线性表链式存储结构(链表) 0.链表的基本概念 1.前插法代码实例 2.链表尾插法完整代码附带各种操作 三. ...

  4. 数据结构(算法)-线性表2(单链表)

    为什么80%的码农都做不了架构师?>>>    /** 单链表 双链表1,空的线性表setNull(L) 2,insert 加入元素 3,根据位置查找元素 4,查找所有元素 5,删除 ...

  5. PHP数据结构之三 线性表中的单链表的PHP实现

    线性表的链式存储:用一组任意的存储单元存储线性表中的数据元素.用这种方法存储的线性表简称线性链表. 链式存储线性表的特点:存储链表中结点的一组任意的存储单元可以是连续的,也可以是不连续的,甚至是零散分 ...

  6. 【 数据结构 】单链表的实现 - 详解(C语言版)

    目录 前言: 顺序表的缺陷: 单链表:(Single Linked List) 概念及结构: 单链表的实现: 头文件:SList.h malloc函数: free函数: 具体函数的实现:SList.c ...

  7. 线性表基本操作,单链表的建立(头插法,尾插法)、插入、删除、遍历操作的实现(c++ 数据结构 实验二)

    大学数据结构课程的实验题目,掌握线性表的链接存储结构,用c++语言描述 一.实验要求 1.分别用头插法和尾插法建立一个含有若干结点的单链表 2.对已建立的单链表进行插入.删除.遍历输出等操作 二.代码 ...

  8. [XJTUSE]数据结构学习——第一章 线性表 1.3 单链表的实现(JAVA)

    文章目录 1.3 单链表的实现(JAVA) 1.curr指针与头结点的说明 2.插入和删除操作的说明 插入 删除 1.3 单链表的实现(JAVA) 链表是由一系列叫做表的结点(node)的对象组成的, ...

  9. C语言-线性表基本操作之单链表

    下面是单链表的基本操作: #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct L ...

  10. java线性表合并_单链表的合并(Java实现)

    C语言能做的,Java照样可以做.但是没有指针的操作,可能使人看起来"不舒服".这个博客是为了个人学习算法和数据结构而开的,以后凡是涉及到这方面的内容,我会尽量用Java和C同时实 ...

最新文章

  1. 安装MHA中清理Relay log报错
  2. vim graphics
  3. 看别人的C/C++代码时发现自己所不知道的语法~
  4. KVM之EPT与影子页表(七)
  5. VMprotect静态跟踪 字节码反编译
  6. Java语言中的-----访问修饰符
  7. xml文件c语言读取函数,读写xml文件的2个小函数
  8. 鸿蒙空间是什么星辰变,飞升之后做什么《星辰变》神魔妖界收伏奇珍异兽
  9. Mom and Dad
  10. c语言gcno文件位置,c – 找不到CMake和lcov:gcno文件
  11. android app应用签名生成工具,Android应用签名证书(.keystore)生成
  12. 小型企业网络规划与组建方案
  13. android转移数据到苹果手机号码,苹果电话号码怎么转到新手机(简单教你两招轻松搞定)...
  14. HQChart实战教程6-自定义分时图
  15. 怎么用计算机求原函数,科学计算器使用教程_科学计算器怎么解方程
  16. Java连接wincc_WinCC中访问SQL SERVER数据库
  17. 洛谷 P3388 【模板】割点(割顶) 根+非根+dfn[]+low[]+不一样的Tarjan算法
  18. 手游平台开发需要哪些人员构成?
  19. win32中SetCapture 和 ReleaseCapture的使用
  20. about_Execution_Policies

热门文章

  1. php imagerotate png,php imagerotate,rotate image,rotateimage opencv
  2. 对抗微软?索尼36亿美金收购游戏开发商Bungie
  3. 烧光86亿元,还是没造出来车!拜腾“造车梦”断?
  4. 大哥特斯拉:造车“三傻”,咱们抱团?
  5. 疫情风向标?苹果宣布将暂时关闭大中华区以外的所有苹果零售店!
  6. 全面提升AR感知能力的新款iPad Pro或将延期发布
  7. 被李小龙女儿起诉侵权后,真功夫态度强硬:不寻求和解,无更换商标计划
  8. 微信灰度测试“相关阅读”功能 公众号文章下推荐延伸内容
  9. 16999元!华为Mate X 5G折叠屏手机正式发布:不愧是限量款
  10. “不差钱”华为刷屏 拟募资60亿!