温习链表类型
初始化链表
实现接口:
尾插
头插
头删
尾删
查找
打印
任意插入,在pos的前面插入x
任意删除,删除pos位置的值
释放空间
上节我们探讨到了头插。详见双链表(上)
头删
本质上来讲就是把哨兵位的后面那个删除,也就是第一个节点删除(注意不是头节点!)
我们首先来看一下原理图:
这里要注意:头节点删了,结构被破坏了,后面就不能进行操作了!头节点是整个结构的核心,删除了就找不到整个链表了,我们后续可能还会进行操作,所以千万不能删除头节点。
我接下来将思想转换为代码
//头删
void ListPopFront(ListNote* phead)
{
//不能为空
assert(phead);
//头节点删了,结构被破坏了,后面就不能进行操作了
assert(phead->next != phead);
//找第二个
ListNote* first = phead->next;
ListNote* secend = first->next;
//删掉第一个
free(first);
first = NULL;
//哨兵位指向原来的第二个
phead->next = secend;
//原来的第二个指向哨兵位
secend->prev = phead;
}
我们可以思考一下如果只有一个节点会不会让程序崩溃呢?我们从原理图得知应该是不会。
这时候有些小伙伴可能想测试一下,这里详见顺序表(三)
为了便于我们测试我们再写一个打印函数。
这个比较简单,我就直接写了。
//打印
void ListPrint(ListNote* phead)
{
//不能为空
assert(phead);
ListNote* cur = phead->next;
while (cur != phead)
{
printf("%d->", cur->data);
//下一个位置
//cur是当前位置的地址,cur->next存的是下一个位置的地址
cur = cur->next;
}
//表示打印完了
printf("NULL\n");
}
有了打印函数就很好测试了,先测一下双链表(上)写的内容
这里如果有结果不一样,详见双链表(上)
接下来我们测试一下头删
因为我们之前打了断言所以我们很清楚的知道双链表中只剩下头节点了,所阻止了继续头删。
没有问题的话我们继续
尾删
我们首先了解原理图:
跟头删一样我们也思考一个问题:我们可以思考一下如果只有一个节点会不会让程序崩溃呢?我们从原理图得知应该是不会。
接下来就是写代码了
//尾删
void ListPopBack(ListNote* phead)
{
//不能为空
assert(phead);
//头节点删了,结构被破坏了,后面就不能进行操作了
assert(phead->next != phead);
//找尾
ListNote* tail = phead->prev;
//找倒数第二个
ListNote* prev = tail->prev;
//该指向
prev->next = phead;
phead->prev = prev;
//释放空间
free(tail);
tail = NULL;
}
为了确保我们写对了,这时候我们最后测试一下
显然没有问题,接下来我们继续。
任意插入,在pos的前面插入x
我们还是首先来看一下原理图:
我们发现首先我们必须解决怎么找到pos,然后才是如何插入的问题
//查找
ListNote* ListFind(ListNote* phead, SLTDateType x)
{
//不能为空
assert(phead);
//倒着走也行
ListNote* cur = phead->next;
while (cur != phead)
{
if(cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
这个查找函数显然也可以倒着走,方法不唯一。
我们再把它结合起来。
//pos前插入x
void ListInsert(ListNote* pos, SLTDateType x)
{
//不能为空
assert(pos);
//pos的前一个节点
ListNote* prev = pos->prev;
//扩容
ListNote* newnode = BuyListNote(x);
//prev指向新节点
prev->next = newnode;
newnode->prev = prev;
//新节点指向pos
newnode->next = pos;
pos->prev = newnode;
}
我们再来测试一下,看看结果是否和我们想要的一样。
结果果然与我们预想的一样。
任意删除,删除pos位置的值
我们还是首先来看一下原理图:
有了上个函数的经验我们很快就写出代码
//删除pos位置的值
void ListErase(ListNote* pos)
{
//不能为空
assert(pos);
//pos的下一个
ListNote* next = pos->next;
//pos的前一个
ListNote* prev = pos->prev;
//prev指向next
prev->next = next;
next->prev = prev;
//释放空间
free(pos);
pos = NULL;
}
那么我们再来测试一下我们写的是否正确呢?
最后面的释放空间就很简单了。
释放空间
//还空间
void ListDestory(ListNote* phead)
{
//不能为空
assert(phead);
ListNote* cur = phead->next;
while (cur != phead)
{
ListNote* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
这里要注意先后顺序,所以最好采用双指针的手法。
写到最后,我们其实可以再来修改一下,因为函数之间可以相互调用,详见顺序表(四)
尾插修改
注意这里为什么传phead ,因为phead后一个就是pos的前一个!
//尾插
void ListPushBack(ListNote* phead, SLTDateType x)
{
//注意:
ListInsert(phead, x);
}
头插修改
//头插
void ListPushFront(ListNote* phead, SLTDateType x)
{
ListInsert(phead->next, x);
}
头删修改
//头删
void ListPopFront(ListNote* phead)
{
ListErase(phead->next);
}
尾删修改
//尾删
void ListPopBack(ListNote* phead)
{
ListErase(phead->prev);
}
这些基本上都能举一反三,就不做重复的解释了。
写到这里如果大家还想继续完善接口的话可以再补充一个计数函数
计数
//计数
int ListSize(ListNote* phead)
{
ListNote* next = phead->next;
//不能为空
assert(phead);
//计数
int count = 0;
while (next)
{
if (next == phead)
{
return count;
}
next = next->next;
count++;
}
return NULL;
}
可以测试一下
写到这里基本就写完了,大家还可以再写个菜单什么的,详见顺序表(四)
这里就不做过多的解释了。
求各位大佬点赞加关注,会有更多关与数据结构的内容呈现哦!