数据结构笔记 | 链表

本文详细介绍了如何使用C语言实现链表的创建、操作,包括单向链表的构造、长度获取、插入、删除以及双向链表的基本操作,如初始化、插入和删除节点等。
摘要由CSDN通过智能技术生成

基本操作

创建链表

typedef struct Node{
    int val;
    struct Node *next;
}*NodePtr;

构造链表并返回表头的指针

NodePtr createList(int a[],int n)      //a[]是数组,n是数组的长度
{
    NodePtr L=NULL,p=NULL,r=NULL;      //p和r是临时的指针
    for(int i=0;i<n;i++)
    {
        p=(NodePtr)malloc(sizeof(struct Node));
        p->val=a[i];
        p->next=NULL;
        if(L==NULL)
            L=p;
        else
            r->next=p;
        r=p;    //将新结点放在链表尾部
    }
    return L;
}

求长度

单向链表
int getLength(NodePtr list)
{
    NodePtr p;     //p为遍历指针
    int n=0;
    for(p=list;p!=NULL;p=p->next)     //移动p的位置
    {
        n++;  
    }
    return n;
}
循环链表
int getCircleLength(NodePtr list)
{
    if(list==NULL)
        return 0;
    int n=0;
    NodePtr p=list;
    do  {
        p=p->next;
        n++;
    }while(p!=list);
    return n;
}

插入

在非空线性链表的第一个结点前插入item
NodePtr insertFirst(NodePtr list,ElemType item)
{
    NodePtr p=NULL;
    p=(NodePtr)malloc(sizeof(struct Node));
    p->val=item;
    p->next=list;
    return p;      
}

调用时:list=insertFirst(list,item);

在p指向的链结点后插入item
void insertNode(NodePtr p,ElemType item)
{
    NodePtr q=NULL;
    q=(NodePtr)malloc(sizeof(struct Node));
    q->val=item;
    q->next=NULL;      

    q->next=p->next;
    p->next=q;
}
线性链表中第n(n>0)个结点后面插入一个数据项为item的新结点
int insertNodeN(NodePtr list,int n,ElemType item)
{
    //寻找第n个
    int i;
    NodePtr p=list,q;
    for(i=1;i<n && p!=NULL;i++)     //ppt上p->next!=NULL一样,因为下面有p==NULL的判断???怎么设计呢
    {
        p=p->next;
    }
    if(p==NULL||i!=n)
    {
        printf("Error.\n");
        return -1;
    }
    
    //生成结点
    q=(NodePtr)malloc(sizeof(struct Node));
    q->val=item;
    q->next=NULL;
    
    //插入
    q->next=p->next;
    p->next=q;
    return 1;
}
在有序(从小到大)线性链表中相应位置上插入一个数据项为item的新结点
NodePtr insertNodeSequently(NodePtr list,ElemType item)
{
    NodePtr p=NULL,q=NULL,r=NULL;
    p=(NodePtr)malloc(sizeof(struct Node));
    p->val=item;
    p->next=NULL;
    
    //应该插在首位
    if(list==NULL||item < list->val)
    {
        p->next=list;
        list=p;
    }
    else
    {
        r=list;     //保存直接前驱点位置
        q=list->next;
        while(q!=NULL && item > q->val)
        {
            r=r->next;
            q=q->next;
        }
        
        p->next=q;
        r->next=p;
    }
    return list;
}

删除

从非空线性链表中删除p指向的链结点
NodePtr deleteNode(NodePtr p,NodePtr list)
{
    //删头结点
    if(p==list)
    {
        list=p->next;
    }
    else
    {
        NodePtr q=list;
        while(q->next!=p && q->next!=NULL)
        {
            q=q->next;
        }
        if(q->next!=NULL)
            q->next=p->next;     //容错机制,毕竟p可能不存在
    }
    free(p);
    return list;
}
删除数据值为item的所有链结点
NodePtr deleteNodeAll(NodePtr list,ElemType item)
{
    NodePtr q=list,p=list->next;      //q是前驱结点,保证相差1,最后再处理头结点
    while(p!=NULL)
    {
        if(p->val==item)
        {
            q->next=p->next;
            free(p);      //把p指的那个结点free
            p=q->next;
        }
        else
        {
            q=q->next;
            p=p->next;
        }
    }
    if(list->val==item)
    {
        q=list;
        list=list->next;
        free(q);
    }
    return list;
}

销毁

void deleteWhole(NodePtr list)
{
    NodePtr p=list;
    while(p!=NULL)
    {
        list=p->next;       //主要设计free
        free(p);
        p=list;
    }
}

逆转

NodePtr invertList(NodePtr list)
{
    NodePtr p,q,r;
    p=list;
    q=NULL;
    while(p!=NULL)
    {
        r=q;
        q=p;
        p=p->next;
        q->next=r;
    }
    list=q;
    return list;
}

合并

将两个按值有序(从小到大)连接的非空链表合并为一个有序链表
NodePtr mergeList(NodePtr lista,NodePtr listb)
{
    NodePtr listc,p=lista,q=listb,r;    //p,q分别指向lista和listb当前待插入的链结点,r指向listc最后的结点
    if(lista->val <= listb->val)
    {
        listc=lista;
        p=p->next;
    }
    else
    {
        listc=listb;
        q=q->next;
    }
    r=listc;    //listc指向a,b中较小的
    while(p!=NULL && q!=NULL)
    {
        if(p->val<=q->val)
        {
            r->next=p;
            r=p;
            p=p->next;
        }
        else
        {
            r->next=q;
            r=q;
            q=q->next;
        }
    }
    r->next=p?p:q;      //插入剩余链结点
    return listc;
}

复制

NodePtr copy(NodePtr lista)     //神奇!!!
{
    NodePtr listb;
    if(lista==NULL)
        return NULL;
    else
    {
        listb=(NodePtr)malloc(sizeof(struct Node));
        listb->val=lista->val;      //复制lista所指的链结点,并将该链结点指针赋予listb
        listb->next=copy(lista->next);      //再复制lista的直接后继点
    }
    return listb;   //复制后的由listb指出
    //因为返回是倒着返回,所以可以返回到首地址
}

双向链表

双向链表定义

typedef struct DNode{
    ElemType data;
    struct DNode *rlink,*llink;
}DNode,*DNodePtr;  

构造

DNodePtr initDLink(int n)
{
    int i;
    DNodePtr list,p;
    list=(DNodePtr)malloc(sizeof(struct DNode));
    scanf("%d",&list->data);
    list->rlink=list;
    list->llink=list;
    for(i=1;i<n;i++)
    {
        p=(DNodePtr)malloc(sizeof(struct DNode));
        scanf("%d",&p->data);
        list->llink->rlink=p;   //新来的都插到list左边
        p->llink=list->llink;
        list->llink=p;
        p->rlink=list;
    }
    return list;
}

插入

在非空双向循环链表中某个数据域的内容为x的链结点右边插入一个数据信息为item的新结点
void insertD(DNodePtr list,ElemType x,ElemType item)
{
    DNodePtr p=list->rlink;
    while(p!=list && p->data!=x)
    {
        p=p->rlink;
    }
    if(p==list)
    {
        printf("error\n");
        return ;
    }
    else
    {
        DNodePtr new=(DNodePtr)malloc(sizeof(struct DNode));
        new->data=item;
        new->llink=p;
        new->rlink=p->rlink;
        p->rlink->llink=new;
        p->rlink=new;
    }
}

删除

删除非空双向循环链表中数据域的内容为x的链结点,带有头结点
void deleteD(DNodePtr list, ElemType x)
{
    DNodePtr p=list->rlink;
    while(p!=list && p->data!=x)
    {
        p=p->rlink;
    }
    if(p==list)
    {
        printf("error\n");
        return ;
    }
    p->llink->rlink=p->rlink;
    p->rlink->llink=p->llink;
    free(p);
    return ;
}

Tips

  1. 定义指针时一定 p=NULL,这样我就可以检查后续是否乱用野指针
  2. 返回值可以是:
    • 1和0表状态
    • NodePtr list,返回起点
    • 返回某个结点NodePtr
  3. 插入操作的特殊情况(要改头结点):
    • 链表为空
    • 在头结点前插入
  4. 参数一般包含
    • list头
    • 操作位置指针p
  5. 删除
    • 一定释放 free(p),要事先保存结点指针
    • 参数一般都有list,因为可能会把list给删除
    • 看看链表是否非空
    • 调用: list=deleteNode(NodePtr list,...)
  6. header(又称哑结点dummy node)
    • 对链表结点的插入及删除操作统一了(不用考虑是否是头结点)

PS. 写的注释中,在表示第一个结点时使用了“头结点”这一称呼,希望大家悉心辨别

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值