链表插入与删除操作中的头、尾指针

来源:互联网 发布:淘宝云客服是干什么的 编辑:程序博客网 时间:2024/06/05 08:01

[题目]:有一个单链表,它的元素全部是整数。head和tail分别是指向该链表第一个元素(即头元素)和最后一个元素(即尾元素)的全局性指针。请实现调用接口如下所示的两个C语言函数:
                                                   int Delete (element *elem);
                                                   int InsertAfter (element *elem, int data);
Delete函数只有一个输入参数,它就是那个将被删除的元素。InsertAfter函数有两个输入参数,第二个输入参数给出了新元素的取值,它将被插到第一个输入参数所指定的元素的后面。当需要把新元素插入到链表的开头作为新的头元素时,函数InsertAfter的第一个输入参数将被设置为NULL。如果执行成功,这两个函数将返回“1”;否则返回“0”。

      这道题看起来很简单。元素的插入和删除操作都是链表的常用操作,用头指针来定位链表的事情也是大家所接受的。这道题唯一不同寻常的地方是它要求你必须正确的调整好head和tail指针。在编写Delete函数时,链表头元素的删除操作需要特殊对待,我们首先要将待删除元素的指针和head指针进行比较。
         if(elem == head)
         { head = elem->next;
           free(elem);
           return 1;          代码1
      对链表的“腹部”元素进行删除是最普通的情况。我们首先需要一个元素指针来记录链表的当前位置(curpos指针)。在链表中删除一个元素,我们需要找到待删除元素前一个元素的指针并修改其next指针。请认真编写你的遍历循环语句,不要遗漏掉某个元素。如果我们把curpos指针初始化为指向链表头元素,那么“curpos->next”将指向链表的第二个元素。从第二个元素开始遍历循环是不会产生错误的,但一定要先进行比较操作后修改curpos指针,要不燃就会陋漏掉链表的第二个元素。如果curpos指针变成了NULL,说明已经到达了链表的末尾,删除操作不成功。在删除元素时,如果我们删除的正好是尾元素,此时我们已经改变了链表的尾元素,那么tail元素也要做相应的修改:
        if(curpos->next == NULL)
        tail = curpos;
      再考虑特殊情况的例子:0元素和1个元素的链表。0元素链表的Delete函数将返回一个“0”,因为空链表里没有可删除的元素。对于只有一个元素的链表,此时head和tail都指向唯一可删除的元素。当“elem==head”时,elem->next肯定等于NULL,于是Delete函数正确地把head指针设置为NULL,并释放这个元素;但是此时tail指针仍然指向着我们已经释放了的这个元素。也就是说,我们需要特意地把tail指针设置为NULL才算正确地完成了删除操作:
        在代码1的后面加上    if(!head)
                                               tail = NULL;
      对于有且只有2个元素的链表又是怎样的情况呢?删除第一个元素将使head指针指向第二个元素,没有问题;删除第二个元素将使tail指向第一个元素,也没有问题。也就是说,缺少“腹部”元素并不会产生什么意外。
    下面是Delete函数的全部代码:
        int Delete (elem *elem)
        {
          element *curpos = head;
          if(!elem)
            return 0;
          if(elem == head)
           { head = elem->next;
             free(elem);
             if(!head)
               tail = NULL;
             return 1;
           }
         while(curpos)
           {
            if(curpos->next==elem)
              {
                curpos->next=elem->next;
                free(elem);
                if(curpos->next==NULL)
                  tail = curpos;
                return 1;
               }
            curpos = curpos->next;
           }
         return 0;
         }   

      在编写InsertAfter函数的时候,需要考虑的因素与我们前面的分析大同小异。因为这个函数需要为新元素分配内存,所以千万不要忘记检查内存分配操作是否成功以免出现内存泄露。Delete函数中的大体部分特殊情况都需要在InsertAfter函数中加以注意,这两个函数在整体结构上很相似:
         int InsertAfter (element *elem, int data)
          {  element *newelem, *curpos = head;
             newelem = (element *)malloc(sizeof(element));
             if(!newelem)
                return 0;
             newelem->data=data;
             if(!elem)
              {
                newelem->next = head;
                head = newelem;
                if(!tail)
                   tail = newelem;
               }
             while(curpos)
              {
                if(curpos == elem)
                 {
                   newelem->next == curpos->next;
                   curpos->next = newelem;
                   if(!(newelem->next))
                      tail = newelem;
                   return 1;
                  }
                curpos =curpos->next;
              }
            free(newelem);
            return 0;
         }

      这道面试题重点考察了求职者对特殊情况的分析和处理能力。虽说不难,也不太容易做到百分之百的满意,它的确是一道非常有针对性的面试题。面试考试题多少会包含有一些特殊情况,所以我们必须有随时会遇到它们的思想准备。在实际编程工作中,未做适当处理的特殊情况就是程序代码中的bug,而且还是那种很难查找、重现和修正的bug。一般说来,一位在编写代码的同时就已经考虑到各种特殊情况的程序员肯定要比那些只知通过调试去查找bug的程序员来的高明,也更有效率。

原创粉丝点击