1.链表的特点
(1)申请的空间可以不连续
(2)链表访问不方便
(3)插入、删除不需要移动元素,比较方便
头结点:数据域无效
首结点:第一个数据域有效的结点
尾结点:指针域无效
空链表:既是头结点又是尾结点
2 .链表的分类
有没有头结点:
带头结点的链表和不带头结点的链表
一般使用的是带头结点的链表
指针域是双向还是单项
单项链表和双向链表
尾结点是否指向头结点:
循环链表和不循环链表
3. 定义数据元素的类型
typedef int data_type;
4. 定义链表中结点的数据类型
typedef struct linknode
{
data_type data;//数据域
struct linknode *pNext;//指针域
}Link;
5.对链表的操作
5.1插入元素
对链表的插入可以分为三种插入方式,分别为:头插法、尾插法和中间插入
这是插入前的准备:创建一个新的节点,并为其赋值
//创建新的节点
Link *pNew = (Link *)malloc(sizeof(Link));
if(NULL == pNew)
{
return MALLOCERROR;
}
memset(pNew,0,sizeof(Link));
//将数据赋值给新建节点的data域
pNew->data = item;
头插法:
顾名思义,头插法是将新的元素插入到头结点的后面。
先将头结点与首节点联系断开,将首节点的地址(存放在头结点的指针域)赋给要插入的新节点的指针域,这样子新节点就和后面的所有节点连接了起来,起到了保护后面所有节点的作用;
然后将新节点的地址赋值给头结点的指针域,覆盖掉原本存放的首节点的地址,这样操作就完成了对链表的头结点插入元素
//头插法
case HEAD:
//保护后面的所有节点
if(pHead->pNext != NULL)
{
pNew->pNext = pHead->pNext;
}
//新节点插入
pHead->pNext = pNew;
break;
尾插法:
顾名思义,尾插法就是将新的节点插入到链表的的最后面,也就是尾节点之后。
它的做法和头插法一样,只是要如何才能找到尾结点
首先,我们定义一个指针变量,并将其初始化为头结点;
然后我们可以发现,只有尾结点的指针域为NULL,则可以执行循环,每当我们所定义的指向头节点的指针变量的指针域不为NULL的时候,就让其后移一位,直到循环结束,找到了指针域为NULL的尾结点,然后将新节点插入到之后,并将新节点的指针域置为NULL
Link *pTail = NULL;
//尾插法
case TAIL:
pTail = pHead;
while(pTail->pNext != NULL)
{
pTail = pTail->pNext;
}
//插入新节点
pTail->pNext = pNew;
break;
中间插入:
中间插入就是将数据元素插入到链表的中间位置
首先,定义一个指针变量,初始化为头结点,需要找到插入节点的前一个节点
因为,要插入的位置的前一个节点保存了要插入位置的后一个节点的地址
知道了后面节点的地址才能对其进行操作
Link *pPre = NULL;
int i = 0;
//中间插入
default:
pPre = pHead;
pTail = pHead->pNext;
if(NULL == pTail)
{
return LINKNULL;
}
while(pTail->pNext != NULL && i < pos-1)
{
pPre = pTail;
pTail = pTail->pNext;
i++;
}
pNew->pNext = pPre->pNext;
pPre->pNext = pNew;
5.2 显示链表
对于链表的显示,由于其物理地址不连续,不能通过下标遍历,需要指针的移动来访问链表的每一个节点。
思路如下:链表的结束遍历标志为遍历到尾结点,即当一个节点的指针域为NULL的时候,就找到了尾结点,链表的遍历也就完成了
代码如下:
//显示链表
//参数:链表的首地址
//返回值:成功返回ok,失败返回原因
int showLink(Link *pHead)
{
//入参判断
if(NULL == pHead)
{
return LINKNULL;
}
//定义一个结构体指针,指向链表的首地址
Link *pTemp = NULL;
pTemp = pHead->pNext;
//循环遍历直到pTemp->pNext为空
while(pTemp != NULL)
{
printf("%-4d",pTemp->data);
pTemp = pTemp->pNext;
}
printf("\n");
return ok;
}
5.3 根据位置删除链表中的元素
删除元素与插入元素思想一致,都是需要先要找到要操作的元素的位置,后续操作与插入相似
代码如下:
//根据位置删除链表中的元素
//参数一:链表的首地址 Link *pHead
//参数二:要删除的位置 int pos
//参数三:保存要删除的数据
//返回值:成功返回ok,失败返回原因
int deleteLink(Link *pHead,int pos,data_type *pData)
{
//入参判断
if(NULL == pHead)
{
return LINKNULL;
}
//定义一个结构体变量,保存要删除的节点的首地址
Link *pDel = NULL;
Link *pPre = NULL;
int i = 0;
switch(pos)
{
//头删法
case HEAD:
//找到要删除的节点
pDel = pHead->pNext;
//保存要删除的数据
*pData = pDel->data;
//保护后面的所有节点
pHead->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
break;
//尾删法
case TAIL:
//找到尾节点和他前面的一个节点
pPre = pHead;
pDel = pHead->pNext;
if(NULL == pDel)
{
return LINKNULL;
}
while(pDel->pNext != NULL)
{
//同时移动两个指针
pPre = pDel;
pDel = pDel->pNext;
}
//保存要删除的数据
*pData = pDel->data;
//pPre的指针域置为空
pPre->pNext = NULL;
//释放pDel
free(pDel);
pDel = NULL;
break;
//中间插入
default:
pPre = pHead;
pDel = pHead->pNext;
if(NULL == pDel)
{
return LINKNULL;
}
//将两个指针同时向后移动pos-1次
while(i < pos-1)
{
pPre = pDel;
pDel = pDel->pNext;
i++;
}
//保存要删除的数据
*pData = pDel->data;
//保护后面的所有节点
pPre->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
}
return ok;
}
5.4 销毁链表
当使用完链表后,就需要对其进行销毁,释放内存
思想:使用头删法的思想,对链表中的节点挨个进行删除,直到为空链表
此时只剩下头结点,再将头结点free释放,指针置空
//销毁链表
//参数:链表的首地址的地址
int destroyLink(Link **pLink) //pLink=&pHead -> *pLink=pHead
{
//入参判断
if(NULL == *pLink)
{
return LINKNULL;
}
while(1)
{
//对链表中的数据挨个删除
//找到要删除的节点
Link *pDel =( *pLink)->pNext;
if(NULL == pDel)
{
break;
}
//保护好要删除节点后面的所有节点
(*pLink)->pNext = pDel->pNext;
//释放pDel
free(pDel);
pDel = NULL;
}
//释放头节点
free(*pLink);
*pLink = NULL;
return ok;
}