[数据结构 - 第3章补充] 线性表之双向链表(C语言实现)

一、什么是循环链表?

双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

既然单链表也可以有循环链表,那么双向链表当然也可以是循环表。

线性表的双向链表存储结构如下:

typedef int ElemType;
typedef struct DulNode
{
    ElemType data;  //数据域
    DulNode *prior;   //指向前驱结点的指针
    DulNode *next;    //指向后继结点的指针
}DulNode, DulList;

双向链表的循环、带头结点的空链表如下:

非空、循环、带头结点的双向链表如下:

二、双向链表的基本操作

2.1 插入操作

双向链表的插入操作:

实现代码如下:

// 插入元素操作
Status insertList(DulList *pList, int i, const ElemType e)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }
    // 只能在位置1以及后面插入,所以i至少为1
    if (i < 1)
    {
        printf("i is invalid!\n");
        return FALSE;
    }

    // 找到i位置所在的前一个结点
    Node *front = pList; // 这里是让front与i不同步,始终指向j对应的前一个结点
    for (int j = 1; j < i; j++) // j为计数器,赋值为1,对应front指向的下一个结点,即插入位置结点
    {
        front = front->next;
        if (front == NULL)
        {
            printf("dont find front!\n");
            return FALSE;
        }
    }

    // 创建一个空节点,存放要插入的新元素
    Node *temp = (Node *)malloc(sizeof(Node));
    if (!temp)
    {
        printf("malloc error!\n");
        return FALSE;
    }
    temp->data = e;

    // 插入结点
    temp->prior = front;
    temp->next = front->next;
    // 当空链表第一次插入结点时,此时head->next = NULL,调用NULL->prior会出错
    if (front->next != NULL)
        front->next->prior = temp;
    front->next = temp;

    return TRUE;
}

注意当空链表第一次插入结点的特殊情况。

2.2 删除操作

双向链表的删除操作:

实现代码如下:

// 删除元素操作
Status deleteList(DulList *pList, int i, ElemType *e)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }
    // 只能删除位置1以及以后的结点
    if (i < 1)
    {
        printf("i is invalid!\n");
        return FALSE;
    }

    // 找到i位置所在的前一个结点
    Node *front = pList; // 这里是让front与i不同步,始终指向j对应的前一个结点
    for (int j = 1; j < i; j++) // j为计数器,赋值为1,对应front指向的下一个结点,即插入位置结点
    {
        front = front->next;
        if (front->next == NULL)
        {
            printf("dont find front!\n");
            return FALSE;
        }
    }

    // 提前保存要删除的结点
    Node *temp = front->next;
    *e = temp->data; // 将要删除结点的数据赋给e

    // 删除结点
    if (front->next->next != NULL) // 删除的不是尾结点,才进入
    {
        front->next->prior = front;
    }
    front->next = front->next->next;

    // 销毁结点
    free(temp);
    temp = NULL;

    return TRUE;
}

注意:当双向链表的空链表第一次插入结点时,或者在尾结点后插入或删除的特殊情况,不需要设置插入位置后一个结点的直接前驱指针,因为此时插入位置的后一个结点为 NULL。

三、完整程序

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

#define TRUE 1
#define FALSE 0
typedef int Status; // Status是函数结果状态,成功返回TRUE,失败返回FALSE

typedef int ElemType;
// 双向非循环链表的结构定义
typedef struct Node
{
    ElemType data;  //数据域
    Node *prior;   //指向前驱结点的指针
    Node *next;    //指向后继结点的指针
}Node, DulList;

void initList(DulList **pList); // 初始化链表操作
Status insertList(DulList *pList, int i, const ElemType e); // 插入元素操作
Status deleteList(DulList *pList, int i, ElemType *e); // 删除元素操作
Status getElem(DulList *pList, int i, ElemType *e); // 获取元素操作
Status insertListHead(DulList *pList, const ElemType e); // 头部后插入元素操作
Status insertListTail(DulList *pList, const ElemType e); // 尾部后插入元素操作
Status clearList(DulList *pList); // 清空链表操作
void traverseList(DulList *pList); // 遍历链表操作
int getLength(DulList *pList); // 获取链表长度操作

// 初始化链表操作
void initList(DulList **pList) // 必须使用双重指针,一重指针申请会出错
{
    *pList = (DulList *)malloc(sizeof(Node));
    if (!pList)
    {
        printf("malloc error!\n");
        return;
    }

    (*pList)->data = 0;
    (*pList)->prior = NULL;
    (*pList)->next = NULL;
}

// 插入元素操作
Status insertList(DulList *pList, int i, const ElemType e)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }
    // 只能在位置1以及后面插入,所以i至少为1
    if (i < 1)
    {
        printf("i is invalid!\n");
        return FALSE;
    }

    // 找到i位置所在的前一个结点
    Node *front = pList; // 这里是让front与i不同步,始终指向j对应的前一个结点
    for (int j = 1; j < i; j++) // j为计数器,赋值为1,对应front指向的下一个结点,即插入位置结点
    {
        front = front->next;
        if (front == NULL)
        {
            printf("dont find front!\n");
            return FALSE;
        }
    }

    // 创建一个空节点,存放要插入的新元素
    Node *temp = (Node *)malloc(sizeof(Node));
    if (!temp)
    {
        printf("malloc error!\n");
        return FALSE;
    }
    temp->data = e;

    // 插入结点
    temp->prior = front;
    temp->next = front->next;
    // 当空链表第一次插入结点时,此时head->next = NULL,调用NULL->prior会出错
    if (front->next != NULL)
        front->next->prior = temp;
    front->next = temp;

    return TRUE;
}

// 删除元素操作
Status deleteList(DulList *pList, int i, ElemType *e)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }
    // 只能删除位置1以及以后的结点
    if (i < 1)
    {
        printf("i is invalid!\n");
        return FALSE;
    }

    // 找到i位置所在的前一个结点
    Node *front = pList; // 这里是让front与i不同步,始终指向j对应的前一个结点
    for (int j = 1; j < i; j++) // j为计数器,赋值为1,对应front指向的下一个结点,即插入位置结点
    {
        front = front->next;
        if (front->next == NULL)
        {
            printf("dont find front!\n");
            return FALSE;
        }
    }

    // 提前保存要删除的结点
    Node *temp = front->next;
    *e = temp->data; // 将要删除结点的数据赋给e

    // 删除结点
    if (front->next->next != NULL) // 删除的不是尾结点,才进入
    {
        front->next->prior = front;
    }
    front->next = front->next->next;

    // 销毁结点
    free(temp);
    temp = NULL;

    return TRUE;
}

// 获取元素操作
Status getElem(DulList *pList, int i, ElemType *e)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }
    // 只能获取位置1以及以后的元素
    if (i < 1)
    {
        printf("i is invalid!\n");
        return FALSE;
    }

    // 找到i位置所在的结点
    Node *cur = pList->next; // 这里是让cur指向链表的第1个结点,与j同步
    for (int j = 1; j < i; j++) // j为计数器,赋值为1,对应cur指向结点
    {
        cur = cur->next;
        if (cur == NULL)
        {
            printf("dont find front!\n");
            return FALSE;
        }
    }

    // 取第i个结点的数据
    *e = cur->data;

    return TRUE;
}

// 头部后插入元素操作
Status insertListHead(DulList *plist, const ElemType e)
{
    Node *head;
    Node *temp;

    // 判断链表是否存在
    if (!plist)
    {
        printf("list not exist!\n");
        return false;
    }

    // 让head指向链表的头结点
    head = plist;

    // 创建存放插入元素的结点
    temp = (Node *)malloc(sizeof(Node));
    if (!temp)
    {
        printf("malloc error!\n");
        return false;
    }
    temp->data = e;

    // 头结点后插入结点
    temp->prior = head;
    temp->next = head->next;
    // 当空链表第一次插入结点时,此时head->next = NULL,调用NULL->prior会出错
    if (head->next != NULL)
        head->next->prior = temp;
    head->next = temp;

    return true;
}

// 尾部后插入元素操作
Status insertListTail(DulList *pList, const ElemType e)
{
    Node *cur;
    Node *temp;

    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }

    // 找到链表尾节点
    cur = pList;
    while (cur->next)
    {
        cur = cur->next;
    }

    // 创建存放插入元素的结点
    temp = (Node *)malloc(sizeof(Node));
    if (!temp)
    {
        printf("malloc error!\n");
        return -1;
    }
    temp->data = e;

    // 尾结点后插入结点
    temp->prior = cur;
    temp->next = cur->next;
    cur->next = temp; // 尾结点的直接后继指针是NULL,所以不用指定NULL的前驱指针

    return TRUE;
}

// 清空链表操作
Status clearList(DulList *pList)
{
    Node *cur; // 当前结点
    Node *temp; // 事先保存下一结点,防止释放当前结点后导致“掉链”

    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return FALSE;
    }

    cur = pList->next; // 指向第一个结点
    while (cur)
    {
        temp = cur->next; // 事先保存下一结点,防止释放当前结点后导致“掉链”
        free(cur); // 释放当前结点
        cur = temp; // 将下一结点赋给当前结点p
    }
    pList->next = NULL; // 头结点指针域指向空

    return TRUE;
}

// 遍历链表操作
void traverseList(DulList *pList)
{
    // 判断链表是否存在
    if (!pList)
    {
        printf("list not exist!\n");
        return;
    }

    Node *cur = pList->next;
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

// 获取链表长度操作
int getLength(DulList *pList)
{
    Node *cur = pList;
    int length = 0;

    while (cur->next)
    {
        cur = cur->next;
        length++;
    }

    return length;
}

int main()
{
    DulList *pList;

    // 初始化链表
    initList(&pList);
    printf("初始化链表!\n\n");

    // 尾部后插入结点
    printf("尾部后插入元素1、2、3\n\n");
    for (int i = 0; i < 3; i++)
    {
        insertListTail(pList, i+1);
    }

    // 头部后插入元素
    insertListHead(pList, 5);
    printf("头部后插入元素5\n\n");

    // 插入结点
    insertList(pList, 1, 9);
    printf("在位置1插入元素9\n\n");

    // 遍历链表并显示元素操作
    printf("遍历链表:");
    traverseList(pList);
    printf("\n");

    // 删除结点
    int val;
    deleteList(pList, 2, &val);
    printf("删除位置2的结点,删除结点的数据为: %d\n", val);
    printf("\n");

    // 遍历链表并显示元素操作
    printf("遍历链表:");
    traverseList(pList);
    printf("\n");

    // 获得链表长度
    printf("链表长度: %d\n\n", getLength(pList));

    // 销毁链表
    clearList(pList);
    printf("销毁链表\n\n");

    return 0;
}

输出结果如下图所示:

参考:

《大话数据结构 - 第3章》 线性表

原文地址:https://www.cnblogs.com/linuxAndMcu/p/11568643.html

时间: 2024-10-20 01:59:12

[数据结构 - 第3章补充] 线性表之双向链表(C语言实现)的相关文章

(续)线性表之双向链表(C语言实现)

在前文实现单向链表的基本操作下,本文实现 双向链表的基本操作. 双向链表与单链表差异,是双向链表结点中有前向指针和后向指针. 所以在插入和删除新结点元素时候不见要考虑后向指针还要考虑 前向指针. 以下是双向链表的C代码: #include<stdio.h> typedef struct node { int data; struct node *next; struct node *prior }Node; //链表的初始化 Node* InitList(int number) { int i

《数据结构》第二章:线性表

第2章:线性表 2.1 线性表的定义和基本操作 线性表是具有相同数据类型的n个数据元素的有限序列.n为表长,当n=0时该线性表是一个空表.a1是唯一的『第一个』数据元素,又称表头元素.An是唯一的『最后一个』数据元素,又称表尾元素.除第一个元素外,每个元素有且仅有一个直接前驱.除最后一个元素外,每个元素有且仅有一个直接后驱.线性表的特点:1) 表中元素个数有限.2) 表中元素具有逻辑上的顺序性,在序列中个元素排序有其先后次序.3) 表中元素都是数据元素,每个元素都是单个元素.4) 表中的数据类型

数据结构线性表链表的C语言实现

                                                                                      数据结构线性表链表的C语言实现      说明:线性表是一种最简单的线性结构,也是最基本的一种线性结构,所以它不仅是学习中的重点,也是应用开发非常常用的一种数据结构.它可以分为顺序表和链表.它的主要操作是数据元素的插入,删除,以及排序等.接下来,本篇文章将对线性表链表的基本操作和运用进行详细的说明(包含在源代码的注释中),并给

小猪的数据结构辅助教程——2.7 线性表中的双向循环链表

小猪的数据结构辅助教程--2.7 线性表中的双向循环链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.了解引入双向循环链表的原因 2.熟悉双向循环链表的特点以及存储结构 3.掌握双向循环链表的一些基本操作的实现逻辑 4.掌握逆序输出双向循环链表元素逻辑 1.双向循环链表的引入 2.双向循环链表的存储结构 双向循环链表的特点: 上面也说了,空间换时间,比起循环链表只是多了一个指向前驱的指针 特点的话: 判断空表:L ->next = L -> prior = L; 存

小猪的数据结构辅助教程——2.4 线性表中的循环链表

小猪的数据结构辅助教程--2.4 线性表中的循环链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.了解单链表存在怎样的缺点,暴露出来的问题 2.知道什么是循环单链表,掌握单链表的特点以及存储结构 3.掌握循环链表的一些基本操作的实现逻辑,最好能手撕代码 1.循环单链表的引入 2.循环链表的特点以及存储结构 循环链表的特点: 上面也说了,比单链表稍微高比格一点的地方就是: 链表最后一个结点的指针域指向了头结点而已,这样形成所谓的环,就是循环单链表了,呵呵! 特点的话有:

小猪的数据结构辅助教程——2.1 线性表中的顺序表

小猪的数据结构辅助教程--2.1 线性表中的顺序表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.抽象数据类型(ADT)的概念,三要素:数据,数据元素间的关系和数据的操作 2.线性表的特点:按照一条线排列的数据集合,1对1,除了首元和尾元,其他元素都有直接前驱和直接后继 3.牢记线性表的存储结构,要理解并熟悉12个基本操作的逻辑,最好能徒手撕出代码 4.求并集,顺序表的经典例子,必须掌握! 1.抽象的数据类型 简单点说: 抽象:有点像我们面向对象语言中的类的思想,将事物

小猪的数据结构辅助教程——2.2 线性表中的单链表

小猪的数据结构辅助教程--2.2 线性表中的单链表 标签(空格分隔): 数据结构 本节学习路线图与学习要点 学习要点: 1.理解顺序表以及单链表各自的有点以及缺点! 2.熟悉单链表的形式,对于头指针,头结点,尾结点,数据域和指针域这些名词要知道是什么! 3.熟悉单链表的结点结构 4.区分头指针与头结点! 5.熟悉创建单链表的两种方式:头插法和尾插法 6.了解单链表12个基本操作的逻辑 7.有趣的算法题:查找单链表的中间结点~ 1.单链表的引入(顺序表与单链表的PK) 2.单链表的结构图以及一些名

数据结构学习小结2 (第二章:线性表)

一.小结(1)认为最重要的内容小结:1.链表和顺序表的操作的代码实现.(初始化.创建.取值.查找.删除.插入)2.线性表的应用:线性表的合并:有序表的合并3.一元多项式的和的代码实现. (2)其他杂七杂八的收获:1.更深理解了->的含义:->是指针的指向运算符,通常与结构体一起使用.[从小组成员上获得的收获]2.通过作业和视频学习了单链表的逆转的实现3.尾指针不为空,尾指针->next才为空4.看了老师“带你打代码”里面有序表合并的代码写法,让我更熟悉一些操作,譬如: 新建一个结点: (

数据结构读书笔记----------第二章 线性表

2.1 线性表的定义 线性结构的特点是数据元素之间是一种线性关系,在一个线性表中,数据元素的类型是相同的,或者说线性表是由同一类型的数据元素构成的线性结构.综上所诉:线性表是具有相同数据类型的n(n>=0)个数据元素的有限序列. 2.2 线性表的顺序存储以及运算实现 //线性顺序存储的定义 #define Maxsize 100 typedef int DataType; typedef struct { DataType data[Maxsize]; int last; //用来记录线性表中最