文章目录
(翻译文,稍加调整,加入了自己的解释)
1. 相关背景介绍
1.1 双链表概念
相比SLL(singly linked list,单链表),DLL(Doubly Linked List,双链表)多了一个名为 previous pointer (Prev)的指针,该指针指向前一节点。SLL与DLL的相同点是都拥有需要存储的数据,和指向下一节点的指针。
C语言中DLL节点的表示:
/* Node of a doubly linked list */
struct Node {
int data;
struct Node* next; // Pointer to next node in DLL
struct Node* prev; // Pointer to previous node in DLL
};
1.2 双链表的优势与劣势
双链表相对于单链表的优势:
1) DLL可以向前和向后遍历。
2)如果给出了要删除的节点的指针,则DLL中的删除操作会更有效。
3)我们可以在给定节点之前快速插入一个新节点。
在单链列表中,要删除节点,需要指向上一个节点的指针。为了获得该先前节点,有时会遍历列表。在DLL中,我们可以使用Prev指针获取先前的节点。
双链表相对于单链表的劣势:
1) DLL的每个节点都需要额外的空间才能存储Prev指针。
2)所有操作都需要一个额外的指针。例如,在插入过程中,我们需要同时修改Prev指针和Next指针。例如,在以下函数中,对于不同位置的插入,我们需要1到2个额外的步骤来设置Prev指针。
1.3 双链表插入节点的位置
可以在四个位置添加节点:
1)在DLL的前端;
2)在给定节点之后;
3)在DLL的末尾;
4)在给定节点之前。
2. 不同位置插入数据
2.1 在DLL的前端添加节点
新节点总是添加到给定链接列表的首节点之前。新添加的节点成为DLL新的首节点。例如,如果给定的链表是10152025,我们在前面加了一个项5,那么链表就变成了510152025。我们把在列表前端添加节点的函数称为push(),push() 需要修改head指针,使head指针指向新的节点。
整个过程需要5个步骤:
/* Given a reference (pointer to pointer) to the head of a list
and an int, inserts a new node on the front of the list. */
void push(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
/* 2. put in the data */
new_node->data = new_data;
/* 3. Make next of new node as head and previous as NULL */
new_node->next = (*head_ref);
new_node->prev = NULL;
/* 4. change prev of head node to new node */
if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;
/* 5. move the head to point to the new node */
(*head_ref) = new_node;
}
以上5个步骤中的4个步骤与单链表的最前面插入步骤相同。唯一的额外步骤是多了第4步,更改原首节点的Prev指针,将指针从NULL指向新增节点。
2.2 在给定节点之前添加节点
步骤:
给定节点为next_node,新节点为new node,其数据为new_data。
1、检查next_node是否为NULL。如果为NULL,则从函数退出,因为在NULL之前不能添加任何新节点;
2、为新节点分配内存,将其称为new_node;
3、设置new_node-> data = new_data
4、将new_node的前一个指针指向next_node的前一个节点,new_node-> prev = next_node-> prev
5、将next_node的前一个指针设置为new_node,next_node-> prev = new_node。在图例中,就是将C节点的prev指针,从指向A节点改为指向B节点。
6、将此new_node的下一个指针设置为next_node,new_node-> next = next_node;
7、如果new_node的前一个节点不为NULL,则将此前一个节点的下一个指针设置为new_node,new_node-> prev-> next = new_node。在图例中,就是修改A节点的next指针,从C指向B。
8、否则,如果new_node的prev为NULL,它将是新的头节点。因此,使(* head_ref)= new_node。
2.3 在DLL的末尾添加节点
新节点总是添加到给定链表的最后一个节点之后。例如,如果给定的DLL是510152025,而我们在末尾添加了第30个项目,则DLL变为51015202530。
共7步:
/* Given a reference (pointer to pointer) to the head
of a DLL and an int, appends a new node at the end */
void append(struct Node** head_ref, int new_data)
{
/* 1. allocate node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
struct Node* last = *head_ref; /* used in step 5*/
/* 2. put in the data */
new_node->data = new_data;
/* 3. This new node is going to be the last node, so
make next of it as NULL*/
new_node->next = NULL;
/* 4. If the Linked List is empty, then make the new
node as head */
if (*head_ref == NULL) {
new_node->prev = NULL;
*head_ref = new_node;
return;
}
/* 5. Else traverse till the last node */
while (last->next != NULL)
last = last->next;
/* 6. Change the next of last node */
last->next = new_node;
/* 7. Make last node as previous of new node */
new_node->prev = last;
return;
}
因为链表通常是由它的head来表示的,所以我们必须遍历列表直到结束,然后将最后一个节点的下一个更改为新节点。第1步创建的last指针用于遍历链表,指向原链表的最后一个节点;第6步修改原链表最后一个节点的next指针,将指向NULL修改为指向新节点;第7步配置新节点的prev指针,为原链表的最后节点。
2.4 在给定节点之后添加节点
共7步:
/* Given a node as prev_node, insert a new node after the given node */
void insertAfter(struct Node* prev_node, int new_data)
{
/*1. check if the given prev_node is NULL */
if (prev_node == NULL) {
printf("the given previous node cannot be NULL");
return;
}
/* 2. allocate new node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
/* 3. put in the data */
new_node->data = new_data;
/* 4. Make next of new node as next of prev_node */
new_node->next = prev_node->next;
/* 5. Make the next of prev_node as new_node */
prev_node->next = new_node;
/* 6. Make prev_node as previous of new_node */
new_node->prev = prev_node;
/* 7. Change previous of new_node's next node */
if (new_node->next != NULL)
new_node->next->prev = new_node;
}
在图例中,new_node为E节点,prev_node为B节点。第4步将E节点的next指针指向C节点;第5步将B节点的next指针指向从C节点改为E节点;第6步将E节点prev指针指向B节点;第7步将C节点prev指针指向从B节点改为E节点。
3. 综合示例
以下是上述方法的实现:
// A complete working C program to demonstrate all
// insertion before a given node
#include <stdio.h>
#include <stdlib.h>
// A linked list node
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
/* Given a reference (pointer to pointer) to the head of a list
and an int, inserts a new node on the front of the list. */
void push(struct Node** head_ref, int new_data)
{
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
new_node->data = new_data;
new_node->next = (*head_ref);
new_node->prev = NULL;
if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;
(*head_ref) = new_node;
}
/* Given a node as next_node, insert a new node before the given node */
void insertBefore(struct Node** head_ref, struct Node* next_node, int new_data)
{
/*1. check if the given next_node is NULL */
if (next_node == NULL) {
printf("the given next node cannot be NULL");
return;
}
/* 2. allocate new node */
struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
/* 3. put in the data */
new_node->data = new_data;
/* 4. Make prev of new node as prev of next_node */
new_node->prev = next_node->prev;
/* 5. Make the prev of next_node as new_node */
next_node->prev = new_node;
/* 6. Make next_node as next of new_node */
new_node->next = next_node;
/* 7. Change next of new_node's previous node */
if (new_node->prev != NULL)
new_node->prev->next = new_node;
/* 8. If the prev of new_node is NULL, it will be
the new head node */
else
(*head_ref) = new_node;
}
// This function prints contents of linked list starting from the given node
void printList(struct Node* node)
{
struct Node* last;
printf("\nTraversal in forward direction \n");
while (node != NULL) {
printf(" %d ", node->data);
last = node;
node = node->next;
}
printf("\nTraversal in reverse direction \n");
while (last != NULL) {
printf(" %d ", last->data);
last = last->prev;
}
}
/* Driver program to test above functions*/
int main()
{
/* Start with the empty list */
struct Node* head = NULL;
push(&head, 7);
push(&head, 1);
push(&head, 4);
// Insert 8, before 1. So linked list becomes 4->8->1->7->NULL
insertBefore(&head, head->next, 8);
printf("Created DLL is: ");
printList(head);
getchar();
return 0;
}
输出结果为:
Created DLL is:
Traversal in forward direction
4 8 1 7
Traversal in reverse direction
7 1 8 4
参考文献
[1] jatinreaper, 29AjayKumar, Murali Krishna Marimekala, rathbhupendra, Akanksha_Rai.Doubly Linked List | Set 1 (Introduction and Insertion)[EB/OL].https://www.geeksforgeeks.org/doubly-linked-list/,2020-01-01.