引言

单链表在数据结构中是很重要的一种线性结构,它是单向的,有着非常广泛的应用领域;虽然现在很多语言中都有封装好的链表类型可以直接使用,但是自己能写一个链表并实现基本操作是至关重要的;

接下来我将用代码展示单链表的创建和一些基本操作;
注:以下代码仅供参考,并不一定是最优代码,只是想让各位了解单链表如何进行的一些基本操作;

单链表的结构

单链表就是由一个一个节点组成,这个节点由一个数据域和指针域组成;
如图:

所以,我们需要先创建节点结构,然后才能依次组成单链表;
注:以下链表的数据域的数据类型都是int类型,实际情况可以修改为任意数据类型;

这里使用结构体来创建,代码如下:

// 单链表节点结构
typedef struct Node {int data; // 数据域struct Node* next; // 指针域
}NODE;

可以看到,指针域指向的其实就是该节点本身的数据类型,所以这一点一定要注意!

单链表的初始化和创建

有了基本组成单元,那么就要创建链表了,这里我分为两个函数来实现:

  • void initList(NODE*& head); // 初始化链表
  • void creatList(NODE*& head); // 创建一个链表

初始化链表很简单,直接看代码:

// 初始化链表
void initList(NODE*& head) {try {head = new NODE;}catch (bad_alloc& e) {cout << "内存分配失败!!" << endl;cout << e.what() << endl; // 输出异常信息} // 捕获异常head->data = 0;head->next = NULL;return;
}

这里还有一点值得一提:
当链表内存分配失败时,我是用的是try…catch捕获的异常,这个使用于现在的大部分新版的编译器,因为新版编译器在内存分配失败的情况下将不再会返回NULL;老版的编译器入VC++6.0等就会返回NULL,所以一定要注意对内存分配失败的处理;

接下来就是创建一个单链表了,这里使用的是尾插法;
为什么使用尾插法呢?因为头插法输出的链表和输入的顺序是相反的,所以最好使用尾插法来创建链表;

这里创建的链表也需要注意:
该链表创建时会有一个没有什么实际意义的头节点,它的数据域存放的是链表的长度,当然创建头节点的目的也是为了方便对链表的操作;

代码如下:

void creatList(NODE*& head) {// 1,确定创建链表长度int len;cout << "请输入创建链表长度:" << endl;cin >> len;// 链表长度不应该为0和负数if (len <= 0) {cout << "创建链表长度不能是0或负数" << endl;return;}head->data = len; // 头节点数据域存放链表长度// 2,创建一个尾节点NODE* tail = head; // 定义一个节点为尾节点,指向头节点,它将代替头节点移动tail->next = NULL;// 3,循环创建新的节点for (int i = 0; i < len; ++i) {cout << "请输入第" << i + 1 << "个数据" << endl;int val;cin >> val;NODE* newNode = NULL;// 创建一个新节点作为临时节点try {newNode = new NODE; }catch (bad_alloc& e) {cout << "内存分配失败!!" << endl;cout << e.what() << endl;} // 捕获异常newNode->data = val; // 新节点数据域赋值tail->next = newNode; // 将新节点挂在尾节点后面newNode->next = NULL; // 新节点指针域为空tail = newNode; // 尾节点为新的节点}return;
}

这一步的操作一定要学会,因为只有创建出来链表后你才能对链表进行其他操作;

遍历输出链表和链表长度

下面的操作是遍历链表并输出和返回链表的长度;

为什么把这两个函数放一起?因为它们的操作可以说是一模一样,只是有很小的改动;

当然对链表遍历也是非常简单,所以不需要有太大的心理负担;

遍历输出链表代码如下:

void traverseList(NODE* head) {NODE* p = head->next; // 临时节点p指向头节点的下一个节点while (p) {cout << p->data << " ";p = p->next; // p移向下一个节点}cout << endl;return;
}

虽然简单,但是还是需要注意一点:
临时节点不要忘记,因为头节点是没有什么实际意义的节点,所以输出头节点并没有什么意义;

获取链表长度代码如下:

int listLength(NODE* head) {NODE* p = head->next; // 临时节点p指向头节点的下一个节点int len = 0; // 链表长度while (p) {++len; // 长度加一p = p->next; // p移向下一个节点}return len;
}

是不是很简单,只是只需要修改关键的一句代码即可求得链表长度;

排序操作

有时我们可能会遇到一些情况需要对链表进行排序,链表是线性存储结构,那么该如何排序呢?
其实也很简单,只需要将数据域的内容进行交换排序即可,也就是只对数据域进行操作,并不改变链表结构;

这里是顺序排序;
代码如下:

void sortList(NODE*& head) {int t;NODE* p;NODE* q;for (p = head->next; p != NULL; p = p->next) {for (q = p->next; q != NULL; q = q->next) {// 交换数据域的内容if (p->data > q->data) {t = p->data;p->data = q->data;q->data = t;}}}
}

这个排序算法并不是最优的,但是非常好理解,所以先学会一种方法再去突破吧!

插入操作和删除操作

单链表和数组都是线性存储结构,数组的优点是:可以实现快速查询;链表的的优点是:可以快速的实现插入和删除操作;
所以,插入和删除操作在单链表中是非常非常非常重要的!!

对于链表的插入和删除操作,只需要记住一点:插入/删除哪个位置,一定要找到该位置的前一个位置;

还是画个图吧:

我来描述一下这张图:

  • 想要在data3的位置插入新节点节点s,首先需要找到data3的前一个位置:data2的位置,也就是p指针指向的位置;
  • 接下来就是插入操作了
  • 第一步1:先把新节点s挂到data3上;(即让s节点指向data3节点)
  • 第二步2:断开p节点和data3节点的联系;
  • 第三步3:让p节点指向s节点;(即p节点指针域存放s节点地址)

下面就来看一下代码:

void insertListByPostion(NODE*& head, int data, int pos) {int i = 1;NODE* p = head;while (p && i < pos) {++i;p = p->next;}if (!p || i > pos) {cout << "插入位置不存在!!" << endl;return;}NODE* newNode = NULL;try {newNode = new NODE;}catch (bad_alloc& e) {cout << "内存分配失败!!" << endl;cout << e.what() << endl;} // 捕获异常newNode->data = data;newNode->next = p->next;p->next = newNode;head->data++;cout << "插入成功!!" << endl;
}

需要注意:插入节点位置必须存在,即首部尾部和中间,所以前面需要先判断插入位置是否存在;

删除操作更简单,如图:

同样描述一下该图:

  • 想要删除q节点,所以先找到q节点前一个位置:p节点;
  • 接下来是删除操作:
  • 第一步:让p节点指向r节点
  • 第二步:释放q节点内存空间

是不是很简单;

来看一下代码如何实现:

void deleteListByPostion(NODE*& head, int pos) {int i = 1;NODE* p = head;while (p->next && i < pos) {p = p->next;++i;}if (!p->next || i > pos) {cout << "删除位置不存在!!" << endl;return;}NODE* q = p->next;p->next = q->next;cout << "删除成功!!删除元素为:" << q->data << endl;delete q;head->data--;
}

同样需要注意:删除节点位置必须存在,所以需要先判断插入位置是否存在,因为删除只能删除节点只能删除存在的节点,所以判断条件和插入节点有些不同,不理解可以画个图细细品味;

按顺序合并两个有序链表

这个操作其实已经不算是单链表的基本操作了,它是将两个顺序的链表合并为一个顺序的链表,并且使用O(1)的空间复杂度,既不能使用额外空间,但是考研时经常会出现,所以就来实现一下;
其实力扣上有原题,可以看看我的这篇文章:21. 合并两个有序链表(C语言),这里就不再写解析了;

代码如下:

void mergeTwoLists(NODE*& l1, NODE*& l2, NODE*& list) {list->data = l1->data + l2->data; // 头节点数据域为两个链表个数之和NODE* p = list;NODE* list1 = l1->next; // list1为l1头节点下一个节点NODE* list2 = l2->next; // list2为l2头节点下一个节点// 要对没有头节点的链表进行操作,一定不能带上头节点进行比较while (list1 && list2) {if (list1->data < list2->data) {p->next = list1;list1 = list1->next;}else {p->next = list2;list2 = list2->next;}p = p->next;}p->next = list1 ? list1 : list2;return;
}

其实这里还是需要注意一点:
力扣上的测试链表是没有头指针的,所以我们的链表不能直接去使用力扣上的代码,需要进行一些小改动,但是基本的算法思想还是一样的;

逆序链表

这个操作是将链表逆置,且同样不能使用额外内存空间;这个力扣上也有原题,解析可以看看我的这篇文章:剑指 Offer 24. 反转链表(C语言)

这个操作其实非常简单,就是一个双指针操作;

代码如下:

void reverseList(NODE*& head, NODE*& list) {// 双指针list = head; // 先把头节点连接到新的链表上NODE* fast = head->next;NODE* slow = NULL;while (fast) {NODE* node = fast->next;fast->next = slow;slow = fast;fast = node;}list->next = slow;
}

同样需要注意:
力扣上的测试链表是没有头指针的,所以我们的链表不能直接去使用力扣上的代码,需要进行一些小改动,但是基本的算法思想还是一样的;

总结

链表的操作其实是非常多的,这里只是为你开一个头,不管你是刚学习数据结构的小白,还是有经验的老手,都希望这篇文章可以给你带来一些启发;
数据结构的学习并不一定会让你瞬间感觉到编程能力的提升,这也是很多人学完数据结构感觉没什么用的原因;但是正是数据结构描述了我们生活抽象的事物,让它们变成了代码展示出来,数据结构在你的编程路上是要一直学习和理解的,只有对底层有更深入的了解,你的编程之路才能走的更顺更远!!!
希望我们一起进步!!

欢迎大家的点评!

数据结构解析——小白也能看懂的单链表相关推荐

  1. c语言程序和plc程序的区别,一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂!...

    原标题:一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂! 你真的了解PLC吗?你知道PLC与计算机的本质区别吗?我来简单解释一下吧. 1.PLC可以工作在极其恶劣的电磁环境中 如果我们把计算机 ...

  2. python进阶(小白也能看懂)——装饰器浅谈(一)

    python进阶(小白也能看懂)--装饰器(一) 第四篇 文章目录 python进阶(小白也能看懂)--装饰器(一) 1.函数基础知识 例子1.1 例子1.2 例子1.3 例子1.4 2.不带参数的装 ...

  3. python进阶(小白也能看懂)——Map、Filter、Reduce

    python进阶(小白也能看懂)--Map.Filter.Reduce 第三篇 Map.Filter.Reduce是python中常用的函数,使用这些函数能够给我们带来很多便捷. Map map(fu ...

  4. python进阶(小白也能看懂)——*args与**kwargs的使用

    python进阶(小白也能看懂)--*args与**kwargs的使用 第一篇 理解*args与**kwargs在定义函数时的作用 假设你写了一个函数multiply(函数定义在下面),专门用来处理两 ...

  5. 随机森林的特征 是放回抽样么_机器学习超详细实践攻略(10):随机森林算法详解及小白都能看懂的调参指南...

    一.什么是随机森林 前面我们已经介绍了决策树的基本原理和使用.但是决策树有一个很大的缺陷:因为决策树会非常细致地划分样本,如果决策树分得太多细致,会导致其在训练集上出现过拟合,而如果决策树粗略地划分样 ...

  6. 小白都能看懂的实战教程 手把手教你Python Web全栈开发(DAY 3)

    小白都能看懂的实战教程 手把手教你Python Web全栈开发 Flask(Python Web)实战系列之在线论坛系统 第三讲 这是小白都能看懂的实战教程 手把手教你Python Web全栈开发 的 ...

  7. armbian清理_小孩子才做选择,OMV、HomeAssistant我全都要,小白也能看懂的N1盒子纯净刷机指南...

    小孩子才做选择,OMV.HomeAssistant我全都要,小白也能看懂的N1盒子纯净刷机指南 2020-04-10 22:46:04 63点赞 609收藏 52评论 创作立场声明:今天我是一个可爱的 ...

  8. 小白也能看懂的网络基础 | 01 什么是网络?

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 引言 欢迎来到网络世界,<小白也能看懂的网络基础>系列文章会从零开始帮助你构建网络的基础知识.如果你完 ...

  9. 小白也能看懂的网络基础 | 02 什么是连接设备?

    公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 引言 欢迎来到网络世界,<小白也能看懂的网络基础>系列文章会从零开始帮助你构建网络的基础知识.如果你完 ...

最新文章

  1. mysql count里面能加条件吗_select count(1) 和 count(*),哪个性能更好?
  2. java 映射施舍呢,黑猴子的家:Java 8 - Stream 中间操作(映射)
  3. Android 系统(60)---JSON
  4. python+requests接口测试基础
  5. C++自学06:sizeof运算符
  6. 社区团购的终局是不是团长被抛弃?
  7. 初始js闭包事件的冒泡和捕获EVENT对象
  8. C#之FileInfo的简单操作
  9. QQ for linux不用udp8000端口?
  10. 一份简短又全面的数学建模技能图谱:常用模型算法总结
  11. 测井曲线fft matlab,Matlab编程实现FFT实践及频谱分析
  12. YAML文件格式详解
  13. 一般纳税人税额计算_一般纳税人应纳税额如何计算?
  14. uniapp开发的多端影视APP,对接的苹果CMS
  15. java常见的设计模式
  16. java 嘻嘻哈哈聊天室
  17. winpe修复计算机无法启动,PE修复系统启动故障的详细教程
  18. Innodb存储引擎-idb文件格式解析
  19. 青少年编程python一级真题_青少年编程能力等级测评试卷二及答案 Python编程(一级)...
  20. 计算机专业硕士毕业论文,硕士毕业论文(计算机专业、自动化专业).doc

热门文章

  1. pyqt5 tableview 设置行颜色_Tools Ⅰ:如何用 PyQt5 和 Qt Designer 在 Pycharm 中愉快地开发软件...
  2. python中接口测试垃圾数据如何清理_接口测试---Python数据处理需要注意的细节
  3. 国家发改委:分两批在8个地区建设全国一体化算力网络国家枢纽节点
  4. 机柜服务器选择学问大!
  5. linux i2c子系统入口,I2C子系统1 - ARM_Linuxx的个人空间 - OSCHINA - 中文开源技术交流社区...
  6. python读取data_转载 “ 理想国@Data ”重拾Python(5):数据读取 博客
  7. CUDA:利用Pytorch查看自己电脑上CUDA版本及其相关信息
  8. 成功解决Windows10环境下运行Linux系统下的.sh文件
  9. ML之DT之CART:分类与回归树CART算法的简介、应用、经典案例之详细攻略
  10. AI:机器学习、深度学习在实际应用(工业应用)中的步骤流程框架、实际场景(案例)之详细攻略