回炉重造之数据结构【二】线性表

本文详细介绍了线性表的概念和特性,包括顺序表示和链式表示两种存储方式。顺序表使用数组实现,特点是随机存取但插入和删除操作可能涉及大量元素移动。链表分为单链表、双链表和循环链表,插入和删除操作更为灵活但失去了随机存取能力。文章还讨论了线性表的优缺点以及在不同场景下的选择,并举例说明了一元多项式的表示和相加方法。
摘要由CSDN通过智能技术生成

回炉重造之数据结构【二】线性表

前情回顾

第一章 绪论



线性结构特点:在数据元素的非空有限集中

(1)存在唯一的一个被称作“第一个”的数据元素;

(2)存在唯一的一个被称作“最后一个”的数据元素;

(3)除第一个之外,集合中的每个数据元素均只有一个前驱;

(4)除最后一个之外,集合中的每个数据元素均只有一个后继

线性表
顺序存储
链式存储
顺序表
单链表
双链表
循环链表
静态链表
借助数组实现
循环单链表
循环双链表

线性表的定义和基本操作

线性表的定义

1、线性表是具有相同数据类型的n(n≥0)个数据元素的有限序列

线性表的位序从1开始

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

线性表的顺序表示

顺序表的定义

1、线性表的顺序存储又称顺序表,通常用数组来描述线性表的顺序存储结构

2、特点:

(1)逻辑上相邻的两个元素在物理位置上也相邻

(2)可随机存取

(3)存储密度高,每个结点值存储数据元素

线性表中元素的位序是从1开始的,而数组中元素的下标是从0开始的

3、静态分配

#define MAXSIZE 50; //定义线性表最大长度
typedef struct {
    ElemType data[MAXSIZE];//顺序表的元素
    int length;//顺序表的当前长度
}SqList; //顺序表的类型定义

4、动态分配语句

L.data=(ElemType *)malloc(sizeof(ElemType)*InitSize);

5、(1)判空:L.length==0;

​ (2)判满:L.length==MAXSIZE;

顺序表上基本操作的实现

1、插入操作

位序1≤i≤L.length+1范围内可插入

bool LinstInsert(SqList &L,int i,ElemType e){
    if(L.length>=MAXSIZE || i<1 || i>L.length+1) //判断i范围是否有效,存储空间是否已满
        return false;
    //j作为位序
    for(int j=L.length;j>=i;j--) //将第i个元素及之后的元素后移
        L.data[j]=L.data[j-1];
    /*j作为下标
    for(int j=L.Length-1;j>=i-1;j--)
    	L.data[j+1]=L.dat[j];
    */
    L.data[i-1]=e;
    L.length++;
    renturn true;
}

最好情况:表尾插入,时间复杂度O(1)

最坏情况:表头插入,时间复杂度O(n)

平均时间复杂度O(n)

平均移动次数

(1)若表长为n,则插入位置有n个,若在任意结点后插入为 ( n − 1 ) 2 (n-1)\over 2 2(n1);在任意结点前插入为 ( n + 1 ) 2 (n+1)\over2 2(n+1)

(2)若在任意位置插入,则插入位置有n+1个,平均移动次数为 1 n + 1 ∑ i = 1 n + 1 ( n − i + 1 ) = n 2 {1\over n+1}\displaystyle\sum_{i=1}^{n+1} (n-i+1)={n\over 2} n+11i=1n+1(ni+1)=2n

2、删除操作

删除1≤i≤L.length范围内某个元素

bool ListDelete(SqList &L,int i,ElemType &e){
    if(i<1||i>L.length)	//判断i的范围是否有效
        return false;
    e=L.data[i-1];	//将被删除的元素赋值给e	
    for(int j=i;j<L.length;j++)	//将第i个位置后的元素前移
        L.data[j-1]=L.data[j];
    L.length--;	//线性表长度减1
    return true;
}

最好情况:删除表尾元素,时间复杂度O(1)

最坏情况:删除表头元素,时间复杂度O(n)

平均时间复杂度O(n)

平均移动次数

​ 删除位置有n个,平均移动次数为 1 n ∑ i = 1 n + 1 ( n − i ) = n − 1 2 {1\over n}\displaystyle\sum_{i=1}^{n+1} (n-i)={n-1\over 2} n1i=1n+1(ni)=2n1

3、按值查找(顺序查找)

在顺序表L中查找第一个元素值等于e的元素,并返回其位序

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

最好情况:查找的元素在表头,时间复杂度O(1)

最坏情况:查找的元素在表尾或不存在,时间复杂度O(n)

平均时间复杂度O(n)

4、按位查找时间复杂度:O(1)

线性表的链式表示

链式线性表不需要使用地址连续的存储再远,只需修改指针达到相关目的

插入和删除不需要移动元素,失去可随机存储的优点

非随机存取

单链表的定义

1、单链表:指通过一组任意的存储单元来存储线性表中的数据元素

2、结构:除了存放元素自身的信息外,还需存放一个指向其后继的指针

3、定义

typedef struct LNode{	//定义单链表结点类型
    ElemType data;	//数据域
    struct LNode *next;	//指针域
}LNode,*LinkList;

4、判空:

(1)带头结点:L->next==NULL;

(2)不带头结点:L==NULL;

5、头结点与头指针的区分

​ 不管带不带头结点,头指针都始终指向链表的第一个结点,而头结点是带头结点的链表中的第一个结点,结点内通常不存储信息

单链表上基本操作的实现

1、建立单链表

(1)头插法

逆序建立单链表,每次插入表头

void CreateLinkList_HEAD(LinkList &L,int n) //头插法,逆序建立单链表
{
    LNode *s;int x;
    L=(LinkList)malloc(sizeof(LNode));	//创建头结点
    L->next=NULL;	//初始为空表
    
    for(int i=0;i<n;i++){
        scanf("%d",&x);	//输入结点的值
        s=(LNode *)malloc(sizeof(LNode));	//创建新节点
        s->data=x;
        s->next=L->next;
        L->next=s;
    }
}

每个结点插入的时间为O(1),总时间复杂度为O(n)

(2)尾插法

正向建立单链表,每次插入表尾

void CreatLinkList_TAIL(LinkList &L,int n)
{
    int x;
    LNode *s,*r=L;	//r为表尾指针
    L=(LinkList)malloc(sizeof(LNode));
    L->next=NULL;
    
    for(int i=0;i<n;i++)
    {
        scanf("%d",&x);
        s=(LNode *)malloce(sizeof(LNode));
        s->data=x;
        r->next=s;
        r=s;
    }
    r->next=NULL;	//尾结点指针置空
}

总时间复杂度O(n)

2、查找

(1)按位查找

//按位查找,返回第i个元素(带头结点)
LNode *GetElem(LinkList L,int i){
    if(i<0)
        return NULL;
    LNode *p;	//指针p指向当前扫描到的结点
    int j=0;	//当前p指向的是第几个结点
    p=L;	//L指向头结点,头结点是第0个结点(不存数据)
    while(p!=NULL && j<i){	//循环找到第i个结点
        p=p->next;
        j++;
    }
    return p;
}

平均时间复杂度O(n)

(2)按值查找

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

平均时间复杂度O(n)

3、求表的长度

int Length(LinkList L){
    int len=0;
    LNode *p=L;
    while(p->next !=NULL){
        p=p->next;
        len++;
    }
    return len;
}

平均时间复杂度O(n)

4、插入

思路:画图,找到第i-1个元素

核心代码

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

语句2和3的顺序不能颠倒

查找前驱时间复杂度O(n),若在给定的结点后面插入时间复杂度O(1)

5、删除

思路:查找第i-1个元素

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

双链表

单链表访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)

双链表结点中有连个指针prior和next,分别指向其前驱结点和后继结点

双链表的插入操作

s->next=p->next;	//将结点*s插入到结点*p之后
p->next->prior=s;
s->prior=p;
p->next=s;

第1和2步必须在第4步之前

双链表的删除操作

p->next=q->next;
q->next->prior=p;
free(q);

循环链表

循环单链表

1、在循环单链表中,表尾结点*r的next域指向L,故表中没有指针域为NULL的结点

2、循环单链表判空:头结点的指针是否等于头指针:L->next==L

循环单链表不舍头指针仅设尾指针会方便某些操作,如两个线性表合并成一个

循环双链表

1、循环双链表中,头结点的prior指针还要指向表尾结点

2、在循环双链表L中,某结点*p为尾结点时,p->next==L;当循环双链表为空表时,其头结点的prior域和next域都等于L

a静态链表

1、静态链表借助数组来描述线性表的链式存储结构,结点包含数据域data和指针域next;指针是结点的相对地址(数组下标),又称游标;

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

3、静态链表以next==-1作为其结束的标志

顺序表和链表的比较

顺序表链表
存取(读写)方式顺序存取、随机存取顺序存取
逻辑结构与物理结构逻辑上相邻,物理上也相邻逻辑上相邻,物理上不一定相邻
按值查找(无序)O(n);(有序) O( l o g 2 n log_2n log2n)O(n)
按序号查找O(1)O(n)
插入、删除平均需要移动半个表长的元素修改相关结点的指针域

选取存储结构的因素

(1)基于存储的考虑

​ 难以估计线性表的长度或存储规模——不宜采用顺序表;

​ 链表——存储密度第,链式存储结构的存储密度小于1;

(2)基于运算的考虑

​ 反复插入、删除——链表

(3)基于环境的考虑

​ 顺序表易于实现,基于数组

​ 链表基于指针

一元多项式的表示及相加

1、若只对多项式进行“求值”等不改变多项式的系数和直属的运算,采用类似于顺序表的顺序存储结构

2、一元多项式相加的运算规则:对于两个一元多项式中所有指数相同的项,对应系数相加,若其和不为0,则构成“和多项式”中的一项;对于两个一元多项式中所有指数不相同的项,则分别复抄到“和多项式”中去。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值