C语言中双向链表和双向循环链表详解

双向链表详解

什么是双向链表?

在C语言中,双向链表是一种常用的数据结构,它是由一系列节点组成,每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。

双向链表与单向链表相比,多了指向前一个节点的指针,这使得我们可以很方便地实现双向遍历,提高了搜索效率。

双向链表中节点的定义

struct Node {
    int data;
    struct Node *prev;  // 指向前一个节点
    struct Node *next;  // 指向后一个节点
};

节点需要包含数据,以及两个指向前后节点的指针。

双向链表的创建

双向链表的创建可以通过循环遍历链表来实现,依次创建节点,让前一个节点的next指针指向当前节点,当前节点的prev指针指向前一个节点。

struct Node *create(int array[], int size) {
    struct Node *head = NULL;
    struct Node *prev = NULL;
    for (int i = 0; i < size; ++i) {
        struct Node *node = (struct Node *)malloc(sizeof(struct Node));
        node->data = array[i];
        node->prev = prev;
        node->next = NULL;
        if (prev) {
            prev->next = node;
        } else {
            head = node;
        }
        prev = node;
    }
    return head;
}

双向链表的插入

双向链表的插入操作可以分为头部插入和尾部插入,分别使用head和tail指针来进行操作。

头部插入

头部插入时,我们需要将新节点的next指向当前头节点,将当前头节点的prev指向新节点,然后再将head指针指向新节点。

void insert_head(struct Node **head, int data) {
    struct Node *node = (struct Node *)malloc(sizeof(struct Node));
    node->data = data;
    node->prev = NULL;
    node->next = *head;
    if (*head) {
        (*head)->prev = node;
    }
    *head = node;
}

尾部插入

尾部插入时,我们需要先遍历到链表的末尾节点,再将新节点插入到末尾节点后面。需要先判断链表是否为空,为空时直接创建新节点作为头节点。

void insert_tail(struct Node **head, int data) {
    struct Node *node = (struct Node *)malloc(sizeof(struct Node));
    node->data = data;
    node->prev = NULL;
    node->next = NULL;
    if (*head == NULL) {
        *head = node;
    } else {
        struct Node *tail = *head;
        while (tail->next != NULL) {
            tail = tail->next;
        }
        tail->next = node;
        node->prev = tail;
    }
}

双向链表的删除

双向链表的删除操作需要注意维护prev和next指针的指向,我们需要先定位到要删除的节点,将它的prev节点的next指针指向它的next节点,将它的next节点的prev指针指向它的prev节点,然后再释放该节点的内存。

void delete(struct Node **head, int key) {
    struct Node *node = *head;
    while (node != NULL) {
        if (node->data == key) {
            if (node->prev) {
                node->prev->next = node->next;
            } else {
                *head = node->next;
            }
            if (node->next) {
                node->next->prev = node->prev;
            }
            free(node);
            return;
        }
        node = node->next;
    }
}

示例1:双向链表的遍历

双向链表的遍历可以从头节点开始,按照next指针依次向后遍历,输出每个节点的数据。

void traverse(struct Node *head) {
    printf("双向链表的数据:");
    struct Node *node = head;
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->next;
    }
    printf("\n");
}

示例2:双向链表的逆序遍历

双向链表可以很方便地逆序遍历,只需要从尾节点开始,按照prev指针依次向前遍历,输出每个节点的数据。

void reverse_traverse(struct Node *head) {
    printf("双向链表逆序遍历的数据:");
    struct Node *node = head;
    while (node != NULL && node->next != NULL) {
        node = node->next;
    }
    while (node != NULL) {
        printf("%d ", node->data);
        node = node->prev;
    }
    printf("\n");
}

双向循环链表详解

双向循环链表是在双向链表的基础上,将头节点和尾节点相连接形成一个环,可以实现头尾节点互相指向,提高了链表的搜索效率和代码的简洁度。

双向循环链表中节点的定义

节点需要包含数据,以及两个指向前后节点的指针。与双向链表不同的是,尾节点的next指针需要指向头节点,头节点的prev指针需要指向尾节点。

struct Node {
    int data;
    struct Node *prev;  // 指向前一个节点
    struct Node *next;  // 指向后一个节点
};

双向循环链表的创建

双向循环链表的创建与双向链表类似,只需要在最后一个节点的next指针指向头节点,并让头节点的prev指向尾节点即可。

struct Node *create(int array[], int size) {
    struct Node *head = NULL;
    struct Node *prev = NULL;
    for (int i = 0; i < size; ++i) {
        struct Node *node = (struct Node *)malloc(sizeof(struct Node));
        node->data = array[i];
        node->prev = prev;
        node->next = NULL;
        if (prev) {
            prev->next = node;
        } else {
            head = node;
        }
        prev = node;
    }
    if (prev) {
        prev->next = head;
    }
    if (head) {
        head->prev = prev;
    }
    return head;
}

示例1:双向循环链表的遍历

双向循环链表的遍历可以从头节点开始,按照next指针依次向后遍历,输出每个节点的数据。需要注意终止条件,当遍历到头节点时,完成一次遍历。

void traverse(struct Node *head) {
    printf("双向循环链表的数据:");
    struct Node *node = head;
    do {
        printf("%d ", node->data);
        node = node->next;
    } while (node != head);
    printf("\n");
}

示例2:双向循环链表的逆序遍历

双向循环链表可以很方便地逆序遍历,只需要从尾节点开始,按照prev指针依次向前遍历,输出每个节点的数据。需要注意终止条件,当遍历到尾节点时,完成一次遍历。

void reverse_traverse(struct Node *head) {
    printf("双向循环链表逆序遍历的数据:");
    struct Node *node = head;
    while (node != NULL && node->next != head) {
        node = node->next;
    }
    do {
        printf("%d ", node->data);
        node = node->prev;
    } while (node != NULL && node->next != head);
    printf("\n");
}

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言中双向链表和双向循环链表详解 - Python技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • Asp.net内置对象之Cookies(简介/属性方法/基本操作及实例)

    Asp.net内置对象之Cookies 简介 Cookies是Asp.net中的一个内置对象,用于在客户端浏览器和服务器之间存储和传递数据。它可以用来跟踪用户会话、存储用户偏好设置、实现记住密码等功能。 属性和方法 Cookies对象提供了一些属性和方法来操作和管理Cookie。 属性 Count:获取当前Cookies集合中的Cookie数量。 Keys:…

    other 2023年10月15日
    00
  • 分享MySQL常用 内核 Debug 几种常见方法

    分享MySQL常用内核Debug几种常见方法 MySQL是一个广泛使用的数据库管理系统,MySQL内核的Debug是MySQL开发人员必不可少的参考和调试工具。本文将详细介绍MySQL常用内核Debug的几种常见方法。 1. 使用GDB进行Debug GDB是一个强大的开源调试器,可以用于各种编程语言的调试,包括MySQL。以下是一个基本的GDB MySQL…

    other 2023年6月26日
    00
  • docker创建redis镜像的方法

    当我们需要在多个应用程序之间共享数据时,Redis是一种优秀的选择,它可以存储双向映射,列表,缓存等,并且以高效的方式进行处理。本文将详细讲解如何使用Docker创建Redis镜像。 准备工作 在开始之前,请确保已经安装了Docker和Docker Compose,并且熟悉基本的Docker命令和Dockefile语法。 创建Dockerfile 首先,在项…

    other 2023年6月27日
    00
  • 怎么提取百度网盘下载地址 提取百度网盘下载地址的详细图文步骤

    怎么提取百度网盘下载地址 百度网盘是一个常用的云存储平台,提供了丰富的文件存储和分享功能。有时候我们需要提取百度网盘中的文件下载地址,以便在其他地方进行下载。下面是提取百度网盘下载地址的详细图文步骤: 步骤一:登录百度网盘 首先,打开浏览器,访问百度网盘官网。如果你还没有百度账号,请先注册一个账号并登录。 步骤二:上传文件到百度网盘 在登录后,你可以点击页面…

    other 2023年8月3日
    00
  • Outliner大纲式笔记软件介绍

    Outliner大纲式笔记软件介绍 简介 Outliner大纲式笔记软件是一款十分实用的笔记应用程序。其主要特点是使用大纲形式组织和管理笔记,便于用户快速的编写和查看笔记内容。同时,Outliner大纲式笔记软件还支持多平台同步,以保证用户可以随时随地的访问自己的笔记内容。 功能特点 1. 大纲编辑 Outliner大纲式笔记软件支持大纲式编辑,用户可以根据…

    其他 2023年3月28日
    00
  • windows下添加Python环境变量的方法汇总

    下面详细讲解在 Windows 系统下添加 Python 环境变量的方法。 1. 下载和安装 Python 首先,需要在 Windows 系统上下载并安装 Python。可以从官网 https://www.python.org/ 上下载相应版本的 Python。 在安装过程中,需要注意勾选 “Add Python to PATH” 选项,这个选项会自动为 P…

    other 2023年6月27日
    00
  • 了解nonheap吗?

    了解nonheap吗? 在Java虚拟机中,内存分为堆内存和非堆内存。堆内存用于存储对象实例,而非堆内存用于存储Java虚拟机自身的数据。其中,非堆内存又分为方法区和直接内存。本文将详细讲解nonheap的概念、作用、示例等内容。 nonheap的概念 nonheap是虚拟机中的非堆内存,用于存储Java虚拟机自身的数据。nonheap包括方法区和直接内存两…

    other 2023年5月8日
    00
  • Android开发仿bilibili刷新按钮的实现代码

    Android开发仿bilibili刷新按钮的实现代码攻略 1. 添加刷新按钮到布局文件 首先,在你的布局文件中添加一个按钮来实现刷新功能。可以使用以下代码示例: <Button android:id=\"@+id/refreshButton\" android:layout_width=\"wrap_content\&q…

    other 2023年9月6日
    00
合作推广
合作推广
分享本页
返回顶部