从链表开始

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

身为一个只学过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 ,问题得以解决。



原文地址:https://www.cnblogs.com/aitashi/p/6363420.html