数据结构之双向链表(C语言实现)

双向链表的几种插入,删除,指定位置输出

双向链表的长相

 双向链表由前驱指针和后项指针还有数据域组成,把节点之间的前后指针相连接形成链表

双向链表节点的封装

双向链表与单链表相比,仅多了一个前驱指针

typedef struct Node {
    int data;//内容
    struct Node* frontNode;//前驱指针
    struct Node* nextNode;//后项指针
}*LPNODE;

双向链表的特点在于可以从左到右,也可以从右到左输出链表,因此需要一个头节点和尾节点分别指向链表的头和尾,实现链表的顺序或逆序打印

封装链表

typedef struct List {
    struct Node* HeadNode;//头节点
    struct Node* TailNode;//尾节点
    int curSize;//万金油参数
}*LPLIST;

万金油参数负责记录节点个数,方便后续条件判断

创建头节点和尾节点

这里主要是起到了封装头节点和尾节点的作用,函数初始化节点,返回相应节点

LPNODE CreateHeadNode() {
    LPNODE headNode = (LPNODE)malloc(sizeof(Node));//动态申请一个节点大小的内存
    assert(headNode);//判空
    headNode->frontNode = NULL;
    headNode->nextNode = NULL;
    return headNode;
}
LPNODE CreateTailNode() {
    LPNODE tailNode = (LPNODE)malloc(sizeof(Node));
    assert(tailNode);
    tailNode->frontNode = NULL;
    tailNode->nextNode = NULL;
    return tailNode;
}

判空是因为内存可能会申请失败,返回NULL。该函数是为了避免因赋值到空指针而影响到后续指针的操作。

创建新节点

LPNODE CreateNode(int data) {
    LPNODE newNode = (LPNODE)malloc(sizeof(Node));
    assert(newNode);
    newNode->data = data;//和头节点,尾节点不同,所要插入的节点是要初始化data的
    newNode->frontNode = NULL;
    newNode->nextNode = NULL;
    return newNode;
}

创建的newNode目的是方便后续新节点的创建

创建链表,初始化数据

LPLIST CreateList() {
    LPLIST list = (LPLIST)malloc(sizeof(List));//申请一个List大小的内存
    assert(list);//判空
    list->curSize = 0;//当前节点为0
    list->HeadNode = CreateHeadNode();//初始化头节点
    list->TailNode =CreateTailNode();//初始化尾节点
    return list;
}

头插法

这里的要点就是,当节点为0时,头节点的下一个节点是NULL,如果要按再次插入的话,必须要求头节点的下一个节点的前驱指针指向新节点,这里会因为该节点是NULL而不能人为操作空指针而引发中断

尾节点指向最后的节点,因为头插法是一直往中间插的,尾节点在末尾的位置一直没有发生改变

 代码如下:

void insertByHead(LPLIST list, int data) {
    LPNODE newNode = CreateNode(data);
    if(list->curSize==0)//当没有节点时
        list->TailNode = newNode;//为节点指向新节点
    else {
        newNode->nextNode = list->HeadNode->nextNode;//新节点的后项指针指向头节点的下一个
        list->HeadNode->nextNode->frontNode = newNode;//新节点的前驱指针指向新节点
    }
    list->HeadNode->nextNode = newNode;//头节点的后项指针指向新节点
    newNode->frontNode = list->HeadNode;//新节点的前驱指针指向头节点
    list->curSize++;//每插入一个,增加一个节点数目
}

尾插法

尾插法的要点和头插法类似,也是因为当节点为0时,后项指针会为空

此外,每插入的新节点就是尾节点,尾节点的指向要改变

代码如下: 

void insertByTail(LPLIST list, int data) {
    LPNODE newNode = CreateNode(data);
    if (list->curSize == 0)//节点为空时
        list->HeadNode =newNode;
    else {
        list->TailNode->nextNode = newNode;//直接插在后面
        newNode->frontNode = list->TailNode;
    }
    list->TailNode = newNode;//改变尾节点
    list->curSize++;
}

指定位置插入

思路:准备一个指针在前,一个指针在后,两个指针齐头并进,当后面的指针指向指定位置时,把数据插入到两个指针中间

这里的要点是如果没有找到指定数据,要把数据插入到链表后面,同时尾节点发生改变

代码如下:

void insertByAppoint(LPLIST list, int data, int posData) {
    LPNODE LeftNode = list->HeadNode->nextNode;//左指针
    LPNODE curNode = list->HeadNode->nextNode;//当前指针
    while (curNode != NULL && curNode->data != posData) {//没有找到就一直找,找到CurNode为空
        LeftNode = curNode;//移动左指针
        curNode = curNode->nextNode;//移动当前指针
    }
    LPNODE newNode = CreateNode(data);//创建新节点
    //后面就是插入中间的操作
    LeftNode->nextNode = newNode;
    newNode->frontNode = LeftNode;
    if (curNode == NULL)//尾节点改变
        list->TailNode = newNode;
    if (curNode != NULL) {
        newNode->nextNode = curNode;
        curNode->frontNode = newNode;
    }
    list->curSize++;//节点个数增加
}

节点删除

这里的思路和上面类似,准备两个指针,一个在前,一个在后,齐头并进。当找到需要删除的节点的位置停止移动,把后面指针的下一个节点和前指针指向的节点连接起来,最后释放后指针所指向节点的内存

代码如下:

void DeleteByAppoint(LPLIST list, int posData) {
    LPNODE frontNode = list->HeadNode;//前指针
    LPNODE curNode = list->HeadNode;//当前指针
    while (curNode != NULL && curNode->data != posData) {
        frontNode = curNode;
        curNode = curNode->nextNode;
    }
    if (curNode == NULL)//如果找不到就返回
        return;
    //连接curNode前后位置的节点
    frontNode->nextNode = curNode->nextNode;
    curNode->nextNode->frontNode = frontNode;
    free(curNode);//释放内存
    curNode = NULL;
    list->curSize--;//删除一个,节点少一个
}

链表的有序插入

准备一堆乱序的数据,通过有序插入,可以返还有序的链表

思路:按顺序一个个插入,当找到下一个节点的数值比所要插入节点中的数值大或者小时,插入该节点前面,形成从小到大或者从大到小的排序,没有找到就插在后面

要点:

1.如果要插入到最后面,就不需要把后项指针的前驱指针指向前面的节点,因为误操作空指针会引发程序中断

2.新节点当插入到最后的位置时,尾节点要发生改变

代码如下:

void insertBySqList(LPLIST list, int Mydata) {
    LPNODE frontNode = list->HeadNode;//前指针
    LPNODE PosNode = list->HeadNode->nextNode;//指定位置指针
    while (PosNode != NULL && PosNode->data < Mydata) {
        frontNode = PosNode;//移动
        PosNode = PosNode->nextNode;//移动
    }
    LPNODE newNode = CreateNode(Mydata);//新节点创建
    frontNode->nextNode = newNode;
    newNode->frontNode = frontNode;
    if (PosNode == NULL)//改变尾节点
        list->TailNode = newNode;
    if (PosNode != NULL) {
        PosNode->frontNode = newNode;
        newNode->nextNode = PosNode;
    }
    list->curSize++;
}

打印链表

可以从头到尾打印,也可以从尾到头打印

1.从头到尾

void printListByHead(LPLIST list) {
    printf("头输出:\t");
    LPNODE pMove = list->HeadNode->nextNode;
    while (pMove) {
        printf("%d\t", pMove->data);
        pMove = pMove->nextNode;
    }
    printf("\n");
}

2.从尾到头

void printListByTail(LPLIST list) {
    printf("尾输出:\t");
    LPNODE pMove = list->TailNode;
    while (pMove!=list->HeadNode) {
        printf("%d\t", pMove->data);
        pMove = pMove->frontNode;
    }
    printf("\n");
}

测试代码:

为了控制台输出数据的美观,我增加了一些换行符和文字修饰

代码如下:

int main() {
    LPLIST list = CreateList();
    printf("\n尾插法\n");
    for (int i = 0; i < 5; i++)
        insertByTail(list, i);
    printListByHead(list);
    printListByTail(list);
    printf("删除指定数据的");
    DeleteByAppoint(list, 3);
    printListByHead(list);
    LPLIST list2 = CreateList();
    printf("\n头插法\n");
    for (int i = 0; i < 5; i++)
        insertByHead(list2, i);
    printListByHead(list2);
    printListByTail(list2);
    printf("删除指定数据的");
    DeleteByAppoint(list2, 2);
    printListByTail(list2);
    printf("插入指定数据的");
    insertByAppoint(list2, 100, 2);
    printListByTail(list2);
    printf("插入指定数据的");
    printListByHead(list2);
    LPLIST list3 = CreateList();
    int array[] = { 3,5,9,7,2,6 };
    printf("有序链表构建的");
    for(int i=0;i<6;i++)
    insertBySqList(list3, array[i]);
    printListByTail(list3);
    printf("有序链表构建的");
    printListByHead(list3);
    return 0;
}

运行效果如下:

 

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值