zoukankan      html  css  js  c++  java
  • 线性表

    线性表学习笔记之链表

     

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

    链表分类:单链表,插入删除和查找的时间复杂度均为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)

    void Del_x(Linklist &L, ElemType x){
    LNode *p; //p指向待删除结点

    if(L==NULL)
    return;
    if(L->data==x){
    p=L;
    L=L->next;
    free(p);
    Del_x(L, x);
    }
    else
    Del_x(L->next, x);
    }

    复制代码
     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 }
    复制代码

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

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

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

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

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

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

            

    LinkList Delete_Min(LinkList &L){
    LNode *pre=L, *p=L->next; //p为工作指针,pre指向其前驱
    LNode *minpre=pre, *minp=p;

    while(p!=NULL){
    if(p->data<minpre->data){
    minp=p;
    minpre=pre;
    }
    pre=p;
    p=p->next;
    }
    minpre->next=minp->next;
    free(minp);
    return 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 }    
    复制代码

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

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

    LinkList Reverse(LinkList &L){
    p=L->next;
    L->next=NULL;
    while(p!=NULL){
    r=p->next;
    p->next=L->next;
    L->next=p;
    p=r;
    }
    return L;
    }

    复制代码
     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 }
    复制代码

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

    LinkList Reverse(LinkList &L){
    LNode *pre,*p=L->next,*r=p->next;
    p->next=NULL;
    while(r!=NULL){
    pre=p;
    p=r;
    r=r->next;
    p->next=pre;
    }
    L->next=p;
    return L;
    }

    复制代码
     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 }
    复制代码

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

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

    void Sort(LinkList &L){
    LNode *p=L->next, *pre
    LNode *r=p->next;
    p->next=NULL;
    p=r;
    while(p!=NULL){
    r=p->next;
    pre=L;
    while(pre->next!=NULL&&pre->next->data<p->data)
    pre=pre->next;
    p->next=pre->next;
    pre->next=p;
    p=r;
    }

    }

    复制代码
     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 }
    复制代码

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

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

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

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

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

    LinkList Search_Common(LinkList &L1,LinkList &L2){
    int len1=Length(L1),len2=Length(L2);
    LinkList longList,shorList;
    if(len1>len2){
    longList=L1->next;shortList=L2->next;
    dist=len1-len2;
    }
    else{
    longList=L2->next,shortList=L1->next;
    dist=len2-len1;
    }
    while(dist--)
    longList=longList->next
    while(longList!=NULL){
    if(longList==shortList)
    return longList;
    else{
    longList=longList->next;
    shortList=shortList->next;
    }
    }
    return NULL;
    }

    复制代码
     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 }
    复制代码

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

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

    LinkList DisCreat(LinkList &A){
    LinkList B=(LinkList)malloc(sizeof(LNode))
    B->next=NULL;
    LNode *p=A->next;
    LNode *ra=A; //ra始终指向A的尾结点
    while(p!=NULL){
    ra->next=p;
    ra=p;
    p=p->next;
    q=p->next;
    p->next=B->next;
    B->next=p;
    p=q;
    }
    ra->next=NULL;
    return B;
    }

    复制代码
     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 }
    复制代码

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

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

    void MergeList(LinkList &A,LinkList &B){
    LNode *pa=A->next, *pb=B->next, *r;
    A->next=NULL;
    while(pa&&pb){
    if(pa->data<pb->data){
    r=pa->next;
    pa->next=A->next;
    A->next=pa;
    pa=r;
    }
    else{
    r=pb->next;
    pb->next=A->next;
    A->next=pb;
    pb=r;
    }
    }
    if(pa)
    pb=pa;
    while(pb){
    r=pb->next;
    pb->next=A->next;
    A->next=pb;
    pb=r;
    }
    }

    复制代码
     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 }    
    复制代码

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

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

    void Get_Common(LinkList &A , LinkList &B){
    LNode *p=A->next, *q=B->next, *r, *s;
    LinkList C =(LinkList)malloc(sizeof(LNode));
    r=C;
    while(p!=NULL&&q!=NULL){
    if(p->data<q->data)
    p=p->next;
    else if(p->data>q->data)
    q=q->next;
    else{
    s=(LNode *)malloc(sizeof(LNode));
    s->data=q->data;
    r->next=s;
    r=s;
    p=p->next;
    q=q->next;
    }
    }
    r->next=NULL;
    }

    复制代码
     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 }
    复制代码

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

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

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

    LinkList Union(LinkList &A , LinkList &B){
    pa=A->next;
    pb=B->next;
    pc=A;
    LNode *u;
    while(pa&&pb){
    if(pa->data==pb->data){
    pc->next=pa;
    pc=pa;
    pa=pa->next;
    u=pb;
    pb=pb->next;
    free(u);
    }
    else if(pa->data>pb->data){
    u=pb;
    pb=pb->next;
    free(u);
    }
    else{
    u=pa;
    pa=pa->next;
    free(u)
    }
    while(pa){
    u=pa
    pa=pa->next;
    free(u)
    }
    while(pb){
    u=pb
    pa=pb->next;
    free(u)
    }
    pc->next-NULL;
    free(B);
    return A;
    }
    }

    复制代码
     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 }
    复制代码

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

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

    int Symmetry(DLinkList L){
    DNode *p=L->next, *q=L->prior;
    while(p!=q&&p->next!=q)
    if(p->data==q->data){
    p=p->next;
    q=q->prior;
    }
    else
    return 0;
    return 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 }
    复制代码

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

    void Del_All(LinkList &L){
    LNode *p, *pre, *minp, *minpre;
    while(L->next!=L){
    p=L->next;pre=L;
    minp=p,minpre=L;
    while(p!=L){
    if(p->data<minp->data){minp=p;minpre=pre;}
    pre=p;
    p=p->next;
    }
    printf("%d",minp->data);
    minpre->next=minp->next;
    free(minp);
    Del_All(L)
    }
    free(L);
    }

    复制代码
     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 }   
    复制代码

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

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

    typedef int ElemType;
    typedef struct LNode{
    ElemType data;
    struct LNode *next;
    }LNode, *LinkList;

    int Search(LinkList L, int k){
    LNode *p=L->next, *q=L->next;
    int count=0;
    while(p!=NULL){
    if(count<k)count ++;
    else q=q->next;
    p=p->next;
    }
    if(count<k)
    return 0;
    else{
    printf("%d",q->data);
    return 1;
    }

    }

    复制代码
     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 }
    复制代码

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

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

    DLinkList Locate(DLinkList &L, ElemType x){
    DNode *p=L->next, *q;
    while(p&&p->data!=x)
    p=p->next;
    if(!p){
    printf("不存在值为x的结点 ");
    exit(0)
    }
    else{
    p->freq++;
    p->next->pred=p->pred;
    p->pred->next=p->next;
    q=p->pred;
    while(q->freq<p->freq&&q!=L)
    q=q->pred;
    p->next=q->next;
    q->next->pred=p;
    q->next=p;
    p->pred=q;
    }
    return p; //返回值为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 }
    复制代码

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

     
     
    分类: 数据结构
  • 相关阅读:
    会议安排最优算法
    Python Singleton
    Android HandlerThread 源代码分析
    [Android]_[0基础]_[adb 有用命令]
    使用sshfs将远程目录挂载到本地
    Netty 中ChannelOption的含义以及使用的场景
    netty4.0 Server和Client的通信
    Netty重要概念介绍
    Zookeeper单机伪集群
    整数集合
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3420368.html
Copyright © 2011-2022 走看看