C语言教材的有序单链表程序的插入我并不满意,因为链表为空,表尾等原因导致有四种情况要处理,给同学们的阅读造成困难。书上的做法较复杂的一个原因是链表是不带头结点的,所以要考虑新结点插入时会不会变成表头,
例如:当链表为空时, 插入3, 3变成表头, 再插入1, 链表为1->3, 链表头指向1, 代码必须处理这样的情况.
第一部分: 不含头结点的链表插入的非常规思路
下面我给出另外一个思路,新结点一律插入表头,这样就不要考虑链表为空的情况了。但是可能无序,可以从表头开始,相邻节点不符合次序要求交换即可,代码的逻辑非常简单,当然效率不高,因为交换(结构可能很大)的缘故。
例如 1->2, 插入3 构成 3->1->2, 先交换变成1->3->2,再交换变成1->2->3
下面是我简单的示例程序,附带说明了另外一个概念,插入的过程即是建表的过程。
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <string.h> 4 struct node /*节点的数据结构*/ 5 { 6 int num; 7 char str[20]; 8 struct node *next; 9 }; 10 /* * * * * * * * * * *主函数 * * * * * * * * * * * * * * * * */ 11 int main(void) 12 { 13 struct node *insert(struct node *head,char *pstr,int n); /*函数声明 */ 14 void print(struct node *head); /*函数声明 */ 15 struct node *head; 16 char str[20]; 17 int i, n; 18 head=NULL; /*做空表*/ 19 20 for(i=0;i<5;i++){ //仅输入5组数据 21 printf("\n input inserted num name:\n"); // 号码 空格 姓名 22 scanf("%d", &n); 23 gets(str); /*输入姓名*/ 24 head=insert(head,str,n); /* 将节点插入链表*/ 25 print(head); /*调用函数输出节点*/ 26 } 27 return 0; 28 } 29 30 /* * * * * * * * * * * * * * 插入节点函数* * * * * * * * * * * * * * * */ 31 struct node *insert(struct node *head,char *pstr,int n) /*插入学号为n、姓名为pstr的节点*/ 32 { 33 struct node *p; 34 p=(struct node*)malloc(sizeof(struct node)); /*分配一个新节点*/ 35 strcpy (p->str,pstr); /*写入节点的姓名字串*/ 36 p->num=n; /* 指向学号*/ 37 38 /*新节点插入表头*/ 39 p->next=head; 40 head=p; 41 42 while(p->next&&p->next->num<p->num) 43 { 44 //交换邻接结点p和p->next的值(但是保留结点的next) 45 struct node t; 46 struct node *pr=p->next, *pn=p->next->next; 47 t=*(p->next); 48 *(p->next) = *p; 49 p->next->next=pn; 50 *p = t; 51 p->next=pr; 52 53 p=p->next; 54 } 55 return(head);/* 返回链表的头指针*/ 56 } 57 58 59 /* * * * * * * * * * * * * * 链表输出函数* * * * * * * * * * * * * */ 60 void print (struct node *head) 61 { 62 struct node *temp; 63 temp=head; 64 printf("output strings:\n"); 65 while (temp!=NULL) 66 { 67 printf("%d -- %s\n",temp->num,temp->str); 68 temp=temp->next; 69 } 70 return; 71 }
输入如下
input inserted num name:
1 zhang
output strings:
1 -- zhang
input inserted num name:
2 li
output strings:
1 -- zhang
2 -- li
input inserted num name:
3 zhao
output strings:
1 -- zhang
2 -- li
3 -- zhao
input inserted num name:
4 qian
output strings:
1 -- zhang
2 -- li
3 -- zhao
4 -- qian
input inserted num name:
5 chen
output strings:
1 -- zhang
2 -- li
3 -- zhao
4 -- qian
5 -- chen
第二部分:带头结点的做法,我们分配一个结点永远做为链表头, 这样的话新插入的结点总是在后面,就没有前面提到的烦恼了.
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <string.h> 4 struct node /*节点的数据结构*/ 5 { 6 int num; 7 char str[20]; 8 struct node *next; 9 }; 10 /* * * * * * * * * * *主函数 * * * * * * * * * * * * * * * * */ 11 int main(void) 12 { 13 void insert(struct node *head,char *pstr,int n); /*函数声明 */ 14 void print(struct node *head); /*函数声明 */ 15 struct node *head; 16 char str[20]; 17 int i, n; 18 head=(struct node*)malloc(sizeof(struct node)); /*head指向头结点*/ 19 head->next=NULL; 20 21 for(i=0;i<5;i++){ //仅输入5组数据 22 printf("\n input inserted num name:\n"); // 号码 空格 姓名 23 scanf("%d", &n); 24 gets(str); /*输入姓名*/ 25 insert(head,str,n); /* 将节点插入链表*/ 26 print(head); /*调用函数输出节点*/ 27 } 28 return 0; 29 } 30 31 /* * * * * * * * * * * * * * 插入节点函数* * * * * * * * * * * * * * * */ 32 void insert(struct node *head,char *pstr,int n) /*插入学号为n、姓名为pstr的节点*/ 33 { 34 struct node *p; 35 p=(struct node*)malloc(sizeof(struct node)); /*分配一个新节点*/ 36 strcpy (p->str,pstr); /*写入节点的姓名字串*/ 37 p->num=n; /* 指向学号*/ 38 39 40 /*寻找p的插入位置*/ 41 while(head->next&&head->next->num<p->num) 42 { 43 head=head->next; 44 } 45 46 /*应该在head之后插入p*/ 47 p->next=head->next; 48 head->next=p; 49 } 50 51 52 /* * * * * * * * * * * * * * 链表输出函数* * * * * * * * * * * * * */ 53 void print (struct node *head) 54 { 55 struct node *temp; 56 temp=head->next; 57 printf("output strings:\n"); 58 while (temp!=NULL) 59 { 60 printf("%d -- %s\n",temp->num,temp->str); 61 temp=temp->next; 62 } 63 return; 64 }
行18,19就是分配头结点并初始化, 头结点的data我们不用.
因为head不会改变, 第32行insert也没有必要返回值, 而且恰好弥补了书中接口设计的缺陷. 另外其实现大大简化了,也没有第一个部分的低效问题. 这里值得注意的是第41行head->next的做法,保证了p一定插在head 和head->next之间.
头结点的另外一个变化就是第56行,head->next才是第一个有效的数据结点.