前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【数据结构】双向链表

【数据结构】双向链表

作者头像
s-little-monster
发布2024-06-06 20:56:02
760
发布2024-06-06 20:56:02
举报

一、双向链表的结构

1、带头双向循环列表

头:指一个固定点,我们叫它哨兵位,它不存储任何数据,只是起到作为一个哨兵的作用,跟头节点的意义不同 双向链表概念图:

每一个节点由一个数据点和两个指针点组成,指针点一指向前面的元素,指针点二指向后边的元素

二、实现双向链表

1、ListNode.h

代码语言:javascript
复制
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* next; 
	struct ListNode* prev; 
	LTDataType data;
}LTNode;
//节点创建函数
LTNode* LTBuyNode(LTDataType x);
//初始化
LTNode* LTInit();
//摧毁
void LTDestroy(LTNode* phead);
//打印
void LTPrint(LTNode* phead);
//检查链表是否只有哨兵位
bool LTEmpty(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead, LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPushFront(LTNode* phead, LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//pos节点后插入
void LTInsert(LTNode* pos, LTDataType x);
//删除pos节点
void LTErase(LTNode* pos);
//查找某个数据的位置
LTNode* LTFind(LTNode* phead, LTDataType x);

2、ListNode.c

代码语言:javascript
复制
#include "ListNode.h"

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

//初始化,建立哨兵位
LTNode* LTInit()
{
    LTNode* head = LTBuyNode(-1);
    return head;
}

//销毁链表
void LTDestroy(LTNode* phead)
{
    assert(phead);
    LTNode* n1 = phead->next;
    while (n1 != phead)
    {
        LTNode* n2 = n1->next;
        free(n1);
        n1 = n2;
    }//将除了哨兵位以外的所有节点销毁
    free(phead);//最后销毁哨兵位
}

//打印链表
void LTPrint(LTNode* phead)
{
    LTNode* pur = phead->next;
    while (pur != phead)
    {
        printf("%d ", pur->data);
        pur = pur->next;
    }
    printf("\n");
}

//检查链表是否只含有哨兵位
bool LTEmpty(LTNode* phead)
{
    if (phead->next == phead && phead->prev == phead)
        return 1;
    else
        return 0;
}//当只含哨兵位时,phead的next与prev指针都是指向phead的

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode = LTBuyNode(x);
    phead->prev->next = newnode;
    newnode->prev = phead->prev;
    newnode->next = phead;
    phead->prev = newnode;//将新创建的newcode节点连接phead以及此时的phead前一个结点
}

//尾删
void LTPopBack(LTNode* phead)
{
    assert(phead && phead->next != phead);
    LTNode* del = phead->prev;
    del->prev->next = phead;
    phead->prev = del->prev;
    free(del);
}

//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
    LTNode* pur = phead->next;
    LTNode* newnode = LTBuyNode(x);
    phead->next = newnode;
    newnode->prev = phead;
    newnode->next = pur;
    pur->prev = newnode;
}//头插的意义是在哨兵位和第一个有效节点之间插入节点

//头删
void LTPopFront(LTNode* phead)
{
    LTNode* pur = phead->next;
    LTNode* purr = pur->next;
    phead->next = purr;
    purr->prev = phead;
    free(pur);
}//头删的意义是删除哨兵位后边的这一个节点

//在指定位置后插
void LTInsert(LTNode* pos, LTDataType x)
{
    LTNode* pur = pos->next;
    LTNode* newnode = LTBuyNode(x);
    pos->next = newnode;
    newnode->prev = pos;
    newnode->next = pur;
    pur->prev = newnode;
}

//删除指定位置
void LTErase(LTNode* pos)
{
    LTNode* before = pos->prev;
    LTNode* after = pos->next;
    before->next = after;
    after->prev = before;
    free(pos);
}

//查找某个节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    LTNode* pur = phead->next;
    while (pur != phead)
    {
        if (pur->data == x)
        {
            return pur;
        }
        pur = pur->next;
    }
    return NULL;
}//找到则返回该节点的地址,找不到则返回空

3、test.c

代码语言:javascript
复制
#include "ListNode.h"
void test()
{
	//测试初始化与检测哨兵位是否有效
	//LTNode* plist = LTInit();
	//printf("%d", LTEmpty(plist));
	
	//测试尾插及打印
	//LTPushBack(plist, 1);
	//LTPushBack(plist, 2);
	//LTPushBack(plist, 3);
	//LTPrint(plist);
	// 
	//测试头插
	//LTPushFront(plist, 5);
	//LTPrint(plist);
	// 
	//测试指定位置后插
	//LTInsert(plist->next->next, 8);
	// 
	// 测试尾删
	//LTPopBack(plist);
	//LTPrint(plist);
	// 
	// 测试头删
	//LTPopFront(plist);
	// 
	// 测试指定位置删
	//LTErase(plist->next->next);
	//LTPrint(plist);
	// 
	//测试查找某一位置
	//LTNode* n = LTFind(plist, 3);
	//printf("%p", n);

	//测试销毁链表
	//LTDestroy(plist);
}
int main()
{
	test();
	return 0;
}

调试结果

三、链表和顺序表的不同点

不同点

顺序表

链表

存储空间

逻辑和物理上连续

逻辑上连续,物理上不一定连续

随机访问

支持

不支持

任意位置插入或者删除元素

可能需要搬移元素,效率低

只需修改指针指向

插入

动态顺序表,空间不够时需要扩容

没有容量的概念

应用场景

元素高效访问/频繁访问

任意位置插入和删除频繁

今天的分享就到这里了~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、双向链表的结构
    • 1、带头双向循环列表
    • 二、实现双向链表
      • 1、ListNode.h
        • 2、ListNode.c
          • 3、test.c
            • 调试结果
            • 三、链表和顺序表的不同点
            相关产品与服务
            腾讯云服务器利旧
            云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档