数据结构(二)单链表

文章目录

  • 一、链表
    • (一)概念
    • (二)有头单向链表示意图
    • (三)操作
      • 1. 节点的结构体定义:
      • 2. 创建链表
        • (1) 函数声明
        • (2)注意点:
        • (3)示意图
        • (4)代码实现
        • 3. 清空
          • (1) 函数声明
          • (2)注意点:
          • (3)代码实现
          • 4. 销毁
            • (1) 函数声明
            • (2)注意点:
            • (3)代码实现
            • 5. 插入(头插、尾插、任意位置插)
              • (1)头插
                • ① 函数声明
                • ② 注意点:
                • ③ 示意图
                • ③ 代码实现
                • (2)尾插
                  • ① 函数声明
                  • ② 注意点:
                  • ③ 代码实现
                  • (3)任意位置插入
                    • ① 函数声明
                    • ② 注意点:
                    • ③ 代码实现
                    • 6. 删除(头删、尾删、任意位置删)
                      • (1)头删
                        • ① 函数声明
                        • ② 注意点:
                        • ③ 代码实现
                        • (2)尾删
                          • ① 函数声明
                          • ② 注意点:
                          • ③ 代码实现
                          • (3)任意位置删除
                            • ① 函数声明
                            • ② 注意点:
                            • ③ 代码实现
                            • 7. 修改
                              • (1) 函数声明
                              • (2)注意点:
                              • (3)代码实现
                              • 8. 查询
                                • (1) 函数声明
                                • (2)注意点:
                                • (3)代码实现
                                • 9. 链表合并
                                  • (1) 函数声明
                                  • (2)注意点:
                                  • (3)代码实现
                                  • 10. 链表排序
                                    • (1) 函数声明
                                    • (2)注意点:
                                    • (3)代码实现
                                    • 11. 链表翻转
                                      • (1) 函数声明
                                      • (2)注意点:
                                      • (3)代码实现
                                      • 12. 链表剔重
                                        • (1) 函数声明
                                        • (2)注意点:
                                        • (3)代码实现
                                        • 13. 打印链表
                                          • (1) 函数声明
                                          • (2)注意点:
                                          • (3)代码实现

                                            一、链表

                                            (一)概念

                                            逻辑结构:线性

                                            存储结构:链式存储,在内存中不连续

                                            分为有头链表和无头链表

                                            同时又细分为单向、循环、双向链表

                                            (二)有头单向链表示意图

                                            以下数据及地址只是为了方便理解设置

                                            (三)操作

                                            1. 节点的结构体定义:

                                            ① 定义:

                                            typedef struct node
                                            { int data; //保存数据
                                                struct node* next;  //记录下一节点的地址,注意此时指针类型不能定义为nd_t*
                                            }nd_t;
                                            

                                            ② 注意点:

                                            1. 定义结构体成员时,指针的类型此时不能使用nd_t定义。

                                            2. 创建链表

                                            (1) 函数声明

                                            create_list(nd_t **phead,int num);

                                            该函数需要在内存中申请一块nd_t类型大小的空间,并将其地址返回给main函数中用户定义的指针中;

                                            num是用来初始化这块空间中数据域的值。

                                            (2)注意点:
                                            1. 必须传入二级指针
                                            2. phead不能为空,即不能传一个NULL作为形参,后面需要对phead取值
                                            3. 需要检查是否申请内存成功
                                            (3)示意图
                                            (4)代码实现
                                            //创建节点
                                            create_list(nd_t **phead,int num){ if(NULL==phead){ printf("传入指针为NULL");
                                                }
                                                //*phead可以为空,即main函数中的phead可以是一个空指针
                                                //但是phead不能为空,即不能传一个NULL作为形参
                                                *phead=(nd_t *)malloc(sizeof(nd_t));
                                                if(NULL==*phead){ printf("申请空间失败!\n");
                                                    return -1;
                                                }
                                                //初始化
                                                (*phead)->data=num;
                                                (*phead)->next=NULL;
                                                return 0;
                                            }
                                            

                                            3. 清空

                                            (1) 函数声明

                                            int clean_list(nd_t *phead);

                                            该函数通过头删的方式依次释放所有数据节点,但是保留头节点。

                                            (2)注意点:
                                            1. 入参合理性检查,传入的参数不能为NULL
                                            2. 表不能为空,即phead->next不能为空,因为后面会用到phead->next->next,如果phead->next为空,会出现段错误
                                            (3)代码实现
                                            int clean_list(nd_t *phead){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(NULL==phead->next){ printf("表为空\n");
                                                    return -1;
                                                }
                                                nd_t *ptemp=NULL;
                                                //使用头删清空
                                                while(NULL!=phead->next)
                                                { ptemp=phead->next;
                                                    phead->next=ptemp->next;
                                                    free(ptemp);
                                                }
                                                ptemp=NULL;
                                                return 0;
                                            }
                                            

                                            4. 销毁

                                            (1) 函数声明

                                            int destory_list(nd_t **phead);

                                            依次删除所有数据节点后,释放头节点,并且将主函数中定义的phead置NULL。

                                            (2)注意点:
                                            1. 必须传入二级指针
                                            2. 需要判断传入的列表指针是否是空指针
                                            3. 还需要判断*phead是否是NULL,因为free不能对NULL操作
                                            (3)代码实现
                                            int destory_list(nd_t **phead){ if(NULL==phead || NULL==*phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                clean_list(*phead);
                                                free(*phead);//free参数不能是一个空指针
                                                *phead=NULL;
                                                return 0;
                                            }
                                            

                                            5. 插入(头插、尾插、任意位置插)

                                            (1)头插
                                            ① 函数声明

                                            int insert_list_by_head(nd_t *phead,int num);

                                            ② 注意点:
                                            1. 传入一级指针即可
                                            2. 需要判断传入的列表指针是否是空指针
                                            ③ 示意图
                                            ③ 代码实现
                                            int insert_list_by_head(nd_t *phead,int num){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                //创建新节点
                                                nd_t *ptemp = NULL;
                                                if(create_node(&ptemp,num)<0)
                                                { printf("内存分配失败\n");
                                                    return -1;
                                                }
                                                ptemp->next=phead->next;
                                                phead->next=ptemp;
                                                return 0;
                                            }
                                            
                                            (2)尾插
                                            ① 函数声明

                                            int insert_list_by_tail(nd_t *phead,int num);

                                            先遍历函数找到尾节点,再插入

                                            ② 注意点:
                                            1. 入参合理性检查
                                            2. 判断创建新节点是否成功
                                            ③ 代码实现
                                            int insert_list_by_tail(nd_t *phead,int num){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead;
                                                //找到尾节点
                                                while(NULL!=ptemp->next){ ptemp=ptemp->next;
                                                }
                                                //创建新节点
                                                nd_t *pnew = NULL;
                                                if(create_node(&pnew,num)<0)
                                                { printf("内存分配失败\n");
                                                    return -1;
                                                }
                                                ptemp->next=pnew;
                                                return 0;
                                            }
                                            
                                            (3)任意位置插入
                                            ① 函数声明

                                            int insert_list_by_pos(nd_t *phead,int pos,int num);

                                            找到要插入的位置的前一个节点,创建新节点,插入新节点

                                            ② 注意点:
                                            1. 传参合理性检查
                                            2. 位置不能小于0,最后一个数据的后面可以插入,最后一个数据的后面的后面就不能再插入了,需要额外进行判定
                                            ③ 代码实现
                                            int insert_list_by_pos(nd_t *phead,int pos,int num){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(0>pos){ printf("位置不合理\n");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead;
                                                //找到要插入的位置的前一个节点
                                                for(int i=0;i //当ptemp->next为0时,说明不能继续向下遍历了
                                                    if(NULL==ptemp->next){ printf("位置不合理\n");
                                                        return -1;
                                                    }
                                                    ptemp=ptemp->next;
                                                }
                                                
                                                //创建新节点
                                                nd_t *pnew = NULL;
                                                if(create_node(&pnew,num)<0)
                                                { printf("内存分配失败\n");
                                                    return -1;
                                                }
                                                pnew->next=ptemp->next;
                                                ptemp->next=pnew;
                                                return 0;
                                            }
                                            

                                            6. 删除(头删、尾删、任意位置删)

                                            (1)头删
                                            ① 函数声明

                                            int delete_list_by_tail(nd_t *phead);

                                            将phead->next=pdel->next,然后释放pdel

                                            ② 注意点:
                                            1. 参数合理性检查
                                            2. 表不能为空,即phead->next不能为空,因为后面会用到phead->next->next,如果表为空,在对NULL->next会报段错误。
                                            ③ 代码实现
                                            int delete_list_by_head(nd_t *phead){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(NULL==phead->next){ printf("表为空\n");
                                                    return -1;
                                                }
                                                nd_t *pdel=phead->next;
                                                phead->next=pdel->next;
                                                free(pdel);
                                                pdel=NULL;
                                                return 0;
                                            }
                                            
                                            (2)尾删
                                            ① 函数声明

                                            int delete_list_by_tail(nd_t *phead,int num);

                                            先找到尾节点的前一个节点

                                            释放尾节点,

                                            将新的尾节点的成员next置NULL

                                            ② 注意点:
                                            1. 参数合理性检查
                                            2. 表不能为空,即phead->next不能为空,因为后面会用到phead->next->next,如果表为空,在对NULL->next会报段错误。
                                            ③ 代码实现
                                            int delete_list_by_tail(nd_t *phead){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(NULL==phead->next){ printf("表为空\n");
                                                    return -1;
                                                }
                                                //找到尾节点的前一个节点
                                                nd_t *ptemp=phead;
                                                while(NULL!=ptemp->next->next){ ptemp=ptemp->next;
                                                }
                                                free(ptemp->next);
                                                ptemp->next=NULL;
                                                return 0;
                                            }
                                            
                                            (3)任意位置删除
                                            ① 函数声明

                                            int delete_list_by_pos(nd_t *phead,int pos);

                                            找到要删除的节点的前一个节点,

                                            定义一个变量保存要删除的节点的指针

                                            将要删除的节点的前一个节点指向要删除的节点的后一个节点

                                            释放要删除的节点

                                            ② 注意点:
                                            1. 传参合理性检查
                                            2. 要删除的位置不能小于0,与任意位置插入不同,最后一个数据的后面就不能再删除了
                                            ③ 代码实现
                                            int delete_list_by_pos(nd_t *phead,int pos){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(0>pos){ printf("位置不合理\n");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead;
                                                //找到要删除的位置的前一个节点
                                                for(int i=0;i //当ptemp->next为0时,说明此时ptemp位于链表的最后一个节点,但是此时ptemp后的节点是空,不能进行删除操作
                                                    //所以如果ptemp->next->next为空时,就不能继续往后移动了
                                                    if(NULL==ptemp->next->next){ printf("位置不合理\n");
                                                        return -1;
                                                    }
                                                    ptemp=ptemp->next;
                                                }
                                                nd_t *pdel=ptemp->next;
                                                ptemp->next=pdel->next;
                                                free(pdel);
                                                pdel=NULL;
                                                return 0;
                                            }
                                            

                                            7. 修改

                                            (1) 函数声明

                                            int modify_list_by_pos(nd_t *phead,int pos,int num);

                                            找到要修改的位置,修改其成员data

                                            (2)注意点:
                                            1. 传参合理性检查
                                            2. 要修改的位置不能小于0,与任意位置插入不同,最后一个数据的后面就不能再修改了
                                            (3)代码实现
                                            int modify_list_by_pos(nd_t *phead,int pos,int num){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(0>pos){ printf("位置不合理\n");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead;
                                                //找到要修改的位置的节点
                                                for(int i=0;i<=pos;i++){ //当ptemp->next为0时,说明此时ptemp位于链表的最后一个节点,不能继续后移了
                                                    if(NULL==ptemp->next){ printf("位置不合理\n");
                                                        return -1;
                                                    }
                                                    ptemp=ptemp->next;
                                                }
                                                ptemp->data=num;
                                                return 0;
                                            }
                                            

                                            8. 查询

                                            (1) 函数声明

                                            int search_list_by_pos(nd_t *phead,int pos,int *num);

                                            找到要查询的位置,将该位置的数据传给num

                                            (2)注意点:
                                            1. 传参合理性检查
                                            2. 要查询的位置不能小于0,与任意位置插入不同,最后一个数据的后面就不能再进行查询了
                                            (3)代码实现
                                            int search_list_by_pos(nd_t *phead,int pos,int *num){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                if(0>pos){ printf("位置不合理\n");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead;
                                                //找到要查找的位置的节点
                                                for(int i=0;i<=pos;i++){ //当ptemp->next为0时,说明此时ptemp位于链表的最后一个节点,不能继续后移了
                                                    if(NULL==ptemp->next){ printf("位置不合理\n");
                                                        return -1;
                                                    }
                                                    ptemp=ptemp->next;
                                                }
                                                *num=ptemp->data;
                                                return 0;
                                            }
                                            

                                            9. 链表合并

                                            (1) 函数声明

                                            int merge_list(nd_t *phead1,nd_t **phead2);

                                            找到第一个链表的尾节点

                                            将链表1的尾节点的next赋值为phead2-next

                                            释放phead2

                                            (2)注意点:
                                            1. 链表2必须传入二级指针
                                            2. 需要判断传入phead1,phead2,*phead2是否为空
                                            (3)代码实现
                                            int merge_list(nd_t *phead1,nd_t **phead2){ if(NULL==phead1||NULL==phead2||NULL==*phead2){ printf("传参为空\n");
                                                    return -1;
                                                }
                                                //先找到表1的尾部
                                                nd_t *ptemp=phead1;
                                                while(NULL!=ptemp->next)
                                                { ptemp=ptemp->next;
                                                }
                                                ptemp->next=(*phead2)->next;
                                                free(*phead2);
                                                *phead2=NULL;
                                                return 0;
                                            }
                                            

                                            10. 链表排序

                                            (1) 函数声明

                                            int sort_list(node_t *phead);

                                            一个定点指针,一个动点指针配合

                                            选择排序的思路

                                            只需要交换两个节点的数据即可

                                            (2)注意点:
                                            1. 入参合理性判断
                                            2. 表为空,表中只有一个节点都不需要进行排序
                                            (3)代码实现
                                            int sort_list(node_t *phead)
                                            { if(NULL==phead){ printf("传参为空\n");
                                                    return -1;
                                                }
                                                if(NULL==phead->next)
                                                { printf("表为空,无需排序\n");
                                                    return -1;
                                                }
                                                if(NULL==phead->next->next)
                                                { printf("只有一个节点,无需排序\n");
                                                    return -1;
                                                }
                                                nd_t *p=phead;
                                                nd_t *q=NULL;
                                                int temp=0;
                                                while(NULL!=p->next){ p=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;
                                                    }
                                                }
                                                return 0;
                                            }
                                            

                                            11. 链表翻转

                                            (1) 函数声明

                                            int overturn_list(node_t *phead);

                                            将第一个数据节点的指针域置NULL,使用头插将第二个节点以及其后的所有节点以头插的方式插入链表

                                            (2)注意点:
                                            1. 传参合理性判断
                                            2. 表为空,表中只有一个节点都不需要进行排序
                                            (3)代码实现
                                            int overturn_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("只有一个节点,无效翻转");
                                                    return -1;
                                                }
                                                node_t *p=phead->next;
                                                node_t *q=p->next;
                                                p->next=NULL;
                                                while (q!=NULL)
                                                { //记录q后面的节点
                                                    p=q->next;
                                                    //头插
                                                    q->next=phead->next;
                                                    phead->next=q;
                                                    q=p;
                                                }
                                                return 0;
                                            }
                                            

                                            12. 链表剔重

                                            (1) 函数声明

                                            int dedup_list(node_t *phead);

                                            动静指针配合,找到需要剔重的节点以及其前一个节点,然后删除该节点

                                            (2)注意点:
                                            1. 传参合理性判断
                                            2. 判断表是否为空
                                            3. 判断表是不是只有一个节点
                                            4. 删除过节点后,动指针无需再后移
                                            (3)代码实现
                                            int dedup_list(nd_t *phead){ if(NULL==phead){ printf("传参为空\n");
                                                    return -1;
                                                }
                                                if(NULL==phead->next)
                                                { printf("表为空,无需翻转\n");
                                                    return -1;
                                                }
                                                if(NULL==phead->next->next)
                                                { printf("只有一个节点,无需翻转\n");
                                                    return -1;
                                                }
                                                nd_t *p=phead->next;
                                                nd_t *m=NULL;
                                                nd_t *q=NULL;
                                                while(NULL!=p){ //*注意此处绝对不可以写作NULL!=p->next
                                                    m=p;
                                                    q=p->next;
                                                    while(q!=NULL){ if(p->data==q->data){ m->next=q->next;
                                                            free(q);
                                                            q=m->next;
                                                        }else{ m=q;
                                                            q=q->next;
                                                        }
                                                    }
                                                    p=p->next;
                                                }
                                            }
                                            

                                            13. 打印链表

                                            (1) 函数声明

                                            int print_list(nd_t *phead);

                                            (2)注意点:
                                            1. 传参不能为NULL
                                            (3)代码实现
                                            //打印
                                            int print_list(nd_t *phead){ if(NULL==phead){ printf("传入指针为NULL");
                                                    return -1;
                                                }
                                                nd_t *ptemp=phead->next;
                                                while(NULL!=ptemp){ printf("%d ",ptemp->data);
                                                    ptemp=ptemp->next;
                                                }
                                                putchar(10);
                                                return 0;
                                            }