在对顺序表有一定的了解之后,我们会发现,顺序表的头插头删不是很方便,每头插一次就要将整个表中的每个值向后移动,效率骤减,对此有没有更好的方法呢,那么链表就可以弥补这个问题,下面我们就来看看链表
一、链表的概念
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序决定的
上图为一个简单的单向链表的图示,值得注意的是
上图的结构在逻辑上是连续的,但是在物理地址上却可能不是连续的
一般节点都是从堆上申请出来的
从堆上申请的空间,可能是连续的,也可能是不连续的
二、链表的分类
单向或双向链表
带头和不带头
循环和不循环
三、链表的实现
typedef int SLTDateType;//为了便于表示结构体的成员
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode*buy=(struct SListNode*)malloc(sizeof(SListNode));
if(buy==NULL)
{
perror("BuySListNode::malloc false");
}
buy->val=x;
buy->next=NULL;
return buy;
}
// 单链表打印
void SListPrint(SListNode* plist)
{
if(plist==NULL)
{
perror("SListPrint::plist==NULL");
return;
}
SListNode*print=plist;
while(print!=NULL)
{
printf("%d ",print->val);
print=ptint->next;
}
printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
//SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
//newlist->val=x;
//newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
SListNode*newlist=BuySListNode(x);
if(*pplist==NULL)
{
SListNode*
}
else
{
while(*pplist->next!=NULL)
{
*pplist=*pplist->next;
}
*pplist->next=newlist;
}
}
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
//SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
//newlist->val=x;
//newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
SListNode*newlist=BuySListNode(x);
newlist->next=*pplist;
*pplist=newlist;
}
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
if(pplist->next==NULL)
{
//只有一个节点
free(*pplist);
*pplist=NULL;
}
else
{
SListNode*tail=*pplist;
while(tail->next->next!=NULL)
{
tail=tail->next;
}
free(tail->next);
tail->next=NULL;
}
}
// 单链表头删
void SListPopFront(SListNode** pplist)
{
if(pplist==NULL)
{
perror("SListPopBack::pplist==NULL");
return;
}
SListNode*first=*pplist;
pplist=first->next;
free(first);
first=NULL;
}
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
if(plist==NULL)
{
printf("找不到\n");
return;
}
SListNode*cur=plist;
while(cur!=NULL)
{
if(cur->val==x)
{
return plist;
}
cur=cur->next;;
}
printf("找不到\n");
return NULL;
}
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
//SListNode*newlist=(struct SListNode*)malloc(sizoef(SListNode));
//newlist->val=x;
//newlist->next=NULL;
//上述步骤可以用已经写好的申请节点函数
SListNode*newlist=BuySListNode(x);
SListNode*tmp=pos->next;
pos->next=newlist;
}
分析思考为什么不在pos位置之前插入?
其实问题还是明显的,若是在pos前的位置插入,我们无法直接得到pos前这个位置,仍需从链表头开始查找。
如图,pos的前一位,若要找到pos的前一位,则必须从头开始找;很不方便
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
if(pos->next==NULL)
{
return ;
}
SListNode*del=pos->next;
pos->next=del->next;
free(del);
del=NULL;
}
// 分析思考为什么不删除pos位置?
原因也很简单,若要删除pos位置的值,我们还得从头遍历找到前一个节点,这样的话才能将pos位前与pos位后的节点连接起来
到这里对链表有了简单认识,建议手敲上述使用代码,看上去很简单,但是画图再理解真的很有用,下次我们针对简单链表出一期练习