第二章 线性表

2.1 线性表的逻辑结构

2.1.1线性表的定义

​ 线性表是一种线性结构。线性结构的特点是数据元素之间是一种线性关系,数据元素“一个接一个的排列”。在一个线性表中数据元素的类型是相同的,或者说线性表是由同一类型的数据元素构成的线性结构。

​ 线性表是具有相同类型的n(n>=0)个数据元素的有限序列,通常记为:

​ (a1,a2,…,ai-1,ai,ai+1,…an)

​ 其中n为表长,n=0时称为空表。

​ 线性表中相邻元素之间存在着顺序关系。将ai-1称为ai的直接前趋,ai+1称为ai的直接后继。而a1是表中第一个元素,它没有前趋,an是最后一个元素无后继。

​ 需要说明的是:

​ ai为序号为i的数据元素(i=1,2,…,n),通常我们将它的数据类型抽象为datatype,datatype根据具体问题而定。

2.1.2线性表的基本操作

​ (1)线性表初始化:Int_List(L)

​ 初始条件:表L不存在

​ 操作结果:构造一个空的线性表

​ (2)求线性表的长度:Length_List(L)

​ 初始条件:表L存在

​ 操作结果:返回线性表中的所含元素的个数

​ (3)取表元:Get_List(L,i)

​ 初始条件:表L存在且1<=i<=Length_List(L)

​ 操作结果:返回线性表L中的第i个元素的值或地址

​ (4)按值查找:Locate_List(L,x),x是给定的一个数据元素。

​ 初始条件:线性表L存在

​ 操作结果:返回在L中首次出现的值为x的那个元素的序号或地址,称为查找成功;否则,在L中未找到值为x的数据元素,返回一特殊值表示查找失败。

​ (5)插入操作:Insert_List(L,i,x)

​ 初始条件:线性表L存在,插入位置正确(1<=i<=n+1,n为插入前的表长)。

​ 操作结果:在线性表L的第i个位置上插入一个值为x的新元素,这样使原序号为i,i+1,…,n的数据元素的序号变为I+1,i+2,…,n+1,插入后表长=原表长+1。

​ (6)删除操作:Delete_List(L,i)

​ 初始条件:线性表L存在,1<=i<=n

​ 操作结果:在线性表L中删除序号为i的数据元素,删除后使序号为i+1,i+2,…,n的元素变为序号为i,i+1,…,n-1,新表长=原表长-1。

需要说明的是:

​ 某数据结构上的基本运算,不是它的全部运算,而是一些常用的基本的运算,而每一个基本运算在实现时也可能根据不同的存储结构派生出一系列相关的运算来。

​ 在上面各操作中定义的线性表L仅仅是一个抽象在逻辑结构层次的线性表,尚未涉及到它的存储结构。

2.2 线性表的顺序存储及运算实现

2.2.1线性表的顺序存储

​ 线性表的顺序存储是指在内存中用地址连续的一块存储空间顺序存放线性表的各元素,用这种存储形式存储的线性表称其为顺序表。

​ 设a1的存储地址为Loc(a1),每个数据元素占d个存储地址,则第i个数据元素的地址为:

​ Loc(ai)=Loc(a1)+(i-1)*d 1<=i<=n

​ 连续的存储空间借助一维数组来实现。

​ 结构性上考虑,通常将data和last封装成一个结构作为顺序表的类型:

typedef struct

{

datatype data[MAXSIZE];

int last;

}SeqList;

SeqList L;//定义一个顺序表类型的变量

SeqList *L;//定义一个顺序表类型的指针变量

L.Last 为最后一个元素的下标

①线性表中数据元素顺序存储的基址为:

​ L.data 或者 L->data

②线性表的表长表示为:

​ L.last+1 或者 L->last+1

③线性表中数据元素的存储表示为:

​ L.data[0]~L.data[last] 或者

​ L->data[0]~L->data[L->Last]

2.2.2顺序表上基本运算的实现

1.顺序表的初始化

​ 顺序表的初始化即构造一个空表,对表是一个加工型的运算。因此,将L设为指针参数,首先动态分配存储空间,然后,将表中last指针置为-1,表示表中没有数据元素。

顺序表的初始化算法SeqList *init_SeqList(){SeqList *L;L=new SeqList;//申请顺序表的存储空间if(L){L->last=-1;return L;//返回顺序表的存储地址}elsereturn -1;
}

2.插入运算

​ 线性表的插入是指在表的第i个位置上插入一个值为x的新元素:

int Insert_SeqList(SeqList *L,int i,DataType x){int j;if(L->last==MAXSIZE-1){cout<<"装满"<<end;retrurn -1;}//表空间已满,不能插入,返回错误代码-1if(i<1||i>L->last){cout<<"位置错"<<end;return 0;}//检查插入位置的正确性for(j=L->last;j>=i-1;j--)L->data[j+1]=L->data[j];//节点移动L->data[i-1]=x;L->last++;return 1;
}

​ 顺序表上的插入运算,时间主要消耗在了数据的移动商。在第i个位置上插入x,从ai到an都要向下移动一个位置,共需要移动n-(i-1)个元素,而i的取值范围为1<=i<=n+1,即有n+1个位置可以插入。

​ 设在第i个位置上作插入的概率为Pi,则平均移动数据元素的次数

​ 考虑等概率情况,即Pi=1/ (n+1) ,则:

​ 结论:在顺序表上做插入操作需移动表中一半的数据元素。显然时间复杂度为O(n)。

⒊.删除运算

线性表的删除运算是指将表中第 i 个元素从线性表中去掉。

int Delete_SeqList(SeqList *L,int i){int j;if(i<1||i>L->last+1){cout<<"不存在第i个元素"<<end;return 0;}for(j=i;j<=L->last;j++)L->data[j-1]=L->data[j];//数据元素向前移动L->last--;return 1;
}

​ 与插入运算相同,其时间主要消耗在了移动表中元素上。删除第i个元素时,其后面的元素 ai+1~an 都要向上移动一个位置,共移动了 n-i 个元素,所以平均移动数据元素的次数:

​ 在等概率情况下,pi =1/ n,则:

​ 结论:顺序表上作删除运算时大约需要移动表中一半的元素,显然该算法的时间复杂度为O(n)。

⒋按值查找

​ 线性表中的按值查找是指在线性表中查找与给定值x相等的数据元素。

int Location_SeqList(SeqList *L,DataType x){int i;i=0;while(i<=L->last&&L->data[i]!=x)//顺序检查数据元素值i++;if(i>L->last)//到最后元素,没有找到return -1;//查找不成功,返回错误代码-1else return i;//查找成功,返回的是存储位置
}

​ 本算法的主要运算是比较。显然比较的次数与x在表中的位置有关,也与表长有关。

​ 结论:平均比较次数为(n+1)/2,时间性能为O(n)。

2.2.3顺序表应用举例

例2.1 将顺序表 (a1,a2,… ,an) 重新排列为以 a1 为界的两部分:a1 前面的值均比 a1 小,a1 后面的值都比 a1 大。

算法 基本思思想:

从第二个元素开始到最后一个元素,逐一向后扫描:

⑴当前数据元素 aI 比 a1 大时,表明它已经在 a1 的后面,不必改变它与 a1 之间的位置,继续比较下一个。

⑵当前结点若比 a1 小,说明它应该在 a1 的前面,此时将它上面的元素都依次向下移动一个位置,然后将它置入最上方。

void part(SeqList *L){int i,j;DataType x,y;x=L->data[0];//将基准数据元素置入x中for(i=1;i<=L->last;i++)if(L->data[i]<x){y=L->data[i];for(j=i-1;j>=0;j--)//前面所有元素向后移动L->data[j+1]=L->data[j];L->data[0]=y;}
}

例2.2 有顺序表A和B,其元素均按从小到大的升序排列,编写一个算法将它们合并成一个顺序表C,要求C的元素也是从小到大的升序排列。

算法思路:

依次扫描通过A和B的元素,比较当前的元素的值,将较小值的元素赋给C,如此直到一个线性表扫描完毕。

然后,将未完的那个顺序表中余下部分赋给C即可。

C的容量要能够容纳A、B两个线性表相加的长度。

void merge(SeqList A,SeqList B,SeqList *C){int i,j,k;//i,j,k分别为顺序表A、B、C当前元素指针i=0;j=0;//i,j分别指向顺序表A、B当前待处理元素k=0;//k指向顺序表C待插入元素位置while(i<=A.last&&j<=B.last)//依次扫描比较顺序表A、B中的数据元素{if(A.data[i]<B.data[j]){C->data[k]=A->data[i];k++;i++;}else   C->data[k++]=B->data[j++];while(i<=A->last)//将A中剩余元素复制到表CC->data[k++]=A->data[i++];while(j<=B->last)//将B中剩余元素复制到表CC->data[k++]=B->data[j++];C->last=k-1;}
}

算法的时间性能是O(m+n),其中m是A的表长,n是B的表长。

例2.3 比较两个线性表的大小。两个线性表的比较依据下列方法:设A、B是两个线性表,均用向量表示,表长分别为m和n。 A’和B’分别为 A 和 B 中除去最大共同前缀后的子表。

​ 例如A=(x,y,y,z,x,z), B=(x,y,y,z,y,x,x,z),两表最大共同前缀为 (x,y,y,z) 。则A’=(x,z),B’=(y,x,x,z),若A’= B’= 空表,则A=B;若A’=空表且B’≠空表,或两者均不空且A’首元素小于B’首元素,则A<B;否则,A>B。

​ 算法思路:

​ ①找出A、B的最大共同前缀;

​ ②求出A’和B’;

​ ③按规则比较进行比较A’和B’ 。

int compare(int A[],int B[],int m, int n){int i,j,AS[],BS[],ms,ns;i=0;while(i<=m&&i<=n&&A[i]==B[i])i++;//找到最大共同前缀ms=ns=0;for(j=i;j<m;j++){AS[j-i]=A[j];ms++;}for(j=i;j<n;j++){BS[j-i]=B[j];ns++;}if(ms==ns&&ms==0) return 0;else if(ms==0&&ns>0||ms>0&&ns>0&&A[i]<B[i]) return -1;else return 1;
}

算法的时间性能是O( m+n )。

2.3 线性表的链式存储和运算实现

​ 线性表的链式存储,即用一段不连续的存储空间存储数据。

2.3.1单链表

​ 单链表作为线性表的一种存储结构,对每个数据元素ai,除了存放数据元素的自身的信息 ai 之外,还需要和ai一起存放其后继 ai+1所在的存贮单元的地址,这两部分信息组成一个“结点”。

​ 在实际应用中对每个结点的实际地址并不关心,关心更多的是结点之间的逻辑结构。所以,通常的单链表用下图的形式表示。

结点定义如下:
typedef struct node{  datatype data;struct node *next;  } LNode,*LinkList;

​ 单链表结点结构

​ 存放数据元素信息的称为数据域,存放其后继地址的称为指针域。

LinkList   H;//定义头指针变量

​ 单链表的形态



2.3.2单链表上基本运算的实现

⒈建立单链表:

① 在不带头结点的链表的尾部插入结点建立单链表

建立单链表的算法一#define Flag;//Flag为根据实际情况设定的结束数据输入的标志数据LinkList Create_LinkList1(){LinkList L;LNode *s,*r;int x; //设数据元素的类型为intL=r=NULL;cin>>x;while(x!=FLag)//Flag表示输入结束{s=new LNode;s->data=x;if(L==NULL) L=s;//第一个结点的处理else r->next=s;//其他结点的处理r=s;//r指向新的尾结点cin>>x;}if(r!=NULL) r->next=NULL;//对于非空表,最后结点的指针域置为空return L;
}

②在带头结点的链表的尾部插入结点建立单链表

#define Flag;
LinkList Creat_LinkList1(){LinkList L;LNode *s,*r;int x; //设数据元素的类型为intL=r=NULL;cin>>x;while(x!=Flag)//Flag表示输入结束{s=new LNode;s->data=x;r->next=s;//插入新结点r=s;cin>>x;}if(r!=NULL) r->next=NULL;//对于非空表,最后结点的指针域置为空return L;
}


③在带头结点的链表的头部插入结点建立单链表

#define Flag;
LinkList Creat_LinkList1(){LinkList L;LNode *s;int x;L=NULL;cin>>x;while(x!=Flag){s=new LNode;s->data=x;s->next=L;L->next=s;//若是不带头结点,此句改为L=s}
}

2.求表长:

①设L是带头结点的单链表(线性表的长度不包括头结点)。

带头结点的单链表求表长算法int Length_LinkList1(LinkList L){LNode *p;int i;p=L;//p指向头结点i=0;while(p->next){p=p->next;i++;}//p指向第i个结点return i;
}

②设L是不带头结点的单链表。

int Length_LinkList2(LinkList L){LNode *p;int i;p=L;i=0;while(p){i++;p=p->next;}//p指向第i+1个结点rerturn i;
}
3.查找操作:

①按值查找,即定位 Locate_LinkList(L,x)

算法思路:从链表的第一个元素结点起,判断当前结点值是否等于x,若是,返回该结点的指针,否则继续后一个,表结束为止。找不到时返回空指针。

LNode *Locate_LinkList(LinkList L,DataType x){//在单链表L中查找值为x的结点,找到后返回其指针,否则返回空LNode *p;p=L->next;//p指向第1个数据元素结点while(p!=NULL&&p->data!=x)p=p->next;return p;
}

②按序号查找 Get_Linklist(L,i)

算法思路:从链表的第一个元素结点起,判断当前结点是否是第i个,若是,则返回该结点的指针,否则继续后一个,表结束为止。没有第i个结点时返回空指针。

LNode *Get_LinkList(LnkList L,Int i){LNode *p;int j;p=L->next;//p指向第1个数据元素结点j=1;while(p!=NULL&&j<i){p=p->next;j++;}//p指向第j个数据元素结点if(j==i) return p;else return NULL;
}
4.插入操作:

⑴后插结点:设p指向单链表中某结点,s指向待插入的值为x的新结点,将s插入到p的后面。操作如下:

​ ①s->next=p->next;

​ ②p->next=s;

注意:两个指针的操作顺序不能交换。

​ ⑵前插结点:设p指向链表中某结点,s指向待插入的值为x的新结点,将s插入到p的前面。与后插不同的是:首先要找到p的前驱q,然后再完成在q之后插入s,设单链表头指针为L,操作如下:

q=L;while (q->next!=p)       q=q->next;       s->next=q->next;  q->next=s;

插入运算 Insert_LinkList(L,i,x)

算法思路:

①找到第i-1个结点;若存在继续2,否则结束。

②申请、填装新结点。

③将新结点插入,结束。

int Insert_LinkList(LinkList L,int i,DataType x){//在单链表L的第i个位置上插入值为x的元素LNode *p,*s;p=Get_LinkList(L,i-1);//查找第i-1个结点if(p==NULL){cout<<"参数错"<<endl;return 0;}//第i-1个不存在,不能进行插入操作else{s=(LNode*)malloc(sizeof(LNode));//申请、填装结点s->data=x;s->next=p->next;//新节点插入在第i-1个结点的后面p->next=s;return 1;}
}
5.删除操作:

​ 设p指向单链表中某结点,删除*p。

​ 要实现对结点*p的删除,首先要找到 *p的前驱结点 *q,然后完成指针的操作即可。指针的操作由下列语句实现:

​ q->next=p->next;

​ free§;

​ 显然,找*p前驱的时间复杂性为O(n)。

​ 若要删除*p的后继结点(假设存在),则可以直接完成。

​ s=p->next;

​ p->next=s->next;

​ free(s);

该操作的时间复杂性为O(1)。

删除运算:Del_LinkList(L,i)

算法思路:

①找到第i-1个结点;若存在继续2,否则结束。

②若存在第i个结点则继续3,否则结束。

③删除第i个结点,结束。

int Del_LinkList(LinkList,int i){//删除单链表L上的第i个节点LinkList p,s;p=Get_LinkList(L,i-1);//查找第i个结点if(p==NULL){cout<<"第i-1个结点不存在"<<endl;return -1;}else if(p->next=NULL){cout<<"第i个结点不存在"<<endl;return 0;}else{s=p->next;//s指向第i个结点p->next=s->next;//从链表中删除free(S);//释放*sreturn 1;}
}

上述算法的时间复杂度为O(n).

2.3.3循环链表

​ 对于单链表而言,最后一个结点的指针域是空指针。如果将该链表头指针置入该指针域,则使得链表头尾结点相连,就构成了单循环链表。

​ 需要说明的是:

①单循环链表上的操作基本上与单链表相同,只是将原来判断指针是否为NULL变为是否是头指针而已;

②对于单循环链表则可以从表中任意结点开始遍历整个链表;

③考虑到对链表常做的操作是在表尾、表头进行,此时可以用一个指向尾结点的指针R来标识整个链表。

例如,对两个单循环链表H1 、H2的连接操作,是将H2的第一个数据结点接到H1的尾结点,如用头指针标识,则需要找到第一个链表的尾结点,其时间复杂性为O(n),而链表若用尾指针R、R2来标识,则时间性能为O(1)。

操作如下:

P= R–>next; /保存第一个表的头结点指针/

R->next=R2->next->next; /头尾连接/

free(R2->next); /释放第二个表的头结点/

R2->next=P; /组成循环链表/

2.3.4双向链表

问题:单链表的结点中只有一个指向其后继结点的指针域next,找后继的时间性能是O(1),找前驱的时间性能是O(n);如何提高找前驱的性能?

办法:可以付出空间的代价使得找前驱的时间性达到O(1),即每个结点再加一个指向前驱的指针域。用这种结点组成的链表称为双向链表。

双向链表结点的定义该如何调整?

typedef struct dlnode

{ datatype data;

struct dlnode *prior,*next;

}DLNode,*DLinkList;


​ 需要说明的是:

①双向链表通常也是用头指针标识,也可以带头结点和做成循环结构。

②通过某结点的指针p即可以直接得到它的后继结点的指针p->next,也可以直接得到它的前驱结点的的指针p->prior。 ③ p->prior->next == p== p->next->prior

在双向链表中插入一个结点:

设p指向双向链表中某结点,s指向待插入的值为x的新结点,将*s插入到 *p的前面。

操作如下:

①s->prior=p->prior;

②p->prior->next=s;

③s->next=p;

④p->prior=s;

思考:上面指针操作的顺序可以改变吗?

解答:指针操作的顺序不唯一,但也不是任意的。操作①必须要放到操作④的前面完成,否则*p的前驱结点的指针就丢掉了。

在双向链表中删除指定结点:

设p指向双向链表中某结点,删除*p。操作如下:

①p->prior->next=p->next;

②p->next->prior=p->prior;

③free§;

2.3.5静态链表

暂省

2.3.6单链表应用举例

暂省

2.4 顺序表和链表的比较

​ 本章介绍了线性表的逻辑结构及它的两种存储结构:顺序表和链表。

两种线性存储结构的特点

顺序存储有三个优点:

(1) 方法简单,各种高级语言中都有数组,容易实现。

(2) 不用为表示结点间的逻辑关系而增加额外的存储开销。

(3) 顺序表具有按元素序号随机访问的特点。

顺序存储也有两个缺点:

⑴ 在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低。

⑵ 需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。

链表的优缺点恰好与顺序表相反。

怎么选择存储结构?

​ 在实际中怎样选取存储结构呢?通常有以下几点考虑:

⒈ 基于存储的考虑

对线性表的长度或存储规模难以估计时,不宜采用顺序表;链表不用事先估计存储规模,但链表的存储密度较低。

⒉ 基于运算的考虑

如果经常做的运算是按序号访问数据元素,顺序表优于链表; 在顺序表中做插入、删除操作时平均移动表中一半的元素,在链表中作插入、删除操作,虽然也要找插入位置,但操作主要是比较操作,从这个角度考虑后者优于前者。

⒊ 基于环境的考虑

顺序表容易实现,任何高级语言中都有数组类型,链表的操作是基于指针的,相对来讲前者简单些,也是用户考虑的一个因素。

【课上笔记】第二章 线性表相关推荐

  1. C语言数据结构-第二章线性表-电大

    第二章线性表--内容简介 本章将进入线性结构的学习. 线性结构是最简单.最常用的一种数据结构. 本章将学习线性表的定义.顺序和链式两种存储方式及相应存储结构上的运算实现.通过典型示例训练,掌握线性表的 ...

  2. 数据结构第二章-线性表(详细知识点总结)

    目录 第二章 线性表 2.1 线性表的定义和操作 2.1.1 线性表的定义 2.1.2 线性表的基本操作 2.2线性表的顺序表示 2.2.1 顺序表的定义 2.2.2 顺序表上基本操作的实现 2.3 ...

  3. 数据结构与算法——慕课作业——第一章 概论 + 第二章 线性表

    重点题: 第一章:小测-2.4.7 第二章:小测-3 & 编程-2.3 第一章 概论 part 1: 小测验 答案: 1.C你选对了 解析:  A.向量:直接访问型线性结构  B.散列表:目录 ...

  4. 数据结构 严蔚敏 第二章 线性表

    数据结构 严蔚敏 第二章 线性表 线性表:由n个(n>=0)数据特征相同的元素构成的有限序列. 线性表的类型定义表示和实现 顺序表 存储单元地址连续 随机存取 若每个元素占用 m 个存储单元,以 ...

  5. 王道408数据结构——第二章 线性表

    文章目录 一.线性表的定义和基本操作 线性表 顺序表 1.插入操作 2.删除操作 3.按值查找(顺序查找) 二.单链表 1. 头插法 2. 尾插法 3. 按序号查找 4. 按值查找 5. 插入结点 6 ...

  6. 考研题目 第二章线性表

    导读:   一 选择题 1.下述哪一条是顺序存储结构的优点?( )[北方交通大学2001 一.4(2分)] A.存储密度大 B.插入运算方便 C.删除运算方便 D.可方便地用于各种逻辑结构的存储表示 ...

  7. 数据结构第二章线性表学习笔记

    1.    C++程序设计模板   (关于template的解释)[以下内容摘自百度] 函数声明格式 template <class(或typename) any(或任意符合规则的名称)> ...

  8. 数据结构与算法第二章 线性表、栈、队列、数组、字符串、树、二叉树、哈希表的增删查

    03 增删查:掌握数据处理的基本操作,以不变应万变 通过前面课时的学习,相信你已经建立了利用数据结构去完成时空转移的思想.接下来,你需要在理论思想的指导下灵活使用.其实,要想灵活使用数据结构,你需要先 ...

  9. 数据结构(C语言版) 第二章 线性表 知识梳理+作业习题详解

    目录 一.线性表顺序存储结构(顺序表) 0.线性表的基本概念 1.样例引入:多项式相加 二.线性表链式存储结构(链表) 0.链表的基本概念 1.前插法代码实例 2.链表尾插法完整代码附带各种操作 三. ...

最新文章

  1. 开始我的c++学习之路
  2. 图解C# 调用Win32 API 示例程序
  3. Lesson 02:变量、数据类型
  4. 致远表单代办状态删除
  5. 撩课-Java每天5道面试题第12天
  6. java学习笔记2022.1.15
  7. P4716-[模板]最小树形图
  8. redis-数据类型一览
  9. 前端都该懂的浏览器工作原理,你懂了吗?
  10. CSDN创始人蒋涛:开发者是泛终端生态的第一推动力
  11. Kafka常见面试问题
  12. mysql purge进程_MySQL数据库之Purge死锁问题解析
  13. GNSS NMEA-0183协议解析
  14. 浅谈PHP语言的优势和劣势
  15. 武神坛任务超详细解说。刷战神任务
  16. tp5子域名index.php,tp5实现绑定子域名,并且根据子域名定义路由规则
  17. linux底层把值传给上层,Android上层如何调用一个底层函数
  18. 在家阳台做个小温室,种点小白菜和菠菜
  19. 一看就觉得特别好的21条感悟
  20. PHP连接本地mysql数据库相应慢的解决方法之一

热门文章

  1. 将服务器安装为域控制器
  2. 获取服务器信息卡在99,进度条总是卡在99%,是怎么回事儿
  3. 抖音小店免费开通指南,小店保证金类目都有哪些?丨国仁网络资讯
  4. 说服别人的六种好方法
  5. 第一节 树莓派开发准备工作
  6. 民营医院咨询师培训,咨询技巧
  7. 销售杂谈(3)--客户为什么选择你?
  8. [C/C++/初学者]飞机大战 原神版
  9. 【OpenCV 例程300篇】207. Photoshop 色阶自动调整算法
  10. android 电影筛选,自己造轮子--android常用多条件筛选菜单实现思路(类似美团,爱奇艺电影票下拉菜单),--android电影票,选择实现方式若是看到第一...