(一)双链表的引入和基本实现
1.单链表的局限性
- 单链表是对数组的一个扩展,解决了数组的大小比较死板不容易扩展的问题。使用堆内存来存储数据,将数据分散到各个节点之间,其各个节点在内存中可以不相连,节点之间通过指针进行单向链接。链表中的各个节点内存不相连,有利于利用碎片化的内存。
- 单链表各个节点之间只由一个指针单向链接,这样实现有一些局限性。局限性主要体现在单链表只能经由指针单向移动(一旦指针移动过某个节点就无法再回来,如果要再次操作这个节点除非从头指针开始再次遍历一次),因此单链表的某些操作就比较麻烦(算法比较有局限)。回忆之前单链表的所有操作(插入、删除节点、 遍历、从单链表中取某个节点的数·····),因为单链表的单向移动性导致了不少麻烦。
- 总结:单链表的单向移动性导致我们在操作单链表时,当前节点只能向后移动不能向前移动,因此不自由,不利于解决更复杂的算法。
2.解决思路:有效数据+2个指针的节点(双链表)
- 单链表的节点 = 有效数据 + 指针(指针指向后一个节点)
- 双向链表的节点 = 有效数据 + 2个指针(一个指向后一个节点,另一个指向前一个节点)
(二)双链表的封装和编程实现
struct node *creat_node(int data)
{
struct node *p = (struct node *)malloc(sizeof(struct node));
if(NULL == p)
{
printf("malloc error.\n");
return NULL;
}
memset(p, 0, sizeof(struct node));
p->data = data;
p->pNext = NULL;
p->pPrev = NULL;
return p;
}
(三)双链表的算法之插入节点
1.尾部插入
void insert_tail(struct node *pH, struct node *new)
{
struct node *p = pH;
while (NULL != p->pNext)
{
p = p->pNext;
}
p->pNext = new;
new->pPrev = p;
}
2.头部插入
void insert_head(struct node *pH, struct node *new)
{
new->pNext = pH->pNext;
if(NULL != pH->pNext)
{
pH->pNext->pPrev = new;
}
pH->pNext = new;
new->pPrev = pH;
}
(四)双链表的算法之遍历节点
- 双链表是单链表的一个父集。双链表中如何完全无视pPrev指针,则双链表就变成了单链表。这就决定了双链表的正向遍历(后向遍历)和单链表是完全相同的。
- 双链表中因为多了pPrev指针,因此双链表还可以前向遍历(从链表的尾节点向前面依次遍历直到头节点)。但是前向遍历的意义并不大,主要是因为很少有当前当了尾节点需要前向遍历的情况。
- 总结:双链表是对单链表的一种有成本的扩展,但是这个扩展在有些时候意义不大,在另一些时候意义就比较大。因此在实践用途中要根据业务要求选择适合的链表。
void bainli(struct node *pH)
{
struct node *p = pH;
printf("-----------遍历开始-----------------.\n");
while(NULL != p->pNext)
{
p = p->pNext;
printf("node = %d.\n", p->data);
}
printf("-----------遍历结束-----------------.\n");
}
(五)双链表的算法之删除节点
int delete_node(struct node *pH, int data)
{
struct node *p = pH;
if(NULL == pH)
{
return -1;
}
while(NULL != p->pNext)
{
p = p->pNext;
if(data == p->data)
{
if(NULL == p->pNext)
{
pH->pNext = NULL;
}
else
{
p->pPrev->pNext = p->pNext;
p->pNext->pPrev = p->pPrev;
}
free(p);
return 0;
}
}
printf("没有找到这样的节点.\n");
return -1;
}