目录
一、链表的原理
1.什么是链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
我们想象中的链表可能是下面的
但实际上的链表是
这是一种物理存储结构上非连续,非顺序的储存结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
2.什么是单链表
实际上的链表的结构非常多样,以下情况组合起来就有8种链表结构
1.单向,双向
2.带头,不带头
3.循环,非循环
而我们下面将要实现链表中最简单的结构,单向,不带头,非循环链表
3.顺序表与链表的对比
二、单链表的实现
1.单链表的定义
typedef int SlTDataType;
typedef struct ListNode
{
SlTDataType val;
struct ListNode* next;
}ListNode;
链表是一个链式结构,在这个节点就可以找到它的下一个节点,同时保存当前节点储存的数据
链表有一个指针域和数据域,一个节点就是一个结构体
2.创建一个链表节点
我们数据结构很多时候都是需要动态开辟内存的,我们每增加一个节点就需要创建一个新节点,所以我们单独写成一个函数来重复调用
//创建新节点
ListNode* BuyListNode(SlTDataType x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
if (newNode == NULL)
{
perror("BuyListNode::malloc");
}
newNode->val = x;
newNode->next = NULL;
return newNode;
}
我们每次动态开辟内存之后必须要检查空间是否开辟成功,因为在现实生活中是有可能开辟失败的
同时我们默认新节点的next指针指向NULL,避免出现野指针问题。
3.单链表数据在尾部插入
尾插时我们首先需要创建一个新节点,同时需要找到链表的尾,因为,链表只能从头开始访问
//尾插
void ListNodePushBack(ListNode** pplist, SlTDataType x)
{
ListNode* newNode = BuyListNode(x);
if (*pplist == NULL)
{
*pplist = newNode;
}
else
{
ListNode* tail = *pplist;
while (tail != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
同时有一个细节需要注意,如果链表一个节点都没有的情况下,我们就需要将新节点作为头节点
4.单链表数据在头部插入
头插我们就不需要考虑没有节点了,因为链表为空时,它的next指针默认指向NULL,我们直接在头上插一个新节点就可以了
//头插
void ListNodePushFront(ListNode** pplist,SlTDataType x)
{
ListNode* newNode = BuyListNode(x);
newNode->next = *pplist;
*pplist = newNode;
}
5.单链表数据在头部删除
头删我们也需要考虑特殊情况,如果为空怎么办?
为空我们就不能删除了,直接return就可以了
//头删
void ListNodePopFront(ListNode** pplist)
{
if (*pplist == NULL)
{
return;
}
else
{
ListNode* next = (*pplist)->next;
free(*pplist);
*pplist = next;
}
}
我们还要注意其中一点,我们如果直接free掉头那么我们就无法访问头后面的节点了,所以我们创建一个next节点来存头的下一个节点的地址
6.单链表数据在尾部删除
尾删就比较特殊了,有三种情况
1.如果一个节点都没有
2.有一个节点
3.有多个节点
我们考虑了这几种情况之后,剩下的思路与头删就差不多了
//尾删
void ListNodePopBack(ListNode** pplist)
{
//没有节点
//一个节点
//多个节点
if (*pplist == NULL)
{
return;
}
else if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
ListNode* tail = *pplist;
ListNode* prev = NULL;
while (tail != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
7.单链表数据在任意地方插入
在任意你的地方插入,我们就需要选在那个节点处插入,我们只实现在某个节点后面插入,因为单链表天然的结构缺陷,我们无法找到当前节点的上一个节点。
//任意位置插入
void ListInsertAfter(ListNode* pos, SlTDataType x)
{
assert(pos);
ListNode* newNode = BuyListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
8.打印单链表
//打印链表
void ListNodePrint(ListNode* plist)
{
assert(plist);
ListNode* cur = plist;
while (cur != NULL)
{
printf("%d ", cur->val);
cur = cur->next;
}
printf("\n");
}
9.查找节点
//查找节点
ListNode* ListFind(ListNode* plist, SlTDataType x)
{
assert(plist);
ListNode * cur = plist;
while (cur != NULL)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
总结
完整代码可从如下网址获得:JAVA-/ListNode at main · 01294268442ww/JAVA- (github.com)