双向链表C语言(非伪代码)

对于双向链表来说,其实和单向链表差别不大,只要你了解单向链表的底层逻辑,实现双向链表其实很简单。

一、双向链表的结点/链表

typedef struct ListNode { // 双向链表的节点
    int data; // 数据域
    struct ListNode* next; // 后继指针
    struct ListNode* prev; // 前驱指针
} ListNode;

typedef struct LinkedList { // 完整的双向链表
    ListNode* head; // 头节点
    int len; // 链表长度
} LinkedList;

二、初始化/创建一个双向链表

与单向链表一致

void Creat_LinkedList(LinkedList* list) // 初始化链表
{
    list->head = NULL;
    list->len = 0;
}

三、销毁链表

与单链表一致

void Destroy_LinkList(LinkedList* list) // 销毁链表
{
    while (list->head)
    {
        ListNode* temp = list->head; // 缓存当前头节点
        list->head = list->head->next; // 移动头指针
        free(temp); // 释放当前节点
    }
    list->len = 0;
}

四、在双向链表中插入元素

4.1、核心代码实现

  • 与单链表一样,分为头插法与尾插法。
  • 与单链表相比,多了两条语句,即prev相关的语句,但难度不大
  • 顺序:先把新结点的链表中的结点建立联系--->再把链表中的结点指向进行修改
// 在链表中插入元素
void Insert_LinkList(LinkedList* list, int i, int value)//第i个位置插入值为value的元素
{
    if (i < 0 || i > list->len) // 越界检查
    {
        printf("插入失败\n");
        return;
    }

    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;

    if (i == 0) // 在头部插入
    {
        newNode->next = list->head; // 新节点指向原头节点
        newNode->prev = NULL; // 新节点前驱为空
        if (list->head) // 如果链表非空,更新原头节点的前驱指针
        {
            list->head->prev = newNode;
        }
        list->head = newNode; // 更新头节点
    }
    else // 插入到其他位置
    {
        ListNode* current = list->head;

        for (int j = 0; j < i - 1; j++) // 找到插入位置的前一个节点
        {
            current = current->next;
        }
        newNode->next = current->next; // 新节点的后继为current的后继
        newNode->prev = current; // 新节点的前驱为current

        if (current->next) // 如果current后有节点,更新其前驱指针
        {
            current->next->prev = newNode;
        }
        current->next = newNode; // 更新current的后继为新节点
    }
    list->len++;
}

4.2、算法图解

4.2.1、当链表为空时

  • 对单结点进行操作,prev与next都置空
  • 头节点指向单节点

4.2.2、插入节点为头节点,但链表不为空时

  • 先让新节点和链表节点搭上线(步骤1/2)
  • 修改链表节点的指向(步骤3)
  • 修改头指针的指向

4.2.3、插入的节点不为头节点时

  • 定义迭代器,找到需要插入的节点位置的前一个结点
  • 让我们需要插入的结点与链表搭上线(步骤3/4)
  • 修改链表元素指向(5/6)

  • 温馨提醒:如果插入为尾结点,则去掉步骤5,因为cur->next=NULL。而步骤3自动变成newNode->next=NULL

五、在双向链表中删除节点

5.1、核心代码实现:

  • 需要一个变量toDelete用来最后的释放节点
  • 当然,如果不为头节点时,同时也需要一个迭代器实现删除节点的查找
// 删除节点
void Delete_LinkList(LinkedList* list, int i)
{
    if (i < 0 || i >= list->len) // 越界检查
    {
        printf("删除失败\n");
        return;
    }

    ListNode* toDelete = list->head;

    if (i == 0) // 删除头节点
    {
        list->head = toDelete->next; // 头节点后移
        if (list->head) // 更新新头节点的前驱指针
        {
            list->head->prev = NULL;
        }
        free(toDelete); // 释放旧头节点
    }
    else // 删除其他节点
    {
        ListNode* current = list->head;

        for (int j = 0; j < i - 1; j++) // 找到删除位置的前一个节点
        {
            current = current->next;
        }
        toDelete = current->next; // 要删除的节点
        current->next = toDelete->next; // 前一个节点的后继指向要删除节点的后继

        if (toDelete->next) // 如果删除节点的后继存在,更新其前驱
        {
            toDelete->next->prev = current;
        }
        free(toDelete); // 释放要删除的节点
    }
    list->len--;
}

5.2算法图解:

5.2.1、删除节点为头节点

  • 定义一个toDelete指向头节点(步骤1,为释放做准备)
  • 移动头节点指向(步骤2)
  • 修改节点指向(步骤2就是为了这步做准备,不然访问不到这个节点)
  • 释放toDelete节点(步骤4)

5.2.2、删除的节点不是头节点:

  • 删除指向toDelete,迭代器cur(步骤1234)
  • 修改cur的next节点,断开cur->next与toDelete的关系,建立起与toDelete->next的关系(步骤5)
  • 修改toDelete->prev的节点指向,断开toDelete->prev与toDelete的关系,建立起与cur的关系(步骤6)

步骤5/6使得toDelete与链表完全断开联系

  • 释放toDelete所指向的节点

六、通过元素来查找链表节点

与单链表没差别,不再赘述

// 通过元素查找节点
ListNode* Find_element_Linked(LinkedList* list, int value)
{
    ListNode* current = list->head; // 迭代器从头开始

    while (current) // 遍历链表
    {
        if (current->data == value) // 找到元素
        {
            return current;
        }
        current = current->next; // 继续下一个节点
    }
    return NULL; // 没有找到
}

七、通过下标来查找链表节点

与单链表没差别,不再赘述

// 通过下标查找节点
ListNode* Find_Index_Linked(LinkedList* list, int i)
{
    if (i < 0 || i >= list->len) // 越界检查
    {
        printf("寻找错误\n");
        return NULL;
    }

    ListNode* current = list->head;

    for (int j = 0; j < i; j++) // 遍历到第i个节点
    {
        current = current->next;
    }
    return current;
}

八、更新链表元素

// 更新链表元素
void Update_LInked(LinkedList* list, int i, int value)
{
    ListNode* temp = Find_Index_Linked(list, i);

    if (temp) // 如果节点存在
    {
        temp->data = value; // 更新数据
    }
}

九、打印链表

// 打印链表
void Print_Linked(LinkedList* list)
{
    ListNode* current = list->head;

    while (current)
    {
        printf("%d <-> ", current->data); // 打印当前节点
        current = current->next; // 移动到下一个节点
    }
    printf("NULL\n");
}

十、完整的代码实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

typedef struct ListNode { // 双向链表的节点
    int data; // 数据域
    struct ListNode* next; // 后继指针
    struct ListNode* prev; // 前驱指针
} ListNode;

typedef struct LinkedList { // 完整的双向链表
    ListNode* head; // 头节点
    int len; // 链表长度
} LinkedList;

void Creat_LinkedList(LinkedList* list) // 初始化链表
{
    list->head = NULL;
    list->len = 0;
}

void Destroy_LinkList(LinkedList* list) // 销毁链表
{
    while (list->head)
    {
        ListNode* temp = list->head; // 缓存当前头节点
        list->head = list->head->next; // 移动头指针
        free(temp); // 释放当前节点
    }
    list->len = 0;
}

// 在链表中插入元素
void Insert_LinkList(LinkedList* list, int i, int value)//第i个位置插入值为value的元素
{
    if (i < 0 || i > list->len) // 越界检查
    {
        printf("插入失败\n");
        return;
    }

    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;

    if (i == 0) // 在头部插入
    {
        newNode->next = list->head; // 新节点指向原头节点
        newNode->prev = NULL; // 新节点前驱为空
        if (list->head) // 如果链表非空,更新原头节点的前驱指针
        {
            list->head->prev = newNode;
        }
        list->head = newNode; // 更新头节点
    }
    else // 插入到其他位置
    {
        ListNode* current = list->head;

        for (int j = 0; j < i - 1; j++) // 找到插入位置的前一个节点
        {
            current = current->next;
        }
        newNode->next = current->next; // 新节点的后继为current的后继
        newNode->prev = current; // 新节点的前驱为current

        if (current->next) // 如果current后有节点,更新其前驱指针
        {
            current->next->prev = newNode;
        }
        current->next = newNode; // 更新current的后继为新节点
    }
    list->len++;
}

// 删除节点
void Delete_LinkList(LinkedList* list, int i)
{
    if (i < 0 || i >= list->len) // 越界检查
    {
        printf("删除失败\n");
        return;
    }

    ListNode* toDelete = list->head;

    if (i == 0) // 删除头节点
    {
        list->head = toDelete->next; // 头节点后移
        if (list->head) // 更新新头节点的前驱指针
        {
            list->head->prev = NULL;
        }
        free(toDelete); // 释放旧头节点
    }
    else // 删除其他节点
    {
        ListNode* current = list->head;

        for (int j = 0; j < i - 1; j++) // 找到删除位置的前一个节点
        {
            current = current->next;
        }
        toDelete = current->next; // 要删除的节点
        current->next = toDelete->next; // 前一个节点的后继指向要删除节点的后继

        if (toDelete->next) // 如果删除节点的后继存在,更新其前驱
        {
            toDelete->next->prev = current;
        }
        free(toDelete); // 释放要删除的节点
    }
    list->len--;
}

// 通过元素查找节点
ListNode* Find_element_Linked(LinkedList* list, int value)
{
    ListNode* current = list->head; // 迭代器从头开始

    while (current) // 遍历链表
    {
        if (current->data == value) // 找到元素
        {
            return current;
        }
        current = current->next; // 继续下一个节点
    }
    return NULL; // 没有找到
}

// 通过下标查找节点
ListNode* Find_Index_Linked(LinkedList* list, int i)
{
    if (i < 0 || i >= list->len) // 越界检查
    {
        printf("寻找错误\n");
        return NULL;
    }

    ListNode* current = list->head;

    for (int j = 0; j < i; j++) // 遍历到第i个节点
    {
        current = current->next;
    }
    return current;
}

// 更新链表元素
void Update_LInked(LinkedList* list, int i, int value)
{
    ListNode* temp = Find_Index_Linked(list, i);

    if (temp) // 如果节点存在
    {
        temp->data = value; // 更新数据
    }
}

// 打印链表
void Print_Linked(LinkedList* list)
{
    ListNode* current = list->head;

    while (current)
    {
        printf("%d <-> ", current->data); // 打印当前节点
        current = current->next; // 移动到下一个节点
    }
    printf("NULL\n");
}

int main()
{
    LinkedList list; // 定义链表

    Creat_LinkedList(&list); // 创建链表

    Insert_LinkList(&list, 0, 10); // 插入数据
    Insert_LinkList(&list, 1, 20);
    Insert_LinkList(&list, 2, 30);
    Insert_LinkList(&list, 3, 40);

    printf("Original List:\n");
    Print_Linked(&list); // 打印链表

    Delete_LinkList(&list, 2); // 删除节点
    Update_LInked(&list, 1, 100); // 更新数据
    printf("Change1 List:\n");
    Print_Linked(&list); // 打印链表

    ListNode* found = Find_element_Linked(&list, 30); // 查找元素

    if (!found)
    {
        printf("此节点不存在\n");
    }

    Destroy_LinkList(&list); // 销毁链表
    system("pause");
    return 0;
}

  纯私人撰写,欢迎大家私信勘误!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值