1 建立单链表

动态地建立单链表的常用方法有如下两种:头插入法,尾插入法。

⑴ 头插入法建表

从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,

然后将新结点插入到当前链表的表头上,直到读入结束标志为止。即每次插入的结点都作为链表的第一个结点。

 1 算法描述:头插法
 2   以32767作为结束标志。
 3 LNode  *create_LinkList(void)
 4     /*  头插入法创建单链表,链表的头结点head作为返回值  */  
 5 {    int data ;
 6 LNode *head, *p;
 7 head= (LNode  *) malloc( sizeof(LNode));
 8 head->next=NULL;       /*  创建链表的表头结点head  */ 
 9 while (1) 
10 {   scanf(“%d”, &data) ;
11 if (data==32767)  break ;
12 p= (LNode  *)malloc(sizeof(LNode));
13 p–>data=data;     /*  数据域赋值  */
14 p–>next=head–>next ;  head–>next=p ; 
15        /*  钩链,新创建的结点总是作为第一个结点  */
16 }
17 return (head);
18 }

 

(2) 尾插入法建表

头插入法建立链表虽然算法简单,但生成的链表中结点的次序和输入的顺序相反。

若希望二者次序一致,可采用尾插法建表。该方法是将新结点插入到当前链表的表尾,使其成为当前链表的尾结点。

 1 算法描述
 2 LNode  *create_LinkList(void)
 3      /*  尾插入法创建单链表,链表的头结点head作为返回值  */  
 4 {   int data ;
 5 LNode *head, *p, *q;
 6 head=p=(LNode  *)malloc(sizeof(LNode)); 
 7 p->next=NULL;        /*  创建单链表的表头结点head  */
 8 while (1)
 9 {    scanf(“%d”,& data);
10 if (data==32767)  break ;
11 q= (LNode  *)malloc(sizeof(LNode)); 
12 q–>data=data;     /*   数据域赋值  */
13 q–>next=p–>next;  p–>next=q; p=q ; 
14  /*钩链,新创建的结点总是作为最后一个结点*/
15 }
16 return (head);   
17 }

无论是哪种插入方法,如果要插入建立的单线性链表的结点是n个,算法的时间复杂度均为O(n)。

 对于单链表,无论是哪种操作,只要涉及到钩链(或重新钩链)

,如果没有明确给出直接后继,钩链(或重新钩链)的次序必须是“先右后左”。

 

2 单链表的查找

(1) 按序号查找 取单链表中的第i个元素。

对于单链表,不能象顺序表中那样直接按序号i访问结点,而只能从链表的头结点出发,沿链域next逐个结点往下搜索,直到搜索到第i个结点为止。

因此,链表不是随机存取结构

 

设单链表的长度为n,要查找表中第i个结点,仅当1≦i≦n时,i的值是合法的。

 1 算法描述
 2 ElemType   Get_Elem(LNode *L , int  i)
 3 {    int j ;   LNode *p;
 4 p=L->next;  j=1;      /*  使p指向第一个结点  */
 5 while  (p!=NULL && j<i)
 6 {   p=p–>next;  j++;  }        /*  移动指针p , j计数  */
 7 if  (j!=i)  return(-32768) ;
 8 else      return(p->data);
 9  /*   p为NULL 表示i太大;  j>i表示i为0  */
10 }
11 移动指针p的频度:
12 i<1时:0次; i∈[1,n]:i-1次;i>n:n次。
13 ∴时间复杂度: O(n)。

(2) 按值查找

按值查找是在链表中,查找是否有结点值等于给定值key的结点?

若有,则返回首次找到的值为key的结点的存储位置;否则返回NULL。查找时从开始结点出发,

沿链表逐个将结点的值和给定值key作比较。

 1 算法描述
 2 LNode *Locate_Node(LNode *L,int key)
 3 /*  在以L为头结点的单链表中查找值为key的第一个结点  */ 
 4 {   LNode *p=L–>next;
 5 while  ( p!=NULL&& p–>data!=key)    p=p–>next;
 6 if  (p–>data==key)   return p;
 7 else  
 8 {    printf(“所要查找的结点不存在!!\n”); 
 9 retutn(NULL);  
10 }
11 }
12 算法的执行与形参key有关,平均时间复杂度为O(n)。

3 单链表的插入

插入运算是将值为e的新结点插入到表的第i个结点的位置上,

插入到ai-1与ai之间。因此,必须首先找到ai-1所在的结点p,

然后生成一个数据域为e的新结点q,q结点作为p的直接后继结点。

 1 算法描述
 2 void  Insert_LNode(LNode *L,int i,ElemType e)
 3     /*  在以L为头结点的单链表的第i个位置插入值为e的结点 */ 
 4 {   int  j=0;  LNode *p,*q;
 5 p=L–>next ;
 6 while  ( p!=NULL&& j<i-1) 
 7 {  p=p–>next;  j++;   }
 8 
 9 if  (j!=i-1)     printf(“i太大或i为0!!\n ”); 
10 else
11 {  q=(LNode *)malloc(sizeof(LNode));
12 q–>data=e;   q–>next=p–>next;
13 p–>next=q;
14 }
15 }
16    设链表的长度为n,合法的插入位置是1≦i≦n。算法的时间主要耗费移动指针p上,故时间复杂度亦为O(n)

4 单链表的删除

⑴ 按序号删除 删除单链表中的第i个结点。 为了删除第i个结点ai,必须找到结点的存储地址。

该存储地址是在其直接前趋结点ai-1的next域中,

因此,必须首先找到ai-1的存储位置p,然后令p–>next指向ai的直接后继结点,即把ai从链上摘下。

最后释放结点ai的空间,将其归还给“存储池”

算法描述
void  Delete_LinkList(LNode *L, int i)
  /*  删除以L为头结点的单链表中的第i个结点  */ 
{  int  j=1;  LNode *p,*q;
p=L;  q=L->next;
while  ( p->next!=NULL&& j<i) 
{  p=q;  q=q–>next;  j++;  }
if  (j!=i)     printf(“i太大或i为0!!\n ”);  
else    
{  p–>next=q–>next;   free(q);    }
}

⑵ 按值删除

删除单链表中值为key的第一个结点。

与按值查找相类似,首先要查找值为key的结点是否存在? 若存在,则删除;否则返回NULL。

算法描述
void  Delete_LinkList(LNode *L,int key)
/*  删除以L为头结点的单链表中值为key的第一个结点  */ 
{     LNode *p=L,  *q=L–>next;
while  ( q!=NULL&& q–>data!=key)     
{  p=q;  q=q–>next;   }
if  (q–>data==key)   
{  p->next=q->next;  free(q);   }
else  
printf(“所要删除的结点不存在!!\n”);
} 

算法的执行与形参key有关,平均时间复杂度为O(n)。

从上面的讨论可以看出,链表上实现插入和删除运算,无需移动结点,仅需修改指针。

解决了顺序表的插入或删除操作需要移动大量元素的问题。

 

变形之一: 删除单链表中值为key的所有结点。

与按值查找相类似,但比前面的算法更简单。

基本思想:从单链表的第一个结点开始,对每个结点进行检查,若结点的值为key,则删除之,然后检查下一个结点,直到所有的结点都检查。

 1 void  Delete_LinkList_Node(LNode *L,int key)
 2 /*  删除以L为头结点的单链表中值为key的第一个结点  */ 
 3 {     LNode *p=L,  *q=L–>next;
 4 while  ( q!=NULL)
 5 {  if (q–>data==key) 
 6      {  p->next=q->next;  free(q);  q=p->next;  }
 7 else
 8      {  p=q;  q=q–>next;   }
 9 }
10 } 

变形之二: 删除单链表中所有值重复的结点,使得所有结点的值都不相同。

与按值查找相类似,但比前面的算法更复杂。

基本思想:从单链表的第一个结点开始,对每个结点进行检查:检查链表中该结点的所有后继结点,

只要有值和该结点的值相同,则删除之;然后检查下一个结点,直到所有的结点都检查。

 1 算法描述
 2 void  Delete_Node_value(LNode *L)
 3 /*  删除以L为头结点的单链表中所有值相同的结点  */ 
 4 {     LNode *p=L->next, *q, *ptr; 
 5 while  ( p!=NULL)   /*  检查链表中所有结点  */ 
 6 {   *q=p, *ptr=p–>next;
 7 /*  检查结点p的所有后继结点ptr  */
 8 while (ptr!=NULL)
 9      {  if (ptr–>data==p->data) 
10             {  q->next=ptr->next;  free(ptr);  
11                 ptr=q->next;  }
12         else  {  q=ptr;  ptr=ptr–>next;   }
13      }
14 p=p->next ;
15 }
16 } 

5 单链表的合并(了解即可)

设有两个有序的单链表,它们的头指针分别是La 、 Lb,

将它们合并为以Lc为头指针的有序链表。合并前的示意图如图2-4所示。

 

合并了值为-7,-2的结点后示意图如图2-5所示。

 

算法说明 算法中pa ,pb分别是待考察的两个链表的当前结点,pc是合并过程中合并的链表的最后一个结点。

 1 LNode  *Merge_LinkList(LNode *La, LNode *Lb)
 2       /*  合并以La, Lb为头结点的两个有序单链表   */
 3 {    LNode *Lc,  *pa ,  *pb ,  *pc, *ptr ;
 4 Lc=La ;  pc=La  ;    pa=La->next ;  pb=Lb->next  ;
 5  while (pa!=NULL    && pb!=NULL)
 6  {  if  (pa->data<pb->data)
 7     {   pc->next=pa ;  pc=pa ;   pa=pa->next  ;   }
 8 /*  将pa所指的结点合并,pa指向下一个结点  */
 9 if  (pa->data>pb->data)
10     {   pc->next=pb ;  pc=pb ;   pb=pb->next  ;   }
11 /*  将pa所指的结点合并,pa指向下一个结点  */
12 if  (pa->data==pb->data)
13     {   pc->next=pa ;  pc=pa ;   pa=pa->next  ; 
14          ptr=pb ; pb=pb->next ; free(ptr) ;   }
15 /*  将pa所指的结点合并,pb所指结点删除  */
16 }
17  if  (pa!=NULL)  pc->next=pa ;
18 else   pc->next=pb ;     /*将剩余的结点链上*/
19 free(Lb) ;
20 return(Lc) ;
21 }
22 算法分析
23         若La ,Lb两个链表的长度分别是m,n,则链表合并的时间复杂度为O(m+n) 。