一、单链表简述
单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素+ 指针,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
1、单链表的定义
链表是通过一组地址任意的存储单元来存储线性表中的数据元素,这些存储单元可以是连续的,也可以是不连续的。
2、单链表的特点
逻辑相邻的数据元素其物理存储位置不一定相邻。为表示数据元素之间的线性关系,在存储数据元素时,除了要存放数据元素自身的信息外,还要额外存放一个其直接后继的存储地址。
3、图解单链表
二、单链表
#pragma once
typedef int ElemType;
typedef struct Node
{
union
{
int length;
ElemType data;
};
struct Node *next;
}LNode, *LinkList;
void InitLinkList(LinkList list);
int InsertLinkListPos(LinkList list, ElemType val, int pos);
int InsertLinkListHead(LinkList list, ElemType val);
int InsertLinkListTail(LinkList list, ElemType val);
void ShowLinkList(LinkList list);
int DeleteLinkListPos(LinkList list, int pos);
int DeleteLinkListHead(LinkList list);
int DeleteLinkListTail(LinkList list);
void ClearLinkList(LinkList list);
void DestoryLinkList(LinkList list);
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include "List.h"
//判断链表是否为空
static void DeterPointIsNULL(LinkList list)
{
assert(list != NULL);
if (list == NULL)
{
printf("list is NULL, Please Check\n");
exit(0);
}
}
//申请一个新节点
static LinkList _ApplyNode(ElemType val, LinkList point)
{
LinkList s = (LinkList)malloc(sizeof(LNode));
assert(s != NULL);
s->data = val;
s->next = point;
return s;
}
//初始化一个单链表
void InitLinkList(LinkList list)
{
DeterPointIsNULL(list);
list->length = 0;
list->next = NULL;
}
//插入
int InsertLinkListPos(LinkList list, ElemType val, int pos)
{
DeterPointIsNULL(list);
if (pos < 0 || pos > list->length)
{
printf("pos is out of range, Insert fail\n");
return 0;
}
LinkList p = list;
while (pos > 0)
{
p = p->next;
pos--;
}
p->next = _ApplyNode(val, p->next);
return 1;
}
int InsertLinkListHead(LinkList list, ElemType val)
{
return InsertLinkListPos(list, val, 0);
}
int InsertLinkListTail(LinkList list, ElemType val)
{
DeterPointIsNULL(list);
return InsertLinkListPos(list, val, list->length);
}
//打印
void ShowLinkList(LinkList list)
{
DeterPointIsNULL(list);
LinkList p = list->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
//删除
int DeleteLinkListPos(LinkList list, int pos)
{
DeterPointIsNULL(list);
if (pos < 0 || pos >= list->length)
{
printf("pos is out of range, Insert fail\n");
return 0;
}
LinkList p = list;
while (pos > 0)
{
p = p->next;
pos--;
}
LinkList q = p->next;
p->next = q->next;
free(q);
list->length--;
return 1;
}
int DeleteLinkListHead(LinkList list)
{
return DeleteLinkListPos(list, 0);
}
int DeleteLinkListTail(LinkList list)
{
DeterPointIsNULL(list);
return DeleteLinkListPos(list, list->length - 1);
}
//清空和销毁
void ClearLinkList(LinkList list)
{
DestoryLinkList(list);
}
void DestoryLinkList(LinkList list)
{
DeterPointIsNULL(list);
while (list->next != NULL)
{
DeleteLinkListHead(list);
}
}
三、单链表的应用
1、判断两个单链表是否相交并返回第一个交点
解析:按照单链表的构成来说,两个单链表相交的条件是两个单链表中有一个结点数据元素相等,存储的下一个元素地址相同,如下图所示:
可以发现,当两个单链表相交时,其后面的结点也一定相等,即可以转化为当两个单链表剩余结点数相等时,同时向后进行移动,相等的结点即为两个单链表第一个相交的结点。
代码:
//相交并返回第一个交点
LinkList IntersectLinkList(LinkList list1, LinkList list2)
{
DeterPointIsNULL(list1);
DeterPointIsNULL(list2);
int len1 = list1->length;
int len2 = list2->length;
LinkList p = list1;
LinkList q = list2;
if (len1 > len2)
{
for (int i = 0; i < len1; ++i)
{
p = p->next;
}
}
if (len1 < len2)
{
for (int i = 0; i < len2; ++i)
{
q = q->next;
}
}
while (p)
{
if (p == q)
{
return p;
}
p = p->next;
q = q->next;
}
return NULL;
}
2、判断单链表是否有环
解析:单链表根据其特点,一旦有环就不会出环,这里可以运用快慢指针来做,下面画图来说明:
代码:
//判断是否有环并返回交点
static LinkList RingList(LinkList list)
{
LinkList p = list, q = list;
while (q != NULL)
{
p = p->next;
q = q->next;
if (q == NULL)
{
return NULL;
}
q = q->next;
if (p == q)
{
return p;
}
}
return NULL;
}
LinkList IsRingList(LinkList list)
{
DeterPointIsNULL(list);
LinkList pNode = RingList(list);
LinkList qNode = list;
while (pNode != qNode)
{
pNode = pNode->next;
qNode = qNode->next;
}
return pNode;
}
3、在O(1)下删除一个结点p
解析:由于单链表不能直接得知一个结点的前驱,所以一般采用的方法需要用到O(n)的时间复杂度。要在O(1)下完成,可以另辟新径,把后一个结点的数据值复制到需要删除的结点,然后删除后一个结点,下面画图:
代码:
void DeleteLinkListNode(LinkList list,LinkList p)
{
if (p == NULL || p->next == NULL)
{
return;
}
LinkList q = p->next;
p->data = q->data;
p->next = q->next;
free(q);
}
4、在O(1)下添加一个结点在结点p前面
解析:这个的做法与上面类似,主要运用的方法还是向后操作,把新节点添加在后面,通过数据交换来达到向前添加结点的作用,如下图:
代码:
void InsertOfNode(LinkList list, LinkList p, ElemType val)
{
if (list == NULL || p == NULL || p == list)
{
return;
}
LinkList s = _ApplyNode(p->data, p->next);
p->data = val;
p->next = s;
}
5、逆置单链表
解析:单链表逆置的其实很简单,只需要保证能依次拿到下一个指针并改变顺序即可,下面画图说明:
代码:
void ResverLinkList(LinkList list)
{
DeterPointIsNULL(list);
if (list->length < 2)
{
return;
}
LinkList s = NULL, p = list->next;
LinkList q = p->next;
while (p != NULL)
{
p->next = s;
s = p;
p = q;
if (q != NULL)
{
q = q->next;
}
}
}