文章目录
链表可以说是数据结构中最基础的一部分,它分为单链表和双链表,他们的区别在于,单链表的结构中只有一个节点来指向它的后面的元素,而双链表中有两个节点,一个节点可以指向它前面的元素,一个可以指向它后面的元素,这就使得链表的使用更加方便和灵活。下面我们来看看双链表具体是怎么实现的吧。
1.定义链表的结构体
首先,我们可以来定义一个链表的结构体,里面包含三个部分,一个是数据块部分,一个是指向后一个节点的结构体指针,还有一个是指向前一个节点的结构体指针。
typedef struct ListNode
{
int date; //数据
struct ListNode* next; //指向后面的节点的指针
struct ListNode* prve; //指向前面的节点的指针
}ListNode;
在实现链表的功能之前,我们先来做一些准备工作。
1.首先是创建一个新的链表节点
ListNode* creatNode(int x)
{
ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->date = x;
newNode->next = NULL;
newNode->prve = NULL;
return newNode;
}
2.然后是链表的初始化,就是创建一个链表的虚拟头结点,方便我们对链表进行增删改查操作。
ListNode* InitList()
{
ListNode* head = BuyListNode(0);
head->next = NULL;
head->prve = NULL;
return head;
}
2.链表的插入
2.1 链表的尾插
首先我们来看一下双链表的结构,双链表结构为一个环状结构如下图所示
那么如果我们有一个新的节点newnode那我们应该怎么进行尾插呢?
第一步:既然我么要尾插,那么我们当然是要先找出链表的尾了。在单链表中,我们要找到一个链表的尾,都是遍历链表,找到最后一个节点,但从双链表的结构中我们不难看出,链表的尾就是phead—>prve所指向的节点。
第二步:然后让尾的next指向新节点,新节点的preve指向尾节点.
第三步:然后让phead的prve 指向的新节点,然后新节点的next指向phead。
下面看代码的具体实现过程
void ListPushBack(ListNode* phead,int x)
{
assert(phead);
ListNode* tail = phead->prve; //找到尾节点
ListNode* newNode = BuyListNode(x); //创建新节点
tail->next = newNode; //尾节点的next指向新节点,
newNode->prve = tail; //新节点的preve指向尾节点.
newNode->next = phead; //新节点的next指向phead
phead->prve = newNode; //phead的prve 指向的新节点
}
2.2链表的头插
理解了链表的尾插后,链表的头插也就很好理解了。但需要注意的是,这里的头插不是在phead的前面插入,这里的phead是我们创建出来的虚拟的头结点,方便我们对链表操作的,真实的头结点是p1,如下图所示:
下面直接看代码
void ListPushFront(ListNode* phead, int x)
{
ListNode* first = phead->next; //创建一个first指向链表的第一个节点
ListNode* newNode = BuyListNode(x); //创建一个新节点
phead->next = newNode; //让phead的next指向新节点
newNode->prve = phead; //新节点的prve指向
newNode->next = first; //新节点的next指向第一个节点
first->prve = newNode; //第一个节点的prve指向新节点
}
2.3任意位置插入
在任意位置插入,首先我们要给定一个pos位置,然后自己设定在pos位置前插还是后插,这里我就以前插举例,下面看代码的具体实现
void ListInsert(ListNode* pos, int x)
{
assert(pos);
ListNode* newNode = BuyListNode(x);//创建出新结点
ListNode* posPrve = pos->prve; //记录pos位置的前一个结点
newNode->next = pos; //新节点的next指向pos;
pos->prve = newNode; //pos的prve 指向newNode
posPrve->next = newNode; //pos的前一个节点指向新节点
newNode->prve = posPrve; //新节点的前一个节点指向pos的前一个节点
}
3.链表节点的删除
3.1链表的尾删
链表的尾删同样是,第一步:先找到链表的尾,也就是之前分析的phead->prve。
第二步:然后就是要找到链表尾的前一个节点,也就是phead->prve->prve;
第三步:然后让尾的前一个节点的next指向phead;让phead的prve指向尾的前一个节点。
下面看代码具体实现部分:
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next!=phead); //只剩phead一个节点时不能删除
ListNode* tail = phead->prve; //找到尾节点
ListNode* tailPrve = tail->prve; //找到尾节点的前一个节点
tailPrve->next = phead; //尾的前一个节点的next指向phead
phead->prve = tailPrve; //让phead的prve指向尾的前一个节点
free(tail); //释放掉尾节点的空间
tail = NULL;
}
3.2链表的头删
链表的头删和尾删的思路也就差不多,就是要找到链表的头,以及链表头的下一个节点,来方便进行删除操作。话不多说,直接来看代码
void ListPopFront(ListNode* phead)
{
assert(phead);
assert(phead != phead->next);
ListNode* first = phead->next; //找到头结点
ListNode* second = first->next; //找到头结点的下一个结点
phead->next = second; //phead的next指向头结点的下一个结点
second->prve = phead;//头结点的下一个节点指向phead
free(first); //释放掉头结点多占的空间
first = NULL:
}
3.3任意位置的删除
任意位置的删除实现起来比较简单,也是只要知道该位置的前一个节点和后一个节点就行了
void ListErase(ListNode* pos)
{
ListNode* posPrve = pos->prve;
ListNode* posNext = pos->next;
posPrve->next = posNext;
posNext->prve = posPrve;
free(pos);
pos=NULL;
}
4.链表的修改
4.1链表的查找
要修改链表中某个结点的值,那么我们必须先要找到这个节点,然后对其中的值进行修改,这个比较简单,直接看代码吧。
ListNode* ListFind(ListNode* phead, int x)
{
assert(phead);
ListNode* cur = phead->next;
while(cur)
{
if(cur->date == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
4.2链表数据的修改
找到这个节点后,然后进行修改
void ListModify(ListNode* phead, int x , int y)
{
assert(phead);
ListNode* pos = ListFind(phead, x);
if(pos!=NULL)
{
pos->date = y;
}
else
{
printf("链表中无此节点\n");
}
}
5.清除链表
清除链表,这里提供两种方式,一种是清除链表中所有的节点,包括头结点,另一种是清除链表中所有的节点,不包括头结点
5.1清理链表中的所有节点,不包括头结点
将链表中所有节点删除,但保留头结点
void ListClear(ListNode* phead)
{
ListNode* cur = phead->next;
while(cur)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
phead->next = phead; //让头结点指向自己
phead->prve = phead;
}
5.2清理链表中的所有节点,包括头结点
void ListDestory(ListNode** phead)
{
assert(*phead);
ListClear(*phead); //调用上面的函数
free(*phead);
*phead = NULL;
}