小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
双向链表 (Doubly Linked List 缩写DLL) 包含一个额外的指针,通常称为前一个指针,以及单链表中存在的下一个指针和数据。
以下是用 C++表示的 DLL 节点。
/* 双向链表的节点 */
class Node
{
public:
int data;
Node* next; //指向DLL中下一个节点的指针
Node* prev; // 指向 DLL 中前一个节点的指针
};
复制代码
以下是双链表相对于单链表的优缺点。
相 对于单向链表的优点
1) 一个 DLL 可以向前和向后两个方向遍历。
2) 如果给出指向要删除的节点的指针,DLL 中的删除操作会更有效率。
3) 我们可以在给定节点之前快速插入一个新节点。
在单链表中,要删除一个节点,需要指向前一个节点的指针。为了获得这个前一个节点,有时会遍历列表。在 DLL 中,我们可以使用前一个指针获取前一个节点。
单向链表的缺点
1) DLL 的每个节点都需要额外的空间用于前一个指针。虽然可以用单指针实现 DLL(参见this和this)。
2) 所有的操作都需要一个额外的指针来维护。例如,在插入时,我们需要同时修改前一个指针和后一个指针。例如,在以下用于在不同位置插入的函数中,我们需要 1 或 2 个额外步骤来设置前一个指针。
插入
可以通过四种方式添加节点
1) 在 DLL 的前面
2) 在给定的节点之后。
3) 在 DLL 的末尾
4) 在给定节点之前。
1)在前面添加一个节点:(一个5个步骤的过程)
新节点总是添加在给定链表的头部之前。并且新添加的节点成为DLL的新头。例如,如果给定的链表是 10152025,我们在前面添加了一个项目 5,那么链表就变成了 510152025。让我们调用在链表前面添加的函数是 push()。push() 必须接收一个指向头指针的指针,因为 push 必须将头指针更改为指向新节点
以下是在前面添加节点的 5 个步骤。
/* 给定一个指向列表头部的引用(指向指针的指针)
和一个 int,在列表的前面插入一个新节点。 */
void push(Node** head_ref, int new_data)
{
/* 1. 分配节点 */
Node* new_node = new Node();
/* 2. 输入数据 */
new_node->data = new_data;
/* 3. 将新节点的下一个节点设为头,上一个节点设为 NULL */
new_node->next = (*head_ref);
new_node->prev = NULL;
/* 4. 将头节点的 prev 更改为新节点 */
if ((*head_ref) != NULL)
(*head_ref)->prev = new_node;
/* 5. 移动头部指向新节点 */
(*head_ref) = new_node;
}
复制代码
以上五步中的四步与单向链表前面插入的4步相同。唯一的额外步骤是改变头的前一个。
2)在给定节点之后添加一个节点。:(一个7个步骤的过程)
我们得到一个节点的指针作为prev_node,并且在给定节点之后插入新节点。\
void insertAfter(Node* prev_node, int new_data)
{
if (prev_node == NULL)
{
cout<<"the given previous node cannot be NULL";
return;
}
Node* new_node = new Node();
new_node->data = new_data;
new_node->next = prev_node->next;
prev_node->next = new_node;
new_node->prev = prev_node;
if (new_node->next != NULL)
new_node->next->prev = new_node;
}
复制代码
上述步骤中的五个步骤过程与用于在单向链表中的给定节点之后插入的5个步骤相同。需要两个额外的步骤来改变新节点的前一个指针和新节点的下一个节点的前一个指针。
3)在最后添加一个节点:(7个步骤过程)
新节点总是添加在给定链表的最后一个节点之后。例如,如果给定的 DLL 是 510152025 并且我们在最后添加了一个项目 30,那么 DLL 就变成了 51015202530。
由于链表通常由它的头部表示,我们必须遍历列表直到最后,然后更改下一个最后一个节点到新节点。
以下是最后添加节点的 7 个步骤。
void append(Node** head_ref, int new_data)
{
Node* new_node = new Node();
Node* last = *head_ref;
new_node->data = new_data;
new_node->next = NULL;
if (*head_ref == NULL)
{
new_node->prev = NULL;
*head_ref = new_node;
return;
}
while (last->next != NULL)
last = last->next;
last->next = new_node;
new_node->prev = last;
return;
}
复制代码
上述 7 个步骤中的 6 个步骤与用于在单向链表中的给定节点之后插入的 6 个步骤相同。需要一个额外的步骤来更改新节点的前一个指针。
4)在给定节点之前添加一个节点: \
步骤
设指向此给定节点的指针为 next_node,将要添加的新节点的数据为 new_data。
- 检查 next_node 是否为 NULL。如果是 NULL,则从函数返回,因为在 NULL 之前不能添加任何新节点
- 为新节点分配内存,命名为new_node
- 设置 new_node->data = new_data
- 设置这个new_node的前一个指针为next_node的前一个节点,new_node->prev = next_node->prev
- 设置next_node的前一个指针为new_node,next_node->prev = new_node
- 设置这个new_node的next指针为next_node,new_node->next = next_node;
- 如果new_node的上一个节点不为NULL,则设置这个上一个节点的next指针为new_node,new_node->prev->next = new_node
- 否则,如果 new_node 的 prev 为 NULL,它将是新的头节点。所以,make (*head_ref) = new_node。
下面是上述方法的实现:
#include <iostream>
using namespace std;
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
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;
}
void insertBefore(struct Node** head_ref,
struct Node* next_node, int new_data)
{
if (next_node == NULL) {
cout <<"the given next node cannot be NULL";
return;
}
struct Node* new_node
= (struct Node*)malloc(sizeof(struct Node));
new_node->data = new_data;
new_node->prev = next_node->prev;
next_node->prev = new_node;
new_node->next = next_node;
if (new_node->prev != NULL)
new_node->prev->next = new_node;
else
(*head_ref) = new_node;
}
void printList(struct Node* node)
{
struct Node* last;
cout <<"\n正向遍历 \n";
while (node != NULL) {
cout <<" "<< node->data;
last = node;
node = node->next;
}
cout <<"\n反向遍历 \n";
while (last != NULL) {
cout <<" "<< last->data;
last = last->prev;
}
}
int main()
{
struct Node* head = NULL;
push(&head, 7);
push(&head, 1);
push(&head, 4);
insertBefore(&head, head->next, 8);
cout <<"创建的DLL是:";
printList(head);
getchar();
return 0;
}
复制代码
输出:
创建的DLL是:
正向遍历
9 1 5 7 6
反向遍历
6 7 5 1 9
复制代码