zoukankan      html  css  js  c++  java
  • 从链表开始

    链表是个很有意思的东西,从这里开始,编程语言基础结束,数据结构开始了。

    身为一个只学过C语言基础的非正式码农,从链表开始似乎是挺不错的,我这么觉得。

    抽象的逻辑结构已经在计算机基础课上水过去了,现在能写一写的也只剩下代码了。

    (好像又有抄书本的嫌疑呐)

    链表顾名思义就是用链子穿起来的表,它有单向和双向之分。目前还在学习中,就先只讲讲单向的啦。

    似乎是源于编程语言本身的特性,一般来说,构造链表都是通过诸如结构体,类等方式实现的,那么也就是说,链表的核心是结点,链是由每个结点对外派生的,或者说,链是结点的一部分。

    (这正好与数组相反,数组是先有了链(连续内存空间和索引),才能往里塞东西)

    这也让我知道,链表不是高级语言里自带的数据结构或者定义类型,需要自己去建立并管理使用。(至少C语言如此,以下的内容也全是建立在C语言上)

    自己建立管理的话,那么大概就包括,生成链表,输出链表,插入结点,删除结点,删除链表,以及对链表数据进行各种运算等等。

    首先是建立链表

      先建立一个带有与结构体类型相同的指针的结构体(这个指针就是用来指向下一个结点的)

    struct student
    {
        long num;
        char name[20];
        char addr[50];
        struct student *next;    //This results in a chain of structs
    };
    
    typedef struct student LIST;

    (这里有一个转义,它可以提供很大的方便,C语言提供这么贴心的服务真是太好了)

      然后是一个创建链表的函数

    LIST *create_list()    //This function creates a length-variable chain and returns the head pointer of the chain
    {
        LIST *h, *prev, *cur;
        int i, n;
        h = NULL;
        prev = NULL;
        printf("Please Input The quantity of Nodes:
    ");
        scanf("%d", &n);
        i = 0;
        while (i<n)
        {
            cur = (LIST *)malloc(sizeof(LIST));    //allocate memory space to initialize pointer 'cur'
            cur->next = NULL;                    
            if (h == NULL)
                h = cur;
            else
                prev->next = cur;
            printf("Please Input The Elements of Node %d:", (i + 1));
            scanf("%d %s %s", &cur->num, cur->name, cur->addr);
            prev = cur;
            i++;
        }
        printf("Infomation Uploaded Successfully!
    
    ");
        return h;
    }

    这里首先建立了三个指针 *h,*prev,*cur。我们之前创建的结构体本身只是一个多个数据的集合而已,这个函数让各个结点通过指针的方式连接起来,形成一条长链,*prev与*cur分别代表previous与current,即前一指针与当前指针,这个函数的流程就是,先构造出相关的结点,然后让*prev和*cur分别指向两个应当链接的结点,让它们连接上去。然后让这两个指针不停向后移动,这样整条链表就成了。而*h代表head,即头指针,指向了这个链表的第一个节点,正是有了它,我们才知道这个链表的存在,它可以说就是这个链表的证明,这个函数的返回值即为这个头指针,而以后对链表进行其他操作,都需要将此头指针作为引路人(实参)。

    此处也反映了单向链表的一些性质,与数组相比,单向链表不存在索引,每一个结点仅仅向下联系,也就是说,如果你想访问某个深处的结点,必须从第一个结点开始,一个一个往下寻找,直到找到为止。

    而具体的形成过程,用到了malloc函数,这个函数很深层次的东西我也尚未挖掘,在此,我对它的认识即是,它可以分配一块固定大小的内存空间(对于上面的代码则是一个LIST的空间),然后返回这块空间的头指针。与直接建立一个LIST类型的结构体相比,malloc的方式是动态的,它没有变量名,分配的内存空间也可以通过free的方式重新“充公”(后文会提到),并且由于没有变量名,可以直接将其套入循环,易于操作(想象一下如果把malloc换成新建一个结构体会怎样)。

    然后就是赋值,成链,移位的过程。空间总是赋给*cur,然后由*cur传出去。

      下一步是输出链表中的数据

    void disp_list(LIST *h)
    {
        LIST *p = h;
        while (p != NULL)
        {
            printf("%-7d %-7s %-7s
    ", p->num, p->name, p->addr);
            p = p->next;
    
        }
        printf("
    ");
    }

    只需要一个简单的循环。

      接下来时重点:插入结点

    LIST *insert_node(LIST *h, LIST *s)    //*h is the head pointer of the entire list 
                                        //and *s is the pointer which points to the new node waiting for being inserted
    {
        LIST *prev, *cur;    //initialize two pointer for the chain which points to the current node and the previous node
        cur = h;            //first let 'cur' points to the head pointer
        prev = NULL;
        if (h == NULL)
        {
            h = s;
            s->next = NULL;
        }
        else
        {
            while ((s->num > cur->num) && (cur->next != NULL))
            {
                prev = cur;
                cur = cur->next;
            }
            if (s->num <= cur->num)
            {
                if (h == cur)
                {
                    h = s;
                    s->next = cur;
                }
                else
                {
                    prev->next = s;
                    s->next = cur;
                }
            }
            else
            {
                cur->next = s;
                s->next = NULL;
            }
        }
        printf("
    ");
        return h;
    }

    两个参数,一个是待插入的链表头指针,另一个是指向待插入结点的指针

    这里用到了一些判断,每一个结点中都有许多数据,其中存在一些索引性的,标记性的数据(毕竟并非数组,自带索引,所以只能自建)

    在LIST中,num就是索引般的存在。

    当然了,前面创立节点的过程是不包含对num合法性的检查的,这里的insert_node函数假定num是从小到大排列的,因此在进行实际运行时,输入的数据也应符合这个要求。

    具体来说,就是逐个比较,直到找到一个比s->num更大的cur->num,然后,让prev->next由指向cur转为指向s,而s->next则指向cur,这样就完成了插入过程。

    这时候链表相对于数组的优势就体现出来了。链表不仅大小长度是动态的,任意两个连续元素的链接也是动态的,这个“链接”本身可以任意进行操纵,而数组本身索引是固定的,实现插入操作需要移动从插入点到末尾的所有数据。

    此外还有一些特殊情况,比如链表本身就是空的啦,插入点在链表头结点前或者末结点后等等,也都要分别考虑。

      然后是删除结点

    LIST *delete_node(LIST *h, int para)//*h is the head pointer of the list 
                                        //and 'para' is the parameter to match the 'num' in every nodes to locate the particular node to be deleted
    {
        LIST *prev, *cur;
        prev = NULL;
        if (h == NULL)
        {
            printf("The Chain Is Empty And No Nodes Can Be Deleted.
    
    ");
            return NULL;
        }
        cur = h;
        while (cur->num != para&&cur->next != NULL)
        {
            prev = cur;
            cur = cur->next;
        }
        if (cur->num == para)
        {
            if (cur == h)
                h = cur->next;
            else
                prev->next = cur->next;
            free(cur);
            printf("Node Has Been Deleted.
    
    ");
        }
        else
            printf("No Node Matches The Given Parameter or Ordinal.
    
    ");
        return h;
    }

    删除的过程与插入类似,需要注意的是,在断开某一结点与其他结点的联系的之后,应立刻对其进行free操作,释放内存空间。(在指针移位之后,被断开的结点就找不到啦)

    而被删除结点的查找工作,也是通过num参数进行的。

    最后来一个简单的应用吧

    int main()
    {
        LIST *head,*p;    //head pointer and the pointer which points to the nodes to be deleted
        int ordinal;    //the parameter which will traversal the chain to find the particular node to delete
        head = create_list();
        printf("Serial    Name    Address
    ");
        disp_list(head);
        printf("Please Input The Node Need To Be Inserted:
    ");
        p = (LIST*)malloc(sizeof(LIST));
        scanf("%d %s %s", &p->num, p->name, p->addr);
        head = insert_node(head, p);    
        printf("Serial    Name    Address
    ");
        disp_list(head);
        printf("Please Insert A Num Which Reflects On A Particular Node:
    ");
        scanf("%d", &ordinal);
        head = delete_node(head, ordinal);    
    
        if (head != NULL)
        {
            printf("Serial    Name    Address
    ");
            disp_list(head);
        }
        else
            printf("No Data Remaining.
    
    ");
    
    }

    下面再介绍一些简单的应用

      ①多项式求和

    #include <stdio.h>
    #include <stdlib.h>
    struct item
    {
        int exp;    
        float coef;    //coefficient
        struct item *next;
    };
    typedef struct item ITEM;
    
    ITEM* create_poly()    //create items for a polynomial
                        //without sorting, so polynomial ought to be input at descending arrangement
                        //which means ,the exp must from large to small
    {
        ITEM *h = NULL, *prev=NULL, *cur=NULL;
        int ex;
        float co;
        int i = 1;
        
        printf("Please Input Coeffient and Exponential %d:
    ", i);
        scanf("%f %d", &co, &ex);
        while (co <-1e-6||co>1e-6)//Caution here!!
        {
            i++;
            cur = (ITEM*)malloc(sizeof(ITEM));
            cur->exp = ex;
            cur->coef = co;
            cur->next = NULL;    //make sure when loop is over,the 'next' of 'cur'(it is also the pointer to the last node) is NULL 
            if (h == NULL)
            {
                h = cur;    //first loop
            }
            else
                prev->next = cur;    //later loop,let 'prev's next' points to this 'cur'
            prev = cur;                //then, let 'cur' cover this 'prev', 
                                    //and the 'next' of old 'prev' in previous line above now points to this new 'prev' 
                                    //so the link was set up, and new 'cur' is coming at next loop.
            printf("Please Input Coeffient and Exponential %d:
    ", i);
            scanf("%f %d", &co, &ex);
        }
        return h;
    }
    
    void disp_poly(ITEM *h)    //display a polynomial and make it similar as a polynomial in form
    {
        ITEM *p = h;
        printf("The Result Is:
    ");
        while (p != NULL)
        {
            if (p->exp == 0)//to prevent to print "Cx^0"
            {
                printf("%.2f", p->coef);
            }
            else
                printf("%fx^%d", p->coef, p->exp);
            p = p->next;    //pointer pushing
            if (p != NULL)    
                printf("+");    //judge whether to add an '+' after pushing pointer  
        }
        printf("
    ");
    }
    
    ITEM *add_poly(ITEM *poly_h1, ITEM *poly_h2)    //Input 2 list and generate a new list and return its head pointer
    {
        ITEM *h_add = NULL, *prev_add = NULL, *cur_add = NULL, *p1, *p2;
        float c_add;
        int e_add;
        p1 = poly_h1;
        p2 = poly_h2;
        while (p1 != NULL&&p2 != NULL)//confirm when to end it
        {
            if (p1->exp > p2->exp)
            {
                c_add = p1->coef;
                e_add = p1->exp;
                p1 = p1->next;
            }//if exp1>exp2,then move p1
            else if (p1->exp == p2->exp)    //multiple 'if' is not the same as 'if,else if,else' chain 
                                            //for multiple 'if' will continue judging after it satisfy former judgements
                                            //and the condition may change in the next judgement
                                            //while the 'if,else if,else' chain will only entry first satisfied judgement 
            {
                c_add = p1->coef + p2->coef;
                e_add = p1->exp;    //
                p1 = p1->next;
                p2 = p2->next;
            }//if exp1=exp2, then add them and move p1&p2
            else
            {
                c_add = p2->coef;
                e_add = p2->exp;
                p2 = p2->next;
            }
            if (c_add<-1e-6 || c_add>1e-6)
            {
                cur_add = (ITEM*)malloc(sizeof(ITEM));
                cur_add->coef = c_add;
                cur_add->exp = e_add;
                cur_add->next = NULL;
                if (h_add == NULL)
                    h_add = cur_add;
                else
                    prev_add->next = cur_add;
                prev_add = cur_add;
            }
        }
        while (p1 != NULL)    //If one polynomial is over ,then the other will add to the result
        {
            c_add = p1->coef;
            e_add = p1->exp;
            p1 = p1->next;
            if (c_add<-1e-6 || c_add>1e-6)
            {
                cur_add = (ITEM*)malloc(sizeof(ITEM));
                cur_add->coef = c_add;
                cur_add->exp = e_add;
                cur_add->next = NULL;
                if (h_add == NULL)    //when p2 is NULL
                    h_add = cur_add;
                else
                    prev_add->next = cur_add;
                prev_add = cur_add;
            }
        }
        while (p2 != NULL)    //If one polynomial is over ,then the other will add to the result
        {
            c_add = p2->coef;
            e_add = p2->exp;
            p2 = p2->next;
            if (c_add<-1e-6 || c_add>1e-6)
            {
                cur_add = (ITEM*)malloc(sizeof(ITEM));
                cur_add->coef = c_add;
                cur_add->exp = e_add;
                cur_add->next = NULL;
                if (h_add == NULL)    //when p2 is NULL
                    h_add = cur_add;
                else
                    prev_add->next = cur_add;
                prev_add = cur_add;
            }
        }
        return h_add;
    }
    
    void delete_poly(ITEM *poly_h)    //delete nodes one by one from front
    {
        ITEM *cur_del, *prev_del = poly_h;
        while (prev_del != NULL)
        {
            cur_del = prev_del->next;
            free(prev_del);
            prev_del = cur_del;
        }
    }
    
    void main()
    {
        ITEM *poly1, *poly2, *poly_add;
        printf("Create The First Polynomial:
    ");
        poly1 = create_poly();
        printf("Create The Second Polynomial:
    ");
        poly2 = create_poly();
        poly_add = add_poly(poly1, poly2);
        disp_poly(poly_add);
        delete_poly(poly_add);
    }

    以上是完整代码,其中有一些很有意思的注释,这也是我在实际写代码时发现的一些东西

    首先是关于如何保证头指针指向正确,而链条又如何形成的。这里用了一个if判断,*h初始化是NULL(空)的,而cur在接收完第一次数据之后,就传给了*h,之后才开始prev对cur的链接,*h是否为NULL,就是判断cur传输方向的标准。而不管是进行了哪种传输,cur都要对prev进行覆盖,实现指针向后移动的过程,具体来说就是

    cur<<data  h<<cur prev<<cur
    cur<<data prev->next<<cur prev<<cur
    cur<<data prev->next<<cur prev<<cur
    cur<<data prev->next<<cur prev<<cur
    …… …… ……

     (从左到右,从上到下)

    然后是关于多个条件互补的并列if语句和if-else if-else语句的区别(这是三个词,‘if’与‘else if’与‘else’,不是两个if-else),多个并列的if,在满足前面的某个if并且执行完相关语句后,会继续对后续if进行判断,而前面的if语句中可能存在使得后续if判断体结果改变的内容,这也就是说,有可能会进行多个判断,然后就不知道跑到哪里去了。而if-else if-else结构就不会有这个问题。以前在写嵌入式模块的代码时,还很少碰到这个问题,但在这里,本例中如果采用多个if会导致结果错误,在例②中,这种多个if的结构会直接导致程序崩溃。

      ②约瑟夫环问题

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 
     4 struct Joseph
     5 {
     6     int num;
     7     struct Joseph *next;
     8 };
     9 typedef struct Joseph NODE;
    10 
    11 NODE *create_joseph_circle(int length)
    12 {
    13     int i;
    14     NODE *head = NULL, *prev = NULL, *cur = NULL;
    15     for (i = 1; i < length+1; i++)
    16     {
    17         cur = (NODE*)malloc(sizeof(NODE));
    18         cur->num = i;
    19         cur->next = NULL;
    20         if (head == NULL)
    21             head = cur;
    22         else
    23             prev->next = cur;
    24         prev = cur;
    25     }
    26     cur->next = head;
    27     return head;
    28 }
    29 
    30 int disp_joseph_circle(NODE *joseph_head)
    31 {
    32     NODE *prev=NULL, *cur;
    33     int i = 1;
    34     if (joseph_head == NULL)
    35     {
    36         printf("No Such Joseph Circle Exists.
    ");
    37         return 0;
    38     }
    39         //prev=joseph_head; 
    40         //cur=prev->next;
    41     cur = joseph_head;
    42     while (cur->next !=NULL)
    43     {
    44         if (cur->next == cur&&i % 3 == 0)
    45         {
    46             printf("%d
    ", cur->num);
    47             cur->next = NULL;
    48         }
    49         else if (i % 3 == 0)    //@@@@@@@@@@@@
    50         {
    51             printf("%d
    ", cur->num);
    52             prev->next = cur->next;
    53             free(cur);
    54             cur = prev->next;
    55         }
    56         else
    57         {
    58             prev = cur;
    59             cur = cur->next;
    60         }
    61         
    62         i++;
    63         
    64     }
    65     return 0;
    66     free(cur);
    67 }
    68 
    69 void main()
    70 {
    71     int n = 13;
    72     NODE * joseph;
    73     joseph = create_joseph_circle(n);
    74     disp_joseph_circle(joseph);
    75 }

    这个题目是这样的,13个人围城一圈,从第一个人开始报数,报到3退出圈子,按顺序输出圈子的序号

    这种题目因为包含首尾相连和人员(结点)退出,用链表处理再合适不过

    原先想的是直接在cur->next==cur的时候结束循环,后来发现,最后一个退出的人(13)无法输出,分析后发现,当只剩一个人时,他的数字可能不是3,因此他需要自己循环若干次,直到成为3的倍数为止,因此修改结束条件,将(cur->next==cur&&i%3==0)作为判断条件,然后就出现了严重bug,在输出13之后程序就报错了。通过调试发现,是cur最后成为了0xdddddddd,原因是当13输出后,cur->next已经为NULL,而在修改之前(即49行)没有else,导致继续进入if判断,导致cur成为了NULL,再次进入循环时,判断便出问题了。于是改为else if ,问题得以解决。



  • 相关阅读:
    新式类、经典类与多继承
    实现抽象类之方式二
    实现抽象类之方式一
    re模块
    28个高频Linux命令
    Git使用教程
    编程语言介绍
    编码
    进制
    操作系统简史
  • 原文地址:https://www.cnblogs.com/aitashi/p/6363420.html
Copyright © 2011-2022 走看看