单向链表的操作(带头结点)

1. 单向链表的简介

逻辑结构一对一
存储结构链式存储

        链表主要是用来处理大数据项目,用于解决无法连续开辟大块连续内存空间的问题。

        单向链表的示意图如下:

 2.单向链表的创建

        单向链表可用于顺序表,栈和队列等数据结构体中。要对单向链表进行操作就要先对其进行创建。

        首先我们需要创造一个结构体来存放单向链表的结点信息。

typedef int data_t;
typedef struct node{
    data_t data;        //结点数据域
    struct node *next;  //结点指针域
}node_t;

        封装好结构体,我们就可以对结点进行操作啦。

        创建链表需要创建一个头结点:
        node_t *L =NULL;                                                               

        为头结点申请空间
        L=(node_t *)malloc(sizeof(node_t));                                   
        初始化头结点
         L->data=-1;//头结点的data可为任意值                               
         L->next=NULL;                                                                   
        将创建好的头结点地址返回,用于后续操作
        return L;                                                                              

        将其封装成函数如下:

node_t *create_link(void){

    //创造头结点
    node_t *L =NULL;

    //为头结点申请空间
    L=(node_t *)malloc(sizeof(node_t));

    //检验头结点是否创建成功
    if(NULL==L){
        puts("头结点创建失败");
        return NULL;
    }

    //初始化头结点

    L->data=-1;//头结点的data可为任意值
    L->next=NULL;

    //将创建好的头结点地址返回,用于后续操作
    return L;

}

3.单向链表的插入

   3.1头插法

        单向链表的头部插入

        ①首先,要将新结点的指针指向头结点的下一个结点

        ②其次,将头结点指针的next指向新结点,在这个过程中头结点H和原头结点的下一个结点p的连接就自动断开了。

        头部插入的主要代码为:

                                              q->next=H->next;       ①    

                                               H->next=q;                ② 

        具体过程如下图所示: 

        头部插入函数的封装如下:

int head_insert(node_t *L,data_t value){

    //健壮性判断
    if(NULL==L){
        puts("传参非法");
        return -1;
    }

    //创建插入的新结点
    node_t *N=(node_t *)malloc(sizeof(node_t));
    if(NULL==N){
        puts("创建新结点失败");
        return -1;
    }

    //初始化新结点
    N->data=value;   //将用户输入的值赋予新结点
    N->next=NULL;

    //将新结点插入到链表中
    N->next=L->next;
    L->next=N;

    return 0;

}

   3.2尾插法

        单向链表的尾部插入

        ①首先,先把链表遍历到尾结点

        ②其次,将尾结点的next指向新结点,新结点取代其成为

        尾部插入的主要代码为:

                                                P->next=N;  

        具体过程如下图所示:

        尾部插入函数的封装如下:

int tail_insert(node_t *L,data_t value){
     //健壮性判断
    if(NULL==L){
        puts("传参非法");
        return -1;
    }

    //创建插入的新结点
    node_t *N=(node_t *)malloc(sizeof(node_t));
    if(NULL==N){
        puts("创建新结点失败");
        return -1;
    }

    //初始化新结点
    N->data=value;
    N->next=NULL;

    //将链表遍历到尾结点
    node_t *p=L;
    while(p->next!=NULL){
        p=p->next;
    }

    //将新结点插入到链表中
    p->next=N;

    return 0;

}

 4.单向链表的遍历

        在单向链表的应用中我们时常需要将链表中的值全部输出出来,为了将链表全部输出我们需要对链表进行遍历。在尾插法中我们就用到了单向链表的遍历。

        遍历的过程就是将链表中的每个数进行输出,一个结点输出过后跳转到下一个结点输出,直到没有结点。

        具体封装函数如下:

int show_link(node_t* L){

    //健壮性判断
    if (NULL == L) {
        puts("传参非法");
        return -1;
    }
    if (NULL == L->next) {
        puts("表为空表 无数据可遍历");
        return -1;
    }

    //创建指针指向第一个有值的结点
    node_t* q = L->next;

    //遍历链表
    while (NULL != q) {
        printf("%d ", q->data); //打印有效数据节点的数据
        q = q->next; //移动指针
    }

    puts("");

    return 0;

}

5.单向链表中任意插入删除

  5.1任意位置插入

        链表对于顺序表的优势就是对表中数据进行插入和删除操作十分方便。单向链表的任意位置插入和头插法类似。

        ①首先,我们要把光标定位到插入位置的前一个结点。

        ②然后,将光标所在位置的结点与插入结点建立关系。

        任意位置插入的主要代码为:

                                                n->next=q->next;       ①    

                                                q->next=n;                ② 

        具体过程如下图所示: 

 任意位置插入函数的封装如下:

int ever_insert(node_t* L, data_t value, data_t pos){
    //健壮性
    if(NULL==L){
        puts("传入参数违法");
        return -1;
    }
    if(NULL==L->next){
        puts("表为空");
        return -1;
    }
    //对pos值进行判断
    node_t *p=L->next;
    //先统计表长
    int len=0;
    while (NULL!=p){
        p=p->next;
        len++;
    }
    if(pos<0||pos>=len+1){
        puts("该处不可插\n");
        return -1;
    }

    //插入操作
    node_t *q=L;
    while (pos!=0)
    {
        q=q->next;
        pos--;
    }

    //创建新结点
    node_t *N= create_link();
    N->data =value;

    //进行插入操作
    N->next =q->next;
    q->next=N;
    
    return 0;
}

5.2任意位置删除

        ​①首先,我们要把光标定位到删除位置的前一个结点。

        ②然后,将光标所在位置的结点与删除的结点的后一个结点建立关系。

        任意位置删除的主要代码为:

                                                q->next=p->next; 

        具体过程如下图所示: 

  任意位置删除函数的封装如下:

int ever_free(node_t* L, data_t pos){
      //健壮性
    if(NULL==L){
        puts("传入参数非法");
        return -1;
    }
   if(NULL==L->next){
        puts("表为空");
        return -1;
    }
    //对pos值进行判断
    node_t *p=L->next;
    //先统计表长
    int len=0;
    while (NULL!=p){
        p=p->next;
        len++;
    }
    if(pos<0||pos>=len){
        puts("该处不可删\n");
        return -1;
    }

    //先找到被删除结点的前一个结点
    node_t *q=L->next;
    node_t *p=L;
    data_t value;
    while (pos!=0)
    {
        p=q;
        q=q->next;
        pos--;
    } 

    //删除操作
    p->next=q->next;
    value=q->data;
    q->next=NULL;
    free(q);
    printf("data:%d was rm\n",value);
    
    return 0;
}

6.链表的反转

        链表的反转也是链表的常规操作之一。比如说原链表中的数据为1 2 3 4 5 6 ,那么反转后的链表中的数据为6 5 4 3 2 1。

        具体操作如下图:

 反转封装函数如下:

int overturn_list(node_t* L){
     //健壮性
    if(NULL==L){
        puts("传入参数非法");
        return -1;
    }

    //进行翻转操作
    node_t *p=NULL;
    node_t *q=L->next;
    node_t *w=q->next;
    while (w!=NULL){;
        q->next=p;
        p=q;
        q=w;
        w=w->next;
    }
    q->next=p;
    p=q;
    L->next=p;

    printf("翻转完毕\n");
    return 0;
}

7.链表的排序

        同样,链表的排序也是重要操作之一。将链表排序的方式我采用的是冒泡排序,利用两次while循环来实现链表中数据的排序。

        本次排序仅仅是将各个结点之间的数值进行交换,并未使结点之间进行移动。

        其具体函数如下:

int sort_list(node_t* L){
    //健壮性
    if(NULL==L){
        puts("传入参数非法");
        return -1;
    }

    //排序操作
    node_t *p=L->next;
    node_t *q=p;
    data_t tmp =0;
    //冒泡排序
    while (p!=NULL){
        q=p->next;                      
        while (q!=NULL){
            if((p->data)<(q->data)){    //可通过>/<的切换实现正序和倒序
                tmp=p->data;
                p->data=q->data;
                q->data=tmp;
            }
            q=q->next;                 //循环推进条件
        }
        p=p->next;                    //循环推进条件
    }

    printf("从大到小排序为:");

    return 0;
}

8.链表的释放

        有利用也有释放。对链表的操作进行完毕后,我们需要将链表进行释放,以实现释放空间的功能,使代码块更加完备。

        具体操作如下图:

         释放的封装函数如下:

int free_link(node_t** L){
    
     //健壮性
    if(NULL==L||NULL==*L){
        puts("传入参数非法");
        return -1;
    }

    //释放操作
    node_t *q=*L;          //定义指针指向 L,由于传入的是二级指针,在此我们需要对其降维
    while (*L!=NULL){
        q=q->next;         //循环推进条件1
        puts("释放");
        free(*L);
        *L=q;              //循环推进条件2
    }
    *L=NULL;               //彻底清除   

    return 0;
}

9.全部函数汇总

        本次介绍的所有函数集合都汇总在这个资源里面,供大家下载和使用。https://download.csdn.net/download/m0_59716409/86265337https://download.csdn.net/download/m0_59716409/86265337

  • 7
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值