zoukankan      html  css  js  c++  java
  • 带头节点的单链表的插入操作优化

    1.偶然看到了十字链表的一些东西,想到之前在《数据结构与算法分析》的链表一章中,需要用多重表实现一个简单的查询功能。功能需求如下:

     “已知 学生 和 学校课程 总数 分别为 40000 和 2500,现在需要得到两份报告,一份显示每门课成注册的所有学生信息,

     一份显示每个学生注册了哪些课程。”

     显然可以用一个 40000 * 2500 个元素的二维数组来解决,但是每个学生选课数目很少,因此会浪费很多空间。因此选择十字链表来实现。

     既然是链表,那么肯定要有插入操作,于是便有了本文。算是对功能实现前的铺垫。

    2.本文会介绍两种带头节点的插入操作,说是两种,其实后一种只是前一种的优化,觉得不错,分享给大家。

     根据代码分析:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    typedef int Elem;
        
    struct LNode
    {    
        Elem elem;
        struct LNode *next; 
    };    
    
    static void*
    MALLOC(int num, size_t size)
    {
        void *new = calloc(num, size);
        if (new == NULL)
        {
            fprintf(stderr, "malloc failed: [%d]
    ", (int)size);
            exit(1);
        }
    
        return new;
    }    
    
    void
    print_link(struct LNode *head)
    {
        struct LNode *cur = head;
    
        while(cur)
        {
            printf("%d -> ", cur->elem);
            cur = cur->next;
        }
    
        printf("end
    ");
    }
    
    void
    insert_link_m1 (struct LNode **head, Elem e) //普通插入
    {
        struct LNode *cur, *prev, *new;
        cur = *head;
        prev = NULL;
    
        while(cur != NULL && cur->elem < e)
        {
            prev = cur;
            cur = cur->next;
        }
    
        new = (struct LNode *)MALLOC(1, sizeof(*new));
        new->elem = e;
    
        //insert
        if (prev == NULL)
        {
            *head = new;
        }
        else
        {
            prev->next = new;
        }
    
        new->next = cur;
    }
    
    void
    insert_link_m2 (struct LNode **head, Elem e)  //优化后插入
    {
        struct LNode *cur, *new;
    
        while((cur = *head) != NULL && cur->elem < e)
        {
            head = &cur->next;
        }
    
        new = (struct LNode *)MALLOC(1, sizeof(*new));
        new->elem = e;
    
        new->next = cur;
        *head = new;
    }
    
    void
    delete_link(struct LNode *head)
    {
        struct LNode *cur, *prev;
        cur = head;
        prev = NULL;
    
        while(cur != NULL)
        {
            prev = cur;
            cur = cur->next;
    
            free(prev);
            memset(prev, 0, sizeof(*prev));
        }
    }    
    
    void
    create_link(struct LNode **head)
    {    
        struct LNode *cur, *new;
        Elem e;
    
        cur = *head;
    
        while(scanf("%d", &e) == 1)
        {
            new = (struct LNode *)MALLOC(1, sizeof(*new));
            new->elem = e;
    
            if(cur == NULL)
            {
                *head = new;
                cur = new;
            }
            else
            {
                cur->next = new;
                cur = new;
            }
        }
    }    
    
    void
    create_ordered_link(struct LNode **head, void (*insert_link) (struct LNode **head, Elem e))
    {
        Elem e;

       while(scanf("%d", &e) == 1) { insert_link(head, e); } } int main(int argc, char *argv[]) { struct LNode *head = NULL; printf("enter link nums: "); create_link(&head); print_link(head); delete_link(head); printf(" ----------------------------- "); printf("enter link nums: "); head = NULL; create_ordered_link(&head, insert_link_m1); print_link(head); delete_link(head); printf(" ----------------------------- "); printf("enter link nums: "); head = NULL; create_ordered_link(&head, insert_link_m2); print_link(head); delete_link(head); return 0; }

     输入用例格式: 数字+空格+数字+空格...输入完以后按下回车到下一行,ctrl+d结束输入。

     1)先来区分一下头节点和首节点:

      首节点:链表中第一个存放 Elem元素 的节点。

      头节点:指向这个首节点的一个指针,给节点不存放Elem元素

     2)带头节点的普通插入:

      看 void insert_link_m1 (struct LNode **head, Elem e ) 参数 **head,为指向头节点的指针,

      因为刚开始head指向NULL(main里边的初始化),我们要用这个head为头节点创建单链表,自然要改变head指向的值,

      因此要传进来 &head .接下来:

      struct LNode *cur, *prev, *new;

      cur = *head;

      prev = NULL;

      *head是head指针里的地址值,而head里的地址值就是首节点的位置,因此,cur = *head便是让cur指向首节点,

      再将 prev置为NULL,这个操作很关键,因为prev是否为NULL,用来当作要插入的位置是否位于首节点之前,即待插入的

      节点是否是新的首节点。

      好了接下来就是遍历链表找到新节点的插入位置:

      while(cur != NULL && cur->elem < e)

      { prev = cur; cur = cur->next; }

      new = (struct LNode *)MALLOC(1, sizeof(*new));

      new->elem = e;

      接下来是插入操作:

      if (prev == NULL)

      { *head = new; }

       else

      { prev->next = new; }

      new->next = cur;

      prev指向当前节点的前一个节点,如果插入的不是链表的首节点的位置,自然便有 prev->next = new; new->next = cur;

      但是若链表为空,或者待插入的位置为首节点的位置,那么此时 prev = NULL。不能按上边那样操作,需要单独处理。

       因此用prev是否等于NULL,便成了判断的标识。 *head = new; new->next = cur;

     3)带头节点的优化插入:

      优化插入其实是思路上的优化,优化针对的地方便是是否能在真正执行插入的地方,不用区分是否插入的地方是首节点的位置。

      即直接执行插入操作 new->next = cur; *head = new; 这里*head是存放new的地址的地方,也即指向new的箭头尾部的地方。

      

      我们可以看到,要想成功执行一次链表的插入操作 只需要 一个指向新节点new的指针x(*head或->next) 和一个指向当前节点的cur指针(->next)。 

      在普通的插入操作中 x可以是头节点指针(当新节点需要插入到头节点后边时),x也可以是 prev->next 当新节点不需要插入到头节点后边时。

      于是我们可以用一个LNode ** 类型的指针N,它既可以存 放头节点的地址,又可以存放 其它节点中->next成员的地址。即可以存放 &x

      当需要执行真正的插入操作的时候可以有 *N = new; new = cur;

       因为N中存放着头节点的地址,或->next成员的地址,当对其进行解引用*N时便是 相当于 *head = new 或 prev-next = new;

       根据这个思路便有了优化插入的代码:

    void
    insert_link_m2 (struct LNode **head, Elem e)  //优化后插入
    {
        struct LNode *cur, *new;
    
        while((cur = *head) != NULL && cur->elem < e)
        {
            head = &cur->next;
        }
    
        new = (struct LNode *)MALLOC(1, sizeof(*new));
        new->elem = e;
    
        new->next = cur;
        *head = new;
    }

       (1)while第一次操作时cur是指向首节点,头节点指向cur,head中存放头节点的地址。倘若条件不满足直接跳出循环,便有new->next = cur; *head = new;

       head存放了头节点的地址, *head作为左值的时候,*head = new;便使得头节点指向了new。

       (2)若条件不满足时,及插入位置不是头节点后边,便执行 head = &cur->next;然后 cur = *head; 此时 head存放cur->next的地址,而cur = *head,便将cur->next的值,即下一个节点的地址赋值给cur,

        倘若此时跳出循环, head中存放了此时cur的前一个节点中(next)成员的地址,cur指向当前位置,于是执行 new->next = cur; *head = new后,便自动完成了链接.

       这样一个技巧便省去了判断prev == NULL的步骤。

      

        参考文献:《c与指针》第十二章。

        思路就是这样,难免表述不清,结合代码,画画图便知,欢迎指正与指点。

      

      

      

      

  • 相关阅读:
    动态投影
    我的比较差的初级的研究成果
    我最近的研究成果(IGeometry.Project and IGeometry.SpatialReference)
    mysql中的数据类型以及常见约束
    面向对象——多态
    java基础
    java中的异常(3)
    mysql中的数据类型
    面向对象——继承
    java中的异常(2)
  • 原文地址:https://www.cnblogs.com/newbeeyu/p/5905799.html
Copyright © 2011-2022 走看看