链表概述
   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
        链表的各类操作包括:学习单向链表的创建、删除、  插入(无序、有序)、输出、  排序(选择、插入、冒泡)、反序等等。

单向链表的图示:
       ---->[NULL]
      head

图1:空链表

---->[p1]---->[p2]...---->[pn]---->[NULL]
      head   p1->next  p2->next   pn->next

图2:有N个节点的链表

创建n个节点的链表的函数为:

[cpp] view plaincopy
  1. #include "stdlib.h"
  2. #include "stdio.h"
  3. #define NULL 0
  4. #define LEN sizeof(struct student)
  5. struct student
  6. {
  7. int num;              //学号
  8. float score;          //分数,其他信息可以继续在下面增加字段
  9. struct student *next;       //指向下一节点的指针
  10. };
  11. int n;  //节点总数
  12. /*
  13. ==========================
  14. 功能:创建n个节点的链表
  15. 返回:指向链表表头的指针
  16. ==========================
  17. */
  18. struct student *Create()
  19. {
  20. struct student *head;       //头节点
  21. struct student *p1 = NULL;  //p1保存创建的新节点的地址
  22. struct student *p2 = NULL;  //p2保存原链表最后一个节点的地址
  23. n = 0;          //创建前链表的节点总数为0:空链表
  24. p1 = (struct student *) malloc (LEN);   //开辟一个新节点
  25. p2 = p1;            //如果节点开辟成功,则p2先把它的指针保存下来以备后用
  26. if(p1==NULL)        //节点开辟不成功
  27. {
  28. printf ("\nCann't create it, try it again in a moment!\n");
  29. return NULL;
  30. }
  31. else                //节点开辟成功
  32. {
  33. head = NULL;        //开始head指向NULL
  34. printf ("Please input %d node -- num,score: ", n + 1);
  35. scanf ("%d %f", &(p1->num), &(p1->score));    //录入数据
  36. }
  37. while(p1->num != 0)      //只要学号不为0,就继续录入下一个节点
  38. {
  39. n += 1;         //节点总数增加1个
  40. if(n == 1)      //如果节点总数是1,则head指向刚创建的节点p1
  41. {
  42. head = p1;
  43. p2->next = NULL;  //此时的p2就是p1,也就是p1->next指向NULL。
  44. }
  45. else
  46. {
  47. p2->next = p1;   //指向上次下面刚刚开辟的新节点
  48. }
  49. p2 = p1;            //把p1的地址给p2保留,然后p1产生新的节点
  50. p1 = (struct student *) malloc (LEN);
  51. printf ("Please input %d node -- num,score: ", n + 1);
  52. scanf ("%d %f", &(p1->num), &(p1->score));
  53. }
  54. p2->next = NULL;     //此句就是根据单向链表的最后一个节点要指向NULL
  55. free(p1);           //p1->num为0的时候跳出了while循环,并且释放p1
  56. p1 = NULL;          //特别不要忘记把释放的变量清空置为NULL,否则就变成"野指针",即地址不确定的指针
  57. return head;        //返回创建链表的头指针
  58. }

输出链表中节点的函数为:

[cpp] view plaincopy
  1. /*
  2. ===========================
  3. 功能:输出节点
  4. 返回: void
  5. ===========================
  6. */
  7. void Print(struct student *head)
  8. {
  9. struct student *p;
  10. printf ("\nNow , These %d records are:\n", n);
  11. p = head;
  12. if(head != NULL)        //只要不是空链表,就输出链表中所有节点
  13. {
  14. printf("head is %o\n", head);    //输出头指针指向的地址
  15. do
  16. {
  17. /*
  18. 输出相应的值:当前节点地址、各字段值、当前节点的下一节点地址。
  19. 这样输出便于读者形象看到一个单向链表在计算机中的存储结构,和我们
  20. 设计的图示是一模一样的。
  21. */
  22. printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);
  23. p = p->next;     //移到下一个节点
  24. }
  25. while (p != NULL);
  26. }
  27. }

单向链表的删除图示:
       ---->[NULL]
       head

图3:空链表

从图3可知,空链表显然不能删除

---->[1]---->[2]...---->[n]---->[NULL](原链表)
      head   1->next  2->next   n->next

---->[2]...---->[n]---->[NULL](删除后链表)
      head   2->next   n->next

图4:有N个节点的链表,删除第一个节点
      结合原链表和删除后的链表,就很容易写出相应的代码。操作方法如下:
      1、你要明白head就是第1个节点,head->next就是第2个节点;
       2、删除后head指向第2个节点,就是让head=head->next,OK这样就行了。
       ---->[1]---->[2]---->[3]...---->[n]---->[NULL](原链表)
       head   1->next  2->next  3->next   n->next

---->[1]---->[3]...---->[n]---->[NULL](删除后链表)
      head   1->next  3->next   n->next

图5:有N个节点的链表,删除中间一个(这里图示删除第2个)
      结合原链表和删除后的链表,就很容易写出相应的代码。操作方法如下:
      1、你要明白head就是第1个节点,1->next就是第2个节点,2->next就是第3个节点;
      2、删除后2,1指向第3个节点,就是让1->next=2->next。

删除指定学号的节点的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:删除指定节点
  4. (此例中是删除指定学号的节点)
  5. 返回:指向链表表头的指针
  6. ==========================
  7. */
  8. struct student *Del (struct student *head, int num)
  9. {
  10. struct student *p1;     //p1保存当前需要检查的节点的地址
  11. struct student *p2;     //p2保存当前检查过的节点的地址
  12. if (head == NULL)       //是空链表(结合图3理解)
  13. {
  14. printf ("\nList is null!\n");
  15. return head;
  16. }
  17. //定位要删除的节点
  18. p1 = head;
  19. while (p1->num != num && p1->next != NULL)    //p1指向的节点不是所要查找的,并且它不是最后一个节点,就继续往下找
  20. {
  21. p2 = p1;            //保存当前节点的地址
  22. p1 = p1->next;       //后移一个节点
  23. }
  24. if(p1->num==num)     //找到了。(结合图4、5理解)
  25. {
  26. if (p1 == head)     //如果要删除的节点是第一个节点
  27. {
  28. head = p1->next; //头指针指向第一个节点的后一个节点,也就是第二个节点。这样第一个节点就不在链表中,即删除
  29. }
  30. else            //如果是其它节点,则让原来指向当前节点的指针,指向它的下一个节点,完成删除
  31. {
  32. p2->next = p1->next;
  33. }
  34. free (p1);      //释放当前节点
  35. p1 = NULL;
  36. printf ("\ndelete %ld success!\n", num);
  37. n -= 1;         //节点总数减1个
  38. }
  39. else                //没有找到
  40. {
  41. printf ("\n%ld not been found!\n", num);
  42. }
  43. return head;
  44. }

单向链表的插入图示:
       ---->[NULL](原链表)
      head

---->[1]---->[NULL](插入后的链表)
      head   1->next

图7 空链表插入一个节点
      结合原链表和插入后的链表,就很容易写出相应的代码。操作方法如下:
     1、你要明白空链表head指向NULL就是head=NULL;
     2、插入后head指向第1个节点,就是让head=1,1->next=NULL,OK这样就行了。

---->[1]---->[2]---->[3]...---->[n]---->[NULL](原链表)
     head   1->next  2->next  3->next   n->next

---->[1]---->[2]---->[x]---->[3]...---->[n]---->[NULL](插入后的链表)
     head   1->next  2->next  x->next  3->next   n->next

图8:有N个节点的链表,插入一个节点(这里图示插入第2个后面)
     结合原链表和插入后的链表,就很容易写出相应的代码。操作方法如下:
    1、你要明白原1->next就是节点2,2->next就是节点3;
    2、插入后x指向第3个节点,2指向x,就是让x->next=2->next,1->next=x。

插入指定节点的后面的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:插入指定节点的后面
  4. (此例中是指定学号的节点)
  5. 返回:指向链表表头的指针
  6. ==========================
  7. */
  8. struct student *Insert (struct student *head, int num, struct student *node)
  9. {
  10. struct student *p1;     //p1保存当前需要检查的节点的地址
  11. if (head == NULL)       //(结合图示7理解)
  12. {
  13. head = node;
  14. node->next = NULL;
  15. n += 1;
  16. return head;
  17. }
  18. p1 = head;
  19. while(p1->num != num && p1->next != NULL)  //p1指向的节点不是所要查找的,并且它不是最后一个节点,继续往下找
  20. {
  21. p1 = p1->next;       //后移一个节点
  22. }
  23. if (p1->num==num)        //找到了(结合图示8理解)
  24. {
  25. node->next = p1->next;    //显然node的下一节点是原p1的next
  26. p1->next = node;     //插入后,原p1的下一节点就是要插入的node
  27. n += 1;         //节点总数增加1个
  28. }
  29. else
  30. {
  31. printf ("\n%ld not been found!\n", num);
  32. }
  33. return head;
  34. }

单向链表的反序图示:
       ---->[1]---->[2]---->[3]...---->[n]---->[NULL](原链表)
       head   1->next  2->next  3->next   n->next

[NULL]<----[1]<----[2]<----[3]<----...[n]<----(反序后的链表)
                1->next  2->next  3->next   n->next  head

图9:有N个节点的链表反序
          结合原链表和插入后的链表,就很容易写出相应的代码。操作方法如下:
          1、我们需要一个读原链表的指针p2,存反序链表的p1=NULL(刚好最后一个节点的next为NULL),还有一个临时存储变量p;
          2、p2在原链表中读出一个节点,我们就把它放到p1中,p就是用来处理节点放置顺序的问题;
          3、比如,现在我们取得一个2,为了我们继续往下取节点,我们必须保存它的next值,由原链表可知p=2->next;
          4、然后由反序后的链表可知,反序后2->next要指向1,则2->next=1;
          5、好了,现在已经反序一个节点,接着处理下一个节点就需要保存此时的信息:
          p1变成刚刚加入的2,即p1=2;p2要变成它的下一节点,就是上面我们保存的p,即p2=p。

反序链表的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:反序节点
  4. (链表的头变成链表的尾,链表的尾变成头)
  5. 返回:指向链表表头的指针
  6. ==========================
  7. */
  8. struct student *Reverse (struct student *head)
  9. {
  10. struct student *p;      //临时存储
  11. struct student *p1;     //存储返回结果
  12. struct student *p2;     //源结果节点一个一个取
  13. p1 = NULL;          //开始颠倒时,已颠倒的部分为空
  14. p2 = head;          //p2指向链表的头节点
  15. while(p2 != NULL)
  16. {
  17. p = p2->next;
  18. p2->next = p1;
  19. p1 = p2;
  20. p2 = p;
  21. }
  22. head = p1;
  23. return head;
  24. }

对链表进行选择排序的基本思想就是反复从还未排好序的那些节点中,选出键值(就是用它排序的字段,我们取学号num为键值)最小的节点,依次重新组合成一个链表。

我认为写链表这类程序,关键是理解:head存储的是第一个节点的地址,head->next存储的是第二个节点的地址;任意一个节点p的地址,只能通过它前一个节点的next来求得。

单向链表的选择排序图示:
         ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)
         head   1->next  3->next  2->next   n->next

---->[NULL](空链表)
        first
        tail

---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)
         first   1->next  2->next  3->next   tail->next

图10:有N个节点的链表选择排序

1、先在原链表中找最小的,找到一个后就把它放到另一个空的链表中;
        2、空链表中安放第一个进来的节点,产生一个有序链表,并且让它在原链表中分离出来(此时要注意原链表中出来的是第一个节点还是中间其它节点);
        3、继续在原链表中找下一个最小的,找到后把它放入有序链表的尾指针的next,然后它变成其尾指针;

对链表进行选择排序的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:选择排序(由小到大)
  4. 返回:指向链表表头的指针
  5. ==========================
  6. */
  7. struct student *SelectSort (struct student *head)
  8. {
  9. struct student *first;     //排列后有序链的表头指针
  10. struct student *tail;      //排列后有序链的表尾指针
  11. struct student *p_min;     //保留键值更小的节点的前驱节点的指针
  12. struct student *min;       //存储最小节点
  13. struct student *p;         //当前比较的节点
  14. first = NULL;
  15. while(head != NULL)       //在链表中找键值最小的节点
  16. {
  17. //注意:这里for语句就是体现选择排序思想的地方
  18. for (p = head, min = head; p->next != NULL; p = p->next)  //循环遍历链表中的节点,找出此时最小的节点
  19. {
  20. if (p->next->num < min->num)     //找到一个比当前min小的节点
  21. {
  22. p_min = p;        //保存找到节点的前驱节点:显然p->next的前驱节点是p
  23. min = p->next;     //保存键值更小的节点
  24. }
  25. }
  26. //上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表
  27. //第一件事
  28. if (first == NULL)     //如果有序链表目前还是一个空链表
  29. {
  30. first = min;        //第一次找到键值最小的节点
  31. tail = min;        //注意:尾指针让它指向最后的一个节点
  32. }
  33. else              //有序链表中已经有节点
  34. {
  35. tail->next = min;    //把刚找到的最小节点放到最后,即让尾指针的next指向它
  36. tail = min;           //尾指针也要指向它
  37. }
  38. //第二件事
  39. if (min == head)            //如果找到的最小节点就是第一个节点
  40. {
  41. head = head->next;      //显然让head指向原head->next,即第二个节点,就OK
  42. }
  43. else            //如果不是第一个节点
  44. {
  45. p_min->next = min->next;  //前次最小节点的next指向当前min的next,这样就让min离开了原链表
  46. }
  47. }
  48. if (first != NULL)      //循环结束得到有序链表first
  49. {
  50. tail->next = NULL;   //单向链表的最后一个节点的next应该指向NULL
  51. }
  52. head = first;
  53. return head;
  54. }

对链表进行直接插入排序的基本思想就是假设链表的前面n-1个节点是已经按键值(就是用它排序的字段,我们取学号num为键值)排好序的,对于节点n在这个序列中找插入位置,使得n插入后新序列仍然有序。按照这种思想,依次对链表从头到尾执行一遍,就可以使无序链表变为有序链表。

单向链表的直接插入排序图示:
         ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)
        head   1->next  3->next  2->next   n->next

---->[1]---->[NULL](从原链表中取第1个节点作为只有一个节点的有序链表)
        head
        图11

---->[3]---->[2]...---->[n]---->[NULL](原链表剩下用于直接插入排序的节点)
        first   3->next  2->next   n->next
        图12

---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)
        head   1->next  2->next  3->next   n->next

图13:有N个节点的链表直接插入排序

1、先在原链表中以第一个节点为一个有序链表,其余节点为待定节点。
       2、从图12链表中取节点,到图11链表中定位插入。
       3、上面图示虽说画了两条链表,其实只有一条链表。在排序中,实质只增加了一个用于指向剩下需要排序节点的头指针first罢了。
       这一点请读者务必搞清楚,要不然就可能认为它和上面的选择排序法一样了。

对链表进行直接插入排序的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:直接插入排序(由小到大)
  4. 返回:指向链表表头的指针
  5. ==========================
  6. */
  7. struct student *InsertSort (struct student *head)
  8. {
  9. struct student *first;    //为原链表剩下用于直接插入排序的节点头指针
  10. struct student *t;        //临时指针变量:插入节点
  11. struct student *p,*q;     //临时指针变量
  12. first = head->next;      //原链表剩下用于直接插入排序的节点链表:可根据图12来理解
  13. head->next = NULL;       //只含有一个节点的链表的有序链表:可根据图11来理解
  14. while(first != NULL)        //遍历剩下无序的链表
  15. {
  16. //注意:这里for语句就是体现直接插入排序思想的地方
  17. for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);  //无序节点在有序链表中找插入的位置
  18. //退出for循环,就是找到了插入的位置,应该将t节点插入到p节点之后,q节点之前
  19. //注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能。原因:你若理解了上面的第3条,就知道了
  20. //下面的插入就是将t节点即是first节点插入到p节点之后,已经改变了first节点,所以first节点应该在被修改之前往后移动,不能放到下面注释的位置上去
  21. first = first->next; //无序链表中的节点离开,以便它插入到有序链表中
  22. if (q == head)      //插在第一个节点之前
  23. {
  24. head = t;
  25. }
  26. else            //p是q的前驱
  27. {
  28. p->next = t;
  29. }
  30. t->next = q;     //完成插入动作
  31. //first = first->next;
  32. }
  33. return head;
  34. }

对链表进行冒泡排序的基本思想就是对当前还未排好序的范围内的全部节点,自上而下对相邻的两个节点依次进行比较和调整,让键值(就是用它排 序的字段,我们取学号num为键值)较大的节点往下沉,键值较小的往上冒。即:每当两相邻的节点比较后发现它们的排序与排序要求相反时,就将它们互换。

单向链表的冒泡排序图示:
        ---->[1]---->[3]---->[2]...---->[n]---->[NULL](原链表)
       head   1->next  3->next  2->next   n->next

---->[1]---->[2]---->[3]...---->[n]---->[NULL](排序后链表)
       head   1->next  2->next  3->next   n->next

图14:有N个节点的链表冒泡排序

任意两个相邻节点p、q位置互换图示:
      假设p1->next指向p,那么显然p1->next->next就指向q,
      p1->next->next->next就指向q的后继节点,我们用p2保存
      p1->next->next指针。即:p2=p1->next->next,则有:
       [  ]---->[p]---------->[q]---->[  ](排序前)
       p1->next  p1->next->next  p2->next
       图15

[  ]---->[q]---------->[p]---->[  ](排序后)

图16

1、排序后q节点指向p节点,在调整指向之前,我们要保存原p的指向节点地址,即:p2=p1->next->next;
      2、顺着这一步一步往下推,排序后图16中p1->next->next要指的是p2->next,所以p1->next->next=p2->next;
      3、在图15中p2->next原是q发出来的指向,排序后图16中q的指向要变为指向p的,而原来p1->next是指向p的,所以p2->next=p1->next;
      4、在图15中p1->next原是指向p的,排序后图16中p1->next要指向q,原来p1->next->next(即p2)是指向q的,所以p1->next=p2;
      5、至此,我们完成了相邻两节点的顺序交换。
      6、下面的程序描述改进了一点就是记录了每次最后一次节点下沉的位置,这样我们不必每次都从头到尾的扫描,只需要扫描到记录点为止。 因为后面的都已经是排好序的了。

对链表进行冒泡排序的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:冒泡排序(由小到大)
  4. 返回:指向链表表头的指针
  5. ==========================
  6. */
  7. struct student *BubbleSort (struct student *head)
  8. {
  9. struct student *endpt;    //控制循环比较
  10. struct student *p;        //临时指针变量
  11. struct student *p1,*p2;
  12. p1 = (struct student *) malloc (LEN);
  13. p1->next = head;        //注意理解:我们增加一个节点,放在第一个节点的前面,主要是为了便于比较。因为第一个节点没有前驱,我们不能交换地址
  14. head = p1;                 //让head指向p1节点,排序完成后,我们再把p1节点释放掉
  15. for (endpt = NULL; endpt != head; endpt = p)    //结合第6点理解
  16. {
  17. for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
  18. {
  19. if (p1->next->num > p1->next->next->num)  //如果前面的节点键值比后面节点的键值大,则交换
  20. {
  21. p2 = p1->next->next;    //结合第1点理解
  22. p1->next->next = p2->next;   //结合第2点理解
  23. p2->next = p1->next;   //结合第3点理解
  24. p1->next = p2;     //结合第4点理解
  25. p = p1->next->next;   //结合第6点理解
  26. }
  27. }
  28. }
  29. p1 = head;              //把p1的信息去掉
  30. head = head->next;       //让head指向排序后的第一个节点
  31. free (p1);          //释放p1
  32. p1 = NULL;          //p1置为NULL,保证不产生“野指针”,即地址不确定的指针变量
  33. return head;
  34. }

有序链表插入节点示意图:

---->[NULL](空有序链表)
        head

图18:空有序链表(空有序链表好解决,直接让head指向它就是了。)

以下讨论不为空的有序链表。
        ---->[1]---->[2]---->[3]...---->[n]---->[NULL](有序链表)
        head   1->next  2->next  3->next   n->next

图18:有N个节点的有序链表

插入node节点的位置有两种情况:一是第一个节点前,二是其它节点前或后。

---->[node]---->[1]---->[2]---->[3]...---->[n]---->[NULL]
       head  node->next  1->next  2->next  3->next   n->next

图19:node节点插在第一个节点前

---->[1]---->[2]---->[3]...---->[node]...---->[n]---->[NULL]
      head   1->next  2->next  3->next    node->next  n->next

插入有序链表的函数为:

[cpp] view plaincopy
  1. /*
  2. ==========================
  3. 功能:插入有序链表的某个节点的后面(从小到大)
  4. 返回:指向链表表头的指针
  5. ==========================
  6. */
  7. struct student *SortInsert (struct student *head, struct student *node)
  8. {
  9. struct student *p;      //p保存当前需要检查的节点的地址
  10. struct student *t;      //临时指针变量
  11. if (head == NULL)       //处理空的有序链表
  12. {
  13. head = node;
  14. node->next = NULL;
  15. n += 1;         //插入完毕,节点总数加
  16. return head;
  17. }
  18. p = head;             //有序链表不为空
  19. while(p->num < node->num && p != NULL)    //p指向的节点的学号比插入节点的学号小,并且它不等于NULL
  20. {
  21. t = p;            //保存当前节点的前驱,以便后面判断后处理
  22. p = p->next;     //后移一个节点
  23. }
  24. if (p == head)      //刚好插入第一个节点之前
  25. {
  26. node->next = p;
  27. head = node;
  28. }
  29. else                 //插入其它节点之后
  30. {
  31. t->next = node;      //把node节点加进去
  32. node->next = p;
  33. }
  34. n += 1;         //插入完毕,节点总数加1
  35. return head;
  36. }

综上所述,链表的各类操作函数的完整代码如下:

[cpp] view plaincopy
  1. #include "stdlib.h"
  2. #include "stdio.h"
  3. #define NULL 0
  4. #define LEN sizeof(struct student)
  5. struct student
  6. {
  7. int num;              //学号
  8. float score;          //分数,其他信息可以继续在下面增加字段
  9. struct student *next;       //指向下一节点的指针
  10. };
  11. int n;  //节点总数
  12. /*
  13. ==========================
  14. 功能:创建n个节点的链表
  15. 返回:指向链表表头的指针
  16. ==========================
  17. */
  18. struct student *Create()
  19. {
  20. struct student *head;       //头节点
  21. struct student *p1 = NULL;  //p1保存创建的新节点的地址
  22. struct student *p2 = NULL;  //p2保存原链表最后一个节点的地址
  23. n = 0;          //创建前链表的节点总数为0:空链表
  24. p1 = (struct student *) malloc (LEN);   //开辟一个新节点
  25. p2 = p1;            //如果节点开辟成功,则p2先把它的指针保存下来以备后用
  26. if(p1==NULL)        //节点开辟不成功
  27. {
  28. printf ("\nCann't create it, try it again in a moment!\n");
  29. return NULL;
  30. }
  31. else                //节点开辟成功
  32. {
  33. head = NULL;        //开始head指向NULL
  34. printf ("Please input %d node -- num,score: ", n + 1);
  35. scanf ("%d %f", &(p1->num), &(p1->score));    //录入数据
  36. }
  37. while(p1->num != 0)      //只要学号不为0,就继续录入下一个节点
  38. {
  39. n += 1;         //节点总数增加1个
  40. if(n == 1)      //如果节点总数是1,则head指向刚创建的节点p1
  41. {
  42. head = p1;
  43. p2->next = NULL;  //此时的p2就是p1,也就是p1->next指向NULL。
  44. }
  45. else
  46. {
  47. p2->next = p1;   //指向上次下面刚刚开辟的新节点
  48. }
  49. p2 = p1;            //把p1的地址给p2保留,然后p1产生新的节点
  50. p1 = (struct student *) malloc (LEN);
  51. printf ("Please input %d node -- num,score: ", n + 1);
  52. scanf ("%d %f", &(p1->num), &(p1->score));
  53. }
  54. p2->next = NULL;     //此句就是根据单向链表的最后一个节点要指向NULL
  55. free(p1);           //p1->num为0的时候跳出了while循环,并且释放p1
  56. p1 = NULL;          //特别不要忘记把释放的变量清空置为NULL,否则就变成"野指针",即地址不确定的指针
  57. return head;        //返回创建链表的头指针
  58. }
  59. /*
  60. ===========================
  61. 功能:输出节点
  62. 返回: void
  63. ===========================
  64. */
  65. void Print(struct student *head)
  66. {
  67. struct student *p;
  68. printf ("\nNow , These %d records are:\n", n);
  69. p = head;
  70. if(head != NULL)        //只要不是空链表,就输出链表中所有节点
  71. {
  72. printf("head is %o\n", head);    //输出头指针指向的地址
  73. do
  74. {
  75. /*
  76. 输出相应的值:当前节点地址、各字段值、当前节点的下一节点地址。
  77. 这样输出便于读者形象看到一个单向链表在计算机中的存储结构,和我们
  78. 设计的图示是一模一样的。
  79. */
  80. printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);
  81. p = p->next;     //移到下一个节点
  82. }
  83. while (p != NULL);
  84. }
  85. }
  86. /*
  87. ==========================
  88. 功能:删除指定节点
  89. (此例中是删除指定学号的节点)
  90. 返回:指向链表表头的指针
  91. ==========================
  92. */
  93. struct student *Del (struct student *head, int num)
  94. {
  95. struct student *p1;     //p1保存当前需要检查的节点的地址
  96. struct student *p2;     //p2保存当前检查过的节点的地址
  97. if (head == NULL)       //是空链表(结合图3理解)
  98. {
  99. printf ("\nList is null!\n");
  100. return head;
  101. }
  102. //定位要删除的节点
  103. p1 = head;
  104. while (p1->num != num && p1->next != NULL)    //p1指向的节点不是所要查找的,并且它不是最后一个节点,就继续往下找
  105. {
  106. p2 = p1;            //保存当前节点的地址
  107. p1 = p1->next;       //后移一个节点
  108. }
  109. if(p1->num==num)     //找到了。(结合图4、5理解)
  110. {
  111. if (p1 == head)     //如果要删除的节点是第一个节点
  112. {
  113. head = p1->next; //头指针指向第一个节点的后一个节点,也就是第二个节点。这样第一个节点就不在链表中,即删除
  114. }
  115. else            //如果是其它节点,则让原来指向当前节点的指针,指向它的下一个节点,完成删除
  116. {
  117. p2->next = p1->next;
  118. }
  119. free (p1);      //释放当前节点
  120. p1 = NULL;
  121. printf ("\ndelete %ld success!\n", num);
  122. n -= 1;         //节点总数减1个
  123. }
  124. else                //没有找到
  125. {
  126. printf ("\n%ld not been found!\n", num);
  127. }
  128. return head;
  129. }
  130. //销毁链表
  131. void DestroyList(struct student *head)
  132. {
  133. struct student *p;
  134. if(head==NULL)
  135. return 0;
  136. while(head)
  137. {
  138. p=head->next;
  139. free(head);
  140. head=p;
  141. }
  142. return 1;
  143. }
  144. /*
  145. ==========================
  146. 功能:插入指定节点的后面
  147. (此例中是指定学号的节点)
  148. 返回:指向链表表头的指针
  149. ==========================
  150. */
  151. struct student *Insert (struct student *head, int num, struct student *node)
  152. {
  153. struct student *p1;     //p1保存当前需要检查的节点的地址
  154. if (head == NULL)       //(结合图示7理解)
  155. {
  156. head = node;
  157. node->next = NULL;
  158. n += 1;
  159. return head;
  160. }
  161. p1 = head;
  162. while(p1->num != num && p1->next != NULL)  //p1指向的节点不是所要查找的,并且它不是最后一个节点,继续往下找
  163. {
  164. p1 = p1->next;       //后移一个节点
  165. }
  166. if (p1->num==num)        //找到了(结合图示8理解)
  167. {
  168. node->next = p1->next;    //显然node的下一节点是原p1的next
  169. p1->next = node;     //插入后,原p1的下一节点就是要插入的node
  170. n += 1;         //节点总数增加1个
  171. }
  172. else
  173. {
  174. printf ("\n%ld not been found!\n", num);
  175. }
  176. return head;
  177. }
  178. /*
  179. ==========================
  180. 功能:反序节点
  181. (链表的头变成链表的尾,链表的尾变成头)
  182. 返回:指向链表表头的指针
  183. ==========================
  184. */
  185. struct student *Reverse (struct student *head)
  186. {
  187. struct student *p;      //临时存储
  188. struct student *p1;     //存储返回结果
  189. struct student *p2;     //源结果节点一个一个取
  190. p1 = NULL;          //开始颠倒时,已颠倒的部分为空
  191. p2 = head;          //p2指向链表的头节点
  192. while(p2 != NULL)
  193. {
  194. p = p2->next;
  195. p2->next = p1;
  196. p1 = p2;
  197. p2 = p;
  198. }
  199. head = p1;
  200. return head;
  201. }
  202. /*
  203. ==========================
  204. 功能:选择排序(由小到大)
  205. 返回:指向链表表头的指针
  206. ==========================
  207. */
  208. struct student *SelectSort (struct student *head)
  209. {
  210. struct student *first;     //排列后有序链的表头指针
  211. struct student *tail;      //排列后有序链的表尾指针
  212. struct student *p_min;     //保留键值更小的节点的前驱节点的指针
  213. struct student *min;       //存储最小节点
  214. struct student *p;         //当前比较的节点
  215. first = NULL;
  216. while(head != NULL)       //在链表中找键值最小的节点
  217. {
  218. //注意:这里for语句就是体现选择排序思想的地方
  219. for (p = head, min = head; p->next != NULL; p = p->next)  //循环遍历链表中的节点,找出此时最小的节点
  220. {
  221. if (p->next->num < min->num)     //找到一个比当前min小的节点
  222. {
  223. p_min = p;        //保存找到节点的前驱节点:显然p->next的前驱节点是p
  224. min = p->next;     //保存键值更小的节点
  225. }
  226. }
  227. //上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表
  228. //第一件事
  229. if (first == NULL)     //如果有序链表目前还是一个空链表
  230. {
  231. first = min;        //第一次找到键值最小的节点
  232. tail = min;        //注意:尾指针让它指向最后的一个节点
  233. }
  234. else              //有序链表中已经有节点
  235. {
  236. tail->next = min;    //把刚找到的最小节点放到最后,即让尾指针的next指向它
  237. tail = min;           //尾指针也要指向它
  238. }
  239. //第二件事
  240. if (min == head)            //如果找到的最小节点就是第一个节点
  241. {
  242. head = head->next;      //显然让head指向原head->next,即第二个节点,就OK
  243. }
  244. else            //如果不是第一个节点
  245. {
  246. p_min->next = min->next;  //前次最小节点的next指向当前min的next,这样就让min离开了原链表
  247. }
  248. }
  249. if (first != NULL)      //循环结束得到有序链表first
  250. {
  251. tail->next = NULL;   //单向链表的最后一个节点的next应该指向NULL
  252. }
  253. head = first;
  254. return head;
  255. }
  256. /*
  257. ==========================
  258. 功能:直接插入排序(由小到大)
  259. 返回:指向链表表头的指针
  260. ==========================
  261. */
  262. struct student *InsertSort (struct student *head)
  263. {
  264. struct student *first;    //为原链表剩下用于直接插入排序的节点头指针
  265. struct student *t;        //临时指针变量:插入节点
  266. struct student *p,*q;     //临时指针变量
  267. first = head->next;      //原链表剩下用于直接插入排序的节点链表:可根据图12来理解
  268. head->next = NULL;       //只含有一个节点的链表的有序链表:可根据图11来理解
  269. while(first != NULL)        //遍历剩下无序的链表
  270. {
  271. //注意:这里for语句就是体现直接插入排序思想的地方
  272. for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);  //无序节点在有序链表中找插入的位置
  273. //退出for循环,就是找到了插入的位置,应该将t节点插入到p节点之后,q节点之前
  274. //注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能。原因:你若理解了上面的第3条,就知道了
  275. //下面的插入就是将t节点即是first节点插入到p节点之后,已经改变了first节点,所以first节点应该在被修改之前往后移动,不能放到下面注释的位置上去
  276. first = first->next; //无序链表中的节点离开,以便它插入到有序链表中
  277. if (q == head)      //插在第一个节点之前
  278. {
  279. head = t;
  280. }
  281. else            //p是q的前驱
  282. {
  283. p->next = t;
  284. }
  285. t->next = q;     //完成插入动作
  286. //first = first->next;
  287. }
  288. return head;
  289. }
  290. /*
  291. ==========================
  292. 功能:冒泡排序(由小到大)
  293. 返回:指向链表表头的指针
  294. ==========================
  295. */
  296. struct student *BubbleSort (struct student *head)
  297. {
  298. struct student *endpt;    //控制循环比较
  299. struct student *p;        //临时指针变量
  300. struct student *p1,*p2;
  301. p1 = (struct student *) malloc (LEN);
  302. p1->next = head;        //注意理解:我们增加一个节点,放在第一个节点的前面,主要是为了便于比较。因为第一个节点没有前驱,我们不能交换地址
  303. head = p1;                 //让head指向p1节点,排序完成后,我们再把p1节点释放掉
  304. for (endpt = NULL; endpt != head; endpt = p)    //结合第6点理解
  305. {
  306. for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
  307. {
  308. if (p1->next->num > p1->next->next->num)  //如果前面的节点键值比后面节点的键值大,则交换
  309. {
  310. p2 = p1->next->next;    //结合第1点理解
  311. p1->next->next = p2->next;   //结合第2点理解
  312. p2->next = p1->next;   //结合第3点理解
  313. p1->next = p2;     //结合第4点理解
  314. p = p1->next->next;   //结合第6点理解
  315. }
  316. }
  317. }
  318. p1 = head;              //把p1的信息去掉
  319. head = head->next;       //让head指向排序后的第一个节点
  320. free (p1);          //释放p1
  321. p1 = NULL;          //p1置为NULL,保证不产生“野指针”,即地址不确定的指针变量
  322. return head;
  323. }
  324. /*
  325. ==========================
  326. 功能:插入有序链表的某个节点的后面(从小到大)
  327. 返回:指向链表表头的指针
  328. ==========================
  329. */
  330. struct student *SortInsert (struct student *head, struct student *node)
  331. {
  332. struct student *p;      //p保存当前需要检查的节点的地址
  333. struct student *t;      //临时指针变量
  334. if (head == NULL)       //处理空的有序链表
  335. {
  336. head = node;
  337. node->next = NULL;
  338. n += 1;         //插入完毕,节点总数加
  339. return head;
  340. }
  341. p = head;             //有序链表不为空
  342. while(p->num < node->num && p != NULL)    //p指向的节点的学号比插入节点的学号小,并且它不等于NULL
  343. {
  344. t = p;            //保存当前节点的前驱,以便后面判断后处理
  345. p = p->next;     //后移一个节点
  346. }
  347. if (p == head)      //刚好插入第一个节点之前
  348. {
  349. node->next = p;
  350. head = node;
  351. }
  352. else                 //插入其它节点之后
  353. {
  354. t->next = node;      //把node节点加进去
  355. node->next = p;
  356. }
  357. n += 1;         //插入完毕,节点总数加1
  358. return head;
  359. }
  360. /*
  361. 以上函数的测试程序:
  362. 提示:根据测试函数的不同注释相应的程序段,这也是一种测试方法。
  363. */
  364. int main(void)
  365. {
  366. struct student *head;
  367. struct student *stu;
  368. int thenumber;
  369. // 测试Create()、Print()
  370. head = Create();
  371. Print(head);
  372. //测试Del()
  373. printf("\nWhich one delete: ");
  374. scanf("%d",&thenumber);
  375. head = Del(head,thenumber);
  376. Print(head);
  377. //测试Insert()
  378. stu = (struct student *)malloc(LEN);
  379. printf("\nPlease input insert node -- num,score: ");
  380. scanf("%d %f",&stu->num,&stu->score);
  381. printf("\nInsert behind num: ");
  382. scanf("%d",&thenumber);
  383. head = Insert(head,thenumber,stu);
  384. Print(head);
  385. //测试Reverse()
  386. printf("\nReverse the LinkList: \n");
  387. head = Reverse(head);
  388. Print(head);
  389. //测试SelectSort()
  390. printf("\nSelectSort the LinkList: \n");
  391. head = SelectSort(head);
  392. Print(head);
  393. //测试InsertSort()
  394. printf("\nInsertSort the LinkList: \n");
  395. head = InsertSort(head);
  396. Print(head);
  397. //测试BubbleSort()
  398. printf("\nBubbleSort the LinkList: \n");
  399. head = BubbleSort(head);
  400. Print(head);
  401. printf("\nSortInsert the LinkList: \n");
  402. //测试SortInsert():上面创建链表,输入节点时请注意学号num从小到大的顺序
  403. stu = (struct student *)malloc(LEN);
  404. printf("\nPlease input insert node -- num,score: ");
  405. scanf("%d %f",&stu->num,&stu->score);
  406. head = SortInsert(head,stu);
  407. Print(head);
  408. //销毁链表
  409. DestroyList(head);
  410. printf ("\n");
  411. system ("pause");
  412. }

C语言链表的操作和讲解相关推荐

  1. c语言统计链表值的总合,C语言链表综合操作

    /*----------------------------------预处理命令-----------------------------------------*/ #include #inclu ...

  2. c语言节点有指针域数据域,学习心得:链表的操作(C语言实现)

    今天将给大家讲述链表的学习心得.学习数据结构,毋庸置疑链表必须学好,后面的栈.队列.树.图都是以链表为基础的:链表的种类很多,有单链表.双链表.循环链表.非循环链表:在此,我们以非循环单链表为例,来讲 ...

  3. C语言 链表创建及操作

    C语言 链表创建及操作 第一部分构建链表,定义结构体,分别用头插法.尾插法实现,这里封装了打印函数:printf();做练习方便后续使用:对链表进行查找,并将查找到的值构建一个新的链表:链表的转置:具 ...

  4. C语言实用算法系列之学生管理系统_单向链表内操作_选择排序

    单向链表实现 #include <stdio.h> #include <malloc.h>typedef int DATA;struct SNode {DATA data;SN ...

  5. C语言链表超简单教程

    笔者作为一名C语言的初学者,在刚接触链表时,几乎找不到教程能用很通俗易懂的语言去讲解链表.大多数时候找到的关于链表的教程,或许是生硬的塞给读者一大段代码,或许是使用了一些过于专业的词汇,对于萌新非常地 ...

  6. c语言课程设计订单管理系统,C语言课程设计订单管理系统讲解.doc

    C语言课程设计订单管理系统讲解 C语言课程设计 随米打印订单管理系统 学 院: 计算机与信息科学学院 学生姓名: 谢润发 指导教师: 王新祥 职称 教授 专 业: 网络工程 班 级: 1501 完成时 ...

  7. 关于C语言链表基础知识

    链表和数组作为算法中的两个基本数据结构,在程序设计过程中经常用到.尽管两种结构都可以用来存储一系列的数据,但又各有各的特点. 数组的优势,在于可以方便的遍历查找需要的数据.在查询数组指定位置(如查询数 ...

  8. C语言与数据库操作入门(Win版)

    数据库,DataBase,学C语言的是不是想说,很想爱她却并不容易呢?不用着急,C语言也可以操作数据库的,既使你不会Windows API,只要参照本文的方法,写数据库应用程序,你也行.本文以MySq ...

  9. [转载 整理]C语言链表实例

    C语言链表有单链表.双向链表.循环链表.单链表由数据域和指针域组成,数据域存放数据,指针域存放该数据类型的指针便于找到下一个节点.双链表则含有头指针域.数据域和尾指针域,域单链表不同,双链表可以从后一 ...

  10. c语言链表p-%3enext,课程设计报告.c语言程序设计.pdf

    课程设计报告.c语言程序设计 学生成绩管理 C语言课程设计报告 学 院 _信息学院_ 专 业 软件工程 班 级 _ _ 学 号 姓 名 课 题 _C语言课程设计_ 指导教师 __ 杨老师 _ 报告成绩 ...

最新文章

  1. python入门经典例题-Python入门练习题(适合Python初学者做的练习题)
  2. ABAP:为Table Control创建Context Menu
  3. html5实现贪吃蛇,分享一个用html5实现的贪吃蛇特效代码
  4. vim改变与选择字休大小的方法
  5. django连接数据库和数据迁移
  6. Gradle中的默认任务和任务依赖关系设置
  7. 设计师和开发人员更快完成工作需求的35个惊人的jquery插件教程(下)
  8. Intel Core Enhanced Core架构/微架构/流水线 (11) - 高速缓存读/写操作 Cache Load/Store
  9. 随想录(ros学习笔记)
  10. [codeVS1204] 寻找子串位置
  11. java holder_Java DataHolder.supports方法代码示例
  12. UVA 1611 Crane
  13. [leetcode]5355. T 秒后青蛙的位置
  14. Cortex-M3 (NXP LPC1788)之UART用法
  15. 产品经理岗位职责及面试指南
  16. fms安装教程 linux_[AS3]linux64下安装FMS5.0的方法
  17. 调整计算机繁体,在线繁体转换
  18. 01表盘控件-08时钟仪表盘-gaugeclock
  19. Python 实现 T00ls 自动签到脚本(邮件+钉钉通知)
  20. 计算机视觉入门(包含论文学习网址)

热门文章

  1. Linux学习之安装jdk
  2. tomcat的server.xml中的Context节配置
  3. 如何实现全选checkbox效果
  4. oracle pl/sql发送邮件多个收件人问题
  5. SpringBoot多数据源切换详解,以及开启事务后数据源切换失败处理
  6. 经典排序算法(六)--归并排序Merge Sort
  7. Linux下编译protobuf
  8. STL 容器迭代器失效总结(超级详细)
  9. 08.Prevent exceptions from leaving destructors
  10. 学python对excel有用吗_程序员必修课:为什么非要用Python做数据分析?Excel不好吗?...