线性表
阅读目录
一、线性表类型定义
二、顺序表
三、链表
四、总结
一、线性表类型定义
1.定义
线性表是n个数据元素的有限序列
2.基本操作
InitList(&L) #构造一个空的线性表L
DestroyList(&L) #销毁线性表L
ClearList(&L) #将L重置为空表
ListLength(L) #返回L中数据元素个数
GetItem(L,i,&e) #用e返回L中第i个数据元素的值
LocateElem(L,e,compare()) #返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据远古三不存在,则返回值为0
ListInsert(&L,i,e) #在L中第i个位置之前插入新的数据元素e,L的长度加1
ListDelete(&L,i,&e) #删除L的dii个数据元素,并用e返回其值,L的长度减1
二、顺序表
1.定义
线性表的顺序存储结构称为顺序表。
假设线性表的每个元素需占用l个存储单元,一般来说,线性表的第i个数据元素ai的存储位置为LOC(ai) = LOC(a1) + (i-1)*l
2.实现
由于高级程序设计语言中的数组类型也有随机存取的特性,因此,通常都用数组来描述数据结构中的顺序存储结构。在此,由于线性表的长度可变,且所需最大存储空间随问题不同而不同,则在C语言中可用动态分配的一维数组
顺序开始为为1,区别于数组下标开始为0
2.1数据静态分配
#define MAXSIZE 50
typedef struct{
ElemType elem[MAXSIZE]
int length;
int listsize
}SqList;
2.2数据动态分配
#define LIST_INIT_SIZE 100 //线性表存储空间的初始分配量
#define LISTINCREMENT 10 //线性表存储空间的分配增量
#define MAXSIZE 50
typedef struct{
ElemType *data; //存储空间的初始化分配量
int length; //当前长度
int listsize //当前分配的存储容量(以sizeof(ElemType)为单位)
}SqList;
动态分配语句:
C L.elem = (ElemType*)malloc(sizeof(ElemType)*InitSize);
C++ L.elem = new ElemType(InitSize);
2.3常用方法(C++)
2.3.1插入
bool ListInsert(sqList &L, int i, ElemType e){
if(i <1 || i>L.length+1){ //插入的时候可以在最后插入,可以在最后插入,即length+1位,但是不能超过这一位(线性连续)
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;
}
2.3.2删除
bool ListDelete(sqList &L, int i, ElemType &e){
if(i <1 || i>L.length){ //范围为1<=i<=L.length
return false;
}
e = L.data[i]
for(int j=i;j<L.length;j++){
L.data[j-1] = L.data[j]
}
L.length--;
return true;
}
2.3.3查找
int LocateElem(sqList L, ElemType e){
int i;
for(i=0;i<L.length;i++){
if(L.data[i] == e){
return i+1; //返回数据索引下标+1即为值
}
}
return 0; //未查找到
}
二、链表
1.线性链表
1.1特点
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。它包含两个域:其中存储数据元素信息的域称为数据域;存储直接后继存储位置的域称为指针域。由于此链表的每个结点只包含一个指针域,固有称为线性链表或单链表。
1.2实现
线性表单链表存储结构
typedef struct LNode{
ElemType data;
struct LNode *next;
}LNode, *LinkList;
NOTE:在单链表第一个结点之前附设一个节点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等类的附加信息,头结点的指针域存储指向第一个及结点的指针
1.3基本操作
1.3.1创建
void CreateList_L(LinkList &L, int n){
//逆位序输入n个元素的值,建立带表头结点的单链线性表L
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL; //建立一个带表头的单链表
for(i=n;i>0;--i){
p=(LinkList)malloc(sizeof(LNode)); //生成新结点
scanf(&p->data); //输入元素值
p->next=L->next; L->next=p;
}
}
1.3.2GetElem
LNode *GetElem(LinkList L, int i){
int j=1;
LNode *p = L->next;
if(i==0){
return L;
}
if(i<1){
return NULL;
}
while(p && j<i){
p = p->next;
j++;
}
return p;
}
1.3.3插入
bool ListInsert_L(LinkList &L, int i ,ElemType e){
//在带头结点的单链线性表L中第i个位置之前插入元素e
p=L;j=0;
while(p && j<i-1){p=p->next;++j;} //寻找第i-1个结点
if(!p || j>i-1) return false; //i小于1或者大于表长加1
s = (LinkList)malloc(sizeof(LNode)); //生成新结点
s->data=e; s->next=p->next; //插入L中
p->next=s;
return true;
}
1.3.4删除
bool ListDelete_L(LinkList &L,int i, ElemType &e){
//带头结点的单链线性表L中,删除第i个元素,并由e返回其值
p=L;j=0;
while(p->next && j<i-1){p=p->next;++j;} //寻找第i-1个结点
if(!q->next || j>i-1) return false;
q = p->next; p->next = q->next; // p->next = p->next->next
e = q->data; free(q); //释放资源
return true;
}
1.3.5定位查找值
LNode *LocateElem(LinkList L, ElemType e){
//L中查找第1个值为e的结点
LNode *p = L->next;
while(p!=NULL && p->data!=e){
p = p->next;
}
return p;
}
2.循环链表
2.1特点
循环链表是另一种形式的链式存储结构,它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环
3.双向链表
3.1特点
单链表中寻找直接前趋结点,需要遍历单链表查找,时间复杂度O(n),为克服这种问题,定义两个指针域,其一指向后继,另一个指向直接前趋
3.2实现
typedef struct DuLNode{
ElemType data;
struct DuLNode *prior, *next;
}DuLNode, *DuLinkList;
3.3基本操作
3.3.1删除
bool ListDelete_Dul(DuLinkList &L, int i, ElemType &e){
//删除带头结点的双链循环线性表L的第i个元素,i的合法值为1<=i<=表长
if(!(p = GetElemP_Dul(L, i))) //在L中确定第i个元素的位置指针p
return false;
e = p->data;
p->prior->next = p->next;
p->next->prior = p->prior;
free(p) //释放p资源
return true;
}
3.3.2插入
bool ListInsert_Dul(DuLinkList &L, int i, ElemType e){
//带头结点的双链循环线性表L中第i个位置之前插入元素e
if(!(p = GetElemP_Dul(L, i))) return false;
if(!(s = (DuLinkList)malloc(sizeof(DuLNode)))) return false;
s->data = e;
s->prior = p->prior; p->prior->next = s;
s->next = p; p-prior = s;
return true;
}
4.静态链表
4.1特点
静态链表用数组实现,类似于单链表,存在数据域及指针域,不过指针域替换为数组的下标,最后一位指针域值为-1
4.2实现
#define MAXSIZE = 50
typedef struct DNode{
ElemType data;
int next;
}SLinkList[MAXSIZE];
四、总结
1.顺序表与链表对比
1.1存取方式
顺序表可以实现顺序存取和随机存取
单链表只能实现顺序存取
1.2逻辑结构及物理结构
顺序表逻辑相邻物理上也相邻,通过相邻表示逻辑关系
单链表逻辑相邻但物理不一定相邻,通过指针表示逻辑关系
1.3基本操作
插入&删除:
单链表为O(1)(结点指针已知);O(n)(结点指针未知)但只需修改指针;
顺序表O(n)且需要大量移动元素;
查找:
按值查找中单链表和顺序表(无序)都为O(n);
按序查找单链表为O(n),顺序表为O(1);
1.4内存空间
顺序存储:无论静态分配还是非静态分配都需要预先分配合适的内存空间
1.动态分配虽然不会溢出但是扩充需要大量移动元素,效率低;
2.静态分配预分配空间太大会造成浪费,太小会导致溢出;
链式存储:在需要时分配结点空间即可,高效方便,但指针需要使用额外空间
1.4如何选择使用
存储规模难估计:单链表
存储密度大:顺序表
按序号访问:顺序表
基于数据:顺序表
插入和删除:单链表
基于指针:单链表
2.常用操作
2.1求最值
2.2倒置
链表倒置
//p表示头结点,r表示尾结点
while(p->next!=r){
tmp = p->next;
p->next = tmp->next;
tmp->next = r->next;
r->next=tmp;
}
2.3归并
//单链表
void MergeList_L(LinkList &La,LinkList &Lb, LinkList &Lc){
//已知单链线性表La和Lb的元素按值非递减排列
pa = La->next; pb = Lb->next;
Lc = pc = La; //用La头结点作为Lc的头结点
while(pa && pb){
if(pa->data <= pb->data){
pc->next = pa; pc=pa; pa=pa->next; //查找帮助结点pc及pa都向后走
}else{
pc->next = pb; pc=pb; pb=pb->next;
}
}
pc->next = pa?pa:pb; //插入剩余段 因为存在La/Lb先插完,则Lb/La剩余直接插入到最后;
free(Lb); //释放Lb
}
//顺序表
void MergeList_XL(LinkList &La,LinkList &Lb, LinkList &Lc){
int i=0;j=0;
int k=0;
for(;i<sizeof(La)/sizeof(*La) && j<sizeof(Lb)/sizeof(*Lb);k++){
if(La[i] < Lb[j]){
L[k] = La[i++];
}else{
L[k] = Lb[j++]
}
}
while(i<sizeof(La)/sizeof(*La)){
L[k++] = La[i++]
}
while(j<sizeof(Lb)/sizeof(*Lb)){
L[k++] = Lb[j++]
}
}