链表各个操作

【教程4>第5章>第19节】基于Gardner环的位同步系统MATLAB仿真与带时偏基带数据导出 由于在FPGA中,时偏的影响主要是由系统收发两端晶振的不同步带来的,所以在仿真测试阶段,很难直接通过FPGA来模拟这个因素,所以我们采用MATLAB模拟带时偏的基带输入信号,并将其导出,由FPGA调用作为测试信号源。可以看到,同步之后(图3)相对于带时偏的基带信号(图2),其更接近标准的数据(图1),且抖动较小。订阅过的朋友可以复习下这个课程,没有订阅的朋友,也可以直接复制如下的程序进行仿真测试。上述程序中,加入时偏的基带信号,通过子函数。同步之后,可以恢复出标准的QPSK星座图。 阅读详情
  链表概述
   链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
        链表的各类操作包括:学习单向链表的创建、删除、  插入(无序、有序)、输出、  排序(选择、插入、冒泡)、反序等等。

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

      图1:空链表

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

      图2:有N个节点的链表

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

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

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

  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. }  
/*
===========================
 功能:输出节点
 返回: 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。

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

  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.     //定位要删除的节点   
  19.     p1 = head;  
  20.     while (p1->num != num && p1->next != NULL)    //p1指向的节点不是所要查找的,并且它不是最后一个节点,就继续往下找   
  21.     {  
  22.         p2 = p1;            //保存当前节点的地址   
  23.         p1 = p1->next;       //后移一个节点   
  24.     }  
  25.   
  26.     if(p1->num==num)     //找到了。(结合图4、5理解)   
  27.     {  
  28.         if (p1 == head)     //如果要删除的节点是第一个节点   
  29.         {  
  30.             head = p1->next; //头指针指向第一个节点的后一个节点,也就是第二个节点。这样第一个节点就不在链表中,即删除   
  31.         }  
  32.         else            //如果是其它节点,则让原来指向当前节点的指针,指向它的下一个节点,完成删除   
  33.         {  
  34.             p2->next = p1->next;  
  35.         }  
  36.   
  37.         free (p1);      //释放当前节点   
  38.         p1 = NULL;  
  39.         printf ("\ndelete %ld success!\n", num);  
  40.         n -= 1;         //节点总数减1个   
  41.     }  
  42.     else                //没有找到   
  43.     {  
  44.         printf ("\n%ld not been found!\n", num);  
  45.     }  
  46.   
  47.     return head;  
  48. }  
/*
==========================
 功能:删除指定节点
  (此例中是删除指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
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。

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

  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.   
  19.     p1 = head;  
  20.     while(p1->num != num && p1->next != NULL)  //p1指向的节点不是所要查找的,并且它不是最后一个节点,继续往下找   
  21.     {  
  22.         p1 = p1->next;       //后移一个节点   
  23.     }  
  24.   
  25.     if (p1->num==num)        //找到了(结合图示8理解)   
  26.     {  
  27.         node->next = p1->next;    //显然node的下一节点是原p1的next   
  28.         p1->next = node;     //插入后,原p1的下一节点就是要插入的node   
  29.         n += 1;         //节点总数增加1个   
  30.     }  
  31.     else  
  32.     {  
  33.         printf ("\n%ld not been found!\n", num);  
  34.     }  
  35.     return head;  
  36. }  
/*
==========================
 功能:插入指定节点的后面
  (此例中是指定学号的节点)
 返回:指向链表表头的指针
==========================
*/
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。

          反序链表的函数为:

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

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,然后它变成其尾指针;

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

  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.   
  15.     first = NULL;  
  16.     while(head != NULL)       //在链表中找键值最小的节点   
  17.     {  
  18.         //注意:这里for语句就是体现选择排序思想的地方   
  19.         for (p = head, min = head; p->next != NULL; p = p->next)  //循环遍历链表中的节点,找出此时最小的节点   
  20.         {  
  21.             if (p->next->num < min->num)     //找到一个比当前min小的节点   
  22.             {  
  23.                 p_min = p;        //保存找到节点的前驱节点:显然p->next的前驱节点是p   
  24.                 min = p->next;     //保存键值更小的节点   
  25.             }  
  26.         }  
  27.   
  28.         //上面for语句结束后,就要做两件事;一是把它放入有序链表中;二是根据相应的条件判断,安排它离开原来的链表   
  29.   
  30.         //第一件事   
  31.         if (first == NULL)     //如果有序链表目前还是一个空链表   
  32.         {  
  33.             first = min;        //第一次找到键值最小的节点   
  34.             tail = min;        //注意:尾指针让它指向最后的一个节点   
  35.         }  
  36.         else              //有序链表中已经有节点   
  37.         {  
  38.             tail->next = min;    //把刚找到的最小节点放到最后,即让尾指针的next指向它   
  39.             tail = min;           //尾指针也要指向它   
  40.         }  
  41.   
  42.         //第二件事   
  43.         if (min == head)            //如果找到的最小节点就是第一个节点   
  44.         {  
  45.             head = head->next;      //显然让head指向原head->next,即第二个节点,就OK   
  46.         }  
  47.         else            //如果不是第一个节点   
  48.         {  
  49.             p_min->next = min->next;  //前次最小节点的next指向当前min的next,这样就让min离开了原链表   
  50.         }  
  51.     }  
  52.   
  53.     if (first != NULL)      //循环结束得到有序链表first   
  54.     {  
  55.         tail->next = NULL;   //单向链表的最后一个节点的next应该指向NULL   
  56.     }  
  57.     head = first;  
  58.     return head;  
  59. }  
/*
==========================
 功能:选择排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
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罢了。
       这一点请读者务必搞清楚,要不然就可能认为它和上面的选择排序法一样了。

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

  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.   
  13.     first = head->next;      //原链表剩下用于直接插入排序的节点链表:可根据图12来理解   
  14.     head->next = NULL;       //只含有一个节点的链表的有序链表:可根据图11来理解   
  15.   
  16.     while(first != NULL)        //遍历剩下无序的链表   
  17.     {  
  18.         //注意:这里for语句就是体现直接插入排序思想的地方   
  19.         for (t = first, q = head; ((q != NULL) && (q->num < t->num)); p = q, q = q->next);  //无序节点在有序链表中找插入的位置   
  20.   
  21.         //退出for循环,就是找到了插入的位置,应该将t节点插入到p节点之后,q节点之前   
  22.         //注意:按道理来说,这句话可以放到下面注释了的那个位置也应该对的,但是就是不能。原因:你若理解了上面的第3条,就知道了   
  23.         //下面的插入就是将t节点即是first节点插入到p节点之后,已经改变了first节点,所以first节点应该在被修改之前往后移动,不能放到下面注释的位置上去   
  24.         first = first->next; //无序链表中的节点离开,以便它插入到有序链表中   
  25.   
  26.         if (q == head)      //插在第一个节点之前   
  27.         {  
  28.             head = t;  
  29.         }  
  30.         else            //p是q的前驱   
  31.         {  
  32.             p->next = t;  
  33.         }  
  34.         t->next = q;     //完成插入动作   
  35.         //first = first->next;    
  36.     }  
  37.     return head;  
  38. }  
/*
==========================
 功能:直接插入排序(由小到大)
 返回:指向链表表头的指针
==========================
*/
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、下面的程序描述改进了一点就是记录了每次最后一次节点下沉的位置,这样我们不必每次都从头到尾的扫描,只需要扫描到记录点为止。 因为后面的都已经是排好序的了。

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

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

       插入有序链表的函数为:

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

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;
}

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

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

相关推荐

记一次wireshark抓取QQ好友IP和火绒抓取微信IP

事情是这样的,,上个周,手机离奇的被盗,但没有关机,为了找回手机,粗学了一手QQ抓ip的手段。 这里我就直接做个演示,解析关键的点。 三种思路i. wireshark 抓取手机在线IPii 火绒抓取 微信在线接听IPiii 火绒抓取 微信在线不接听IP i. wireshark 抓取手机在线IP wireshark抓好友QQ的ip要求和对方打语音的时候要手机在线。 这里说一下,打语音和视频是直接能和对方建立连接,如果发文件,是直接发给腾讯的,抓不到包。 1.打开wireshark,通过电脑Tim给对方打语音

qq_29437513的博客 1万+

数据结构-----链表

一、链表 概念:线性表的链式存储结构,我们叫做链表----单向链表 带头结点的单向链表 [1]头结点:数据域默认不使用,指针域保存第一个有效数据结点的地址。 [2]尾结点:指针域默认为NULL,作为链表结束的标志。 二、数据类型的设计 typdef int DataType; //数据域存储数据的类型 typedef struct node { DataType data;

hello_world的博客 379

计算机视觉——SIFT特征匹配

文章目录一、SIFT特征检测原理1.1基本思想1.2二、SIFT特征检测图片兴趣点三、SIFT特征检测描述子匹配四、输入图片检测与之 一、SIFT特征检测原理 1.1基本思想 1.2 二、SIFT特征检测图片兴趣点 代码: 检测结果: 三、SIFT特征检测描述子匹配 四、输入图片检测与之 ...

weixin_43869874的博客 3853

指针与链表(二)

1.链表的组成:由一系列结点组成。结点(包括数据域和指针域) 例如:将一个结点设置为一个结构体 typedef struct student { char name; int score; struct student * next; }node; 2.链表分为两种:1)带头结点的链表; 2)不带头结点的链表; (通常为带头结点的链表)所以链表的第一个结点只有指针域储存第二个...

qq_43310396的博客 205

MS17-010(Eternal blue永恒之蓝)漏洞利用+修复方法

msf永恒之蓝漏洞复现过程以及修复建议

记录学习 8506

用AI科研绘图全新升级!借助谷歌新发布模型Nano Banana pro实测一键绘制机制图与技术路线图,直接告别文字乱码与低画质

之前有同仁反馈,用Nano Banana画科研插图的时候,要么文字部分容易乱码,要么抓不住关键内容,画出来的效果反复修改后,还是达不到理想的效果。终于在Gemini 3发布没几天,万众期待的大香蕉模型也成功升级到了Pro版本,官方名称叫“Gemini 3 Pro Image”,实际上也就是我们说的Nano Banana 2。这次的Nano Banana Pro模型,不仅可以直出4K高清图,还能自定义比例,推理能力和中文文字的稳定性也得到了提升。

智能AI让学术科研更容易 1887

单向链表的各个操作

/***************************************************** copyright (C), 2016-2017, Lighting Studio. Co., Ltd. File name: Author:王凡 Version:0.1 Date: Description: Funcion List: **************

luoyeyeye的博客 242

基于STM32、HAL库的TLV320AIC3204IRHBR音频接口芯片驱动程序设计

* 开启所有数字部分 */#define TLV320AIC3204_REG_LEFT_HEADPHONE_OUT_VOLUME 0x0C /* 左声道耳机输出音量 */#define TLV320AIC3204_REG_DIGITAL_AUDIO_PATH_CONTROL 0x05 /* 数字音频路径控制 */#define TLV320AIC3204_REG_ANALOG_HP_FILTER_CONTROL 0x34 /* 模拟耳机滤波器控制 *//* 最大音量 */

corlin6688的博客 520

C语言实现队列、栈以及单链表各个操作

实现了队列和栈的插入删除,实现了链表的一系列简单操作

i_forever_的博客 263

Amber&pdb4amber | 准备PDB文件

PDB文件中唯一需要或有用的数据来设置AMBER模拟是:原子名称,残基名称,也许还有链标识符(如果存在多条链),以及重原子的坐标。除非加载额外的文库,否则非蛋白质结构(特别是低分子量配体)会引起问题;如果它们在PDB文件中的名称对应于AMBER库中的内部名称,则通常会识别水和单原子离子。–dry选项移除结构中的结晶水和–reduce选项在最理想的位置添加氢原子.为tleap程序调整从外部获得的PDB文件。

ASKCOS博客 896

从 CDS 接口到 ABAP 运行时,深入理解 SAP RAP Custom Entity

CDS Custom Entity 是一种非 SQL、瞬态的 CDS 实体,不依赖 HANA SQL 读取数据,而是通过 ABAP 类实现查询逻辑(如 RFC、OData、BAPI 等)。它在 CDS 层定义数据契约(字段、键、关联、注解),由 @ObjectModel.query.implementedBy 指向实现 IF_RAP_QUERY_PROVIDER~SELECT 的类,真正执行数据获取。适用于无法用 SQL 表达的数据源,如远程系统或 API。其核心是“接口 + 实现”模式,运行时由 RAP

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 169

C语言实现单链表的基本操作(各个功能:初始化,插入,删除,遍历,查询等)

最近在看数据结构,看完单链表之后去网上想找个完整的代码实现一下,结果找了好几个,都有错误,修改运行之后还是感觉不完美,我就想自己写一下,实现各个功能,大一学的C语言,就想用C语言写出来,然后又去看C语言,复习了struct,typedef和指针(可以说是复习了C的半边天吧,尤其是指针),磕磕绊绊的写完了,中途遇到问题也参考了一下网上的代码,现在我把代码贴出,初学者还是可以借鉴一下的,大神就别看了,...

waly_tong的博客 3万+

基于 Qt C++ 的 船舶导航系统 UI

你想实现一个基于 **Qt C++** 的 **船舶导航系统 UI**,核心功能包括 **Qt Quick** 电子海图显示和 **NMEA 0183** 协议解析。"断开" : "连接"创建一个 Qt Quick 项目,选择 **Qt 6** 版本,确保勾选 **Qt Quick**、**Qt Network** 和 **Qt SQL** 模块。Label { text: "纬度:" }3. **连接设备**:点击 "连接" 按钮连接到 GPS 设备或模拟数据源。title: "连接设置"

寒寒豆豆软件公司的博客 127

第1关:单循环链表的实现—链表的添加、遍历任务描述相关知识单循环链表添加操作遍历循环链表编程要求测试说明任务描述在操作单链表时,

在操作单链表时,我们有时希望从单链表中的任一结点出发都能遍历整个链表,但对于单链表来说,只有从头结点开始才能扫描表中的全部结点。因此我们需要改动链表,使其首尾相接,这样就能满足我们的需求。单循环链表的添加操作与普通的单链表操作类似,只需添加时注意处理尾结点,使其指向头结点。改为指向表头结点或开始结点,就得到单链形式的循环链表,称为单循环链表。由于有头结点,所以空表和非空表的处理方式是一样的。本关任务:完成带头结点的单循环链表的添加功能,遍历链表并输出。这里采用的是尾插法,即在链表的末尾添加结点。

qq_64001869的博客 3379

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

c++链表获取长度,链表翻转,查找链表倒数第K个节点以及中间点

c++链表获取长度,链表翻转, 查找链表倒数第K个节点以及中间点 测试数据 ListNode* pHead = new ListNode(); pHead->m_key = 1; ListNode* pNode = pHead; for (int i = 2; i <= 5; i++) { ListNode* pNew = new ListNode(); pNew->m_key = i; pNew->pNext = nullptr; pNode->pNe

Tang_AHMET的博客 6362

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

链表操作

#include <stdio.h> #include <stdlib.h> #include <stdbool.h> typedef struct node { int data; struct node * pNext; }NODE, *PNODE; PNODE createList(void); void tra...

weixin_30549657的博客 55

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

LeetCode 707. 设计链表 【知识点:链表操作集】

链表操作集

鸿雁丨红豆灬自学计算机学习记录 212

链表篇(二)——链表的基础操作

链表篇(二)——链表的基础操作链表篇(二)设计链表题解代码:感想 链表篇(二) 设计链表 本题涉及到链表的基本操作: 链表节点值的获取,链表的插入(分为链表在表头插入和在表尾插入和指定位置插入) 题解 第一个要求——get()函数中,目的是获取链表中index个节点的值,先开始非常不理解这个while循环的工作原理,在文献查询之后,我明白了—— 在链表中,index可视作一个计数器,告诉链表还有几次循环可以返回目标值,所以在index = 0 时跳出循环,cur不断向后迭代直到index = 0,完成查

Luciferzcy的博客 382

单链表的基本操作

#include<bits×dc++.h> using namespace std; //创建结点 typedef struct node { int age; char name[20]; struct node *next; }Student; //创建链表 Student * creatList(int n) { Student * head = new Student; Student * pre = head; for(int i = 0;i < n;i++) { printf

m0_52729386的博客 191

java链表赋值给另一个链表_java实现双向链表

一 前言之前知识知识追寻者写了一篇单链表的实现,感觉不是很满意,写的逻辑不够清晰,有些地方实现的不过好,不能连成一个整体,伪单链表;为此研究了一会双向链表的简单实现;本篇的实现方式是以方法的形式展现,读者可以将其整合为一个类;二 双向链表简介双向链表的定义是,一个节点有两个方向,分别储存当前节点的前驱节点,和后续节点;双向链表的删除只需要指定前驱节点,或者后续节点就可以进行删除操作;但是缺点也很明...

weixin_39805087的博客 444

c语言单向动态链表,C语言 单向 动态链表的增 删 改 查操作, 非常易懂!

1 //全局结构体 struct2 typedef structStu {3 union {4 intnum;5 char name[10];6 }Stu;7 intnum;8 intage;9 char name[10];10 struct Stu*Next;11 }Stus;1213 //定义个人用户方法操作14 typedef ...

weixin_42143092的博客 212

链表

链表 动态化创建一个链表:动态内存申请+模块化设计 1创建链表(创建一个表头表示整个链表) #include<stdio.h> #include <stdlib.h> struct Node { int data; //数据域 struct Node* next; //指针域 }; struct Node* creatList() { struct Node*headNode=(struct Node*)malloc( sizeof (struct N) );

我渊啊我渊啊的博客 137

动态链表/静态链表

链表也是一类老生常谈的问题,以前也学习过,没什么比较新鲜的东西,所以这里提及一下静态链表和动态链表的概念区分; 所谓动态链表就是使用人工操作方式使得一个个Node节点指向下一个节点,其每个节点中的连接方式使用的是指针连接; 而静态链表使用的方式是构造一个数组,其数组元素就是一个个节点,节点的前驱后继关系使用索引的方式。 构造方式大致如下: struct Node{ typenam...

InNoVaion_yu的博客 296

单链表操作元素为整型的实现增加,删除,查找,修改。

单链表操作元素为整型,实现 增加,删除,查找,修改。 源代码如下: /* * author:xwq *date:2021/9/28 10:00 * description:实现单链表的初始化,增删查改 * 首节点为 0,首元节点(即一个元素的节点)为 1 */ #include<stdio.h> #include<string> #include <malloc.h> #pragma warning(disable:4996) typedef struct LNod

weixin_43614384的博客 382

单链表的增、删、改、查操作

定义一个接口,里面包含增、删、改、查等操作 public interface List<E> { void addHead(E value); void addTail(E value); void removeHead(); void removeTail(); void removeValue(E value); void change(E srcValue, E aimValue); boolean cont...

Angety 的博客 452

链表各类操作详解(讲得非常详细)

原文链接:http://blog.youkuaiyun.com/hackbuteer1/article/details/6591486/    链表概述    链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的

时光、疏离了记忆づ童话的博客 1417
上一篇: 架构师
下一篇: 各种排序算法
下雨不打伞126
博客等级 码龄13年 4粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值