数据结构:双向链表

目录

一、【双向链表】

1.  定义

2. 双向链表与单向链表的区别

 单向链表

 双向链表

3.  优缺点

 优点

 缺点

二、【 双向链表的接口实现】

1. 创建双向链表

2. 销毁双向链表

3. 双向链表打印

4. 双向链表尾插

5. 双向链表尾删

6. 双向链表头插

7. 双向链表头删

8. 双向链表查找

9. 双向链表在pos的前面进行插入

10. 双向链表删除pos位置的结点

三、【完整代码】


一、【双向链表】

1.  定义

双向链表是一种常见的数据结构,它允许在链表中的元素之间双向移动。双向链表中的每个节点都包含指向前一个节点和后一个节点的指针。

// 定义双向链表节点结构体
typedef struct Node 
{
    int data;           // 节点数据
    struct Node* prev;  // 指向前一个节点的指针
    struct Node* next;  // 指向下一个节点的指针
} Node;

2. 双向链表与单向链表的区别

双向链表和单向链表之间的主要区别在于节点内部指针的数量和方向。

 单向链表

  • 定义:单向链表中的每个节点包含一个指针,该指针指向下一个节点。
  • 遍历:只能从头节点开始,沿着指针的方向依次向后遍历,无法反向遍历。
  • 插入和删除:在单向链表中,如果要在某个节点之后插入一个新节点,需要修改前一个节点的指针,而删除节点时需要修改前一个节点的指针指向被删除节点的下一个节点。

 双向链表

  • 定义:双向链表中的每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。
  • 遍历:可以从头节点或尾节点开始,沿着相应的指针方向进行遍历,因此可以双向遍历。
  • 插入和删除:在双向链表中,插入和删除节点时不需要修改前一个节点的指针,因为每个节点都有指向前一个节点的指针,这使得插入和删除操作更加高效。 

3.  优缺点

 优点

  1. 双向遍历:双向链表可以从头部向尾部或者从尾部向头部进行遍历,这使得在某些情况下遍历操作更加方便。

  2. 插入和删除效率高:在双向链表中,插入和删除节点时不需要像单向链表那样需要查找前一个节点,这使得插入和删除操作更加高效。

  3. 便于逆序操作:双向链表可以更方便地进行逆序操作,因为每个节点都有指向前一个节点的指针。

 缺点

  1. 占用更多内存:双向链表中每个节点需要额外的一个指针来指向前一个节点,因此相比单向链表会占用更多的内存空间。

  2. 实现复杂:相比单向链表,双向链表的实现会稍微复杂一些,因为每个节点需要维护两个指针。

  3. 额外的指针开销:双向链表中每个节点都需要额外的指针来指向前一个节点,这意味着在频繁插入和删除节点时,会有更多的指针操作开销。

二、【 双向链表的接口实现】

1. 创建双向链表

  1. 分配内存以存储一个新的ListNode结构体。
  2. 将新节点的prev和next指针都设置为NULL,表示这是一个空的链表头结点。
  3. 返回指向新创建的头结点的指针。
// 创建返回链表的头结点
ListNode* ListCreate() 
{
    // 分配内存以存储一个新的ListNode结构体
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));

    // 将新节点的prev和next指针都设置为NULL,表示这是一个空的链表头结点
    head->prev = NULL;
    head->next = NULL;

    // 返回指向新创建的头结点的指针
    return head;
}

2. 销毁双向链表

  1. 从链表的第一个节点开始,依次释放每个节点的内存。
  2. 为了实现这一点,我们使用一个循环来遍历链表,释放每个节点的内存。
  3. 在循环中,我们首先保存下一个节点的指针,然后释放当前节点的内存,并将下一个节点设置为当前节点,继续释放。
  4. 最后,我们释放头结点的内存,完成整个链表的销毁操作。
// 销毁双向链表
void ListDestroy(ListNode* head) 
{
    ListNode* current = head->next;
    ListNode* next;

    // 从链表的第一个节点开始,依次释放每个节点的内存
    while (current != NULL) 
    {
        next = current->next; // 保存下一个节点的指针
        free(current); // 释放当前节点的内存
        current = next; // 将下一个节点设置为当前节点,继续释放
    }

    // 释放头结点的内存
    free(head);
}

3. 双向链表打印

  1. 从链表的第一个节点开始,依次打印每个节点的数值。
  2. 为了实现这一点,我们使用一个循环来遍历链表,打印每个节点的数值。
  3. 在循环中,我们首先打印当前节点的数值,然后将下一个节点设置为当前节点,继续打印。
  4. 最后,我们在所有节点打印完毕后输出换行符,以便于格式化输出。
// 打印双向链表
void ListPrint(ListNode* head) 
{
    ListNode* current = head->next;

    // 从链表的第一个节点开始,依次打印每个节点的数值
    while (current != NULL) 
    {
        printf("%d ", current->data); // 打印当前节点的数值
        current = current->next; // 将下一个节点设置为当前节点,继续打印
    }
    printf("\n");
}

4. 双向链表尾插

  1. 创建一个新的节点,准备进行尾插操作。
  2. 通过循环找到链表的最后一个节点,以便将新节点插入到末尾。
  3. 设置新节点的数据和指针,使其成为链表中的最后一个节点。
  4. 将新节点链接到链表的末尾,完成尾插操作。
// 双向链表尾插
void ListAppend(ListNode* head, int data) 
{
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点
    ListNode* current = head;

    // 找到链表的最后一个节点
    while (current->next != NULL) 
    {
        current = current->next;
    }

    // 设置新节点的数据和指针
    newNode->data = data;
    newNode->prev = current;
    newNode->next = NULL;

    // 将新节点链接到链表的末尾
    current->next = newNode;
}

5. 双向链表尾删

  1. 首先进行判断,如果链表为空(即头节点的next指针为空),则无法进行尾删操作,直接返回。
  2. 通过循环找到倒数第二个节点,以便进行尾删操作。
  3. 释放链表中的最后一个节点的内存,并将倒数第二个节点的next指针置为NULL,表示它成为了新的尾部节点。
// 双向链表尾删
void ListRemoveLast(ListNode* head) 
{
    if (head->next == NULL) 
    {
        // 链表为空,无法删除
        return;
    }

    ListNode* current = head;

    // 找到倒数第二个节点
    while (current->next->next != NULL) 
    {
        current = current->next;
    }

    // 释放最后一个节点的内存
    free(current->next);
    current->next = NULL; // 倒数第二个节点的next指针置为NULL,表示它是新的尾部节点
}

6. 双向链表头插

  1. 创建一个新的节点,准备进行头插操作。
  2. 设置新节点的数据和指针,使其成为链表中的第一个节点。
  3. 如果链表不为空,将原先的头节点的prev指针指向新节点。
  4. 更新头节点指针,使其指向新插入的节点,完成头插操作。
// 双向链表头插
void ListInsertFront(ListNode** head, int data) 
{
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点

    // 设置新节点的数据和指针
    newNode->data = data;
    newNode->prev = NULL;
    newNode->next = *head;

    if (*head != NULL) 
    {
        (*head)->prev = newNode;
    }

    *head = newNode; // 更新头节点指针
}

7. 双向链表头删

  1. 首先进行判断,如果链表为空(即头节点为空),则无法进行头删操作,直接返回。
  2. 保存头节点的地址以便后续释放内存。
  3. 更新头节点指针,使其指向原先头节点的下一个节点。
  4. 如果新的头节点不为空,将其prev指针置为NULL。
  5. 释放原先的头节点内存,完成头删操作。
// 双向链表头删
void ListRemoveFront(ListNode** head) 
{
    if (*head == NULL) 
    {
        // 链表为空,无法删除
        return;
    }

    ListNode* temp = *head; // 保存头节点的地址

    *head = (*head)->next; // 更新头节点指针

    if (*head != NULL) 
    {
        (*head)->prev = NULL; // 更新新的头节点的prev指针为NULL
    }

    free(temp); // 释放原先的头节点内存
}

8. 双向链表查找

  1. 从头节点开始,使用一个指针current来遍历链表。
  2. 在遍历过程中,判断当前节点的数据是否与目标数据匹配,如果匹配则返回当前节点。
  3. 如果当前节点的数据与目标数据不匹配,则将指针current移动到下一个节点,继续遍历。
  4. 如果遍历完整个链表仍未找到匹配的数据,则返回NULL,表示未找到目标数据。
// 双向链表查找
ListNode* ListFind(ListNode* head, int data) 
{
    ListNode* current = head; // 从头节点开始查找

    while (current != NULL) 
    {
        if (current->data == data) 
        {
            // 找到匹配的数据,返回当前节点
            return current;
        }
        current = current->next; // 继续向后遍历
    }

    // 没有找到匹配的数据,返回NULL
    return NULL;
}

9. 双向链表在pos的前面进行插入

  1. 首先进行判断,如果插入位置pos为空,则无法进行插入,直接返回。
  2. 创建一个新的节点newNode,并为其分配内存空间,然后设置新节点的数据为待插入的数据。
  3. 将新节点的prev指针指向pos的前一个节点,这样建立了新节点与pos前面节点的连接。
  4. 如果pos的前一个节点不为空,将其next指针指向新节点;如果pos的前一个节点为空(即pos是头节点),则更新头节点指针为新节点。
  5. 将新节点的next指针指向pos,建立了新节点与pos节点的连接。
  6. 最后,将pos的prev指针指向新节点,完成插入操作。
// 双向链表在指定位置pos的前面进行插入
void ListInsertBefore(ListNode** head, ListNode* pos, int data) 
{
    if (pos == NULL) 
    {
        // 如果插入位置为空,无法插入,直接返回
        return;
    }

    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点
    newNode->data = data; // 设置新节点数据
    newNode->prev = pos->prev; // 新节点的prev指针指向pos的前一个节点

    if (pos->prev != NULL) 
    {
        pos->prev->next = newNode; // pos的前一个节点的next指针指向新节点
    } 
    else 
    {
        *head = newNode; // 如果pos是头节点,则更新头节点指针为新节点
    }

    newNode->next = pos; // 新节点的next指针指向pos
    pos->prev = newNode; // pos的prev指针指向新节点
}

10. 双向链表删除pos位置的结点

  1. 首先进行判断,如果待删除节点pos为空,则无法进行删除操作,直接返回。
  2. 如果pos的前一个节点不为空,将其next指针指向pos的下一个节点,这样跳过了pos节点,将pos节点从链表中移除。
  3. 如果pos的前一个节点为空(即pos是头节点),则更新头节点指针为pos的下一个节点,同时将头节点的prev指针置为NULL。
  4. 如果pos的下一个节点不为空,将其prev指针指向pos的前一个节点,建立了pos节点前后节点的连接。
  5. 最后,释放pos节点的内存空间,完成删除操作。
// 双向链表删除指定位置pos的节点
void ListDelete(ListNode** head, ListNode* pos) 
{
    if (pos == NULL) 
    {
        // 如果待删除节点为空,无法删除,直接返回
        return;
    }

    if (pos->prev != NULL) 
    {
        pos->prev->next = pos->next; // 将pos前一个节点的next指针指向pos的下一个节点
    } 
    else 
    {
        *head = pos->next; // 如果pos是头节点,更新头节点指针为pos的下一个节点
    }

    if (pos->next != NULL) 
    {
        pos->next->prev = pos->prev; // 将pos下一个节点的prev指针指向pos的前一个节点
    }

    free(pos); // 释放pos节点的内存空间
}

三、【完整代码】

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点数据类型
typedef int LTDataType;

// 定义链表节点结构体
typedef struct ListNode 
{
    LTDataType data;         // 节点数据
    struct ListNode* prev;   // 指向前一个节点的指针
    struct ListNode* next;   // 指向下一个节点的指针
} ListNode;

// 创建返回链表的头结点
ListNode* ListCreate() 
{
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
    head->prev = NULL;
    head->next = NULL;
    return head;
}

// 双向链表销毁
void ListDestory(ListNode* plist) 
{
    ListNode* cur = plist->next;
    while (cur) 
    {
        ListNode* next = cur->next;
        free(cur);
        cur = next;
    }
    free(plist);
}

// 双向链表打印
void ListPrint(ListNode* plist)  
{
    ListNode* cur = plist->next;
    while (cur) 
    {
        printf("%d -> ", cur->data);
        cur = cur->next;
    }
    printf("NULL\n");
}

// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x) 
{
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = x;
    newNode->prev = NULL;
    newNode->next = NULL;

    ListNode* tail = plist;
    while (tail->next) 
    {
        tail = tail->next;
    }

    tail->next = newNode;
    newNode->prev = tail;
}

// 双向链表尾删
void ListPopBack(ListNode* plist) 
{
    if (plist->next == NULL) 
    {
        return;
    }

    ListNode* tail = plist;
    while (tail->next) 
    {
        tail = tail->next;
    }

    tail->prev->next = NULL;
    free(tail);
}

// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x) 
{
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = x;
    newNode->prev = NULL;
    newNode->next = plist->next;

    if (plist->next) 
    {
        plist->next->prev = newNode;
    }

    plist->next = newNode;
}

// 双向链表头删
void ListPopFront(ListNode* plist)   
{
    if (plist->next == NULL) 
    {
        return;
    }

    ListNode* first = plist->next;
    plist->next = first->next;
    if (first->next) 
    {
        first->next->prev = NULL;
    }
    free(first);
}

// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x)  
{
    ListNode* cur = plist->next;
    while (cur) 
    {
        if (cur->data == x) 
        {
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

// 双向链表在指定位置pos的前面进行插入
void ListInsertBefore(ListNode** head, ListNode* pos, int data) 
{
    if (pos == NULL) 
    {
        // 如果插入位置为空,无法插入,直接返回
        return;
    }

    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode)); // 创建新节点
    newNode->data = data; // 设置新节点数据
    newNode->prev = pos->prev; // 新节点的prev指针指向pos的前一个节点

    if (pos->prev != NULL) 
    {
        pos->prev->next = newNode; // pos的前一个节点的next指针指向新节点
    } 
    else 
    {
        *head = newNode; // 如果pos是头节点,则更新头节点指针为新节点
    }

    newNode->next = pos; // 新节点的next指针指向pos
    pos->prev = newNode; // pos的prev指针指向新节点
}

// 双向链表删除指定位置pos的节点
void ListDelete(ListNode** head, ListNode* pos) 
{
    if (pos == NULL) 
    {
        // 如果待删除节点为空,无法删除,直接返回
        return;
    }

    if (pos->prev != NULL) 
    {
        pos->prev->next = pos->next; // 将pos前一个节点的next指针指向pos的下一个节点
    } 
    else 
    {
        *head = pos->next; // 如果pos是头节点,更新头节点指针为pos的下一个节点
    }

    if (pos->next != NULL) 
    {
        pos->next->prev = pos->prev; // 将pos下一个节点的prev指针指向pos的前一个节点
    }

    free(pos); // 释放pos节点的内存空间
}

int main() 
{
    // 创建双向链表
    ListNode* head = ListCreate();

    // 尾插数据
    ListPushBack(head, 1);
    ListPushBack(head, 2);
    ListPushBack(head, 3);

    // 打印链表
    printf("双向链表的内容:\n");
    ListPrint(head);

    // 头删数据
    ListPopFront(head);

    // 打印链表
    printf("删除头结点后的双向链表内容:\n");
    ListPrint(head);

    // 销毁链表
    ListDestory(head);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值