引言
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
双向循环链表
双向循环链表节点
typedef int CycleList_Data;
typedef struct Cycle_Node
{
CycleList_Data data;
struct Cycle_Node* front;
struct Cycle_Node* next;
}Cycle_List;
双向循环链表和单链表相比增加了一个指向前驱的指针域,可以通过某节点的指针p即可以直接得到他的前驱和后继,这样在有些操作中需要找前驱时,无需再从头寻找。
假设pMove指向双链表的某一节点,即pMove是该节点的指针。
注意:
1)
pMove->front->next
表示的是pMove
节点的前驱节点的后继节点的指针,即与pMove相等。
2)pMove->next->front
表示的是pMove
节点的后继节点的前驱节点的指针,也与pMove相等
创建头节点
Cycle_List* Create_HeadNode()
{
Cycle_List* Head_Node = (Cycle_List *)malloc(sizeof(Cycle_List));
if(Head_Node == NULL)
{
// printf("创建失败\n");
return NULL;
}
else
{
Head_Node->front = Head_Node;
Head_Node->next = Head_Node;
return Head_Node;
}
}
当链表中只有一个头节点时,要清楚指针的指向,前驱指针域和后继指针域指向本身
创建节点
Cycle_List* Create_NewNode(CycleList_Data data)
{
Cycle_List* New_Node = (Cycle_List*)malloc(sizeof(Cycle_List));
if(New_Node == NULL)
{
//printf("创建失败\n");
return NULL;
}
New_Node->data = data;
New_Node->front = NULL;
New_Node->next = NULL;
return New_Node;
}
表尾插入
void Insert_List(Cycle_List* Head_Node,CycleList_Data data)
{
Cycle_List* New_Node = Create_NewNode(data);
New_Node->next = Head_Node;
New_Node->front = Head_Node->front;
Head_Node->front->next = New_Node;
Head_Node->front = New_Node;
}
双向循环链表有表尾插入和表头插入,在这里使用的是表尾插入。
记住,在插入过程中顺序不可搞错
1、New_Node->next = Head_Node;
2、New_Node->front = Head_Node->front;
3、Head_Node->front->next = New_Node;
4、Head_Node->front = New_Node;
表头插入
表头插入思路和表尾类似
打印链表*
void Printf_List(Cycle_List* Head_Node)
{
Cycle_List* pMove = Head_Node->next;
while(pMove != Head_Node)
{
printf("%d->",pMove->data);
pMove = pMove->next;
}
printf("NULL\n");
}
定义一个移动指针pMove=Head_Node->next
开始遍历整个链表,当pMove=Head_Node
时,链表遍历一遍
查找某值
void Find_Bydata_List(Cycle_List* Head_Node,CycleList_Data posdata)
{
Cycle_List* posNode = Head_Node->next;
while(posNode->data != posdata)
{
if(posNode == Head_Node)
{
printf("查找失败\n");
return ;
}
posNode = posNode->next;
}
printf("查找成功\n");
return ;
}
插入数据
void Insert_Bydata_List(Cycle_List* Head_Node,CycleList_Data posdata,CycleList_Data data)
{
Cycle_List* posNode = Head_Node->next;
Cycle_List* New_Node = Create_NewNode(data);
while(posNode->data != posdata)
{
if(posNode == Head_Node)
{
printf("插入失败\n");
return ;
}
posNode = posNode->next;
}
New_Node->next = posNode;
posNode->front->next = New_Node;
New_Node->front = posNode->front;
posNode->front = New_Node;
}
插入数据首先找到插入到哪个位置,然后进行插入操作。
先看下面的图片
找到“位置”posNode
,然后依次进行①②③④操作。
上述操作为指定位置前驱插入
后继插入
void InsterData_Cycle_List(Cycle_List* Head_Node,CycleList_Data posdata,CycleList_Data data)
{
Cycle_List* posNode = Head_Node->next;
Cycle_List* New_Node = Create_NewNode(data);
while(posNode->data != posdata)
{
if(posNode == Head_Node)
{
printf("插入失败\n");
return ;
}
posNode = posNode->next;
}
New_Node->next = posNode->next;
posNode->next = New_Node;
New_Node->front = posNode;
New_Node->next->front = New_Node;
printf("插入成功\n");
return ;
}
分析方法和前驱一样,这里不过多叙述。
删除某值
void Delete_Cycle_List(Cycle_List* Head_Node,CycleList_Data posData)
{
Cycle_List* posNode = Head_Node->next;
while(posNode->data != posData)
{
if(posNode == Head_Node)
{
printf("不存在该值,删除失败\n");
return ;
}
posNode = posNode->next;
}
posNode->front->next = posNode->next;
posNode->next->front = posNode->front;
free(posNode);
printf("删除成功\n");
}
看上面的图片,删除节点是不是和插入节点很类似?
删除节点时直接执行图中的1、2步骤就行了,然后再释放一下posNode
节点所申请的内存即可。
①、posNode->front->next = posNode->next;
②、posNode->next->front = posNode->front;
简单总结一下,双向链表相对于单链表来说,要更复杂一些,毕竟它多了front
指针,对于插入和删除时,需要格外小心。另外它由于每个结点都需要记录两份指针,所以在空间上是要占用略多一些的。不过,由于它良好的对称性,使得对某个结点的前后结点的操作,带来了方便,可以有效提高算法的时间性能。说白了,就是用空间来换时间
在插入节点时,要注意一下执行顺序,我们以插入某值来分析一下:
一定要记住第4步posNode->front = New_Node
不能放到第一步。
假设先执行了第4步,此时posNode
的前驱变成了New_Node
,而原先链表posNode
前面的节点我们就丢失了,我们也就插入失败了.
我测试了几遍发现,1、2、3步骤不固定,看个人爱好。