C语言实现数据结构和双向链表操作

下面是详细讲解 "C语言实现数据结构和双向链表操作" 的完整攻略。

什么是数据结构?

数据结构是计算机中存储、组织和管理数据的方式。数据结构可以分为线性结构和非线性结构两种。其中,线性结构包括数组、链表、栈、队列等,非线性结构包括树、图等。

什么是链表?

链表是一种动态的数据结构,它由许多个结点组成。每个结点包含两个部分:数据域和指针域。数据域存储数据,指针域则存储指向下一个结点(单向链表)或上一个结点(双向链表)的指针。其中,第一个结点称为头结点,最后一个结点称为尾结点。

双向链表的实现

以下是双向链表的结构体实现:

typedef struct Node{
    int data;
    struct Node *prev;  // 指向前一个结点的指针
    struct Node *next;  // 指向下一个结点的指针
}Node, *LinkList;

其中,prev指向前驱结点,next指向后继结点。

以下是创建双向链表的函数:

/**
 * 创建双向链表
 */
LinkList create(){
    Node *head = (Node*) malloc(sizeof(Node));  // 创建头结点
    head->prev = NULL;
    head->next = NULL;

    Node *tail = head;  // 记录尾结点位置

    int n;
    printf("请输入双向链表的长度:\n");
    scanf("%d",&n);  // 输入结点个数

    for (int i = 0; i < n; i++){
        Node *node = (Node*) malloc(sizeof(Node));  // 创建一个新结点
        printf("请输入第%d个结点的数据:\n",i+1);
        scanf("%d",&node->data);  // 输入结点数据

        node->prev = tail;  // 设置前驱指针
        node->next = NULL;  // 设置后继指针
        tail->next = node;  // 将新结点插到尾部
        tail = node;  // 更新尾结点位置
    }
    return head;
}

该函数会先创建一个头结点,然后根据输入的结点个数,依次创建新的结点。在创建新结点时,我们需要将该结点的前驱指针指向链表的尾结点,将尾结点的后继指针指向该结点,最后更新尾结点位置。

以下是双向链表的遍历函数:

/**
 * 遍历双向链表
 */
void traverse(LinkList L){
    Node *p = L->next;  // 从头结点的后继结点开始遍历
    while (p != NULL){
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
}

遍历时,我们从头结点的后继结点开始遍历,直到遍历到尾结点为止。

以下是双向链表的插入函数:

/**
 * 双向链表中插入新结点
 */
void insert(LinkList L,int pos,int data){
    Node *node = (Node*) malloc(sizeof(Node));  // 创建新结点
    node->data = data;

    Node *p = L->next;
    int i = 1;
    while (p != NULL && i < pos){
        p = p->next;
        i++;
    }
    if (p == NULL){
        printf("插入的位置非法!\n");
        return;
    }
    node->prev = p->prev;  // 插入新结点
    node->next = p;
    p->prev->next = node;
    p->prev = node;
}

插入时,我们需要定位到插入位置上一个结点的位置,然后将插入结点的前驱指针指向该结点的前驱结点,将插入结点的后继指针指向该结点,将该结点的前驱结点的后继指针指向插入结点,将该结点的前驱指针指向插入结点。

以下是双向链表的删除函数:

/**
 * 双向链表中删除指定结点
 */
void remove_node(LinkList L,int pos){
    Node *p = L->next;
    int i = 1;
    while (p != NULL && i < pos){
        p = p->next;
        i++;
    }
    if (p == NULL){
        printf("删除的位置非法!\n");
        return;
    }
    p->prev->next = p->next;
    if (p->next != NULL){
        p->next->prev = p->prev;
    }
    free(p);
}

删除时,我们同样需要定位到删除结点的位置,然后将删除结点的前驱结点的后继指针指向删除结点的后继结点,将删除结点的后继结点的前驱指针指向删除结点的前驱结点,最后释放删除结点占用的内存空间。

示例1

假设我们要创建一个包含10个结点的双向链表,并对该双向链表进行遍历、插入和删除操作,具体代码如下:

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

typedef struct Node{
    int data;
    struct Node *prev;
    struct Node *next;
}Node, *LinkList;

/**
 * 创建双向链表
 */
LinkList create(){
    Node *head = (Node*) malloc(sizeof(Node));  // 创建头结点
    head->prev = NULL;
    head->next = NULL;

    Node *tail = head;  // 记录尾结点位置

    int n;
    printf("请输入双向链表的长度:\n");
    scanf("%d",&n);

    for (int i = 0; i < n; i++){
        Node *node = (Node*) malloc(sizeof(Node));  // 创建一个新结点
        printf("请输入第%d个结点的数据:\n",i+1);
        scanf("%d",&node->data);

        node->prev = tail;  // 设置前驱指针
        node->next = NULL;  // 设置后继指针
        tail->next = node;  // 将新结点插到尾部
        tail = node;  // 更新尾结点位置
    }
    return head;
}

/**
 * 遍历双向链表
 */
void traverse(LinkList L){
    Node *p = L->next;
    while (p != NULL){
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
}

/**
 * 双向链表中插入新结点
 */
void insert(LinkList L,int pos,int data){
    Node *node = (Node*) malloc(sizeof(Node));  // 创建新结点
    node->data = data;

    Node *p = L->next;
    int i = 1;
    while (p != NULL && i < pos){
        p = p->next;
        i++;
    }
    if (p == NULL){
        printf("插入的位置非法!\n");
        return;
    }
    node->prev = p->prev;  // 插入新结点
    node->next = p;
    p->prev->next = node;
    p->prev = node;
}

/**
 * 双向链表中删除指定结点
 */
void remove_node(LinkList L,int pos){
    Node *p = L->next;
    int i = 1;
    while (p != NULL && i < pos){
        p = p->next;
        i++;
    }
    if (p == NULL){
        printf("删除的位置非法!\n");
        return;
    }
    p->prev->next = p->next;
    if (p->next != NULL){
        p->next->prev = p->prev;
    }
    free(p);
}

int main(){
    LinkList L = create();  // 创建双向链表
    traverse(L);  // 遍历双向链表
    insert(L, 3, 99);  // 向双向链表中插入新结点
    traverse(L);  // 遍历插入新结点后的双向链表
    remove_node(L, 5);  // 从双向链表中删除指定结点
    traverse(L);  // 遍历删除指定结点后的双向链表
    return 0;
}

以上代码会首先让用户输入该双向链表的长度,然后依次输入每个结点的数据。创建完成后,打印该双向链表的所有结点。接着,向该链表的第3个位置插入一个新结点(data=99),再打印插入结点后的所有结点。最后,从该链表的第5个位置删除一个结点,并打印删除结点后的所有结点。

示例2

假设我们已经有一个双向链表,其中包含了若干结点,我们想要从该链表中查找某个特定数据的结点并打印出该结点的位置,如果没有找到则打印未找到的提示信息。这个问题可以通过以下代码实现:

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

typedef struct Node{
    int data;
    struct Node *prev;
    struct Node *next;
}Node, *LinkList;

/**
 * 遍历双向链表
 */
void traverse(LinkList L){
    Node *p = L->next;
    while (p != NULL){
        printf("%d ",p->data);
        p = p->next;
    }
    printf("\n");
}

/**
 * 查找双向链表中特定数据的结点
 */
void find(LinkList L,int data){
    Node *p = L->next;
    int pos = 1;
    while (p != NULL && p->data != data){
        p = p->next;
        pos++;
    }
    if (p == NULL){
        printf("未找到数据为%d的结点!\n",data);
    }
    else{
        printf("数据为%d的结点在链表中的位置是%d。\n",data,pos);
    }
}

int main(){
    LinkList L = (LinkList) malloc(sizeof(Node));  // 创建头结点
    L->prev = NULL;
    L->next = NULL;

    Node *tail = L;  // 记录尾结点位置

    for (int i = 1; i <= 5; i++){
        Node *node = (Node*) malloc(sizeof(Node));  // 创建一个新结点
        node->data = i;

        node->prev = tail;  // 设置前驱指针
        node->next = NULL;  // 设置后继指针
        tail->next = node;  // 将新结点插到尾部
        tail = node;  // 更新尾结点位置
    }
    traverse(L);  // 遍历双向链表
    find(L, 3);  // 查找数据为3的结点
    find(L, 7);  // 查找数据为7的结点
    return 0;
}

以上代码会首先创建一个双向链表,并打印该链表中的所有结点。然后,查找该链表中数据为3和7的结点(其中只有数据为3的结点能被找到)。如果找到了,则打印结点的位置;如果没找到,则打印未找到的提示信息。

总而言之,在使用C语言来实现数据结构和双向链表操作的过程中,我们需要清楚每个方法的实现原理,同时遵守良好的编程规范,例如使用函数、结构体等方式将代码模块化,并进行模块间的相互调用。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C语言实现数据结构和双向链表操作 - Python技术站

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

相关文章

  • C#获取真实IP地址(IP转为长整形、判断是否内网IP的方法)

    C#获取真实IP地址(IP转为长整形、判断是否内网IP的方法) 在C#中,获取真实IP地址并进行IP转为长整形以及判断是否为内网IP的操作可以通过以下步骤完成: 步骤1:引入必要的命名空间 首先,我们需要引入System.Net和System.Net.Sockets命名空间,以便使用相关的类和方法。 using System.Net; using Syste…

    other 2023年7月30日
    00
  • 电脑启动不起来怎么办 电脑启动失败解决办法

    电脑启动不起来怎么办? 当我们打开电脑时,电脑无法正常启动,通常会出现蓝屏、黑屏或卡在启动画面等问题。这些问题可能由于硬件故障、软件问题、驱动程序错误或电源供应问题等多种原因引起。 一、硬件相关故障排查 确认电脑是否插上电源插头并通电 检查电脑电源与显示器的连接是否正确 排查电脑是否存在硬件问题,比如硬盘的坏道、内存的损坏等 如果电脑上有外设(如鼠标、键盘、…

    other 2023年6月27日
    00
  • MySQL中不能创建自增字段的解决方法

    如何在MySQL创建自增字段 在MySQL中创建表时,我们经常使用自增字段作为主键。但是有时,我们在创建数据库时会发生错误: ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server vers…

    other 2023年6月27日
    00
  • 详解Java中的有参构造方法与无参构造方法

    详解Java中的有参构造方法与无参构造方法 Java中的构造方法可以分为无参构造和有参构造,本文将详细讲解二者的区别和用法。 什么是无参构造方法? 无参构造方法是指不需要传入参数的构造方法,也叫默认构造方法。当我们在定义一个类时,如果没有手动定义构造方法,系统会自动为我们生成一个无参构造方法。 class Demo { int i; String s; //…

    other 2023年6月26日
    00
  • Android获取其他包的Context实例代码

    Android获取其他包的Context实例代码 在Android开发中,有时候我们需要获取其他应用程序的Context实例,以便进行跨应用的操作。下面是获取其他包的Context实例的代码示例: 示例一:通过包名获取Context实例 String packageName = \"com.example.otherapp\"; Cont…

    other 2023年10月13日
    00
  • 正则表达式话题

    正则表达式攻略 正则表达式是一种强大的文本匹配工具,可以用来查找、替换和提取文本中的特定模式。本攻略将详细介绍正则表达式的基本语法和常用操作符,以及两个示例说明。 基本语法 正则表达式由字符和操作符组成,用于定义匹配模式。下面是一些常用的基本语法: 字符:可以是字母、数字、特殊字符等。 操作符:用于定义匹配规则,如*、+、?等。 元字符:具有特殊含义的字符,…

    other 2023年7月28日
    00
  • Spring Boot Gradle发布war到tomcat的方法示例

    让我来详细讲解一下“Spring Boot Gradle发布war到Tomcat的方法示例”的完整攻略: 准备工作 在开始发布war到Tomcat之前,我们需要做以下准备工作: 安装Tomcat服务器 在Gradle项目中添加Tomcat插件,并且配置Tomcat服务器的信息 添加Tomcat插件 在Gradle项目中,添加war和tomcat插件: plu…

    other 2023年6月26日
    00
  • 聊聊Golang的语言结构和变量问题

    当涉及到Golang的语言结构和变量问题时,以下是一个完整的攻略,其中包含两个示例说明。 … … 语言结构 Golang是一种静态类型、编译型的编程语言,具有简洁、高效和并发性强的特点。以下是一些关于Golang语言结构的要点: Golang程序由包(package)组成,每个文件都属于一个包。 … 每个包可以包含多个函数(function)。 …

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