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) 。