线性表学习笔记之链表

原创博文,转载请注明出处

链表分类:单链表,插入删除和查找的时间复杂度均为O(n)

              双链表,插入、删除和查找的时间复杂度为O(1)

              循环链表,表中最后一个节点的指针不是NULL,而改为指向头结点,从而整个链表形成一个环。

             静态链表,借助数组来描述线性表的链式存储结构,这儿的指针是结点的相对地址。和顺序表一样需要预先分配一块连续的内存空间。以next==0作为其结束的标志。

综合应用:

    1.设计一个递归算法,删除不带头节点的单链表L中所有值为x的节点。

            思路:可以设计一个函数f(L,x)删除以L为首结点指针的单链表中所有值为x的结点,那么f(L->next,x)则是删除以L->next为首结点指针的单链表中所有值等于x的结点。

                    借助一个递归工作栈,深度为O(n),时间复杂度为O(n)

 1 void Del_x(Linklist &L, ElemType x){
 2       LNode *p;   //p指向待删除结点
 3       
 4       if(L==NULL)
 5             return;
 6       if(L->data==x){
 7             p=L;
 8             L=L->next;
 9             free(p);
10             Del_x(L, x);    
11     }           
12      else
13            Del_x(L->next, x);       
14 }
View Code

          2. 设L为带头结点 的单链表,编写算法实现从尾到头反向输出每个结点的值。

           思路:方法一、将链表逆置,改变链表的方向。

                  方法二、借助一个栈,将结点放入栈中。在遍历完整个链表后,再从栈顶开始输出结点值。

                  方法三、使用递归,当访问一个结点时,先递归输出它后面的结点,再输出该结点自身。实现如下

1 void R_Print(LinkList L){
2         if(L->next!=NULL){
3               R_Print(L->next)
4     }
5        print(L->next)
6 }
View Code

        3.试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点唯一)

        

 1 LinkList Delete_Min(LinkList &L){
 2          LNode *pre=L, *p=L->next; //p为工作指针,pre指向其前驱
 3          LNode *minpre=pre, *minp=p;
 4         
 5          while(p!=NULL){
 6                  if(p->data<minpre->data){
 7                           minp=p;
 8                           minpre=pre;
 9         }  
10                pre=p;
11                p=p->next;
12     }
13     minpre->next=minp->next;
14     free(minp);
15     return L;
16 }    
View Code

        4.编写算法将带头结点的单链表就地逆置,就地指辅助空间为O(1)

         方法一:将头结点摘下,然后从第一结点开始,依次前插入到头节点后面(头插法建立链表),直到最后一个节点为止。

 1 LinkList Reverse(LinkList &L){
 2            p=L->next;
 3            L->next=NULL;
 4            while(p!=NULL){
 5                   r=p->next;
 6                   p->next=L->next;
 7                   L->next=p;
 8                   p=r;
 9      }
10             return L;
11 }
View Code

        方法二:将结点的next域指向前驱结点。在处理完最后一个结点时,需要将头结点的指针指向它。时间复杂度均为O(n)

 1 LinkList Reverse(LinkList &L){
 2              LNode *pre,*p=L->next,*r=p->next;
 3              p->next=NULL;
 4              while(r!=NULL){
 5              pre=p;
 6              p=r;
 7              r=r->next;
 8              p->next=pre;
 9     }
10             L->next=p;
11             return L;
12 }
View Code

      5. 有一个带头结点的单链表L,设计一个算法使其元素递增有序。

      思路:采用直接插入排序的算法,先构成只含一个数据结点的有序单链表,然后依次扫描单链表中剩下的结点*p。

 1 void Sort(LinkList &L){
 2         LNode *p=L->next, *pre
 3         LNode *r=p->next;
 4         p->next=NULL;
 5         p=r;
 6         while(p!=NULL){
 7                 r=p->next;
 8                 pre=L;
 9                 while(pre->next!=NULL&&pre->next->data<p->data)
10                          pre=pre->next;
11                p->next=pre->next;
12                pre->next=p;
13                p=r;
14     }
15 
16 }
View Code

    6.在单链表L中删除p所指结点,能够实现在O(1)的时间内删除该结点?

     思路:传统的做法需要顺序查找删除结点的前驱结点,再修改链接。但是时间复杂度为O(n)。由于我们知道该结点的下一结点P->next,所以我们只需要将下一结点的数据复制到该结点,然后删除它的下一结点。如果该结点位于链表尾部 即P=NULL,这时候我们需要从链表的头结点开始顺序遍历给定节点的前驱结点,这时虽然时间复杂度为O(n),但在平均情况下,仍为O(1)。

   7.给定两个单链表,编写算法找出两个链表的公共结点。

    思路:比较麻烦的做法就是在第一个链表上顺序遍历每个节点,每遍历一个结点都要在第二个链表上顺序遍历所有结点。找到两个相同的结点,该算法时间复杂度为O(len1*len2)。

            由于每个单链表结点只有一个next域,因此从第一个公共结点开始,之后所有的结点都是重合的,拓扑形状看起来像Y,而不是X。但是,两个链表有可能不一样长,所以我们需要截取长链表多余的部分。

 1 LinkList Search_Common(LinkList &L1,LinkList &L2){
 2            int len1=Length(L1),len2=Length(L2);
 3            LinkList longList,shorList;
 4           if(len1>len2){
 5                 longList=L1->next;shortList=L2->next;
 6                 dist=len1-len2;
 7     }
 8          else{
 9                 longList=L2->next,shortList=L1->next;
10                 dist=len2-len1;
11     }
12      while(dist--)
13                longList=longList->next
14     while(longList!=NULL){
15            if(longList==shortList)
16                   return longList;
17            else{
18             longList=longList->next;
19             shortList=shortList->next;
20          }
21     }
22      return NULL;
23 }
View Code

   8.设C={a1,b1,a2,b2,...,an,bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1,a2,...,an}, B={bn,...,b2,b1}.

   思路:采用头插法新建B表,而A表则使用尾插法。

 1 LinkList DisCreat(LinkList &A){
 2          LinkList B=(LinkList)malloc(sizeof(LNode))
 3          B->next=NULL;
 4          LNode *p=A->next;
 5          LNode *ra=A;   //ra始终指向A的尾结点
 6          while(p!=NULL){
 7                  ra->next=p;
 8                  ra=p;
 9                  p=p->next;
10                  q=p->next;
11                  p->next=B->next; 
12                  B->next=p;
13                  p=q;
14     }  
15         ra->next=NULL;
16         return B;
17 }
View Code

  9.假设有两个按元素值递增次序排列的线性表,均以单链形式存储。请编写算法将这两个链表归并为一个按元素值递减的单链表,并要求利用原来两个单链表的结点存放归并后的链表。

  思路:由于两个链表均是递增的,将其合并时,从第一个结点起进行比较,将小的结点存入链表中,同时后移工作指针。由于要求链表元素递减,故采用头插法。

 1 void MergeList(LinkList &A,LinkList &B){
 2             LNode *pa=A->next, *pb=B->next, *r;
 3             A->next=NULL;
 4             while(pa&&pb){
 5                      if(pa->data<pb->data){
 6                              r=pa->next;
 7                              pa->next=A->next;
 8                              A->next=pa; 
 9                              pa=r;
10                     }
11                    else{
12                             r=pb->next;
13                             pb->next=A->next;
14                             A->next=pb;
15                             pb=r;
16                     }
17           }
18           if(pa)
19              pb=pa;
20           while(pb){
21               r=pb->next;
22               pb->next=A->next;
23               A->next=pb;
24               pb=r;
25       }
26 }    
View Code

  10.设A和B 是两个单链表带头结点,其中元素递增有序。设计一个算法从A和B中公共元素产生链表C,要求不破坏A和B的结点。

       思路:尾插法新建链表C ,要求不破坏A和B的结点,所以才有比较复制的方法。

 1 void Get_Common(LinkList &A , LinkList &B){
 2              LNode *p=A->next, *q=B->next, *r, *s;
 3             LinkList C =(LinkList)malloc(sizeof(LNode));
 4             r=C;
 5             while(p!=NULL&&q!=NULL){
 6                    if(p->data<q->data)
 7                         p=p->next;
 8                   else if(p->data>q->data)
 9                         q=q->next;
10                   else{
11                   s=(LNode *)malloc(sizeof(LNode));
12                   s->data=q->data;
13                   r->next=s;
14                   r=s;
15                  p=p->next;
16                  q=q->next;
17             }
18     }
19     r->next=NULL;
20 }
View Code

11. 已知两个链表A和B分别表示两个集合,其元素递增有序。编制函数,求A与B的交集,并存放于A的链表中。

  思路:采用归并思想,设置两个工作指针pa和pb,对两个链表进行扫描,当同时出现在两集合中的元素才链接到结果表中,且仅保留一个,其他的结点全部释放。

当一个链表遍历结束后,释放另一个表剩下的全部结点。

 1 LinkList Union(LinkList &A , LinkList &B){
 2           pa=A->next;
 3           pb=B->next;
 4           pc=A;
 5           LNode *u;
 6           while(pa&&pb){
 7                 if(pa->data==pb->data){
 8                        pc->next=pa;
 9                        pc=pa;
10                        pa=pa->next;
11                        u=pb;
12                        pb=pb->next;
13                        free(u);
14            }
15            else if(pa->data>pb->data){
16                     u=pb;
17                     pb=pb->next;
18                     free(u);
19          }
20          else{
21                  u=pa;
22                  pa=pa->next;
23                  free(u)
24         }
25         while(pa){
26                 u=pa
27                 pa=pa->next;
28                 free(u)
29          }
30         while(pb){
31                 u=pb
32                 pa=pb->next;
33                 free(u) 
34          }
35        pc->next-NULL;
36        free(B);
37        return A;
38     }
39 }
View Code

12.设计一个算法用于判断带头结点的循环双链表是否对称。

思路:让P从左向右扫描,q从右向左扫描。直到它们指向同一结点(结点个数为奇数时)或相邻(结点个数为偶数时)为止,若它们所指结点值相同,则继续进行下去,否则返回0,若比较全部相同则返回1。

 1 int Symmetry(DLinkList L){
 2           DNode *p=L->next, *q=L->prior;
 3           while(p!=q&&p->next!=q)
 4                  if(p->data==q->data){
 5                      p=p->next;
 6                      q=q->prior;
 7                  }
 8                  else
 9                      return 0;
10           return 1;
11 }
View Code

13. 设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出,然后将该结点从中删除,直到单链表空为止,再删除表头结点。

 1 void Del_All(LinkList &L){
 2           LNode *p, *pre, *minp, *minpre;
 3           while(L->next!=L){
 4                  p=L->next;pre=L;
 5                  minp=p,minpre=L;
 6                  while(p!=L){
 7                        if(p->data<minp->data){minp=p;minpre=pre;}
 8                        pre=p;
 9                        p=p->next;
10                }
11                printf("%d",minp->data);
12                minpre->next=minp->next;
13                free(minp);
14                Del_All(L)
15     }
16          free(L);
17 }   
View Code

14. 已知一个带有表头结点的单链表,假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效的算法,查找链表中倒数第K个位置上的结点。若查找成功,算法输出该结点的data域的值,否则,只返回0。

     设计思想:定义两个指针变量p和q,初始时均指向头结点的下一个结点,即链表的第一个结点。p指针沿链表移动;当p指针移动到第k个结点时,q指针开始于p指针同步移动;当p指针移动到最后一个结点时,q指针所指示的结点为倒数第k个结点。

 1 typedef int ElemType;
 2              typedef struct LNode{
 3                    ElemType data;
 4                    struct LNode *next;
 5     }LNode, *LinkList;
 6 
 7 int Search(LinkList L, int k){
 8            LNode *p=L->next, *q=L->next;
 9            int count=0;
10            while(p!=NULL){
11                  if(count<k)count ++;
12                  else q=q->next;
13                  p=p->next;
14     }
15        if(count<k)
16                return 017        else{
18         printf("%d",q->data);
19         return 1;
20     }
21 
22 }
View Code

15.设头指针为L的带有表头结点的非循环双向链表,其每个结点除了有pred(前驱指针),data和next域外,还有一个访问频度域freq。在链表被启用前,其值均初始化为零。每当在链表中进行一次Locate(L ,x )运算时,令元素值为x的结点中freq域的值增1,并使此链表中结点保持按访问频度递减的序列排列,同时最近访问的结点排在频度相同的结点的前面,以便使频繁访问的结点总是靠近表头。使编写符合上述要求的Locate(L ,x )运算的算法,该运算为函数过程,返回找到结点的地址,类型为指针型。

   设计思想: 首先找到链表中数据值为x的结点,查到后,将结点从链表上摘下,然后顺着结点的前驱查到该结点的插入位置。频度递减,且排在同频度的第一个。

 1 DLinkList Locate(DLinkList &L, ElemType x){
 2           DNode *p=L->next, *q;
 3           while(p&&p->data!=x)           
 4                  p=p->next;
 5           if(!p){
 6               printf("不存在值为x的结点
");
 7               exit(0)
 8     }
 9         else{
10               p->freq++;
11               p->next->pred=p->pred;
12               p->pred->next=p->next;
13               q=p->pred;
14               while(q->freq<p->freq&&q!=L)
15                        q=q->pred;
16               p->next=q->next;
17               q->next->pred=p;
18               q->next=p;
19               p->pred=q;
20     }
21              return p;          //返回值为x的结点的指针
22 }
View Code

 欢迎查看关于顺序表的学习,见上篇http://www.cnblogs.com/tracylining/p/3394038.html

原文地址:https://www.cnblogs.com/tracylining/p/3418215.html