目录
3.SearchLinklist(给定pos下标,查找链表)
5.DeleteLinklist(删除下表为i的后一个结点)
一.什么是链表
一般链表每个结点可以划分为数据域data(用来存储数据),指针域ptr(用来连接每个结点)两个部分.
就像侦探破案一样,进入一个房间后,可以得到下一个新的空间的地址,从而找寻到新的空间.
(PS:两个房间可能紧挨着,也可能分离).
可以说指针是实现链表最核心的部分.
二.链表的类型
1.单向或双向
2.带头或不带头
在链表最前面设置一个哨兵结点,里面不存放任何数据,称为带头链表,反之,称为不带头链表.
3.循环或非循环
三.单链表实现
在实现上来说,带头结点的单链表会比不带头结点的单链表要轻松,因为头结点可以把增删的代码
进行统一,而不带头结点的链表则不能.这也是带头结点的链表的一大优势.在后面的实现中我们也
能体会到.
但oj题中大多是不带头结点的单链表,并且在后面哈希桶、图的邻接表中,也并没有带头结点,所
以,不带头结点的单链表也有其实际的意义,需要优先进行实现.
下面采取的形式呢
主要是给出函数实现时所需要思考的地方,而在代码实现中,也会逐一解答这些思考点.
1.不带头结点的单链表
单链表声明:
SlistNode为单Single链表的意思,SLT为其缩写,Node是结点的意思
关于不带头结点的单链表,大致有以下操作.
在开始之前,我们必须记住一件很重要的事.
形参的改变,不会影响实参.
如果要改变结构体,我们传进函数的是结构体的指针.
如果要改变指针,我们传进函数的就必须是二级指针.
接下来,将会逐一介绍并实现函数.
1.BuySLTNode函数(创建一个新结点)
思考点:1.1结点申请有没有可能失败?假如失败,我们需要进行什么操作?
1.2返回参数是什么?
2.CreateSList(创建单链表)
思考点:2.1 现在已经有了创建一个结点的函数,如果将它们连接起来,形成链表呢?
2.2 假如链表为空,操作和链表中已经有元素操作是一样的吗?
3.SLTPrint(打印单链表)
思考点: 3.1 如何判断链表不再需要打印?
4.SLTPushback(尾插)
思考点: 4.1 链表为空的时候可以尾插吗?如果可以尾插,该如何实现呢?(函数需要什么参数)
Tips:考虑二级指针,这样就可以实现修改头指针的操作.
4.2 如何找到链表尾部呢?
5.SLTPopback(尾删)
思考点: 5.1 链表为空的时候可以尾删吗?如何区分链表是否为空?
5.2 可以直接释放最后一个结点吗?是否需要考虑修改前面结点next的值?
5.3 链表只有一个元素和有多个元素,操作是否有什么不同?
6.SLTPushFront(头插)
思考点:6.1 空链表可以头插吗?
6.2 头指针是否需要修改?如果需要,如何实现呢?
7.SLTPopFront(头删)
思考点:7.1 空链表可以头删吗?
7.2 头指针是否需要修改?如果需要,如何实现呢?
8.SLTFind(查找链表中元素x的位置)
思考点:8.1 空链表可以查找吗?
8.2 可以用cur->next来作为循环条件吗?
9.SLTInsertAfter(指定pos位置后插入)
思考点:9.1 需要判断头指针为空吗?
9.2 头指针是否需要修改?如何设置函数参数
10.SLTInsert(指定pos位置前插入)
思考点:10.1 空链表可以插入吗?和头插有区别吗?(头结点是否有改变?)
10.2 如何找到pos的前一个结点?
11.SLTEraseAfter(删除pos位置后的结点)
思考点:11.1 删除的是最后一个元素,或者第一个元素后面没有元素的情况是否需要单独考虑?
11.2 pos是否需要置空?
12.SLTErase(删除pos位置结点)
思考点:12.1 只有头结点的情况和头删有区别吗?(头结点是否有改变?)
12.2 pos是否需要置空?
13.SLTDestroy(清空链表)
思考点:12.1 头结点是否有改变?
2.带头结点的单链表
由于带头结点的单链表操作上其实和不带头结点的类似,所以这里就不再展示所有功能的代码.
编写的代码风格主要和严蔚敏老师的《数据结构》一书一致.
链表声明:
1.CreateLinklist(创建单链表)
2.ShowLinklist(打印单链表)
3.SearchLinklist(给定pos下标,查找链表)
4.InsertLinklist(插入元素进入链表)
5.DeleteLinklist(删除下表为i的后一个结点)
四.带头双向循环链表实现
在实现单链表的时候,其实我们已经可以发现
单链表的优势不在尾插尾删,而在于头插和头删
那是否存在一个链表,无论是尾插尾删,或者是头插和头删,都能轻易实现呢?
答案就是我们接下来介绍的带头双向循环链表.
链表声明:
为了区分和单链表的数据类型,将数据域命名为LTDataType
和单链表不同,它有一个前驱指针prev,和一个后继指针next
需要实现的函数功能大致有下面几个:
下面,我们将逐一介绍这些函数.
1.BuyListNode(创建新结点)
思考点: 1.1 申请结点是否一定可以成功?如果不成功,我们需要进行什么操作?
2.LTInit(初始化链表)
思考点: 2.1 为什么我们要设计一个这样的函数?
2.2 我们需要进行什么操作?头结点的prev和next指针初始化指向NULL可以吗?
3.LTPrint(打印链表)
思考点: 3.1 打印链表的时候,可以直接移动头指针去打印吗?
3.2 需要判断传入的链表指针是否为空吗?
3.3 终止条件还是尾指针的next指向NULL吗?
4.LTPushBack(尾插)
思考点: 4.1 还需要逐一遍历去找尾部吗?
4.2 需要判断传入的链表指针是否为空吗?
4.3 初始化和它是否又有关系呢?
5.LTPopBack(尾删)
思考点: 5.1 还需要逐一遍历去找尾部吗?
5.2 需要判断传入的链表指针是否为空吗?
5.3 链表为空,是否可以进行尾删呢?如何判断链表为空?
6.LTPushFront(头插)
思考点: 6.1 先改前面,再改后面,这样可行吗?
PS:先改前,就找不到哨兵头结点后面的结点了.
6.2 需要单独判断只有一个结点的情况吗?
7.LTPopFront(头删)
思考点: 7.1 链表为空,可以头删吗?如何判断链表为空?
PS:同上.
7.2 需要单独判断只有一个结点的情况吗?
8.LTFind(找指定元素的指针)
思考点: 8.1 为什么要创建一个这样的函数?它的时间复杂度是多少?
9.LTInsert(在pos位置前插入新结点)
思考点: 9.1 联系函数8,如何找到pos位置呢?
9.2 和头插一样,可以先改前面,再改后面吗?
有了函数 LTInsert后,我们其实就可以简化头插函数,使用时传phead->next进去即可.
10.LTErase(删除pos位置结点)
有了函数 LTErase后,我们其实就可以简化头删函数,使用时传phead->next进去即可.
11.LTEmpty(判断链表是否为空)
思考点: 11.1 是判断尾指针的next为空吗?
12.LTSize(返回链表长度)
思考点: 12.1 可以指针相减吗?
PS:链表的存储结构决定不可能指针相减.
13.LTDestroy(摧毁链表)
思考点: 13.1 释放空间的同时,可以在函数内部将指针置为NULL吗?
PS:形参改变,实参不改变.
13.2 可以直接释放掉phead吗?删除操作如何进行?
五.顺序表与链表的对比
两者所有的区别,其实就在于它们的存储结构是不一样的.
顺序表是一块连续空间进行存储,而链表则未必是连续的.
这就决定了顺序表可以随机访问,可以进行排序算法操作,缓存利用率高.但删除,插入等操作相应
时间复杂度也会更高.
而链表没有容量的概念,像我们实现的带头结点的双向循环链表,甚至可以做到头尾插,头尾删,
都是O(1)的复杂度.但随机访问,排序算法等等显然是不现实的.
两者各有优劣,如何使用,取决于我们当时所需.