链表的基础知识
链表的基础概念
链表概念:
-
链表是一种常用的数据结构,它通过指针将一些列数据结点,连接成一个数据链。相对于数组,链表具有更好的动态性(非顺序储存)。
-
一个结点分为数据域和指针域,数据域用来储存数据,指针域用于建立与下一个结点的联系。链表在内存中是非连续的。
-
建立链表时无需预先知到数据总量的,可以随机的分配空间,可以高效的在链表中实现灵活的内存动态管理。
链表与数组的不同:
- 操作上,如果数组在指定位置插入和删除会导致元素大量移动,耗时低效;而链表在指定位置插入和删除不需要移动元素,只需要修改指针即可。但是链表在查找数据时没有数组方便,查找效率相对于数组低。
- 内存上,链表相对于数组来讲,多了指针域空间开销。
链表分类:
-
1、静态链表 动态链表
静态链表和动态链表是线性表链式存储结构的两种不同的表示方式:
• 所有结点都是在程序中定义的,不是临时开群的,也不能用完后释放 ,这种链表 称为"静态链表"
•所谓动态链表,是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,井建立起前后相链的关系。 -
2、单向链表 双向链表 循环链表 单向循环链表 双向循环链表(下面的讲解用单向链表为例)
静态链表
链表节点类型定义:
#include <stdio.h>
//#include <string.h>
//#include <stdlib.h>
struct LinkNode
{
int data;//数据域
struct LinkNode* next;//指针域
};
void test()
{
struct LinkNode node1 = { 10,NULL };
struct LinkNode node2 = { 20,NULL };
struct LinkNode node3 = { 30,NULL };
struct LinkNode node4 = { 40,NULL };
struct LinkNode node5 = { 50,NULL };
node1.next = &node2;
node2.next = &node3;
node3.next = &node4;
node4.next = &node5;//将结点串起
}
int main()
{
test();
return 0;
}
链表的初始化与遍历
如何实现链表遍历?
链表的结点是通过指针来访问的,所以我们可以从指针下手:
我们考虑先创建一个指针struct LinkNode *pcurrent = &node1
,通过指针访问当前链节,再修改指针访问下一链节。
- 静态链表的遍历:
#include <stdio.h>
//#include <string.h>
//#include <stdlib.h>
struct LinkNode
{
int data;
struct LinkNode* next;
};
void test()
{
struct LinkNode node1 = { 10,NULL };
struct LinkNode node2 = { 20,NULL };
struct LinkNode node3 = { 30,NULL };
struct LinkNode node4 = { 40,NULL };
struct LinkNode node5 = { 50,NULL };
node1.next = &node2;
node2.next = &node3;
node3.next = &node4;
node4.next = &node5;//将结点串起
//遍历链表
//先定义一个辅助指针变量
struct LinkNode* pCurrent = &node1;
while (pCurrent != NULL)
{
printf("%d ", pCurrent->data);
//指针移动到下一个元素的首地址
pCurrent = pCurrent->next;
}
}
int main()
{
test();
return 0;
}
- 动态链表的初始化:
代码和注释:
#include <stdio.h>
//#include <stdlib.h>
#include <stdbool.h>
struct LinkNode* Init_LinkList()//头结点形式
{
//创建头结点
struct LinkNode* header = malloc(sizeof(struct LinkNode));//malloc可以实现动态内存分配
header->data = -1;
header->next = NULL;//头结点一般定义为空
//尾部指针
struct LinkNode* pRear = header;//先指向头结点
int val = -1;
while (true)
{
printf("请输入插入的数据:>\n");
scanf("%d", &val);
if (-1 == val)
{
break;
}
//先创建新结点
struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
newnode->data = val;
newnode->next = NULL;
//新节点插入到链表中
//利用pRear将新结点与前一个结点联系
pRear->next = newnode;
//更新尾部指针pRear,将pRear指针往后移
pRear = newnode;
}
return header;//返回头结点的地址可以找到这个链表的头从而找到整个链表
};
- 动态链表的遍历:
代码和注释:
void Foreach_Linklist(struct LinkNode* header)
{
if (NULL == header)
{
return;
}
//辅助指针变量
struct LinkNode* pcurrent = header->next;//因为头结点一般不储存数据,所以让指针变量指向头结点的下一节点
while (pcurrent != NULL)
{
printf("%d ", pcurrent->data);
pcurrent = pcurrent->next;//指针后移
}
}
动态链表的插入、清空、删除、销毁
- 链表的插入
考虑创建两个辅助指针,pCurrent指针记录oldval,pPrev指针记录oldval前一个节点。将新的结点插入pCurrent和oldval两个指针之间
代码和注释如下:
void InsertByValue_Linklist(struct LinkNode* header, int oldval, int newval)
{
if (NULL == header)
{
return;
}
//创建两个辅助指针变量
struct LinkNode* pCurrent = header->next;
struct LinkNode* pPrev = header;
while (pCurrent != NULL)
{
if (pCurrent->data == oldval)
{
break;
}
pPrev = pCurrent;
pCurrent = pCurrent->next;
}
//如果pCurrent为NULL说明链表中不存在值为oldval的结点
/*if (pCurrent == NULL)//将该代码注释掉后如果链表中不存在值为oldval的结点,则将newval插入到最后
{
return;
}*/
//创建新节点
struct LinkNode* newnode = malloc(sizeof(struct LinkNode));
newnode->data = newval;//将数据赋给新结点
newnode->next = NULL;//先初始化为NULL
//新结点插入链表中
newnode->next = pCurrent;
pPrev->next = newnode;
}
- 链表的清空(只剩下头结点,其余部分释放掉)
代码和注释如下:
void Clear_LinkList(struct LinkNode* header)
{
if (NULL == header)
{
return;
}
struct LinkNode* pCurrent = header->next;//因为头结点不释放,所以将pPcurrent指向下一节点
//但是释放前还要保存pPcurrent的指针域,所以还要再创建一个辅助指针
if (pCurrent != NULL)
{
//先保存当前结点的下一结点地址
struct LinkNode* pNext = pCurrent->next;
//释放当前结点内存
free(pCurrent);
pCurrent = pNext;//pCurrent指针往后移
}
header->next = NULL;
}
- 链表的删除(删除某个结点)
void RemoveByValue_Linklist(struct LinkNode* header, int delValue)
{
if (NULL == header)
{
return;
}
//创建两个辅助指针
struct LinkNode* pPrev = header;
struct LinkNode* pCurrent = header->next;
while (pCurrent != NULL)
{
if (pCurrent->data == delValue)
{
break;
}
//移动两个辅助指针
pPrev = pCurrent;
pCurrent = pCurrent->next;
}
if (pCurrent == NULL)//没找到delValue
{
return;
}
//重新建立待删除结点的前驱和后继结点关系
pPrev->next = pCurrent->next;
//释放删除结点内存
free(pCurrent);
pCurrent = NULL;
}
- 链表的销毁(全部释放掉,包括头结点)
void Destroy_Linkrist(struct LinkNode* header)//连头结点都销毁
{
if (NULL == header)
{
return;
}
//创建两个辅助指针
struct LinkNode* pCurrent = header;//与链表的清空区别在这里
while (pCurrent != NULL)
{
struct LinkNode* pNext = pCurrent->next;
//释放当前结点内存
free(pCurrent);
pCurrent = pNext;//pCurrent指针往后移
}
}
最后感谢大家的支持!!!
也祝大家圣诞快乐!!!