线性表


线性表是最简单,前后排的数据结构

线性表基本概念

具有相同特性的数据元素的有限序列

线性表特点

  • 元素个数有限
  • 元素具有逻辑上的顺序性
  • 元素是数据元素
  • 元素具有抽象性

线性表是逻辑结构,表示元素之间一对一的相邻关系
顺序表和单链表是存储结构

顺序表

存储方式

  • 除第一个元素与最后一个元素,其他每个元素都有一个直接前驱和一个直接后继
  • 存储地址连续,逻辑顺序与物理顺序相同
  • 顺序表中每个元素占$k$个存储单元,第1个元素存储地址为$m$,则第$i$个元素的存储地址是$mk(i-1)$

代码

定义

静态分配

typedef struct{  // 顺序表定义 
	ElemType data[MaxSize];  // 顺序表的元素
	int length;  // 当前长度 
}SeqList;

动态分配

typedef struct{  // 顺序表定义 
	ElemType *data;  // 数据表元素基址
	int MaxSize;  // 数据最大容量 
	int length;  // 数据的当前个数 
}SeqList;

时间复杂度:$O(n)$

初始化

Status Init(SeqList L){  // 初始化顺序表 
	L.data = (ElemType*)malloc(sizeof(ElemType)*InitSize); // 分配存储空间
	if(! L.data)  // 分配空间失败 
		return false; 
	L.length = 0;  // 空表长度为0
	L.MaxSize = InitSize;  // 初始存储容量	
    return true;
}

插入元素

/* 插入元素
L:顺序表
i:插入位置  1 <= i <= L.length + 1 
x:插入元素
返回:成功 1
      失败 0
*/ 
bool ListInsert(SeqList &L, int i, ElemType x){  
	if(i < 1 || i > L.length + 1)
		return false;
	if(L.length >= L.MaxSize)  // 存储空间已满
		return false;
	for(int j = L.length ; j > i ; j--)  // i元素及之后的元素后移
		L.data[j] = L.data[j-1];
	L.data[i-1] = x;  // 赋值 
	L.length++;  // 表长+1
	return true; 
}

最好情况:$\text{O}(1)$
最坏情况:$\text{O}(n)$
平均情况:$\text{O} (n)$

  • 插入$i$位置移动$n-i$个元素
  • 平均移动个数$\dfrac{n}{2}$

删除元素

/*删除元素
L:顺序表
i:删除位置  1 <= i <= L.length
x:删除元素
返回:成功 1
      失败 0
*/
bool ListDelete(SeqList &L, int i, ElemType &x){
	if(i < 1 || i > L.length)  // 判断i是否符合范围 
		return false;
	x = L.data[i-1];  // 删除元素赋值
	for(int j = i ; j < L.length ; j++)  // 将i位置后的元素前移
		L.data[j-1] = L.data[j];
	L.length--;  // 表长-1
	return true; 
}

最好情况:$\text{O}(1)$
最坏情况:$\text{O}(n)$
平均情况:$\text{O} (n)$

  • 在$i$位置删除元素需要移动$n-i-1$个元素
  • 平均移动:$\dfrac{n-1}{2}$

元素查找

/*查找素
L:顺序表
x:查找元素
返回:成功 1
      失败 0
*/
ElemType LocateElem(SeqList L, ElemType x){
	for(int i = 0 ; i < L.length ; i++)
		if(L.data[i] == x)
			return i+1;  // 下标为i,位置i+1
	return  false;
}

最好情况:$\text{O}(1)$
最坏情况:$\text{O}(n)$
平均情况:$\text{O} (n)$

逆置

/*
逆置顺序表
left:左起始
right:右起始
i<j:i和j互换或者i和j相等都停止
*/
for(int i=left, j=right ; i < j ; ++i, --j){  
	temp = L.data[i];
	L.data[i] = L.data[j];
	L.data[j] = temp;
}

单链表

存储方式

链式存储结构

代码

定义

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

带表头单链表

  • 空表——head->link == NULL

初始化

头插法

/*
逆向建立单链表
L:链表
返回L 
*/
LinkList List_HeadInsert(LinkList &L){
	LNode *node;  // 结点
	ElemType x;  // 每个结点的数据
	L = (LinkList)malloc(sizeof(LNode));  // 创建头结点
	L->next = NULL;  //初始为空链表
	scanf("%d", &x);  // 输入结点的值
	while(x != 9999){  // 输入9999表示结束 
		node = (LNode*)malloc(sizeof(LNode));  // 创建新结点
		node->data = x;  // x赋值 
		L->next = node;  // 头指针指向node结点
		scanf("%d", &x); 
	} 
	return L;
}

时间复杂度:$\text{O}(n)$

尾插法

/*尾插法
正向建立单链表
L:链表头结点 
返回L 
*/
LinkList List_TailInsert(LinkList &L){
	ElemType x;  // 插入元素
	LNode *node;  // 结点指针
	LNode *rear = L;  // r为尾指针;初始尾指针与头指针一起
	scanf("%d", &x);  // 输入结点值 
	while(x != 9999){  // 输入9999表示结束 
		 node = (LNode*)malloc(sizeof(LNode));  // 创建头结点
		 node->data = x;  // x赋值 
		 rear->next = node;  // 尾指针指向结点
		 rear = node;  // 该结点成为新的尾结点
		scanf("%d", &x); 
	} 
	rear->next = NULL;  // 尾结点置空
	return L; 
} 

时间复杂度:$\text{O}(n)$

查找

按序号查找

/*按序号查找结点 
L:链表头结点 
i:查询位置
返回:第i个元素的指针 
*/
LNode* GetElem(LinkList L, int i){
	int j = 1;  // 计数,初始为1
	LNode *node = L->next;  // 头结点指向结点
	if(i < 0)  // 若i无效,则返回NULL
		return NULL; 
	if(i==0)  // 若i==0,则返回头结点 
		return L;
	while(node && j<i){ // 从第一个结点开始,查找第i个结点
		node = node->next;  // 指向下一个结点
		j++;  // 计数+1 
	}
	return node;  // 找到后返回该指针,否则返回NULL 
} 

时间复杂度:$\text{O}(n)$

按值查找

/*按序值找结点 
L:链表头结点 
x:查询元素 
返回:元素结点指针 
*/
LNode* LocateElem(LinkList L, ElemType x){
	LNode *node = L->next;  // 结点为头结点所指向
	while(node != NULL  && node->data == x)  // 结点不为空且结点数据为x 
		node = node->next;  // 结点指向下一个 
	return node;  // 找到后返回该指针,否则返回NULL 
}

时间复杂度:$\text{O}(n)$

插入

按位置插入结点

p = GetElem(L, i-1);  // 插入位置的前驱结点
node->next = p->next;  // 待插入结点指向前驱结点所指向的后结点
p->next = node;  //前驱结点指向插入节点

时间复杂度:$\text{O}(n)$
已知结点后插:$\text{O}(1)$

某一结点前插

node->next = p->next;
p->next = node;  // 先执行后插

temp = p->data;
p->data = node->data;
node->data = temp;  // 交换node和p的数据值

时间复杂度:$\text{O}(1)$

删除

按位置删除结点

p = GetElem(L, i-1);  // 插入位置的前驱结点
node = p->next;  // node指向被删除结点
p->next = node->next;  // 前驱直接指向后结点
free(node);  // 释放结点存储空间

时间复杂度:$\text{O}(n)$

删除指定结点*p

node = p->next;  // node指向p后继
p->data = p->next->data;  // 交换数据域
p->next = node->next;  // node断开
free(node);  // 释放存储空间

时间复杂度:$\text{O}(1)$

求表长

时间复杂度:$\text{O}(n)$

逆置

p = L;  // p为头结点
q = rear;  // q为尾结点
do{
    temp = p->next;  // 临时指针取p后继结点
    p->next = temp->next;  // temp结点断开
    temp->next = q->next;  
    q->next = temp;  // temp结点移动q之后
}while(p->next == q)  // p q相遇停止

双向链表

存储方式

 双向链表存储结构

代码

定义

双向链表结点

typedef struct DNode
{
    ElemType data; // 数据域
    struct DNode *prior; //左指针域
    struct DNode *next; //右指针域
}DNode,DLinkList;

插入

q->llink = p->llink; // q左指针指向p左指针指向的结点
q->rlink = p; // q右指针指向p结点
p->llink->rlink = q; // p左指针指向的结点(即p原左结点)右指针指向q结点
p->llink = q; // p左指针指向q

删除

p->llink->rlink = p->rlink; // p的左结点直接指向p的右结点
p->rlink->llink = p->llink; // p的右结点直接指向p的左结点
free(p); // 释放p

循环链表

存储方式

循环链表

  • p指针指向尾结点条件p->link = first
  • 链表为空的条件first == NULL

静态链表

存储方式

用数组描述线性表的链式存储

下标datanext的下标

结束标志:next==-1

代码

定义

typedef struct{  // 定义静态链表 
	ElemType data;  // 数据域 
	int next;  // 下一个元素的下标 
}SLinkList[MaxSize];
int p = index_0;  // 定义一个指针
SL[p].data;  // p指针数据域
SL.next;  // p指针指针域

线性表优劣比较

各线性结构比较

顺序表 单链表 带表头的链表 循环链表 双向链表
查找速度$O(1)$ 查找速度$O(n)$
不需要估计存储长度
方便插入和删除操作的实现 从表中任意结点出发都能扫描整个链表 可快速访问直接前驱
增删速度$O(n)$
需要先估计存储空间
增删速度$O(1)$
增删中头结点需要单独考虑

顺序表 & 链表

顺序表 链表
存取方式 可顺序存储,也可随机存储 只能表头顺序存储
逻辑结构 & 物理结构 逻辑上相邻物理存储也相邻 逻辑上相邻物理上不一定相邻
CURD操作 按值查找:
无序:$O(n)$
有序:$O(n\log n)$
按序号查找:$O(1)$
查找:$O(n)$
空间分配 存储密度大
要预分配空间
存储密度小
只要又空间就可以分配,更灵活高效

选择存储结构

顺序表 链表
基于存储的考虑 难以预测存储规模时,不宜采用顺序表
基于运算考虑 按序号访问元素较多 增删较多
基于环境考虑 实现较为简单

文章作者: Jarrycow
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jarrycow !
评论
  目录