深入理解链表的各类操作详解

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


代码如下:

#include "stdlib.h"
#include "stdio.h"
#define NULL 0
#define LEN sizeof(struct student)
struct student
{
 int num;     //学号
 float score;       //分数,其他信息可以继续在下面增加字段
 struct student *next;  //指向下一节点的指针
};
int n; //节点总数
/*
==========================
功能:创建n个节点的链表
返回:指向链表表头的指针
==========================
*/
struct student *Create()
{
 struct student *head;  //头节点
 struct student *p1 = NULL; //p1保存创建的新节点的地址
 struct student *p2 = NULL; //p2保存原链表最后一个节点的地址
 n = 0;   //创建前链表的节点总数为0:空链表
 p1 = (struct student *) malloc (LEN); //开辟一个新节点
 p2 = p1;   //如果节点开辟成功,则p2先把它的指针保存下来以备后用
 if(p1==NULL)  //节点开辟不成功
 {
  printf ("\nCann't create it, try it again in a moment!\n");
  return NULL;
 }
 else    //节点开辟成功
 {
  head = NULL;  //开始head指向NULL
  printf ("Please input %d node -- num,score: ", n + 1);
  scanf ("%d %f", &(p1->num), &(p1->score)); //录入数据
 }
 while(p1->num != 0)  //只要学号不为0,就继续录入下一个节点
 {
  n += 1;   //节点总数增加1个
  if(n == 1)  //如果节点总数是1,则head指向刚创建的节点p1
  {
   head = p1;
   p2->next = NULL;  //此时的p2就是p1,也就是p1->next指向NULL。
  }
  else
  {
   p2->next = p1; //指向上次下面刚刚开辟的新节点
  }
  p2 = p1;   //把p1的地址给p2保留,然后p1产生新的节点
  p1 = (struct student *) malloc (LEN);
  printf ("Please input %d node -- num,score: ", n + 1);
  scanf ("%d %f", &(p1->num), &(p1->score));
 }
 p2->next = NULL;  //此句就是根据单向链表的最后一个节点要指向NULL
 free(p1);   //p1->num为0的时候跳出了while循环,并且释放p1
 p1 = NULL;   //特别不要忘记把释放的变量清空置为NULL,否则就变成"野指针",即地址不确定的指针
 return head;     //返回创建链表的头指针
}

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


代码如下:

/*
===========================
 功能:输出节点
 返回: void
===========================
*/
void Print(struct student *head)
{
 struct student *p;
 printf ("\nNow , These %d records are:\n", n);
 p = head;
 if(head != NULL)  //只要不是空链表,就输出链表中所有节点
 {
  printf("head is %o\n", head);  //输出头指针指向的地址
  do
  {
   /*
   输出相应的值:当前节点地址、各字段值、当前节点的下一节点地址。
   这样输出便于读者形象看到一个单向链表在计算机中的存储结构,和我们
   设计的图示是一模一样的。
   */
   printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);
   p = p->next;  //移到下一个节点
  }
  while (p != NULL);
 }
}

单向链表的删除图示:
       ---->[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。
      删除指定学号的节点的函数为:


代码如下:

/*
==========================
 功能:删除指定节点
  (此例中是删除指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
struct student *Del (struct student *head, int num)
{
 struct student *p1;  //p1保存当前需要检查的节点的地址
 struct student *p2;  //p2保存当前检查过的节点的地址
 if (head == NULL)  //是空链表(结合图3理解)
 {
  printf ("\nList is null!\n");
  return head;
 }
 //定位要删除的节点
 p1 = head;
 while (p1->num != num && p1->next != NULL) //p1指向的节点不是所要查找的,并且它不是最后一个节点,就继续往下找
 {
  p2 = p1;   //保存当前节点的地址
  p1 = p1->next;  //后移一个节点
 }
 if(p1->num==num)  //找到了。(结合图4、5理解)
 {
  if (p1 == head)  //如果要删除的节点是第一个节点
  {
   head = p1->next; //头指针指向第一个节点的后一个节点,也就是第二个节点。这样第一个节点就不在链表中,即删除
  }
  else   //如果是其它节点,则让原来指向当前节点的指针,指向它的下一个节点,完成删除
  {
   p2->next = p1->next;
  }
  free (p1);  //释放当前节点
  p1 = NULL;
  printf ("\ndelete %ld success!\n", num);
  n -= 1;   //节点总数减1个
 }
 else    //没有找到
 {
  printf ("\n%ld not been found!\n", num);
 }
 return head;
}

单向链表的插入图示:
       ---->[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。
    插入指定节点的后面的函数为:


代码如下:

/*
==========================
 功能:插入指定节点的后面
  (此例中是指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
struct student *Insert (struct student *head, int num, struct student *node)
{
 struct student *p1;  //p1保存当前需要检查的节点的地址
 if (head == NULL)  //(结合图示7理解)
 {
  head = node;
  node->next = NULL;
  n += 1;
  return head;
 }
 p1 = head;
 while(p1->num != num && p1->next != NULL)  //p1指向的节点不是所要查找的,并且它不是最后一个节点,继续往下找
 {
  p1 = p1->next;  //后移一个节点
 }
 if (p1->num==num)  //找到了(结合图示8理解)
 {
  node->next = p1->next; //显然node的下一节点是原p1的next
  p1->next = node;  //插入后,原p1的下一节点就是要插入的node
  n += 1;   //节点总数增加1个
 }
 else
 {
  printf ("\n%ld not been found!\n", num);
 }
 return head;
}

单向链表的反序图示:
       ---->[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。
          反序链表的函数为:


代码如下:

/*
==========================
 功能:反序节点
  (链表的头变成链表的尾,链表的尾变成头)
 返回:指向链表表头的指针
==========================
*/
struct student *Reverse (struct student *head)
{
 struct student *p;  //临时存储
 struct student *p1;  //存储返回结果
 struct student *p2;  //源结果节点一个一个取
 p1 = NULL;   //开始颠倒时,已颠倒的部分为空
 p2 = head;   //p2指向链表的头节点
 while(p2 != NULL)
 {
  p = p2->next;
  p2->next = p1;
  p1 = p2;
  p2 = p;
 }
 head = p1;
 return head;
}

对链表进行选择排序的基本思想就是反复从还未排好序的那些节点中,选出键值(就是用它排序的字段,我们取学号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,然后它变成其尾指针;
        对链表进行选择排序的函数为:


代码如下:

/*
==========================
 功能:选择排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *SelectSort (struct student *head)
{
 struct student *first;    //排列后有序链的表头指针
 struct student *tail;      //排列后有序链的表尾指针
 struct student *p_min;    //保留键值更小的节点的前驱节点的指针
 struct student *min;    //存储最小节点
 struct student *p;     //当前比较的节点
 first = NULL;
 while(head != NULL)    //在链表中找键值最小的节点
 {
  //注意:这里for语句就是体现选择排序思想的地方
  for (p = head, min = head; p->next != NULL; p = p->next) //循环遍历链表中的节点,找出此时最小的节点
  {
   if (p->next->num < min->num)  //找到一个比当前min小的节点
   {
    p_min = p;       //保存找到节点的前驱节点:显然p->next的前驱节点是p
    min = p->next;   //保存键值更小的节点
   }
  }
  //上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表
  //第一件事
  if (first == NULL)    //如果有序链表目前还是一个空链表
  {
   first = min;  //第一次找到键值最小的节点
   tail = min;     //注意:尾指针让它指向最后的一个节点
  }
  else     //有序链表中已经有节点
  {
   tail->next = min;    //把刚找到的最小节点放到最后,即让尾指针的next指向它
   tail = min;        //尾指针也要指向它
  }
  //第二件事
  if (min == head)      //如果找到的最小节点就是第一个节点
  {
   head = head->next;    //显然让head指向原head->next,即第二个节点,就OK
  }
  else   //如果不是第一个节点
  {
   p_min->next = min->next; //前次最小节点的next指向当前min的next,这样就让min离开了原链表
  }
 }
 if (first != NULL)  //循环结束得到有序链表first
 {
  tail->next = NULL; //单向链表的最后一个节点的next应该指向NULL
 }
 head = first;
 return head;
}

对链表进行直接插入排序的基本思想就是假设链表的前面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罢了。
       这一点请读者务必搞清楚,要不然就可能认为它和上面的选择排序法一样了。
       对链表进行直接插入排序的函数为:


代码如下:

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

对链表进行冒泡排序的基本思想就是对当前还未排好序的范围内的全部节点,自上而下对相邻的两个节点依次进行比较和调整,让键值(就是用它排 序的字段,我们取学号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、下面的程序描述改进了一点就是记录了每次最后一次节点下沉的位置,这样我们不必每次都从头到尾的扫描,只需要扫描到记录点为止。 因为后面的都已经是排好序的了。
       对链表进行冒泡排序的函数为:


代码如下:

/*
==========================
 功能:冒泡排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *BubbleSort (struct student *head)
{
 struct student *endpt;   //控制循环比较
 struct student *p;    //临时指针变量
 struct student *p1,*p2;
 p1 = (struct student *) malloc (LEN);
 p1->next = head;     //注意理解:我们增加一个节点,放在第一个节点的前面,主要是为了便于比较。因为第一个节点没有前驱,我们不能交换地址
 head = p1;          //让head指向p1节点,排序完成后,我们再把p1节点释放掉
 for (endpt = NULL; endpt != head; endpt = p) //结合第6点理解
 {
  for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
  {
   if (p1->next->num > p1->next->next->num) //如果前面的节点键值比后面节点的键值大,则交换
   {
    p2 = p1->next->next;   //结合第1点理解
    p1->next->next = p2->next;   //结合第2点理解
    p2->next = p1->next;  //结合第3点理解
    p1->next = p2;   //结合第4点理解
    p = p1->next->next; //结合第6点理解
   }
  }
 }
 p1 = head;       //把p1的信息去掉
 head = head->next;  //让head指向排序后的第一个节点
 free (p1);   //释放p1
 p1 = NULL;   //p1置为NULL,保证不产生“野指针”,即地址不确定的指针变量
 return head;
}

有序链表插入节点示意图:
        ---->[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
       插入有序链表的函数为:


代码如下:

/*
==========================
 功能:插入有序链表的某个节点的后面(从小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *SortInsert (struct student *head, struct student *node)
{
 struct student *p;  //p保存当前需要检查的节点的地址
 struct student *t;  //临时指针变量
 if (head == NULL)  //处理空的有序链表
 {
  head = node;
  node->next = NULL;
  n += 1;   //插入完毕,节点总数加
  return head;
 }
 p = head;     //有序链表不为空
 while(p->num < node->num && p != NULL)    //p指向的节点的学号比插入节点的学号小,并且它不等于NULL
 {
  t = p;     //保存当前节点的前驱,以便后面判断后处理
  p = p->next;  //后移一个节点
 }
 if (p == head)  //刚好插入第一个节点之前
 {
  node->next = p;
  head = node;
 }
 else     //插入其它节点之后
 {
  t->next = node;  //把node节点加进去
  node->next = p;
 }
 n += 1;   //插入完毕,节点总数加1
 return head;
}

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


代码如下:

#include "stdlib.h"
#include "stdio.h"
#define NULL 0
#define LEN sizeof(struct student)
struct student
{
 int num;     //学号
 float score;       //分数,其他信息可以继续在下面增加字段
 struct student *next;  //指向下一节点的指针
};
int n; //节点总数
/*
==========================
功能:创建n个节点的链表
返回:指向链表表头的指针
==========================
*/
struct student *Create()
{
 struct student *head;  //头节点
 struct student *p1 = NULL; //p1保存创建的新节点的地址
 struct student *p2 = NULL; //p2保存原链表最后一个节点的地址
 n = 0;   //创建前链表的节点总数为0:空链表
 p1 = (struct student *) malloc (LEN); //开辟一个新节点
 p2 = p1;   //如果节点开辟成功,则p2先把它的指针保存下来以备后用
 if(p1==NULL)  //节点开辟不成功
 {
  printf ("\nCann't create it, try it again in a moment!\n");
  return NULL;
 }
 else    //节点开辟成功
 {
  head = NULL;  //开始head指向NULL
  printf ("Please input %d node -- num,score: ", n + 1);
  scanf ("%d %f", &(p1->num), &(p1->score)); //录入数据
 }
 while(p1->num != 0)  //只要学号不为0,就继续录入下一个节点
 {
  n += 1;   //节点总数增加1个
  if(n == 1)  //如果节点总数是1,则head指向刚创建的节点p1
  {
   head = p1;
   p2->next = NULL;  //此时的p2就是p1,也就是p1->next指向NULL。
  }
  else
  {
   p2->next = p1; //指向上次下面刚刚开辟的新节点
  }
  p2 = p1;   //把p1的地址给p2保留,然后p1产生新的节点
  p1 = (struct student *) malloc (LEN);
  printf ("Please input %d node -- num,score: ", n + 1);
  scanf ("%d %f", &(p1->num), &(p1->score));
 }
 p2->next = NULL;  //此句就是根据单向链表的最后一个节点要指向NULL
 free(p1);   //p1->num为0的时候跳出了while循环,并且释放p1
 p1 = NULL;   //特别不要忘记把释放的变量清空置为NULL,否则就变成"野指针",即地址不确定的指针
 return head;     //返回创建链表的头指针
}
/*
===========================
 功能:输出节点
 返回: void
===========================
*/
void Print(struct student *head)
{
 struct student *p;
 printf ("\nNow , These %d records are:\n", n);
 p = head;
 if(head != NULL)  //只要不是空链表,就输出链表中所有节点
 {
  printf("head is %o\n", head);  //输出头指针指向的地址
  do
  {
   /*
   输出相应的值:当前节点地址、各字段值、当前节点的下一节点地址。
   这样输出便于读者形象看到一个单向链表在计算机中的存储结构,和我们
   设计的图示是一模一样的。
   */
   printf ("%o   %d   %5.1f   %o\n", p, p->num, p->score, p->next);
   p = p->next;  //移到下一个节点
  }
  while (p != NULL);
 }
}
/*
==========================
 功能:删除指定节点
  (此例中是删除指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
struct student *Del (struct student *head, int num)
{
 struct student *p1;  //p1保存当前需要检查的节点的地址
 struct student *p2;  //p2保存当前检查过的节点的地址
 if (head == NULL)  //是空链表(结合图3理解)
 {
  printf ("\nList is null!\n");
  return head;
 }
 //定位要删除的节点
 p1 = head;
 while (p1->num != num && p1->next != NULL) //p1指向的节点不是所要查找的,并且它不是最后一个节点,就继续往下找
 {
  p2 = p1;   //保存当前节点的地址
  p1 = p1->next;  //后移一个节点
 }
 if(p1->num==num)  //找到了。(结合图4、5理解)
 {
  if (p1 == head)  //如果要删除的节点是第一个节点
  {
   head = p1->next; //头指针指向第一个节点的后一个节点,也就是第二个节点。这样第一个节点就不在链表中,即删除
  }
  else   //如果是其它节点,则让原来指向当前节点的指针,指向它的下一个节点,完成删除
  {
   p2->next = p1->next;
  }
  free (p1);  //释放当前节点
  p1 = NULL;
  printf ("\ndelete %ld success!\n", num);
  n -= 1;   //节点总数减1个
 }
 else    //没有找到
 {
  printf ("\n%ld not been found!\n", num);
 }
 return head;
}
//销毁链表
void DestroyList(struct student *head)
{
 struct student *p;
 if(head==NULL)
  return 0;
 while(head)
 {
  p=head->next;
  free(head);
  head=p;
 }
 return 1;
}
/*
==========================
 功能:插入指定节点的后面
  (此例中是指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
struct student *Insert (struct student *head, int num, struct student *node)
{
 struct student *p1;  //p1保存当前需要检查的节点的地址
 if (head == NULL)  //(结合图示7理解)
 {
  head = node;
  node->next = NULL;
  n += 1;
  return head;
 }
 p1 = head;
 while(p1->num != num && p1->next != NULL)  //p1指向的节点不是所要查找的,并且它不是最后一个节点,继续往下找
 {
  p1 = p1->next;  //后移一个节点
 }
 if (p1->num==num)  //找到了(结合图示8理解)
 {
  node->next = p1->next; //显然node的下一节点是原p1的next
  p1->next = node;  //插入后,原p1的下一节点就是要插入的node
  n += 1;   //节点总数增加1个
 }
 else
 {
  printf ("\n%ld not been found!\n", num);
 }
 return head;
}
/*
==========================
 功能:反序节点
  (链表的头变成链表的尾,链表的尾变成头)
 返回:指向链表表头的指针
==========================
*/
struct student *Reverse (struct student *head)
{
 struct student *p;  //临时存储
 struct student *p1;  //存储返回结果
 struct student *p2;  //源结果节点一个一个取
 p1 = NULL;   //开始颠倒时,已颠倒的部分为空
 p2 = head;   //p2指向链表的头节点
 while(p2 != NULL)
 {
  p = p2->next;
  p2->next = p1;
  p1 = p2;
  p2 = p;
 }
 head = p1;
 return head;
}
/*
==========================
 功能:选择排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *SelectSort (struct student *head)
{
 struct student *first;    //排列后有序链的表头指针
 struct student *tail;      //排列后有序链的表尾指针
 struct student *p_min;    //保留键值更小的节点的前驱节点的指针
 struct student *min;    //存储最小节点
 struct student *p;     //当前比较的节点
 first = NULL;
 while(head != NULL)    //在链表中找键值最小的节点
 {
  //注意:这里for语句就是体现选择排序思想的地方
  for (p = head, min = head; p->next != NULL; p = p->next) //循环遍历链表中的节点,找出此时最小的节点
  {
   if (p->next->num < min->num)  //找到一个比当前min小的节点
   {
    p_min = p;       //保存找到节点的前驱节点:显然p->next的前驱节点是p
    min = p->next;   //保存键值更小的节点
   }
  }
  //上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表
  //第一件事
  if (first == NULL)    //如果有序链表目前还是一个空链表
  {
   first = min;  //第一次找到键值最小的节点
   tail = min;     //注意:尾指针让它指向最后的一个节点
  }
  else     //有序链表中已经有节点
  {
   tail->next = min;    //把刚找到的最小节点放到最后,即让尾指针的next指向它
   tail = min;        //尾指针也要指向它
  }
  //第二件事
  if (min == head)      //如果找到的最小节点就是第一个节点
  {
   head = head->next;    //显然让head指向原head->next,即第二个节点,就OK
  }
  else   //如果不是第一个节点
  {
   p_min->next = min->next; //前次最小节点的next指向当前min的next,这样就让min离开了原链表
  }
 }
 if (first != NULL)  //循环结束得到有序链表first
 {
  tail->next = NULL; //单向链表的最后一个节点的next应该指向NULL
 }
 head = first;
 return head;
}
/*
==========================
 功能:直接插入排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *InsertSort (struct student *head)
{
 struct student *first;   //为原链表剩下用于直接插入排序的节点头指针
 struct student *t;    //临时指针变量:插入节点
 struct student *p,*q;     //临时指针变量
 first = head->next;  //原链表剩下用于直接插入排序的节点链表:可根据图12来理解
 head->next = NULL;  //只含有一个节点的链表的有序链表:可根据图11来理解
 while(first != NULL)  //遍历剩下无序的链表
 {
  //注意:这里for语句就是体现直接插入排序思想的地方
  for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next); //无序节点在有序链表中找插入的位置
  //退出for循环,就是找到了插入的位置,应该将t节点插入到p节点之后,q节点之前
  //注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能。原因:你若理解了上面的第3条,就知道了
  //下面的插入就是将t节点即是first节点插入到p节点之后,已经改变了first节点,所以first节点应该在被修改之前往后移动,不能放到下面注释的位置上去
  first = first->next; //无序链表中的节点离开,以便它插入到有序链表中
  if (q == head)  //插在第一个节点之前
  {
   head = t;
  }
  else   //p是q的前驱
  {
   p->next = t;
  }
  t->next = q;  //完成插入动作
  //first = first->next;
 }
 return head;
}
/*
==========================
 功能:冒泡排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *BubbleSort (struct student *head)
{
 struct student *endpt;   //控制循环比较
 struct student *p;    //临时指针变量
 struct student *p1,*p2;
 p1 = (struct student *) malloc (LEN);
 p1->next = head;     //注意理解:我们增加一个节点,放在第一个节点的前面,主要是为了便于比较。因为第一个节点没有前驱,我们不能交换地址
 head = p1;          //让head指向p1节点,排序完成后,我们再把p1节点释放掉
 for (endpt = NULL; endpt != head; endpt = p) //结合第6点理解
 {
  for (p = p1 = head; p1->next->next != endpt; p1 = p1->next)
  {
   if (p1->next->num > p1->next->next->num) //如果前面的节点键值比后面节点的键值大,则交换
   {
    p2 = p1->next->next;   //结合第1点理解
    p1->next->next = p2->next;   //结合第2点理解
    p2->next = p1->next;  //结合第3点理解
    p1->next = p2;   //结合第4点理解
    p = p1->next->next; //结合第6点理解
   }
  }
 }
 p1 = head;       //把p1的信息去掉
 head = head->next;  //让head指向排序后的第一个节点
 free (p1);   //释放p1
 p1 = NULL;   //p1置为NULL,保证不产生“野指针”,即地址不确定的指针变量
 return head;
}
/*
==========================
 功能:插入有序链表的某个节点的后面(从小到大)
 返回:指向链表表头的指针
==========================
*/
struct student *SortInsert (struct student *head, struct student *node)
{
 struct student *p;  //p保存当前需要检查的节点的地址
 struct student *t;  //临时指针变量
 if (head == NULL)  //处理空的有序链表
 {
  head = node;
  node->next = NULL;
  n += 1;   //插入完毕,节点总数加
  return head;
 }
 p = head;     //有序链表不为空
 while(p->num < node->num && p != NULL)    //p指向的节点的学号比插入节点的学号小,并且它不等于NULL
 {
  t = p;     //保存当前节点的前驱,以便后面判断后处理
  p = p->next;  //后移一个节点
 }
 if (p == head)  //刚好插入第一个节点之前
 {
  node->next = p;
  head = node;
 }
 else     //插入其它节点之后
 {
  t->next = node;  //把node节点加进去
  node->next = p;
 }
 n += 1;   //插入完毕,节点总数加1
 return head;
}
/*
以上函数的测试程序:
提示:根据测试函数的不同注释相应的程序段,这也是一种测试方法。
*/
int main(void)
{
 struct student *head;
 struct student *stu;
 int thenumber;
 // 测试Create()、Print()
 head = Create();
 Print(head);
 //测试Del()
 printf("\nWhich one delete: ");
 scanf("%d",&thenumber);
 head = Del(head,thenumber);
 Print(head);
 //测试Insert()
 stu = (struct student *)malloc(LEN);
 printf("\nPlease input insert node -- num,score: ");
 scanf("%d %f",&stu->num,&stu->score);
 printf("\nInsert behind num: ");
 scanf("%d",&thenumber);
 head = Insert(head,thenumber,stu);
 Print(head);
 //测试Reverse()
 printf("\nReverse the LinkList: \n");
 head = Reverse(head);
 Print(head);
 //测试SelectSort()
 printf("\nSelectSort the LinkList: \n");
 head = SelectSort(head);
 Print(head);
 //测试InsertSort()
 printf("\nInsertSort the LinkList: \n");
 head = InsertSort(head);
 Print(head);
 //测试BubbleSort()
 printf("\nBubbleSort the LinkList: \n");
 head = BubbleSort(head);
 Print(head);
 printf("\nSortInsert the LinkList: \n");
 //测试SortInsert():上面创建链表,输入节点时请注意学号num从小到大的顺序
 stu = (struct student *)malloc(LEN);
 printf("\nPlease input insert node -- num,score: ");
 scanf("%d %f",&stu->num,&stu->score);
 head = SortInsert(head,stu);
 Print(head);
 //销毁链表
 DestroyList(head);
 printf ("\n");
 system ("pause");
}

(0)

相关推荐

  • javascript写的一个链表实现代码

    本来要用Array来保存数据的,没试过用JS来数据结构,就用JS来试试吧. JS效率真的很低一个链表装1000个对象浏览器就提示运行缓慢了. 之前觉得AJAX3D挺用前景的,现在看来还没有流行就要夭折了.用delphi开发的游戏人们都觉得太慢了,何况用JS. 下面是我实现的一个链表: 复制代码 代码如下: /*@author eric *@mail shmilyhe@163.com *blog.csdn.net/shmilyhe */ <script> function Student(no,

  • js链表操作(实例讲解)

    如下所示: <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script> function Node(v){ this.value=v; this.next=null; } function ArrayList(){ this.head=new Node(

  • js单向链表的具体实现实例

    复制代码 代码如下: function linkNode(_key, _value){    /// <summary>    /// 链表类的节点类    /// </summary>    this.Key = _key;    this.Value = _value;    this.next = null;}function Link(){    /// <summary>    /// 创建一个链表类    /// </summary>    th

  • JavaScript实现的链表数据结构实例

    此例是javascript来建立链表.. 并对此进行了排序.. 还可以在GenericList一般链表上进行扩展. 实现各种排序及增,删,改结点.. 复制代码 代码如下: function Node(){   this.data=null;   this.next=null; } function GenericList(){   this.head=null;   this.current=null;   //打出所有的链表结点   this.print= function(){   this

  • JavaScript数据结构链表知识详解

    最近在看<javascript数据结构和算法>这本书,补一下数据结构和算法部分的知识,觉得自己这块是短板. 链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中不是连续放置的.每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成. 好处:可以添加或移除任意项,它会按需扩容,且不需要移动其他元素. 与数组的区别: 数组:可以直接访问任何位置的任何元素: 链表:想要访问链表中的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元素. 做点小笔记. funct

  • 深入理解链表的各类操作详解

    链表概述链表是一种常见的重要的数据结构.它是动态地进行存储分配的一种结构.它可以根据需要开辟内存单元.链表有一个"头指针"变量,以head表示,它存放一个地址.该地址指向一个元素.链表中每一个元素称为"结点",每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址.因此,head指向第一个元素:第一个元素又指向第二个元素:--,直到最后一个元素,该元素不再指向其它元素,它称为"表尾",它的地址部分放一个"NULL&qu

  • MySQL数据库基础学习之JSON函数各类操作详解

    目录 前言 一.JSON语法规则 二.JSON函数 1.JSON_CONTAINS(json_doc,value)函数 2.JSON_SEARCH()函数 3.JSON_PRETTY(json_doc)函数 4.JSON_DEPTH(json_doc)函数 5.JSON_LENGTH(json_doc[,path])函数 6.JSON_KEYS(json_doc[,path])函数 7. JSON_INSERT(json_doc,path,val[,path,val] ...)函数 8.JSON

  • C语言数据结构之单链表操作详解

    目录 1.插入操作 2.删除操作 3.查找操作 4.修改操作 5.完整代码 1.插入操作 (1)创建一个新的要插入的结点 (2)将新结点的 next 指针指向插入位置后的结点 (3)将插入位置前的节点指针 next 指向新的结点 注意:步骤(2)(3)的顺序不能颠倒,否则会导致插入位置后的部分链表丢失. 插入位置一共分三种,分别是头部插入.中间插入和尾部插入. 如图: 代码: link* insertElem(link* p,int elem,int pos){ link* temp = p;/

  • Java数据结构之环形链表和约瑟夫问题详解

    目录 一.环形链表 1.创建结点 2.添加小结点 3.显示循环链表 二.约瑟夫问题 1.问题描述 2.首先确定圈大小及开始位置 3.出圈操作 4.出圈方法完整代码 总结 一.环形链表 1.创建结点 环形链表其实也很好理解,就是将单链表的头和尾连接起来,就形成了环形链表. public class Node { public int data; public Node next; public Node(int data) { this.data = data; } @Override publi

  • django 常用orm操作详解

    Django流程: 1 创建Django项目 : django-admin startproject projectname 2 创建应用: : python manage.py startapp appname 3 在控制器(urls.py)创建 url 与 视图函数的映射关系(一一对应) 4 创建视图函数,完成逻辑代码 5 从数据库取出集合对象 5 把数据库变量嵌入到模板进行渲染(render方法) 6 将渲染后的html页面返回给客户端 URL:协议+域名+端口+路径 协议:http 域名

  • JS Attribute属性操作详解

    Attribute是属性的意思,文章仅对部分兼容IE和FF的Attribute相关的介绍. attributes:获取一个属性作为对象 getAttribute:获取某一个属性的值 setAttribute:建立一个属性,并同时给属性捆绑一个值 createAttribute:仅建立一个属性 removeAttribute:删除一个属性 getAttributeNode:获取一个节点作为对象 setAttributeNode:建立一个节点 removeAttributeNode:删除一个节点 a

  • C语言数据结构 链表与归并排序实例详解

    C语言数据结构 链表与归并排序实例详解 归并排序适合于对链表进行原址排序,即只改变指针的连接方式,不交换链表结点的内容. 归并排序的基本思想是分治法:先把一个链表分割成只有一个节点的链表,然后按照一定顺序.自底向上合并相邻的两个链表. 只要保证各种大小的子链表是有序的,那么最后返回的链表就一定是有序的. 归并排序分为分割和合并两个子过程.分割是用递归的方法,把链表对半分割成两个子链表:合并是在递归返回(回朔)的时候,把两个有序链表合并成一个有序链表. (注意:只有一个节点的链表一定是有序的) 这

  • C语言之复杂链表的复制方法(图示详解)

    什么是复杂链表? 复杂链表指的是一个链表有若干个结点,每个结点有一个数据域用于存放数据,还有两个指针域,其中一个指向下一个节点,还有一个随机指向当前复杂链表中的任意一个节点或者是一个空结点.今天我们要实现的就是对这样一个复杂链表复制产生一个新的复杂链表. 复杂链表的数据结构如下: typedef int DataType; //数据域的类型 //复杂链表的数据结构 typedef struct ComplexNode { DataType _data ; // 数据 struct Complex

  • 基于Python对数据shape的常见操作详解

    这一阵在用python做DRL建模的时候,尤其是在配合使用tensorflow的时候,加上tensorflow是先搭框架再跑数据,所以调试起来很不方便,经常遇到输入数据或者中间数据shape的类型不统一,导致一些op老是报错.而且由于水平菜,所以一些常用的数据shape转换操作也经常百度了还是忘,所以想再整理一下. 一.数据的基本属性 求一组数据的长度 a = [1,2,3,4,5,6,7,8,9,10,11,12] print(len(a)) print(np.size(a)) 求一组数据的s

  • Django多层嵌套ManyToMany字段ORM操作详解

    在用django写项目时,遇到了许多场景,关于ORM操作获取数据的,但是不好描述出来,百度搜索关键词都不知道该怎么搜,导致一个人鼓捣了好久.这里细化下问题,还原场景,记录踩下的坑 首先先列举model,我举些生活中的例子,更方便理解问题 # 习题 class Problem(models.Model): desc = models.CharField() answer = models.TextField() is_pass = models.BooleanField(default=False

随机推荐