数据结构学习之单向链表(各种操作合集)

单向链表(各种操作合集)

单向链表的两种创建方式

方式1:根据函数的返回值创建

  • 通过返回值返回所申请的头结点所在的内存空间首地址,即创建单向链表的头结点,代码如下:
  • 示例代码:
node_t *create_link_node_1(){

    node_t *phead = (node_t *)malloc(sizeof(node_t));
    if(NULL == phead){

        printf("内存分配失败\n");

        exit(-1);

    }
    //或者memset(phead, 0, sizeof(node_t));
    phead->data = -1;
    phead->next = NULL;

    return phead;

}
  • 注意事项:
  • 1.分配完头结点的内存地址空间后,一定要检查内存分配是否成功
  • 2.若头结点的内存分配失败,需要使用shell命令exit(-1)退出
  • 3.单向链表的每个结点都有数据域和指针域,而头结点的数据域可以不储存任何数据,头结点的数据域在此函数中,被赋值 -1
  • 4.头结点的指针域被赋值 NULL,表示此时的单向链表只有一个头结点

方式2:根据地址传参创建

  • 使用地址传参的方式创建单向链表的头结点,代码如下:
  • 示例代码:
int create_link_node_2(node_t **phead,int data){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    *phead = (node_t *)malloc(sizeof(node_t));

    if(NULL == *phead){


        printf("内存分配失败\n");

        return -1;

    }
     //或者memset(*phead, 0, sizeof(node_t));
    (*phead)->data = data;
    (*phead)->next = NULL;

    return 0;

}

注意事项:

  • 1.所传入的形参必须是二级指针变量,因为二级指针用来存储一级指针变量的地址,即所申请的单向链表头结点在内存空间的地址
  • 2.形参传入到创建单向链表头结点的功能函数后,一定要做入参合理性检查
  • 3.同方式1一样,分配完头结点的内存地址空间后,一定要检查内存分配是否成功
  • 4.头结点的数据域被赋值 -1
  • 5.头结点的指针域被赋值 NULL

单向链表的三种插入方式

方式1:头插法

  • 在单向链表的头结点和第0个结点之间插入新结点,即头插法,代码如下:
  • 示例代码:
int insert_link_list_1(node_t *phead,int data){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    //创建新结点

    node_t *pnew = NULL;
    
    create_link_node_2(&pnew,data);
    //头插到链表
    pnew->next = phead->next;

    phead->next = pnew;

    return 0;
}
  • 操作步骤:
  • 1.创建新结点pnew
  • 2.将第0个结点的地址指向新结点的指针域,即pnew->next = phead->next
  • 3.再将新结点的地址指向头结点的指针域,即phead->next = pnew

方式2:尾插法

  • 在单向链表的最后一个结点后面插入新结点,即尾插法,代码如下:
  • 示例代码:
int insert_link_list_2(node_t *phead,int data){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    //创建新结点

    node_t *pnew = NULL;
    
    create_link_node_2(&pnew,data);

    //遍历链表,找到最后一个结点

    node_t *ptemp = phead;

    while(NULL != ptemp->next){


        ptemp = ptemp->next;

    }

    ptemp->next = pnew;

    return 0;
}
  • 操作步骤:
  • 1.创建新结点pnew
  • 2.遍历链表,找到链表的最后一个结点ptemp
  • 3.将新结点的地址指向最后一个结点的指针域,即 ptemp->next = pnew

方式3:任意位置插入新节点

  • 在单向链表的任意一个位置,插入新结点,代码如下:
  • 示例代码:
int insert_link_list_3(node_t *phead,int pos,int data){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    if(pos < 0){

        printf("插入位置不合理,插入失败\n");

        return -1;

    }

    node_t *ptemp = phead;

    int i = 0;

    for(i = 0; i < pos; i++){

        if(NULL == ptemp){

            break;

        }
        ptemp = ptemp->next;
    }
    if(i < pos){

        printf("插入位置不合理,插入失败\n");

        return -1;


    }
    node_t *pnew = NULL;

    create_link_node_2(&pnew,data);

    pnew->next = ptemp->next;

    ptemp->next = pnew;

    return 0;
}
  • 操作步骤:
  • 1.遍历链表,找到待插入位置的前一个结点,即ptemp;
  • 2.创建新结点pnew
  • 3.将待插位置结点的地址指向新结点的指针域,即 pnew->next = ptemp->next
  • 4.再将新结点的地址指向待插入位置的前一个结点的指针域,即 ptemp->next = pnew

单向链表的三种删除方式

方式1:头删法

  • 删除单向链表头结点后的结点,即头删法,代码如下:
  • 示例代码:
int delete_link_list_1(node_t *phead){

    if(NULL == phead){

        printf("入参为NULL\n");

        return -1;

    }

    if(NULL == phead->next){


        printf("链表只有一个头结点,无其他的结点\n");

        return -1;

    }

    node_t *pdel = phead->next;

    phead->next = pdel->next;

    free(pdel);

    pdel = NULL;

    return 0;

}
  • 操作步骤:
  • 1.定义待删结点pdel,并将头结点的指针域指向待删结点的地址,即node_t *pdel = phead->next
  • 2.待删结点后的结点的地址指向头结点的指针域,即phead->next = pdel->next
  • 3.用free函数释放待删结点所占用的空间,即 free(pdel)
  • 4.防止野指针产生,给待删结点的地址赋值NULL,即pdel = NULL

方式2:尾删法

  • 删除单向链表的最后一个结点,即尾删法,代码如下:
  • 示例代码:
int delete_link_list_2(node_t *phead){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    if(NULL == phead->next){


        printf("链表只有一个头结点,无其他的结点\n");

        return -1;

    }

    //遍历链表,找到倒数第二个结点

    node_t *ptemp = phead;

    while(NULL != ptemp->next->next){

        ptemp = ptemp->next;

    }
    free(ptemp->next);

    ptemp->next = NULL;

    return 0;

}
  • 操作步骤:
  • 1.利用while循环,遍历单向链表,找到倒数第二个结点,即ptemp
  • 2.释放ptemp的指针域,并赋值NULL,这样就删除了链表的最后一个结点;

方式3:任意位置删除旧节点

  • 指定结点在链表中的位置,然后根据位置,删除待删结点,代码如下:

  • 示例代码:

int delete_link_list_3(node_t *phead,int pos){

    if(NULL == phead){


        printf("入参为NULL\n");

        return -1;

    }

    if(NULL == phead->next){


        printf("链表只有一个头结点,无其他的结点\n");

        return -1;

    }

    if(pos < 0){

        printf("删除位置不合理,删除失败\n");

        return -1;

    }
    node_t *ptemp = phead;

    int i = 0;

    for(i = 0; i < pos; i++){

        ptemp = ptemp->next;


        if(NULL == ptemp->next){

            break;

        }
        
        

    }
    if(i < pos){

        printf("删除位置不合理,删除失败\n");

        return -1;


    }
    node_t *pdel = ptemp->next;

    ptemp->next = pdel->next;

    free(pdel);

    pdel = NULL;

    return 0;

}

  • 操作步骤:
  • 1.找到待删结点的前一个结点ptemp;
  • 2.定义待删结点,让待删结点前一个结点的指针域指向待删结点地址,备份待删结点,即node_t *pdel = ptemp->next
  • 3.待删结点的指针域指向待删结点前一个结点的指针域,也就是待删结点后面那个结点的地址指向待删结点前一个结点的指针域,即ptemp->next = pdel->next
  • 4.用free函数释放待删结点所占用的空间,即 free(pdel)
  • 5.防止野指针产生,给待删结点的地址赋值NULL,即pdel = NULL

单向链表的查找与修改

单向链表的查找:

  • 根据单向链表数据结点的位置查找数据,代码如下:
  • 示例代码:
int search_link_list(node_t *phead,int pos,int *data){
    
    if(NULL == phead || NULL == data){

        printf("入参为NULL\n");

        return -1;

    }

    if(NULL == phead->next){


        printf("链表只有一个头结点,无其他的结点\n");

        return -1;

    }

    if(pos < 0){

        printf("查找位置不合理\n");

        return -1;

    }
    node_t *ptemp = phead;

    int i = 0;

    for(i = 0; i < pos; i++){

        ptemp = ptemp->next;


        if(NULL == ptemp->next){

            break;

        }
        
    }
    if(i < pos){

        printf("查找位置不合理,查找失败\n");

        return -1;


    }

    *data = ptemp->next->data;

    return 0;
}
  • 操作步骤:
  • 1.入参合理性检查;
  • 2.找到所要查找数据结点的前一个结点,即ptemp;
  • 3.将所要查找位置的数据域赋值给取*后的形参指针data,即 *data = ptemp->next->data

单向链表的修改:

  • 根据单向链表数据结点的位置修改数据,代码如下:
  • 示例代码:
int modify_link_list(node_t *phead,int pos,int new_data){

    if(NULL == phead){

        printf("入参为NULL\n");

        return -1;

    }

    if(NULL == phead->next){


        printf("链表只有一个头结点,无其他的结点\n");

        return -1;

    }

    if(pos < 0){

        printf("修改位置不合理\n");

        return -1;

    }
    node_t *ptemp = phead;

    int i = 0;

    for(i = 0; i < pos; i++){

        ptemp = ptemp->next;


        if(NULL == ptemp->next){

            break;

        }
        
    }
    if(i < pos){

        printf("修改位置不合理,修改失败\n");

        return -1;

    }

    ptemp->next->data = new_data;

    return 0;
}

  • 操作步骤:
  • 1.入参合理性检查;
  • 2.找到所要修改数据结点的前一个结点,即ptemp;
  • 3.将新的数据赋值给所要修改位置的数据结点的数据域,即ptemp->next->data = new_data

单向链表的清空与销毁

单向链表的清空:

  • 所谓的清空,就是清空除了头结点之外的所有结点的数据域和指针域,区别于顺序表的清空,代码如下:
int clean_link_list(node_t *phead){
    if(NULL == phead){
        printf("入参为NULL\n");
        return -1;
    }
    node_t *ptemp = phead;
    node_t *pdel = NULL;
    while(ptemp->next != NULL){
        pdel = ptemp->next;
        ptemp->next = pdel->next;
        free(pdel);
        pdel = NULL;
    }
    return 0;
}

  • 操作步骤:
  • 1.入参合理性检查;
  • 2.定义一个新结点ptemp,并将头结点的地址赋值给新结点的地址,即备份头结点防止头结点被删除
  • 3.定义待删结点pdel,赋初值为空指针
  • 4.利用while循环,借助头删法除头结点外,删除剩余结点

单向链表的销毁:

  • 包含单向链表的头结点在内销毁单向链表的所有结点,代码如下:
int destroy_link_list(node_t **phead){
    if(NULL == phead || NULL == *phead){
        printf("入参为NULL\n");
        return -1;
    }
    clean_link_list(*phead);
    free(*phead);
    *phead = NULL;
    return 0;
}
  • 操作步骤:
  • 1.入参合理性检查;
  • 2.清空单向链表,是为了防止单向链表直接销毁后,导致内存空间泄漏的情况产生;
  • 3.删除头结点,即释放头结点所占用的内存空间,并赋值NULL

单向链表的翻转与排序

单向链表的翻转:

  • 第0个数据结点后面的所有数据结点,依次头插头结点和第0个数据结点之间即可,代码如下:
  • 示例代码:
int filp_link_list(node_t *phead){

    if(NULL == phead){
        printf("入参为NULL,请检查..\n");
        return -1;
    }

    if(NULL == phead->next){

        printf("只有一个头结点\n");

        return -1;
    }

    if(NULL == phead->next->next){

        printf("只有一个数据结点\n");

        return -1;

    }

    node_t *p = phead->next;

    node_t *q = p->next;

    node_t *ptemp = NULL;

    p->next = NULL;

    while(NULL != q){

        ptemp = q->next;

        q->next = phead->next;

        phead->next = q;

        q = ptemp;

    }

    return 0;
}

  • 操作步骤:
  • 1.定义第0个数据结点指针,并将头结点的指针域指向第0个结点的指针,即node_t *p = phead->next
  • 2.定义第1个数据结点指针,并把第0个数据结点的指针域指向第1个数据节点的指针,即node_t *q = p->next
  • 3.定义一个指针,用来保存指针q的指针域,即ptemp = q->next,以便于循环遍历单向链表的所有数据结点,即每次循环结束前,令q = ptemp,就可以继续向后遍历其他的数据结点
  • 4.采用头插法的形式,并利用while循环,将第1个数据结点其以后的所有数据结点依次头插到头结点和第0个数据结点之间,直到指针q为NULL,即原单向链表第0个结点的指针域为NULL,就完成了单向链表的所有数据结点的翻转

单向链表的排序:

  • 可以使用冒泡排序,即可完成单向链表所有数据结点的排序,代码如下:
  • 示例代码:
nt sort_link_list(node_t *phead){

    if(NULL == phead){
        printf("入参为NULL,请检查..\n");
        return -1;
    }

    if(NULL == phead->next){

        printf("只有一个头结点\n");

        return -1;
    }

    if(NULL == phead->next->next){

        printf("只有一个数据结点\n");

        return -1;

    }

    node_t *p = phead->next;
    node_t *q = NULL;
    int temp = 0;

    while(NULL != p->next){

        q = p->next;

        while(NULL != q){
            
            if(p->data > q->data){

                temp = p->data;

                p->data = q->data;

                q->data = temp;

            }
            q = q->next;
        }
        p = p->next;
        
    }

    return 0;

}
  • 操作步骤:
  • 1.定义指针p,让头结点的指针域指向p,即 node_t *p = phead->next
  • 2.定义指针q,让其他的结点指针域指向q,用于循环中,即 q = p->next;
  • 3.第一层循环结束的条件是NULL != p->next
  • 4.第二层循环结束的条件是NULL != q
  • 5.比较指针p和指针q的数据域不符合if条件语句内条件,就交换,然后指针p不动,向后移动指针q符合if条件语句内条件,指针p不动,向后移动指针q,直到指针q为NULL,最后最大值就是指针p指向的结点数据域,即p->data

单向链表的去重

  • 所谓的去重,就是去除单向链表中重复的数据结点,代码如下:
  • 示例代码:
int del_rep_link_list(node_t *phead){

    if(NULL == phead){
        printf("入参为NULL,请检查..\n");
        return -1;
    }

    if(NULL == phead->next){

        printf("只有一个头结点\n");

        return -1;
    }

    if(NULL == phead->next->next){

        printf("只有一个数据结点\n");

        return -1;

    }

    node_t *p = phead->next;

    node_t *q = NULL;

    node_t *k = NULL;

    while(NULL != p){

        k = p;

        q = p->next;

        while(NULL != q){

            if(p->data != q->data){

                k = k->next;

                q = q->next; 

            } else {

                //删除结点q
                k->next = q->next;
                free(q);
                //q = NULL;
                //也可直接让q后面的那个结点的地址覆盖q的地址
                q = k->next;
            }          

        }

        p = p->next;

    }

    return 0;
}
  • 操作步骤:
  • 1.定义一个指针p,将头结点的指针域指向p,此时的p就是第0个结点的地址
  • 2.定义一个指针q,用于保存p后面的结点地址,即循环中的q = p->next
  • 3.定义一个指针k,始终用来保存q前面的那个结点的地址,也可以备份指针p,即 k = p;
  • 4.结点p指针域指向q;
  • 5.内层while循环,比较p和q的数据域,若不等,就向后移动指针k和q,若相等,就删除q指向的结点直到q=NULL;
  • 6.外层while循环,遍历整个单向链表,直到p=NULL;

你可能感兴趣的:(数据结构学习系列,数据结构,学习)