链表是顺序表的一种,它是由一个一个的结点组成,逻辑上是一种顺序表,在物理地址上看来是离散的,这些结点随机分配在内存中的各个位置,要想将他们链接起来则需要依靠我们的指针
首先:结点的结构由一个指向下一个结点的指针和数据段所组成(如下图)
typedef int ElemType;
typedef struct SLNode
{
struct SLNode* next;
ElemType a;
}SLNode;
接口实现预览
void SLPrint(SLNode* plist);
void SLPushBack(SLNode** pplist, Elemtype x);
void SLPopBack(SLNode** pplist);
void SLPushFront(SLNode** pplist, Elemtype x);
void SLPopFront(SLNode** pplist);
void SLPushPosBefore(SLNode** pplist, SLNode* pos, Elemtype x);
void SLPushPosAfter(SLNode* pos, Elemtype x);
SLNode* SLFind(SLNode* pplist, Elemtype x);
int SLSize(SLNode* plist);
//删除pos之前和删除pos之后
void SLErasePosAfter(SLNode* pos);
void SLErasePosBefore(SLNode** pplist, SLNode* pos);
int SLIsEmpty(SLNode* plist);
首先我们先准备一下创建一个结点和打印整个链表这两个接口,方便我们后续的使用和查看
需要注意!!新建的结点的next指针一定要置空!!
void SLPrint(SLNode* pplist)
{
SLNode* cur = pplist;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
SLNode* SLBuyNode(ElemType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
newnode->next = NULL;
newnode->data = x;
return newnode;
}
尾插和尾删
1.尾插
插入分为两种情况:第一种 链表为空的 第二种 链表有元素
第一种,创建一个结点直接链接到头指针
第二种,创建一个结点,遍历整个链表找到最后一个结点,将最后一个结点的next链接到这个新的结点上
实现如下:
void SLPushBack(SLNode** pplist,Elemtype x)
{
SLNode* newnode = BuySLNode(x);
//如果节点为空 头指针直接等于新节点的地址
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
//不是空节点时 创建一个新的节点 插到尾部 所以要找尾
SLNode* cur = *pplist;
while (cur->next!=NULL)//cur的下一个是空则停止遍历
{
cur = cur->next;
}
cur->next = newnode;
}
2.尾删
分三种情况: 第一种 一个结点都没有什么也不干
第二种 只有一个结点删除该结点,将指针设置为空
第三种 两个或两个以上的结点,我用一个pre指针存储尾删的上一个结点,tail存储准备删除的结点,遍历一遍使tail结点是最后一个结点,让pre->next指针指向空
实现如下
void SLPopBack(SLNode** pplist)
{
//如果为空链表,则什么也不干
if (*pplist == NULL)
{
return;
}
else if( (* pplist)->next==NULL)//只有一个节点
{
free(*pplist);
*pplist = NULL;
}
else//有两个及以上的节点个数
{
//遍历找到要删除的最后那个节点 并且要找到他的前一个节点
SLNode* tail = *pplist;
SLNode* prev = NULL;
while(tail->next)
{
prev = tail;
tail = tail->next;
}//找到了
free(tail);
prev->next = NULL;
}
}
测试一下啊,尾插几个元素,打印一遍,删除几个元素再打印一遍
头插和头删
1.头插
头插分两种情况:第一种 链表为空,则直接将头指针指向新的结点,其他什么也不干
第二种 链表有一个或者一个以上的结点,则需要将新结点指向头指针指向的结点,因为头指针指向的结点就是第一个结点,再将头指针指向新的结点,完成头插
注意一定要先让新结点指向原来的第一个结点,如果让头指针先指向新结点的话,就会丢失原来第一个结点
假设链表为空,他的操作和不为空其实是一样的,以下一段代码足够了
void SLPushFront(SLNode** pplist ,Elemtype x)
{
SLNode* newnode = BuySLNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
2.头删
头删分为两种情况:第一种 空链表,我们什么也不敢,或者你可以给个断言之类的看个人喜好
第二种 非空链表,用一个结点指针存储要删除的结点,这样就不会丢失结点地址了,再把头结点指向要删除的结点的下一个结点
代码段如下
void SLPopFront(SLNode** pplist)
{
//如果是空节点 就什么也不干
if (*pplist == NULL)
{
return;
}
else//一个和多个节点的删除方式一样,一个的删除后下一个节点为空
{
SLNode* next = (*pplist)->next;
free(*pplist);
*pplist = next;
}
}
测试一下
在给定的pos位置处插入和删除
那么有个问题我们怎么知道pos的位置呢?答案是得我们自己找,就需要给定一个查找的词条如我要找到链表中1的位置,我们传入1就可以了,不过链表不适合随机访问所以我们要从头开始遍历才行,所消耗的时间也会大大增加,不过我们也要实现一下,代码如下
找到了就返回那个节点的地址,如果没找到就返回NULL
SLNode* SLFind(SLNode* pplist,Elemtype x)
{
SLNode* cur = pplist;
//遍历一遍如果找到返回地址
while (cur) //如果是空的的话就不找了,说明没找到,返回空
{
//进来了就说明现在不是空
if (cur->data == x)
{//找到了
return cur;
}
//没找到遍历
cur = cur->next;
}
return NULL;
}
插入
1.插入又分为在pos前插入和在pos之后插入
pos前插入
由于我们使用的是无头不循环单链表,所以我们没有办法知道pos之前的那个结点的位置,所以我们只能遍历一遍来找到pos之前的位置,那么我们则需要两个指针来干这个活
第一个cur指针,他存储的是当前位置的结点的地址,第二个指针prev存cur前一个结点的地址,当cur==pos时,prev正好是cur上一个结点,这样就可以在prev后插入结点了,插入结点前面以及讲过了,就不再重复了
void SLInsertBefore(SLNode** pplist, SLNode* pos, Elemtype x)
{
assert(pos);
SLNode* newnode = BuySLNode(x);
//需要两个指针 ,一前一后,找到pos的位置时前一个指针正好是pos之前那个节点
if (pos==*pplist)//如果只有一个节点 那就是头插 要注意啊
{
newnode->next = *pplist;
*pplist = newnode;
}
else//两个及两个以上的节点的操作
{
SLNode* prev = NULL;
SLNode* next = *pplist;
while (next != pos)
{
prev = next;
next = next->next;
}//找到了next==pos节点开始插入
newnode->next = pos;
prev->next = newnode;
}
}
2.在pos后插入结点就很容易了,只需要将新节点插入到pos后就行了,但是要注意需要先让新节点的next存入pos的下一个节点的地址 哦!否则链表就断开了
void SLInsetAfter(SLNode* pos, Elemtype x)
{
assert(pos);
SLNode* newnode = BuySLNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
测试一下:
删除
1.在pos位置之后删除
这个也相对简单,只需要将pos后的节点删除即可,但是要注意要先保存删除节点的下一个节点哦,要不然链表会断开,让pos去链接上删除节点的下一个节点
也要考虑到如果pos后面没有节点的情况,一般我们不处理直接return,有特殊要求再处理
void SLErasePosAfter(SLNode* pos)
{
if (pos->next==NULL)
{
return;
}
else
{
SLNode* next = pos->next;
pos->next = next->next;
free(next);
}
}
2.删除pos位置之前的节点
和插入相同,我们并不知道pos前一个节点的位置,并且我们还得知道pos节点的上上一个节点的地址,不然无法链接,这就相对麻烦一些了
也要考虑很多种情况,如果pos前没有节点了,或者pos之前只有一个节点
代码如下:
void SLErasePosBefore(SLNode** pplist, SLNode* pos)
{
SLNode* cur = *pplist;
SLNode* prev = NULL;
if (pos == *pplist)
{
return;
}
else if(( * pplist)->next==pos)
{
free(cur);
*pplist = pos;
}
else
{
while (cur->next != pos)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = pos;
}
}
测试一下
其他接口
计算链表的长度size
int SLSize(SLNode* plist)
{
int count = 0;
SLNode* cur = plist;
while (cur)
{
count++;
cur = cur->next;
}
return count;
}
判断链表是否为空链表
int SLIsEmpty(SLNode* plist)
{
return plist == NULL;
}
测试一下
感谢观看不妨点个关注?您的支持是我最大的动力,如有错误请大力指正,谢谢!!!