【数据结构】双向链表

1.双向链表

1.1 概念与结构

1.2 实现双向链表

c 复制代码
//List.h 
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LTDataType; 
typedef struct ListNode 
{ 
    struct ListNode* next; //指针保存下⼀个结点的地址 
    struct ListNode* prev; //指针保存前⼀个结点的地址 
    LTDataType data; 
}LTNode; 

//第一种方法:要修改头结点,要用LTNode** pphead接收
//void LTInit(LTNode** pphead); 
//第二种方法:不需要二级指针
LTNode* LTInit(); 

//第一种销毁方法:
//void LTDestroy(LTNode** pphead);
//第二种方法:
void LTDestroy(LTNode* phead);

//创建
LTNode* LTBuyNode(LTDataType x);

//打印
void LTPrint(LTNode* phead);
//判断链表是否为空
bool LTEmpty(LTNode* phead); 

//在双向链表中,增删改查都不会改变哨兵位结点,所以用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);
c 复制代码
//List.c
#include"List.h"

LTNode* LTBuyNode(LTDataType x)
{
    LTNode* newnode=(LTNode*)malloc(sizeof(LTNode));
    if(newnode==NULL)
    {
        perror("malloc");
        exit(1);
    }
    newnode->data=x;
    newnode->next=newnode->prev=newnode;//空指针也可以用这句代码
    return newnode;
}

//void LTInit(LTNode** pphead)
//{
//    *pphead=LTBuyNode(-1);
//}
LTNode* LTInit()
{
    LTNode* phead=LTBuyNode(-1);
    return phead;
}

//void LTDestroy(LTNode** pphead)
//{
    //先从第一个有效结点开始删除
//   LTNode* pcur=(*pphead)->next;
//    while(pcur!=*pphead)
//    {
//        LTNode* next=pcur->next;//先把下一个结点存起来
//        free(pcur);
//        pcur=next;
//    }
    //销毁头结点
//    free(*pphead);
//    *pphead=NULL;
//}
void LTDestroy(LTNode* phead)
{
    LTNode* pcur=(phead)->next;
    while(pcur!=phead)
    {
        LTNode* next=pcur->next;//先把下一个结点存起来
        free(pcur);
        pcur=next;
    }
    free(phead);
    phead=NULL;
}



void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode=LTBuyNode(x);
    //phead->prev是原本的尾结点
    //先修改newnode,不会改变原链表
    newnode->prev=phead->prev;
    newnode->next=phead;
    //先连后断
    phead->prev->next=newnode;
    phead->prev=newnode;
}

//插入在第一个有效的结点的前面
void LTPushFront(LTNode* phead, LTDataType x)
{
    LTNode* newnode=LTBuyNode(x);
    //先修改newnode,不会改变原链表
    newnode->next=phead->next;
    newnode->prev=phead;
    phead->next->prev=newnode;
    phead->next=newnode;
}

bool LTEmpty(LTNode* phead)
{
    assert(phead);
    return phead->next==phead;//链表的next指向自己就返回
}

//尾删
//头删和尾删要确保链表不为空
void LTPopBack(LTNode* phead)
{
  	assert(!LTEmpty(phead));
    LTNode* del=phead->prev;
    del->prev->next=phead;
    prev->prev=del->prev;
    free(del);
    del=NULL;
}
//打印
void LTPrint(LTNode* phead)
{
    LTNode* pcur=phead->next;
    //只要pcur->next!=phead
    while(pcur->next!=phead)
    {
        printf("%d->",pcur->data);
        pcur=pcur->next;
    }
    printf("\n");
}
//删除第一个有效结点
void LTPopFront(LTNode* phead)
{
    assert(!LTEmpty(phead));
    LTNode*del=phead->next;
    del->next->prev=phead;
    phead->next=del->next;
    free(del);
    del=NULL;
}
//查找
LTNode *LTFind(LTNode* phead,LTDataType x)
{
    assert(phead);
    LTNode* pcur=phead->next;
    while(pcur!=head)
    {
        if(pcur->data==x)
            return pcur;
        pcur=pcur->next;
    }
    //未找到
    return NULL;
}

//在pos之后插入一个值为x的结点
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);
    LTNode* newnode=LTBuyNode(x);
    //先修改newnode,不会改变原链表
    newnode->prev=pos;
    newnode->next=pos->next;
    //先连再断
    pos->next->prev=newnode;
    pos->next=newnode;
}

void LTErase(LTNode* pos)
{
    assert(pos);
    pos->prev->next=pos->next;
    pos->next->prev=pos->prev; 
    free(pos);
    pos=NULL;
}
c 复制代码
//test.c
#include"List.h"
void test01()
{
    //LTNode* plist=NULL;
    //LTInit(&plist);
    LTNode* plist=LTInit();
    
    //尾插
    LTPushBack(plist,1);
    LTPushBack(plist,2);
    LTPushBack(plist,3);
    LTPushBack(plist,4);
    
    //头插
    LTPushFront(plist,1);
    LTPushFront(plist,2);
    LTPushFront(plist,3);
    LTPushFront(plist,4);
    
    //尾删
    LTPopBack(plist);
    LTPrint(plist);
    LTPopBack(plist);
    LTPrint(plist);
    LTPopBack(plist);
    LTPrint(plist);
    
    //头删
    LTPopFront(plist);
    LTPrint(plist);
    LTPopFront(plist);
    LTPrint(plist);
    LTPopFront(plist);
    LTPrint(plist);
    
    //查找
    LTNode* pos=LTFind(plist,2);
    if(pos)
        printf("找到了\n");
    else
        printf("未找到\n");
    
    //在pos之后插入100
    LTInsert(pos,100);
    LTPrint(plist);
    //删除pos结点
    LTErase(pos);
    LTPrint(plist);
    
    //销毁
    //LTDesTroy(&plist);
    LTDesTroy(plist);
    plist=NULL;
}
int main()
{
    test01();
    return 0;
}

2.顺序表与链表的分析

不同点 顺序表 链表(单链表)
存储空间上 物理上⼀定连续 逻辑上连续,但物理上不⼀定连续
随机访问 ⽀持O(1) 不⽀持:O(N)
任意位置插⼊或者删除元素 可能需要搬移元素,效率低O(N) 在指定位置之后插入或删除元素只需修改指针指向
插⼊ 动态顺序表,空间不够时需要扩容和空间浪费 没有容量的概念,按需申请释放,不存在空间浪费
应⽤场景 元素⾼效存储+频繁访问 任意位置⾼效插⼊和删除
相关推荐
CSharp精选营2 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假5 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠6 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦13 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠14 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾14 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82114 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q14 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒14 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
WL学习笔记14 天前
单项不带头不循环链表
数据结构·链表