链表的实质,在我认为是线性表,线性表的实现分为两种,一种是顺序存储另外一种就是链式存储结构,本文中所讨论的线性表为链式存储结构(以下称链表)。
链表相比于顺序存储结构的优势在于不要求逻辑上相邻的元素在物理位置上也相邻,在获得这个优点的同时,链表也失去了顺序存储结构的优点---随机存储。
简单的来说,链表由一系列不必在内存中连续的结构组成。每一个结构都含有表元素和指向包含该元素的后继元的结构的指针,我们称之为Netx指针,最后一个单元的Next指针指向NULL,即指向空;因此就有以下定义的结构体表示的链表节点:
/*定义线性链表的结构*/
typedef struct LNode
{
int date;//数据域struct LNode *next;//指向下一个节点的指针,即指针域}LNode;
链表又细分为带有头结点和不带头结点的链表,在这里主要讲述带有头结点的链表。
定义了链表的存储结构之后,万事开头难,有了节点的定义,这个时候我们就需要创建新的链表并且初始化(即给节点的数据域赋值),这里介绍常用的创建方法之一:尾插法
图中就是尾插法创建链表的算法原理(图源水印),具体代码实现:
LNode* CreateList(int n)
{
LNode* s;
LNode* r;//分别创建用来指向新节点(s)和尾节点(r)的指针
/*我认为这里是对节点L的重复初始化,但是在主函数中不对节点L进行初始化会报错 L未被初始化*/
LNode *L = (LNode*)malloc(sizeof(LNode));
L->next = NULL;
r = L;
for (int i = 0; i < n; i++)
{
s = (LNode*)malloc(sizeof(LNode));//为新节点申请内存空间
printf("输入数据:");
scanf("%d", &s->date);
r->next = s;//将新节点“连接”在尾节点之后
r = s;//新节点变为尾节点
}
if(L->next != NULL)
r->next = NULL;//将尾节点的指针域置位空
return L;
}
链表中是否成功保存了自己所输入的数据,这个时候就需要遍历链表,将所保存的数据打印出来瞧一瞧:
//遍历链表,输出数据
void OutputLinkList(LNode *L)
{
LNode* temp = L;//使用临时变量temp暂存头结点的值 防止遍历链表时头结点移动到尾节点导致链表丢失
while (temp->next != NULL)
{
//由于链表含有头结点 所以应该从链表的第二个节点开始输出并且依次后移
temp = temp->next;
printf("%d ", temp->date);
printf("\n");
}
}
在这里对为什么要创建临时变量temp做一个解释说明,链表就好比一条铁链子,链表第一个节点的地址是唯一的,这就好像我们只能一个手拿着这个铁链子的一端,如果想要仔细查看这个铁链子的其他节点,就需要用另一个手同时拿着这个铁链子,然后慢慢的往后查看,否则就会丢失链表的首地址,链表的首地址丢失意味着链表的丢失,这就是要创建临时变量并且使用这个临时变量去遍历链表的原因。
!!!注意:由于链表带有头结点,所以应该从链表中的第二个节点开始输出数据!!!如图:
验证链表是否为空是链表中经常用到的操作,例如在链表的删除和遍历操作中就会先验证链表是否为空,所以讲验证链表是否为空单独介绍一下:
//判断是否为空链表
void IsEmpty(LNode *L)
{
/*判断是否为空列表只需要判断头结点的指针域是否置空,若置空则为空链表,反之则为非空链表*/
if (L->next == NULL)
printf("空链表!");
else printf("非空链表!");
}
我们经常说,我们写的代码是在执行增删改查的操作,所以,以教材的形式实现一下链表的增删改查。
链表的查:
//定位获取链表中某一个元素 参数position是指定的位置
int GetNode(LNode *L, int *n, int position)
{
LNode *P = L;//创建临时变量并赋值为头结点 以免后移时丢失头结点位置
int j = 0;//计数器 由于这是带有头结点的链表 头结点中的数据域数据位空 所以计数器从0开始
while ((P != NULL) && (j <= position))
{
//指针后移 查找指定元素
P = P->next;
++j;
}
//未查找到指定元素的条件
if ((P == NULL) || (j > position))
return false;
else
{
*n = P->date;
return true;
}
}
由于带有头结点链表的特殊性,而且定位数值比这个值实际在链表中的位置总是小1,所以节点的计数器从0开始就刚好契合了这个特殊的性质,当累加器小于或者等于所定位的值并且下一个节点的地址不为NULL时,都要对链表遍历。
链表的增,也叫作向链表中插入节点:
//插入新节点到链表中指定的位置
LNode* InsertNode(LNode *L)
{
LNode *P = L;
int j = 0;//计数器 由于有数据域为空的头结点所以从0开始
int inser_position;//新节点将插入到position之前
printf("插入到链表中的位置:");
scanf("%d", &inser_position);
//定位第inset_position-1个节点的位置
while ((P != NULL) && (j < inser_position - 1))
{
P = P->next;
++j;
}
LNode *new_node = (LNode*)malloc(sizeof(LNode));//为新插入的节点申请内存空间
printf("输入节点的值:");
scanf("%d", &new_node->date);
new_node->next = P->next;
P->next = new_node;
return L;
}
由于链表在内存中的不连续性,所以要先将新节点和第position个节点链接起来,然后再讲第position-1个节点和新节点链接起来,插入操作的算法示意图如下:
链表节点的删除:
//删除链表中指定位置的节点
LNode* DeleteNode(LNode *L)
{
int j = 0;//计数器
LNode *P = L;
int delete_position;//删除节点的位置
printf("输入需要删除节点的位置:");
scanf("%d", &delete_position);
//寻找第delete_position - 1个节点的地址并保存在 P 中
while ((P->next != NULL) && j < delete_position - 1)
{
P = P->next;
++j;
}
if ((P->next == NULL) && (j > delete_position - 1))
{
printf("输入的位置非法,程序退出!\n");
return L;
}
LNode *Q = P->next;//记录第delete_position节点的地址
P->next = Q->next;//删除指定的节点
free(Q);//释放指定节点的内存空间
return L;
}
与链表的插入相类似,由于在内存中的不连续性,所以不需要大量的移动节点,只需要将要删除的节点的上一个节点的数据域值赋值为要删除节点的下一个节点的地址即可,执行完更改地址的操作之后,需要将被删除节点的空间释放,这一段话虽然听起来比较拗口,但其实不难理解。
由于链表值得改动只需要遍历链表找到需要更改的节点,将数据域的值改动即可,所以就不在此作赘述。
讲一个合并有序表吧,在LeetCode上也有类似的题目:
int main()
{
int La_node_size;//链表La的大小
int Lb_node_size;//链表Lb的大小
LNode *La, *Pa;
LNode *Lb, *Pb;
LNode *Lc, *Pc;
printf("输入链表La 和 Lb的大小:");
scanf("%d %d", &La_node_size, &Lb_node_size);
La = CreateList(La_node_size);
Lc = La;
OutputLinkList(La);
Lb = CreateList(Lb_node_size);
OutputLinkList(Lb);
Pa = La->next;
Pb = Lb->next;
Pc = Lc;
/*下面就是合并有序表的核心算法*/
while (Pa && Pb)
{
if (Pa->date <= Pb->date)
{
Pc->next = Pa;
Pc = Pa;
Pa = Pa->next;
}
else
{
Pc->next = Pb;
Pc = Pb;
Pb = Pb->next;
}
}
Pc->next = Pa ? Pa : Pb;
free(Pb);
OutputLinkList(Lc);
return 0;
}
链表是最为基本的数据结构知识,实现起来也相对于容易和简单,因此,这篇文章的技术性内容就到此结束。以后会经常更新相关的内容,也算是加强一下自己学的东西。
作者只是一个末流二本大学的大二学生,在大一下学期的时候开了数据结构的课程,最近正好有邀请我写了这个文章,所以临时写出来的东西有很多不足,还请各位看官提出来,欢迎友好的交流讨论。