zoukankan      html  css  js  c++  java
  • 数据结构笔记二:线性表

    线性表

    线性表的定义

    线性表是具有相同数据类型的(n(nge 0))​个数据元素的有限序列,其中(n)​为表长,当(n=0)​时线性表是一个空表。若用(L)​命名线性表,则其一般表示为

    [L(a_1,a_2,...,a_i,a_{i+1},...,a_n) ]

    (a_i)是线性表中的“第i个”元素线性表中的位序(位序从1开始,数组下标从0开始)

    (a_1)是表头元素;(a_n)是表尾元素

    除第一个元素外,每个元素有且仅有一个直接前驱;除最后一个元素外,每个元素有且仅有一个直接后继。

    线性表的基本操作

    InitList(&L ):         //初始化表。构造一个空的线性表L,分配内存空间
    DestoryList(&L ):      //销毁操作。销毁线性表,并释放线性表L所占用的内容空间。
    
    ListInsert(&L,i,e):	   //插入操作。在表L中的第i个位置上指定元素e
    ListDelete(&L,i,&e);   //删除操作。删除表中L中第i个位置的元素,并用e返回删除元素的值
    
    LocateElem(L,e);	   //按值查找操作。在表L中查找具有给定关键字的元素。
    GetElem(L,i);		   //按位查找操作。获取表L中第i个位置的元素的值。
    
    Length(L);			   //求表长。返回线性表L的长度,即L中数据元素的个数
    PrintList();            //输出操作。按前后顺序输出线性表L的所有元素值
    Empty(L);			  //判空操作。若L为空表,则返回true,否则返回false;
    

    线性表的顺序表示

    顺序表的定义

    顺序表——用顺序存储的方式实现线性表。

    image-20210807204113026

    //定义
    //静态分配
    #define MaxSize 10			//定义最大长度
    typedef struct{
        Elemtype data[Maxsize];	 //顺序表的元素
        int length;	             //顺序表的当前长度 
    }Sqlist;
    
    //动态分配
    #define InitSize  100		//表长度的初始定义
    typedef struct{
        Elemtype *data;	 		//指示动态分配数组的知识
        int MaxSize,length;	     //数组的最大容量和当前个数
    }SeqList;
    
    
    //c动态分配语句
    L.data=(Elemtype*)malloc(sizeof(Elemtype)*InitSize);
    
    //c++动态分配语句
    L.data=new Elemtype[InitSize];
    
    

    顺序表的特定:

    1. 随机访问,即可以在(O(1))s时间内找到第i个元素
    2. 存储密度高,每个节点只存储数据元素
    3. 拓展容量不方便(基表采用动态分配的方式实现,拓展长度的时间复杂度也比较高)
    4. 插入,删除操作不方便,需要移动大量元素

    顺序表上的基本操作的实现

    插入操作

    //静态分配下
    bool ListInsert(SqList &L,int i,Elemtype e)
    {
        if(i<1||i>L.length+1)			//判定i的范围是否有效
            return false;
        if(L.length>=MaxSize)			//当前存储空间已满,不能插入
            return false;
        for(int j=L.length;j>=i;--j)	//后移
            L.data[j]=L.data[j-1];
        L.data[i-1]=e;				
        L.length++;
        return true;
    }
    

    最好情况:在表尾插入(即(i=n+1)),循环0次;最好时间复杂度为(O(1))

    最坏情况:在表头插入(即(i=1)),循环n次;最好时间复杂度为(O(n))

    平均情况:假设(p_i(p_i=1/(n+1)))是在第(i)个位置上插入一个结点的概率,则移动结点的平均次数为

    [sum^{n+1}_{i=1}p_i(n-i+1)=frac {1}{n+1} sum^{n+1}_{i=1}(n-i+1)=frac {1}{n+1}frac {n(n+1)}{2}=frac {n}{2} ]

    插入算法的平均时间复杂度为(O(n))

    删除操作

    //静态分配下
    bool ListDelete(SqList &L,int i,Elemtype& e)
    {
        if(i<1||i>L.length+1)			//判定i的范围是否有效
            return false;
        e=L.data[i-1];					//赋值给e
        for(int j=i;j<L.length;j++)
           L.data[j-1]=L.data[j];
        L.length--;
        return true;
    }
    

    最好情况:删除表尾元素(即(i=n)​),无需移动元素;最好时间复杂度为(O(1))​​

    最坏情况:删除表头元素(即(i=1)),需移动除表头元素外的所有元素;最好时间复杂度为(O(n))

    平均情况:假设(p_i(p_i=1/n))​是在第(i)​​个位置上删除一个结点的概率,则移动结点的平均次数为

    [sum^{n}_{i=1}p_i(n-i)=frac {1}{n} sum^{n}_{i=1}(n-i)=frac {1}{n}frac {n(n-1)}{2}=frac {n-1}{2} ]

    删除算法的平均时间复杂度为(O(n))

    查找操作(按值查找)

    int LocateELem(SqList L,Elemtype e)
    {
        int i=0;
        for(i=0;i<L.Length;++i)
            if(L.data[i]==0)
                return i+1;	·		 //下标为i的元素值等于e,返回其位序i+1
        return 0;					//退出循环,说明查找识别
    }
    

    最好情况:查找的元素就在表头,仅需比较一次;最好时间复杂度为(O(1))

    最坏情况:查找的元素在表尾(或不存在)时,需比较n次;最好时间复杂度为(O(n))

    平均情况:假设(p_i(p_i=1/n))是查找的元素在第(i)​个位置的概率,则移动结点的平均次数为

    [sum^{n}_{i=1}p_i imes i=frac {1}{n} sum^{n}_{i=1}i=frac {1}{n}frac {n(n+1)}{2}=frac {n+1}{2} ]

    查找算法的平均时间复杂度为(O(n))

    线性表的链式表示

    单链表的定义

    //单链表中的结点类型
    typedef struct Lnode{
        Elemtype data;			//数据域
        struct Lnode* next;		//指针域
    }Lnode,*LinkList;
    

    要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点

    为了操作上的方便,在单链表第一个结点之前附加一个头结点。头节点的数据域可以不设任何信息。引入头结点后:

    1. 在链表的第一个位置上的操作与其他位置的一致,无须进行特殊处理
    2. 无论链表为空,其头指针都指向头结点的非空指针(空表中头节点的指针域为空),实现空表与非空表的处理统一。

    单链表的基本操作

    建立操作

    头插法

    该方法从一个空表开始,生成一个新结点,并将读取到的数据存放到新结点的数据域中,然后讲新结点插入到当前链表的表头。

    image-20210811201305880

    //带头结点
    LinkList List_HeadInsert(LinkList &L)
    {
        LNode *s;int x;
        L=(LinkList)malloc(sizeof(LNode));		//创建头结点
        L->next=NULL;						  //初始为空链表
        scanf("%d",&x);						  //输入结点的值
        whille(x!=9999)						  //输入9999表示结点
        {
            s=(LNode*)malloc(sizeof(LNode));
            s-data=x;
            s-next=L->next;					  //将新结点插入表中,L为头结点
            L-next=s;
            scanf("%d",&x);
        }
        return L:
    }
    

    平均时间复杂度为(O(n))

    头插法应用:链表的逆置

    尾插法

    image-20210811201321828

    该方法将新结点插入到当前链表的表尾,为此必须增加一个尾指针人,使其始终指向当前链表的尾结点。

    //带头结点
    LinkList List_TailInsert(LinkList &L)
    {
        int x;+									
    	LNode *s,*r=L;						 //r为表尾指针
        scanf("%d",&x);						  //输入结点的值
        whille(x!=9999)						  //输入9999表示结点
        {
            s=(LNode*)malloc(sizeof(LNode));
            s-data=x;
            r->next=s;
            r=s;							  //r指向新的表尾结点
            scanf("%d",&x);
        }
        r->next=NULL;						   //尾指针置空
        return L:
    }
    

    平均时间复杂度为(O(n))

    查找操作

    按位(序号)查找

    LNode *GetElem(LinkList L,int i){    int j=1;				//计数,初始为1    LNode *p=L->next;		 //头结点赋值给p    if(i==0)        return L;			//若i等于0,则返回头结点    if(i<1)        return NULL;		//若i无效,则返回NUll    while(p&&j<i)			//第一个结点开始,查找第i个结点    {        p=p->next;        j++;    }    return p;				//返回第i个结点的指针,若i大于表长,则返回NULL}
    

    平均时间复杂度为(O(n))

    按值查找

    LNode *LocateElem(LinkList L,ELemtype e){    LNode *p=L->next;    while(p!=NULL&&p->data!=e)		//第一个结点开始查找data域为e的结点        p=p->next;    return p;				//找到后返回该结点指针,否则返回NULL}
    

    平均时间复杂度为(O(n))

    插入操作

    后插操作:

    image-20210811201336312

    p=GetElem(L,i-1);				//查找插入位置的前驱结点s->next=p->next;				//第二步p->next=s;					    //第三步
    

    时间复杂度为(O(n))

    扩展:对某一结点进行前插操作

    s->next=p->next;				//修改指针域,不能颠倒p->next=s;temp=p->data;					//交换数据域部分p->data=s=>data;s->data=temp;
    

    时间复杂度为(O(1))

    删除操作

    image-20210811201643934

    p=GetElem(L,i-1);				//查找删除位置的前驱结点q=p->next;					    //令q指向被删除结点p->next=q->next;				//将*q结点从链中“断开”free(q);					    //释放结点的存储空间
    

    时间复杂度为(O(n))

    扩展:删除结点*p

    q=p->next;					//令q指向*p的后续结点p->data=p->next->data;		 //和后续结点交换数据域(注意是否为最后一个结点)p->next=q->next;			//将*q结点从链中“断开”free(q);
    

    时间复杂度为(O(1))

    双链表的定义

    引入前驱指针

    image-20210811202846619

    //定义双链表结点类型typedef struct DNode{    Elemtype data;						//数据域    struct DNode* prior,*next;			 //前驱和后继指针 }DNode,*DLinkList;
    

    双链表的基本操作

    插入操作

    image-20210811202854150

    s->next=p->next;			//将结点*s插入到*p之后if(p->next!=NULL)		    //判断是否为最后一个结点    p->next->prior=p;s->next=p;p->next=s;
    

    删除操作

    image-20210811202904796

    //删除*p后续结点*qp->next=q->next;q->next->priot=p;free(q);
    

    循环链表的定义

    循环单链表

    表中最后一个结点的指针不是NULL,,而改为指向头结点。

    判空条件:是否等于头结点

    循环双链表

    头结点的prior指针还要指向表尾结点。

    image-20210811203619346

    当循环双链表为空表是,其头结点和prior域与next域都等于L。

    具体操作自己学习实现。

    静态链表的定义

    静态链表借助数组来描述线性表的链式存储结构,结点也有数据域data与指针域next;

    这里的指针是结点的相对地址(数组下标),又称游标。

    静态链表要预先分配一块连续的内存空间。

    image-20210811204748663

    typedef struct{    Elemtype data;			//存储数据元素    int next;				//下一个元素的数组下标}SLinkList[MaxSize];		//定义一个结构体数组
    

    优点:增,删操作不需要大量移动元素

    缺点:不能随机存储,只能从头结点开始依次往后查找;容量固定不可变

    适用场景:①不支持指针的低级语言 ②数据元素数量固定不变的场景(如操作系统的文件分配表FAT)

    顺序表和链表的对比

    存储结构

    image-20210811205253208

    基本操作

    image-20210811205541297

    image-20210811205803791

    image-20210811210004944

    image-20210811210112730

    选择

    image-20210811210339643

  • 相关阅读:
    nginx学习二:快速入门
    nginx学习一:http协议
    使用itext生成pdf的,各种布局
    关于java poi itext生成pdf文件的例子以及方法
    半透明全屏蒙层+全屏屏蔽+内容居中+css
    通过html文件生成PDF文件
    mybatis中文官网
    经典sql语句
    关于Cell中的各种值的类型判断
    bootstrap表格参数说明
  • 原文地址:https://www.cnblogs.com/Ligo-Z/p/15190604.html
Copyright © 2011-2022 走看看