声明:本文转自http://blog.csdn.net/pf4919501/article/details/38818335

链表概述
   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以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. }

2016年12月12日学习总结----各类链表操作相关推荐

  1. 12月26日学习记录

    今天把好久没登录的刷题网站重新上去了一遍写一一点点简单的代码,发现很多东西都已经忘掉了,现在把写过的题目都记录下来,留作笔记. 题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符. 例如 ...

  2. 2017年12月6日 学习笔记(JAVA面试题)

    前段时间都在泡图书馆,看<java核心技术卷1>.感觉好多东西会用了,但是并不知道底层实现原理,还有好多没见过的知识点概念.觉得果然还是要多读书. 接下来准备边读书,边把各种面试题提及的知 ...

  3. 2011年12月1日学习内容总结

    时间过得好快,12月了.今天学习的内容总结如下: 1.什么是javascript? 2.注意代码中的分析思路... 多写,多练.... 转载于:https://www.cnblogs.com/yxnc ...

  4. 10月31日到12月04日学习总结

    在虚拟机上调试输入法 在自己电脑(win10 2004 专业版)上可以运行,但是到了虚拟机(win7 x86 sp1专业版)上不能运行. 参考<VS远程调试虚拟机中的程序>,在虚拟机上调试 ...

  5. 12月7日学习内容整理:ORM单表操作

    补充:url(r"^$")  代表只匹配域名,没有路径部分 一.添加: 1.方式一:昨天讲过的实例化对象 2.方式二: stu_obj=类名(就是表名).objects.creat ...

  6. JavaSe基础2022年12月05日学习内容

    JavaSe-2022-12-05 每日一句:先众人而为,后众人而言. 学习内容:方法.递归 方法 概念:将一堆代码重用的机制: 用自己的话来说:方法是一个概念从古至今就有,如原始人钻木取火得到火种的 ...

  7. 12月20日学习内容整理:博客系统之media配置

    1. static指的是css,js,imgs这些文件 media指的是用户上传的文件,models中filefield,imagefield类型的字段 2. settings文件中设置 设置用户上传 ...

  8. 12月15日学习内容整理:ORM中的queryset类型,中介模型,extra函数和分组补充

    一.queryset类型:只和ORM有关 1.切片 支持索引切片,但不支持负数索引(不能用-1代表最后一个元素) 2.是可迭代的 3.属于惰性查询 我们构建出一个queryset类型,比如obj=Bo ...

  9. 12月30日学习总结

    单词接龙 ## 题目背景 注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据. ## 题目描述 单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词, ...

最新文章

  1. 单点登录(SSO)—简介
  2. mysql单机多实例——方法1
  3. 银行卡为何要使用ISO8583格式
  4. python中的reduce、lambda函数
  5. asp.net访问sqlserver获取数据、IsPostBack属性和VS可视化调试的概念
  6. box2d 碰撞检测_(译)如何使用box2d来做碰撞检测(且仅用来做碰撞检测)
  7. Python创建简单的HTTP服务
  8. mess系统可以读取opc服务器,C3. Messages
  9. Python中如何创建元素为ndarray的list
  10. win10系统下安装Linux虚拟机以及在虚拟机上安装Ubuntu
  11. 历法 —— 星期与“日月火水木金土”
  12. 爬虫—分析Ajax爬取今日头条图片
  13. Linux音频驱动-ASOC(ALSA System on Chip)
  14. 在struts中实现验证码
  15. flexray VPX控制板设计方案详细
  16. 未来科技计算机作文600字,未来科技作文600字
  17. 《公路测设技术》课程网课最新作业测验考试
  18. android自动播放音乐代码,Android MediaPlayer实现音乐播放器实例代码
  19. springboot 调用方法事物_springboot中使用@Transactional注解事物不生效的坑
  20. 如何运用InSAR技术进行数据处理、地形三维重建、形变信息提取、监测

热门文章

  1. IE6下png背景不透明——张鑫旭博客读书笔记
  2. 高通9xxx系列4G模块modem linux编译环境安装及配置详细说明
  3. 项目团队的信任问题探讨
  4. java,js获取本周和下周开始结束日期
  5. 移动互联网时代,浅谈旅行社的转型升级
  6. 局域网内终端设备观看电脑上的视频/图片
  7. 甲骨文2013二季报解析
  8. 推流用本地地址收报错,http://127.0.0.1:1935/live/test:connection refused
  9. C# 如何更改程序集名称
  10. gitee提交代码碰见的报错:error:failed to push some refs to ‘https: //gitee.com/....‘