暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

单链表

Code小燕儿 2021-09-01
210

hello,大家好,本文学习单链表及其基本操作,话不多说,直接开讲吧

  • 单链表定义

单链表是一种链式存取的数据结构,数据元素的逻辑顺序通过指针链接,下图为单链表的结构示意图。

下述代码为C语言下定义的节点结构

    struct LinkList{
    int val;
        char name[40];
        struct LinkList *next;
    };
    复制
    • 单链表的插入

    单链表的插入方式有头插法与尾插法两种,首先介绍头插法。

    • 单链表头插法

    下图为单链表头插法示意图,我们将新数据插入到单链表的头部位置。

    头插法的代码如下:

      /* 单链表头插法 */
      void insertHead(struct LinkList **head){
          struct LinkList *list, *temp;
          list = (struct LinkList *)malloc(sizeof(struct LinkList));
          if(list == NULL){
              printf("哦豁,内存分配失败了\n");
              exit(1);
          }
          getInput(list);
          if(*head == NULL){  // 单链表为空
              *head = list;
              list->next = NULL;
          }else{
              temp = *head;
              *head = list;
              list->next = temp;
          }
      }


      /* 获取用户输入的节点数据 */
      void getInput(struct LinkList *list){
          printf("请输入姓名:");
          scanf("%s", list->name);
          printf("请输入学号:");
          scanf("%d", &list->val);
      }


      /* 打印单链表数据信息 */
      void printLinkList(const struct LinkList *head){
      const struct LinkList *temp = head;
      while(temp != NULL){
      printf("姓名:%s\t学号:%d\n", temp->name, temp->val);
      temp = temp->next;
      }
      }


      /* 释放堆空间 */
      void freeLinkList(struct LinkList **head){
      struct LinkList *temp;
      while(*head != NULL){
      temp = *head;
      *head = (*head)->next;
      free(temp);
      temp = NULL;
      }
      }


      复制

      需要注意的是,head是指向第一个节点的指针,插入元素需要修改head本身的值,因此参数传递时,需要将head的地址作为参数传递,也即此时参数类型为" struct LinkList ** ";此外,堆空间申请的内存需要及时释放以防止内存泄漏。下图为上述头插法示例程序的执行结果:

      • 单链表尾插法

      顾名思义,单链表的尾插法则是每次将新加入的数据添加到单链表的尾部,下图为尾插法示意图。

      尾插法代码如下:

        void insertTail(struct LinkList **head){
            static struct LinkList *tail;  // 引入哨兵节点
            struct LinkList *list;
            list = (struct LinkList *)malloc(sizeof(struct LinkList));
        if(list == NULL){
        printf("哦豁,内存分配失败了\n");
        exit(1);
        }

            if(*head == NULL){  // 单链表为空 
        *head = list;
        list->next = NULL;
        }else{
        tail->next = list;
        list->next = NULL;
        }
            tail = list; // 更新tail节点位置
        }


        复制

        在使用单链表尾插法时,为防止每次新插入数据时遍历得到尾节点,我们定义一个静态哨兵节点,使其一直指向单链表的尾部节点,这样可以大大提升尾插法的效率。下图为上述程序的运行结果:

        • 单链表的查找

        本文以给定待查找的姓名为例进行演示,代码如下:

          struct LinkList *serchLinkList(struct LinkList *head, char *name){
              struct LinkList *node = head;
              while(node != NULL){
                  if(!strcmp(node->name, name)){
                   break;
                  }
                  cur = cur->next;
              }
              return cur;
          }
          复制

          上述代码的运行结果如下:

          • 单链表的删除

          单链表的删除节点操作也就是指针操作,首先修改待删除节点的上一节点指针,使其指向待删除节点的下一节点,然后释放删除节点的空间即可,代码描述如下:

            void deleteNode(struct LinkList **head, int flag){
                struct LinkList *cur, *pre;
                cur = *head, pre = NULL;
                while(cur && cur->val != flag){
                    pre = cur;
                    cur = cur->next;
                }
                if(cur == NULL){
                 printf("找不到对应节点\n");
                    return ;
                }else{
                 if(pre == NULL){ // 待删除节点为头节点
                 *head = cur->next;
                    }else{
                     pre->next = cur->next;

                    }
                    free(cur);
                    cur = NULL;
                }
            }
            复制

            下图为上述程序运行结果:

            • 单链表反转

            单链表反转为经典的双指针问题,实现代码如下:

              struct LinkList *reverseLinkList(struct LinkList **head){
                  struct LinkList *cur, *pre; // 定义两个指针
                  // cur指针指向头节点,pre指针指向cur的上一节点
                  cur = *head, pre = NULL;
                  while(cur != NULL){
                      //临时变量保存cur的下一节点
                      struct LinkList *temp = cur->next;
                      // cur节点的next指针指向前一节点
                      cur->next = pre;
                      // pre指针与cur指针分别前移
                      pre = cur, cur = temp;
                  }
                  return pre;
              }
              复制

              上述链表反转的运行结果如下:

              • 后记

              本文讲述了单链表最基本的一些操作,在实际应用中仍存在诸多其他有关单链表的操作,例如两两交换链表节点以及K个一组翻转链表等等,建议大家在LeetCode网站上多多练习。

              另外有关单链表的算法,有以下几点建议:

              1. 善用哨兵节点简化实现难度,例如在尾插法实现时,定义哨兵节点指向链表的尾节点,可降低算法的时间复杂度

              2. 善于画图思考,将指针的链接逻辑清晰化,厘清链表结构,防止指针丢失与内存泄漏

              讲完啦,欢迎读者留言,无论是文章勘误亦或是有关文章内容的讨论与建议,都欢迎,谢谢大家。

              文章转载自Code小燕儿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论