【初阶数据结构】实现带哨兵位的的双向循环链表

1.认识双链表

双链表的全称一般为带哨兵位的双向循环链表,有如下两个优点:

1)哨兵位的存在可以使增、删、查、改等操作的代码实现过程更加统一。

1)单链表无法找到前驱节点,双链表则更加灵活,可以访问前驱和后继节点。


2.代码实现

2.1 创建文件

DoubleLinkedList.h包含头文件,函数的声明。

DoubleLinkedList.c函数功能的具体实现过程。

Test.c测试函数功能的实现。

2.2 创建双链表

DoubleLinkedList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

//使用typedef重命名类型——提高链表的通用性
typedef int SLDataType;
/*本文是实现存储整形数据的顺序表,如果想利用现有的顺序表代码逻辑存储其他数据,
只需要将这里的int改为对应的数据类型名称,而不需要去具体过程中更改*/

//定义双向链表中节点的结构
typedef struct ListNode 
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

2.3 初始化双链表 

双链表是带有哨兵位的,插入数据之前链表中必须要先初始化一个哨兵位。

2.3.1 第一种初始化方式

需要传参,并且因为需要改变哨兵位的指向,所以需要传二级指针,因为哨兵位本身就是一个指针,需要使用指针的指针即二级指针才能在其他函数中修改哨兵位。

DoubleLinkedList.c

void LTInit(LTNode** pphead)
{
	*pphead = (LTNode*)malloc(sizeof(LTNode));
	if (*pphead == NULL) 
    {
		perror("malloc fail!");//检验malloc是否成功开辟动态空间
		exit(1);//调用函数exit(),参数为1时表示程序执行出现了错误或异常情况,需要提前终止程序的执行
	}
	(*pphead)->data = -1;
	(*pphead)->next = (*pphead)->prev = *pphead;//哨兵位的前驱和后继都是自己本身
}

2.3.2 第二种初始化方式

不需要传参数,但是需要返回初始化后节点的地址,所以要先定义一个创建新节点的函数。

DoubleLinkedList.c

//创建新节点
LTNode* LTBuyNode(LTDataType x)
{
    LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
    if (newnode == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->next = newnode->prev = newnode;

    return newnode; 
}

//初始化
LTNode* LTInit()
{
    LTNode* phead = LTBuyNode(-1);
    return phead;
}

 2.4 从尾部插入新节点

DoubleLinkedList.c

void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);//assert断言,检验哨兵位是否为空
    LTNode* newnode = LTBuyNode(x);

    newnode->next = phead;
    newnode->prev = phead->prev;
    phead->prev->next = newnode;
    phead->prev = newnode;
}
/*不需要遍历寻找尾节点,因为双向循环链表可以直接通过头节点找到尾节点*/

 注意,核心的四条语句顺序不能调换,下图更直观的展示尾插过程,读者可自行思考调换顺序后会引起什么问题。

2.5 从头部插入新节点

DoubleLinkedList.c

void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode = LTBuyNode(x);

    newnode->next = phead->next;
    newnode->prev = phead;
    phead->next->prev = newnode;
    phead->next = newnode;
}

2.6 打印双链表

为了测试尾插函数是否能正常运行,需要将尾插和头插后的链表打印出来。

DoubleLinkedList.c

void LTPrint(LTNode* phead)
{
    assert(phead);
    LTNode* pcur = phead->next;

    while (pcur != phead)
    {
        printf("%d->", pcur->data);
        pcur = pcur->next;//临时指针移动到下一个节点
    }
    printf("\n");
}

2.7 从尾部删除节点

DoubleLinkedList.c

void LTPopBack(LTNode* phead)
{
    assert(phead);
    //链表为空:只有一个哨兵位节点
    assert(phead->next != phead);

    LTNode* del = phead->prev;
    LTNode* prev = del->prev;

    prev->next = phead;
    phead->prev = prev;

    free(del);
    del = NULL;
}

 2.8 从头部删除节点

DoubleLinkedList.c

void LTPopFront(LTNode* phead)
{
    assert(phead);
    assert(phead->next != phead);

    LTNode* del = phead->next;
    LTNode* next = del->next;

    next->prev = phead;
    phead->next = next;

    free(del);
    del = NULL;
}

2.9 通过数据域查找节点,返回其对应的位置

DoubleLinkedList.c

LTNode* LTFind(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* pcur = phead->next;

    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        pcur = pcur->next;
    }

    return NULL;
}

2.10 在pos位置之后插入节点

DoubleLinkedList.c

void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode = LTBuyNode(x);

    newnode->next = pos->next;
    newnode->prev = pos;
    pos->next->prev = newnode;
    pos->next = newnode;
}

2.11 删除pos位置的节点

DoubleLinkedList.c

void LTErase(LTNode* pos)
{
    assert(pos);

    pos->next->prev = pos->prev;
    pos->prev->next = pos->next;

    free(pos);
    pos = NULL;
}

2.12 销毁链表       

DoubleLinkedList.c

void LTDesTroy(LTNode* phead)
{
    assert(phead);

    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }

    //链表中只有一个哨兵位
    free(phead);//可以释放掉plist的哨兵位,因为phead的值是地址
    //phead = NULL;//因为phead只是一个临时变量,在函数内部修改,并不会影响到plist的哨兵位
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值