数据结构之单向链表

本文详细介绍了单向链表的基本操作,包括链表的创建、插入、删除、逆序、归并及特殊应用如逆序存储整数加法。通过实例讲解了链表的动态内存管理,以及如何在链表中进行高效的数据处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单向链表从生成到销毁小结:

1.定义一个结构体用来生成结点:

typedef struct node
{
    int date;
    struct node *next;    //这里只能用struct node  不能用Node;因为Node在这还没生效;
}Node;


即定义一个struct node型的结构体,在以后的程序中用Node来代替struct node出现(方便更改和操作);


2.定义一个结构体用来存放链表的头结点和尾结点:

typedef struct node
{
    Node *head;
    Node *tail;
}LNode;


注意:该结构体中存放的是两个结点,(是地址!!!!)不可以直接对他进行取值!!!


3.生成一个元原始链表(空链表,没有指向任何结点)

LNode *createlist()
{
    LNode *list=(LNode *)malloc(sizeof(*list));
    list->head=NULL;
    list->tail=NULL;
    return list;
}


注意:新生成的原始链表要返回给主调函数用,注意返回值类型和指针函数类型;


4.给原始链表赋值:

void crea(LNode *list)
{    
    while(1)
    {
        int i;
        scanf("%d",&i);
        if(i==100)
        {
            break;
        }
        //生成孤立结点
        Node *pnew=(Node *)malloc(sizeof(*pnew));    //注意这里要在while()里面定义和分配空间!!!
        pnew->next=NULL;                            //因为每添加一个新结点就需要多增加一块存储空间!!!
        pnew->date=i;                                //这里错  1  次!!
//判断链表是否为空链表,是空链表就让链表的头和尾都指向孤立结点
        if(list->head==NULL)//注意不能直接用head!!!    //这里错  1  次!!
        {
            list->head=pnew;    
            list->tail=pnew;
        }
        else            //不是孤立结点就插入结点生成链表
        {
            list->tail->next=pnew;    //尾插法;1.让原链表的next指向新结点,
                                    //使新结点成为原链表尾结点的下一个结点;
            list->tail=pnew;        //2.让原链表的尾结点指向新结点,更新尾结点的位置;
        
        //    pnew->next=list->head;    //头插法:1.让新结点的next指向原链表的头结点;
        //    list->head=pnew;        //2.让原链表的头结点指向新结点,更新头结点的位置;
        }
    }
}


注意:lise->head才是链表的头结点!!!不是head。
插入和判断都是对链表的 (结点) 进行的,注意自己操作的是不是结点!!!

5.打印链表:
1).只打印

void print(LNode *list)
{
    if(list->head==NULL)    //若链表为空,直接退出
    {
        return ;
    }
    Node *p=list->head;        //注意这里p指向的是链表的头结点,p的类型是结点型的!!不是链表型的。
    while(p)                
    {
        printf("%d  ",p->date);        
        p=p->next;            //对结点进行操作!!
    }
    printf("\n");
}

2).打印并销毁链表

void print(LNode *list)
{
    if(list->head==NULL)    //若链表为空,直接退出
    {
        return ;
    }
    Node *p=list->head;        //用来指向要打印的结点
    Node *pre=NULL;            //用来指向打印完的结点
    while(p)                
    {
        printf("%d  ",p->date);
        pre=p;                //指向打印完的结点
        p=p->next;
        free(pre);            //销毁打印完的结点(释放其所占空间),即逐渐销毁链表所有的结点。
    }
    printf("\n");
    free(list);                //把空链表也销毁
}


注意这里是对结点进行操作


6.销毁链表

void destrylist(DNode *list)
{
    if(list->head==NULL)
    {
        return ;
    }
    Node *p=list->head;        //用来指向被销毁结点的下一个结点        
    Node *pre=NULL;            //用来指向被销毁的结点
    while(p)    //p与pre指向的是链表的结点,p与pre的类型是结点型的!!不是链表型的。
    {
        pre=p;                //指向要被销毁的结点
        p=p->next;            //指向被销毁结点的下一个结点
        free(pre);            //销毁结点
    }
    free(list);                //销毁空链表
}


注意这里是对结点进行操作
7.在链表中插入结点(顺序链表中插入后仍顺序)或者生成有序链表

void insertlist(LNode *list,int date)
{
    //1.生成孤立结点;
    Node *pnew=(Node *)malloc(sizeof(*pnew));    //为新结点分配空间
    pnew->date=date;                   //为新结点赋值
    pnew->next=NULL;                   //让新结点成为孤立结点
    
    //2.做插入前准备;        
    Node *p=list->head;        //指向链表的头结点,用来充当游标
    Node *pre=NULL;          //用来指向游标的上一个结点
    
    //3.找位置
    while(p)
    {
        if(p->date > date)
        {
            break;
        }
        pre=p;           //指向游标的上一个结点
        p=p->next;        //充当游标,最终指向要插入的位置
    }
    
    //4.插入  分4种情况
    if(pre==p)              //若游标与游标的上一结点相等,即没有进while循环,链表为空
    {
        list->head=pnew;
        list->tail=pnew;    //直接让链表的头尾结点都指向孤立结点;
    }    
/***************
特别注意:    
虽然定义了        (p=list->head),
但是绝对不能用(p=pnew)代替(list->head=pnew);
因为我要改变的是list这个链表,
而不是p这个副本!!  
****************/          
    else{
        if(p==NULL)                  //若链表不为空但游标为空,即链表中找不到比date大的,插在尾
        {
            pre->next=pnew;       //让游标的上一结点(即尾结点)的next指向孤立结点
        }            
       //注意这里不能用(p->next=pnew;) 因为此时p==NULL;会导致段错误!!    
       else if(pre==NULL)             //若链表不为空但游标的上一结点为空,即第一个数就比date大,插在头
        {
            pnew->next=p;       //让孤立结点的next指向头结点;
            list->head=pnew;        //更新头结点的位置,使孤立结点成为新的头结点(很重要且不能用p=pnew;)!!
        }        
/***************
为什么这里不能用p!!!用p最终改变的是p而不是链表list!!
注意使孤立结点成为新的头结点很重要且不能用(p=pnew;)!!
明明已经定义了(p=list->head) 为什么不能用p=pnew???
因为我传进来的是list->head,用p只是改变了p,list->head还是指向原来的位置  
****************/            
        else                    //否则 插到链表中两个结点中间
        {
            pnew->next=p;      //孤立结点的next指向游标
            pre->next=pnew;    //游标的上一结点的next指向孤立结点
        }
    }
}

8.删除链表中的某个结点

void deletelist(LNode *list,int date)
{
    //1.做删除前准备
    Node *p=list->head;        //定义一个结点型结构体指针充当游标用来指向要删除的结点    (是结点型的)
    Node *pre=NULL;           //定义一个结点型结构体指针用来指向游标的上一个结点

    //2.找位置
    while(p)
    {
        if(p->date==date)    //若该结点要删除
        {
            break;       //直接跳出
        }
        pre=p;           //指向游标的上一个结点
        p=p->next;        //充当游标,最终指向要插入的位置
    }

    //3.删除,    分4种情况
    if(p==NULL)              //若链表为空,或者链表中找不到要删除的结点
    {
        return ;            //直接退出
    }
    else
    {
        if(pre==NULL)        //若链表不为空但是要删除结点的上一个为空,即要删除头结点
        {
/******************
明明已经定义了(p=list->head) 为什么不能用p=p->next???
因为我传进来的是list->head,用p只是改变了p,list->head还是指向原来的位置,
并且该位置会为空,输出是会将该位置打印成 0 并且显示 已放弃(核心已转储)
******************/
            list->head=list->head->next;        //让头结点后移(特别注意:不是让游标后移!!!)
            free(p)                     //释放游标指向的结点的空间
        }
        else if(p->next==NULL)                //若游标的下一结点为空,即要删除的是尾结点
        {
            pre->next=NULL;                //让游标的上一结点指向NULL;(本来是指向要删除的结点的)
            free(p);                    //释放游标指向的结点的空间
        }  
        else                          //否则上两个结点中间的结点
        {
            pre->next=p->next;//游标的上一结点的next指向游标的next(原来是指向游标的,现在跳过游标使之孤立)
            free(p);                    //释放游标指向的结点的空间
        }
    }
    
}

9.将链表逆置

LNode *inversion(LNode *list)
{
    //1.生成新的原始链表(空链表)
    LNode *lnew=(LNode *)malloc(sizeof(*lnew));
    lnew->head=NULL;
    lnew->tail=NULL;
    //2.做拆除老链表的准备工作
    Node *p=list->head;         //用来指向下一个要被拆除的结点
    Node *pre=NULL;          //充当游标指向要拆除并重装的结点
    //3.拆除老链表并生成新链表
    while(p)
    {    
        pre=p;             //指向要拆除并重装的结点
        p=p->next;              //注意要先让pre指向p的下一个在把p的指向断掉!!!
        
        //    pre=p->next;    不能把两步并到一起写成这样,否则会死循环!!!
        pre->next=NULL;      //把pre(要拆除重装的结点)的指向断掉
        if(lnew->head==NULL)
        {
            lnew->head=pre;   //若新链表为空,让新链表的头和尾都指向要拆除重装的结点
            lnew->tail=pre;
        }
        else
        {
            pre->next=lnew->head; //用头插法把要拆除重装的结点放进新链表
            lnew->head=pre;
        }
    }
    return lnew;    //返回新链表;
}

10.创建一个链表(尾插法),这个链表有正有负数,乱序输入,最后输出的结果负数在,正数在后。

LNode *separate(LNode *list)
{    //1.生成原始链表(空链表)
    LNode *lnew=(LNode *)malloc(sizeof(*lnew));
    lnew->head=NULL;
    lnew->tail=NULL;
    //2.做拆除老链表的准备工作
    Node *p=list->head;
    Node *pre=NULL;
    //3.拆除老链表并生成新链表
    while(p)
    {                     //注意控制循环不要成为死循环或则只运行一次!! 错 2 次
        pre=p;             //指向要拆除并重装的结点
        p=p->next;           //注意要先让pre指向p的下一个在把p的指向断掉!!!
        pre->next=NULL;        //把pre(要拆除重装的结点)的指向断掉
        if(lnew->head==NULL)        
        {
            lnew->head=pre;    //若新链表为空,让新链表的头和尾都指向要拆除重装的结点
            lnew->tail=pre;        
        } else {
            if(pre->date > 0)               //若要插入的为正数
            {
                lnew->tail->next=pre;    //用尾插法向后插入孤立结点
                lnew->tail=pre;
            } else {
                pre->next=lnew->head;    //否则用头插法向前插入孤立结点
                lnew->head=pre;
            }
        }
    }
    return lnew;        //返回新链表
}

11.归并有序链表

LNode *merger(LNode *list1,LNode *list2)
{
    //1.定义新链表
    LNode *lnew=(LNode *)malloc(sizeof(*lnew));
    lnew->head=NULL;
    lnew->tail=NULL;
    //2.为老链表的拆除做准备
    Node *p1=list1->head;
    Node *pre1=NULL;
    Node *p2=list2->head;
    Node *pre2=NULL;
    //3.进行归并
    while(p1&&p2)                       //若有一个链表为空,不再归并
    {
        pre1=p1;                     //用来指向链表1中被拆除后孤立的结点    
        pre2=p2;                     //用来指向链表2中被拆除后孤立的结点    
        if(pre1->date > pre2->date)        
        {                       //若链表1中的孤立结点大于链表2中的
            
            if(lnew->head==NULL)
            {
                lnew->head=pre2;
                lnew->tail=pre2;
            }
            else
            {                   //用尾插法把小的孤立结点插到新链表中
                lnew->tail->next=pre2;
                lnew->tail=pre2;
            }    
            p2=p2->next;            //孤立结点被使用过的链表的游标后移
            pre2->next=NULL;        //链表中被使用过的的孤立结点结点指向NULL
    //        free(pre2);        //不能释放结点,因为有别的链表指向了他
        }
        else if(pre1->date < pre2->date)
        {            //若链表2中的孤立结点大于链表1中的
            
            if(lnew->head==NULL)
            {
                lnew->head=pre1;
                lnew->tail=pre1;
            }
            else
            {                     //用尾插法把小的孤立结点插到新链表中
                lnew->tail->next=pre1;
                lnew->tail=pre1;
            }    
            p1=p1->next;           //孤立结点被使用过的链表的游标后移
            pre1->next=NULL;        //链表中被使用过的的孤立结点结点指向NULL
        } else {
            if(lnew->head==NULL)
            {
                lnew->head=pre1;
                lnew->tail=pre1;
            }
            else
            {                //用尾插法把链表1的孤立结点插到新链表中
                lnew->tail->next=pre1;
                lnew->tail=pre1;
            }    
            p1=p1->next;
            p2=p2->next;            //孤立结点被使用过的链表的游标后移(这里两个都被使用过)
            pre1->next=NULL;
            pre2->next=NULL;        //链表中被使用过的的孤立结点结点指向NULL(这里两个都被使用过)
            free(pre2);          //释放没有进入新链表的孤立结点
        }    
    }
    lnew->tail->next=(p1?p1:p2);         //用尾插法把非空的链表的剩余部分整个接到新链表中
    return lnew;                    //返回新链表
}


12.逆序存储一个整数,实现加法
    整数1:1 2 3 4
    整数2:3 4 5 6
    得到的链表:
           4 6  9  0
代码运行结果:
        //    7 8 9            1 5 9
         +     2 1 1         +    3 5 7
         =    1 0 0 0        =     5 1 6    
         

int getdate(Node *head)                             //取出结点中的数
{    //1.写函数专门取出结点中的数
    if(head==NULL)
    {
        return 0;
    }
    return head->date;                          //函数返回结点中的数
}

LNode *ADD(LNode *list1,LNode *list2)
{    //2.新建一个链表
    LNode *add=(LNode *)malloc(sizeof(*add));    //给新链表分配空间
    add->head=NULL;
    add->tail=NULL;
    //3.为计数做准备
    int x=0,c=0,sum=0;
    Node *p1=list1->head;
    Node *p2=list2->head;
    Node *pre1=p1;    //为了让循环多进行一次
    Node *pre2=p2;    //为了让循环多进行一次
    //4.计数
    while(pre1||pre2)                           //想办法让循环多进行一次
    {    
        sum=(getdate(p1)+getdate(p2));          //得到两个结点中数字之和
        x=(sum+c)%10;                  //得到两个结点中数字之和的低位
        if((x==0)&&((p1==NULL)&&(p2==NULL)))    //若p1和p2都为空 且 x为0;

        {
            return add;                //直接返回新链表;
        }            
          //if((x==0)&&(p1||p2))    不等于    if((x==0)&&((p1==NULL)||(p2==NULL)))!!!
        c=(sum+c)/10;                   //存储两个结点中数字之和的高位(进位)
        Node *a=(Node *)malloc(sizeof(*a));    //生成新的结点
        a->next=NULL;                 //让新结点孤立
        a->date=x;                   //把低位放进新生成的结点
        if(add->head==NULL)
        {
            add->head=a;
            add->tail=a;
        }
        else        
        {                            //用头插法生成新链表
            a->next=add->head;
            add->head=a;
        }
        pre1=p1;                //想办法让循环多进行一次
        pre2=p2;                //想办法让循环多进行一次
        if(p1)
        {
            p1=p1->next;    //若p1已经为NULL,再让p1=p1->next就会段错误!!很危险
        }
        if(p2)
        {
            p2=p2->next;    //若p2已经为NULL,再让p2=p2->next就会段错误!!很危险
        }
    }
    return add;            //返回新生成的链表
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值