[线性表](5.1)双向链表的基本操作

通过单向链表的学习,我们已经明白了链表的形成,链表就如同一个铁链一样

一个结点连着下一个结点,形成一条链型的数据结构。

现在我们来简单了解双向链表实现增删改查功能

先上全部代码:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include<string.h>
void intput(void);/*增加数据*/
void output(void);/*输出数据*/
void deleteInfo(void);/*删除数据*/
void modifyInfo(void);/*修改数据*/
void head(void);
struct student
{
    char name[20];
    int score;
    struct student* llink;
    struct student* rlink;
};
struct student* p1, * p2, * ptr, * h;
int main()
{
    head();
    while (1)
    {
        printf("\n请选择:1.输入 2.输出 3.删除 4.修改");
        char a = _getche();
        switch (a)
        {
        case'1':
            intput();
            break;
        case'2':
            output();
            break;
        case'3':
            deleteInfo();
            break;
        case'4':
            modifyInfo();
            break;
        }
    }


}
void head(void)
{
    ptr = (struct student*)malloc(sizeof(struct student));
    h = ptr;
    h->llink = NULL;
    h->rlink = NULL;
}
void intput(void)
{
    ptr = (struct student*)malloc(sizeof(struct student));
    printf("\n请输入姓名:");
    scanf("%s", ptr->name);
    printf("请输入分数:");
    scanf("%d",&ptr->score);
    p1 = h;
    p2 = h->rlink;

    while (p2 != NULL)
    {
        p1 = p2;
        p2 = p2->rlink;
    }

    ptr->rlink = p2;
    ptr->llink = p1;

    p1->rlink = ptr;

    printf("\n输入成功\n");
}
void output(void)
{
    p1 = h;
    p2 = h->rlink;
    if (p2 != NULL)
    {
        while (p2 != NULL)
        {
            printf("\n姓名:%s,分数:%d\n", p2->name, p2->score);
            p2 = p2->rlink;
        }
    }
    else
    {
        printf("\n未找到数据\n");
    }

}
void deleteInfo(void)
{
    char name[20], a;
    
    p1 = h;
    p2 = h->rlink;
    if (p2 == NULL)
    {
        printf("\n未找到数据");
    }
    else
    {
        printf("\n请输入需要删除的姓名:");
        scanf("%s", name);
        while (p2 != NULL && strcmp(p2->name, name) != 0)
        {
            p1 = p2;
            p2 = p2->rlink;
        }
        if (p2 == NULL)
        {
            printf("未找到该学生数据\n");
        }
        else
        {
            printf("\n确定是否删除:Y/N");
            printf("\n该学生数据为:%s,%d\n", p2->name, p2->score);
            a = _getche();
            if (a == 'Y' || a == 'y')
            {
                p1->rlink = p2->rlink;
                p1->rlink->llink = p1;
                free(p2);
                printf("已删除该学生数据\n");
            }

        }
    }
}
void modifyInfo(void)
{
    char name[20], s_temp[10];
    p1 = h;
    p2 = h->rlink;
    printf("\n请输入需要修改的学生姓名:\n");
    scanf("%s", name);

    if (p2 == NULL)
    {
        printf("\n未找到学生数据\n\n");
    }
    else
    {
        while (p2 != NULL && strcmp(p2->name, name) != 0)
        {
            p1 = p2;
            p2 = p2->rlink;
        }
        if (p2 == NULL)
        {
            printf("\n没找到需要调整的学生数据\n");
        }
        else
        {
            printf("\n学生姓名:%s,分数:%d", p2->name, p2->score);
            printf("请输入新的分数:");
            scanf("%s",s_temp);
            p2->score = atoi(s_temp);
            printf("\n调整后:\n学生姓名:%s,分数:%d", p2->name, p2->score);
        }
    }

}

在程序中实现了链表的四个基础操作:增,删,改,查

先来看看如何创建一个双向链表:声明结构体,定义结构体指针

链表是通过结构体自引用的特性形成的数据结构,然而操作结点需要用到指针

img

程序定义了一个拥有姓名,分数的结构体,在结构体内,成员除了数据域的姓名和分数,还多出了左结点指针:llink(访问前置结点)右结点指针:rlink(访问后置节点)

同时定义了四个student结构的指针变量,利用这四个指针来操作结点

初始化链表

先创建一个头结点:

img

使用head函数,我们来剖析一下该函数的内容

img

首先利用分配指针ptr,分配一片内存形成头结点

让头指针指向该节点,同时让头结点的左指针域与右指针域全部指向空

就完成了头结点的初始化,如图所示

img

头结点用于控制整条链表,所以一般头结点的数据域内是不存储数据的

创建结点与链接

如何把一个个结点链接起来?我们看intput函数内

img

  • 先利用分配指针创建一个新的结点
  • 再给该节点的数据域赋值

完成上述两个动作后,一个结点就创建完成了,我们如何将它链接其他结点呢?这时候就要用到操作指针P1,P2了

先让操作指针p1指向头结点,再让操作指针p2指向头结点的右指针域,我们从左到右链接,或许我们可以这样理解

  • p1:管理当前节点
  • p2:管理后置节点

如图所示:

img

通过循环来判断头结点的右指针域是否有数据

如果有:将p1移到下一个节点,p2移到下一个结点的右指针域

来寻找尾结点(无后置结点的结点),来链接新结点!

  • 链接新结点我们需要操作三个指针域
  • 一:让新结点的右指针域指向当前尾结点的右指针域
  • 二:让新结点的左指针域指向当前尾结点
  • 三:让当前尾结点的右指针域指向新结点

img

我们可以清楚的看到,前两句操作的是新结点,第一句是让新结点链接当前尾结点的后置结点,第二句是让新结点的左指针域链接当前尾结点。

注意:当前尾结点不一定是无后置指针域,而是满足循环要求的后p1(操作指针一)所指向的结点!

最后一句是操作p1(当前尾结点),让它的右指针域指向新结点

从而完成链接,如图所示:

img

我们将图整理一下,看的更为直观:

img

如何删除结点

img

在重复了以上几个动作后,我们得到了一条链表:如图所示:

img

当我们需要删除ptr(2)结点时应该怎么做呢?首先通过数据域来找到结点所在

img

依旧是:让p1管理头结点,p2管理头结点的右指针域,或许我们可以这样理解

  • p1:管理当前结点
  • p2:管理后置节点

因为程序当中只有ptr第一次分配的地址是有迹可循的!h指向了它!第二次,第三次,分配的地址均由h的指针域链接起来!所以先从头结点开始!

先判断头结点后是否有结点,如果没有,则未添加结点,完成不了动作

如果有,则进入循环,找到符合要求的结点:

img

通过p2去一次一次的访问结点,知道走完整个链表,或者找到符合条件的结点进入判断

如果是走完整个链表而结束的循环,则整条链表中没有所需要的数据

如果是输入的数据与结点中的数据相同,则有该数据,我们就删除它

img

我们可以看到,在释放之前,有两个动作

  • p1->rlink=p2->rlink
  • 让当前结点的右指针域指向需要释放的右指针域从而实现链接后置结点
  • p1->rlink->llink=p1
  • 这时当前节点的右指针域已经连接了后置结点,让后置节点的左指针域指向当前结点

如图所示:

img

从图可以看到,在未释放的时候,p1管理着ptr(1),p2管理着ptr(2),通过上述操作让p1的右指针域指向p2的右指针域,让p2的右指针域的左指针域指向p1

最后释放p2,如图所示

img

这样就完成了删除后链接的功能

修改指定结点内的数据

通过上述的删除我们已经明白了如何操作指定结点,完成自己需要的动作

我们来看看如何修改节点内的数据

img

  • 操作指针一:p1继续管理头结点
  • 操作指针二:p2继续管理头结点的右指针域

先判断头结点后是否连接了结点,如果没有,则链表中没有结点

如果有,则进行循环来找到符合条件的结点

img

通过p2去一次一次的访问结点,知道走完整个链表,或者找到符合条件的结点进入判断

如果是走完整个链表而结束的循环,则整条链表中没有所需要的数据

如果是输入的数据与结点中的数据相同,则有该数据,则p2管理着这个节点

通过p2来操作它

img

剩下的操作相信不用讲,都懂,这儿是在原有链表的基础上做动作,所以不涉及链表的链接,比较简单

输出链表中的数据

通过指针遍历链表,每走到一个结点输出结点数据域中的数据,直到走到空的结点

img

总结:

  • 双向链表的操作主要在创建链接,删除链接两个方面,我们需要通过操作指针来完成链接的工作
  • 由于有两个指针域,则需要两个操作指针来管理,完成两结点当中的链接
  • 在这里我们主要是弄明白了双向链表是如何建立的,它的指向关系是怎么样的
  • 弄懂这些,就能进入之后的链表学习!

本文链接:

https://nullcode.fun/17.html
1 + 3 =
快来做第一个评论的人吧~