zoukankan      html  css  js  c++  java
  • 链表基础

    1.链表操作

    删除、插入、查找、排序、合并、复制

    单链表的操作

    GelElem(Lnode *head,int i): 找到第i个元素。(必须从头指针出发寻找,复杂度为O(n))

    Insert(Lnode *head,int i,ElemType e): 在位置i之前插入元素e。(对于线性表的非线性存储结构,在已知链表中元素的插入或删除的确切位置的情况下,在单链表中插入或者删除一个节点时,只需要修改指针而不需要移动元素)(必须找到第i-1个节点,复杂度为O(n))

    Delete(Lnode *head,int i):删除第i个节点。(必须找到第i-1个节点,复杂度为O(n))

    remove(Lnode *head,Lnode *ToBeRemoved): 删除某一节点。(对于非尾结点,复杂度为O(1),尾结点的复杂度为O(n))

    length(struct Lnode*head):求长度。(从头遍历,复杂度为O(n))

    MergeList(Lnode *a,Lnode *b,Lnode *c):将2个有序链表合并为一个有序链表。(时间复杂度?空间复杂度?)

    删除操作的说明:

    (1)单向链表。

    对于带头结点的单向列表,分两种情况讨论,如果待删除的不是尾结点,则直接将后面的结点往前复制;如果待删除的是尾结点,则需要从头开始找到尾结点前面的一个结点,将尾结点的next指针置空。可以在O(1)复杂度下删除某一非尾部结点,删除尾部结点时复杂度为O(n)。 

    循环单向链表的操作与线性单向链表基本一致,差别仅仅在于算法中的循环条件不是p或者p->next是否为空,而是它们是否为头指针。

    但是在循环列表中仅设尾指针而不设头指针时,可以将某些操作简单化,比如将2个线性表合并时,仅需将一个表的表尾和另一个表的表头相连接,运算时间为O(1)。 

    2.存储结构

    线性存储;(数组存储,插入或者删除时需要移动数组)

    静态链表;(也用数组表示,其中一个分量表示结点,另一个分量代替指针来表示结点在数组中的相对位置。插入或者删除时不需要移动元素,仍具有链式结构存储的主要优点)

    单链表:不带表头结点的单链表,head指向首结点,当head==NUll,为空表,否则为非空表;

               带表头结点的单链表,head指向头结点,data为null,pnext指向首结点,当head->pNext==NULL时为空表。

    #include"stdlib.h"    //或"malloc.h",动态存储分配头文件
    #include"stdio.h"     //scanf需要的头文件
    #include <iostream>
    using namespace std;
    
    #define NULL 0                       //定义空指针NULL
    struct Lnode          //Lnode为结点(结构)类型
    {  int data;          //假定data为整型
       struct Lnode *next;//next为指针类型
    };
    
    #define LENG sizeof(struct Lnode)    //结点所占的字节数
    
    //生成带表头结点的单链表。一定以0作为末尾结点
    struct Lnode*createl()
    {
        struct Lnode *head,*tail,*p;
        int e;
        head=(struct Lnode*)malloc(LENG);    //新建这样一个结点时,data随机,next也是一个随机的地址值
        tail=head;
        do 
        {
            p=(Lnode*)malloc(LENG);
            scanf("%d",&e);
            p->data=e;
            tail->next=p;
            tail=p;
        } while (e);    //在检查条件是否为真时,首先执行一次代码块。这里e不为0时继续
        tail->next=NULL;     //使得尾指针为空。当链表为空时,head->next=NULL
        return head;
    }
    
    //打印带表头的单链表
    void printl(Lnode *head)
        {
            struct Lnode* ToBePrint;
            ToBePrint=head->next;
            while (ToBePrint!=NULL)
            {    
                 cout<<ToBePrint->data;
                ToBePrint=ToBePrint->next;
             } 
    }
    
    
    void main()
    {
        Lnode* head=createl();
        printl(head);
    }            
    //删除单向列表的结点
    void remove(Lnode *head,Lnode *ToBeRemoved)
    {
        if(!head||!ToBeRemoved||head->next==NULL||ToBeRemoved==head)  // 空表||要删除的是head结点
            return;
        if(ToBeRemoved->next!=NULL)    //要删除的不是尾结点。如果要删除的是结点i
        {
            Lnode *p=ToBeRemoved->next;
            ToBeRemoved->data=p->data;
            ToBeRemoved->next=p->next;
            free(p);   //和malloc对应
            p=NULL;
        }
        else   //要删除的是尾结点(包括只有一个结点的情况),必须找到尾结点以前的结点
        {
            Lnode *p=head;
            while(p->next!=ToBeRemoved)
            {
                p=p->next;
            }
            p->next=NULL;
            free(ToBeRemoved);
            ToBeRemoved=NULL;
        }
    }
    
    //找到带头结点的单向列表的第k个结点
    Lnode* find(Lnode* head,int k)
    {
        if(k==0||head->next==NULL)
            return NULL;
        int count=1;
        Lnode* r=head->next;
        while(count<k)
        {
            if(r->next==NULL)   //已经是尾结点了,但是仍然没有找到第k个结点
                return NULL;
            r=r->next;
            count++;
        }
        return r;
    }
    //求带头结点的线性链表的长度,并依次输出结点的值
    int length(struct Lnode*head)
    {
        int leng=0;
        Lnode* p;
        p=head->next;   //p指向首结点
        while(p!=NULL)  //p非空
        {
            //cout<<p->data;
            leng++;
            p=p->next;
        }
        return leng;
    }

     循环链表:

    循环链表在生成时唯一与单链表不同的就是尾指针指向了头结点

    (1)带头结点的循环链表:

    非空:H->next≠H, H≠NULL

    空:H->next==H, H≠NULL

    (2)只设尾指针的循环链表:

    非空:tail 指向尾结点,tail->data==an

        tail->next 指向表头结点

        tail->next->next指向首结点

        tail->next->next->data==a1

    空:tail->next==tail

     双向链表:

    (1)非空表:L为头指针,L指向表头结点,L->next指向首结点

           L->next->data==a1

           L->prior指向尾结点

           L->prior->data==an

           L->next->prior== L->prior->next==L

    (2)空表:L->next==L->prior==NULL

    双向循环链表:

    (1)空表:L->next==L->prior==L

    (2)非空表:设p指向a1,  则:

    p->next指向a2 , p->next->prior指向a1

    有:  p==p->next->prior, p==p->prior->next

    题目:删除双向链表中的某个结点

    p->prior->next = p->next;  //结点A的next指向结点C

    p->next->prior = p->prior; //结点C的prior指向结点A

    free(p)

    (1)对于非循环的双向链表:

    题目:双向链表中插入结点

     ① f->prior=p->prior;  //结点B的prior指向结点A

       ② f->next=p;          //结点B的next指向结点C

       ③ p->prior->next=f;   //结点A的next指向结点B

       ④ p->prior=f;         //结点C的prior指向结点B

    3.思考

    (1).线性表的顺序存储结构有什么优点和缺点?

    线性表的顺序存储

     优点: 具有简单、运算方便等优点,特别是对于小线性表或长度固定的线性表,采用顺序存储结构的优越性更为突出;

     缺点:1.顺序存储插入与删除一个元素,必须移动大了的数据元素,以此对大的线性表,特别是在元素的插入和删除很频繁的情况下,采取顺序存储很是不方便,效率低;

             2.顺序存储空间容易满,出现上溢,程序访问容易出问题,顺序存储结构下,存储空间不便扩充;

             3.顺序存储空间的分配问题,分多了浪费,分少了空间不足上溢。

    对于大的线性表,特别是元素变动频繁的大线性表不宜采用顺序存储空间,而采用链式存储结构。

    (2).试比较单链表、双链表、循环链表的优、缺点。

    单链表:如果访问任意结点每次只能从头到尾顺序向后访问
    单循环链表:可以从任何一个结点开始,顺序向后访问到达任意结点
    双向链表:可以从任何结点开始任意向前向后双向访问操作
    单链表和单循环链表:只能在当前结点后插入和删除
    双链表:可以在当前结点前面或者后面插入,可以删除前趋和后继(包括结点自己)
    存储:单链表和单循环链表存储密度大于双链表

  • 相关阅读:
    从 QSplitter 中移除 QWidget(使用隐藏与显示,切换十分方便,不要真正销毁)
    Qt虽然自己的源代码里不使用Exception,但也提供了一个QException及其子类QUnhandledException
    细说new与malloc的10点区别
    垃圾回收算法
    服务追踪数据使用 RabbitMQ 进行采集 + 数据存储使用 Elasticsearch + 数据展示使用 Kibana
    缓存穿透、缓存击穿与缓存雪崩
    微服务介绍
    分库分表
    Spring Boot、微服务架构和大数据
    Linux基本的操作
  • 原文地址:https://www.cnblogs.com/wy1290939507/p/4750156.html
Copyright © 2011-2022 走看看