数据结构详细笔记——线性表

文章目录


线性表的三要素

逻辑结构(定义)

线性表是具有相同数据类型的n(n>=0)个数据元素的有限 序列,其中n为表长,当n=0时线性表是一个空表。

若用L命名线性表,则一般表示为 L= (a1,a2,...,ai,ai+1,...an)

总结一下:线性表的逻辑结构只需要掌握以下几点:

1、数据元素同类型、有限、有序

2、表长为0则是一个空表

3、数据元素的位序(从1开始),数组的位序(从0开始)

数据的运算(基本操作)

cpp 复制代码
InitList(&L)
初始化表:构造一个空的线性表L,分配内存空间

DestroyList(&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个位置的元素的值

存储结构(物理结构)

顺序表(顺序存储)

顺序表的定义

顺序表------用顺序存储的方式实现线性表,把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中

顺序表的实现------静态分配

cpp 复制代码
#define MaxSize 10
typedef stuct{
	int data[MaxSize];
	int length;
}SqList;

初始化静态分配顺序表

cpp{​ 复制代码
void InitList(SqList &L){
	for(int i=0; i < MaxSize; i++){
		L.data[i] = 0;
	}
	L.length = 0;
}

顺序表的实现------动态分配

cpp 复制代码
#define InitSize 10
typedef struct{
	int *data;
	int MaxSize;
	int length;
}SeqList;

初始化动态分配顺序表

cpp 复制代码
void InitList(SeqList &L){
	L.data = (int *) malloc(InitSize * (sizeof(int));
	L.Maxzise = InitSize;
	L.length = 0;
}

增加动态数组的长度

cpp 复制代码
void IncreseSize(SeqList &L, int len){
	int *p = L.data;
	L.data = (int *) malloc((L.Maxsize + len) * sizeof(int));
	for(int i = 0; i< L.length; i++){
		L.data[i] = p[i];    // 将数据复制到新的区域
	}
	L.Maxsize = L.Maxsize + len;
    free(p);
 }
顺序表的特点

1、随机访问,即可以在O(1)时间内找到第i个元素。

2、存储密度高,每个节点只存储数据元素。

3、拓展容量不方便(即便采用动态分配方式实现,拓展长度的时间复杂度也比较高)。

4、插入、删除操作不方便,需要移动大量的元素。

顺序表的基本操作

插入操作------ 时间复杂度 O(n)

cpp 复制代码
#define MaxSize 10
typedef struct{
	int data[MaxSize];
	int length;
}SqList;

void InitList(SqList &L){
	for(int i = 0; i < MaxSize; i++){
		L.data[i] = 0;
	}
	L.length = 0;
}

bool ListInsert(SqList &L, int i, int 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--){  // 将第i个元素及之后的元素后移
		L.data[j] = L.data[j-1];
	}
	L.data[i-1] = e;    // 在位置i处放入e
	L.length ++;        // 长度加1
	return true;
}

int main(){
	SqList L;
	InitList(L);
	ListInsert(L,3,9);
}

删除操作 ------ 时间复杂度 O(n)

cpp 复制代码
bool ListDelete(SqList &L, int i, &e){
	if(i < 1 || i > L.length)
		return false;
	e = L.data[i-1];
	for(int j = i; j < L.length; j++){
		L.data[j-1] = L.data[j];
	}
	L.length --;
	return true;
}
int main(){
	SqList L;
	InitList(L);
	...
	int e = -1;
	ListDelete(L,3,e);
}

按位查找 ------ 时间复杂度 O(1)

cpp 复制代码
int GetElem(SqList L, int i){
	return L.data[i-1];
}

按值查找 ------ 时间复杂度 O(n)

cpp 复制代码
int LocateElem(SqList L, int e){
	for(let i = 0; i < L.length; i++){
		if(L.data[i] == e){
			return i+1;   // 返回的是位序,需要加一
		}
	}
	return 0;
}

链表(链式存储)

单链表
单链表的定义
cpp 复制代码
typedef struct LNode{
	ElmeType data;          // 每个节点存放一个数据元素
	struct LNode *next;     // 指针指向下一个节点
}LNode, *LinkList;

不带头结点的单链表

cpp 复制代码
typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;

// 初始化一个空的单链表
bool initList(LinkList &L){
	L = NULL;  // 空表,暂时还没有任何结点
	return true;
}

// 判断单链表是否为空
bool Empty(LinkList L){
	return (L == NULL)
}

void mian(){
	LinkList L;
	InitList(L);
}

带头结点的单链表

cpp 复制代码
typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

bool InitList(LinkList &L){
	L = (LNode *) malloc(sizeof(LNode));  // 分配一个头结点
	if(L == NULL)      // 内存不足,分配失败
		return false;
	L->next = NULL;     // 头结点之后暂时还没有结点
	return true;
}

// 判断单链表是否为空
bool Empty(LinkList L){
	return (L->next == NULL)
}

void mian(){
	LinkList L;
	InitList(L);
}

值得注意的点是:

LinkList 等价于 LNode *

LinkList 强调的是链表

LNode * 强调的是结点

单链表的基本操作

按位序插入(带头结点) ------ 时间复杂度 O(n)

cpp 复制代码
bool ListInsert(LinkList &L, int i, int e){
	if(i < 1)
		return false;
	LNode *p;  // 指针p指向当前扫描到的结点
	int j = 0;  // 当前p指向的是第几个结点
	p = L; // L指向头结点
	while(p != NULL && j < i-1){
		p = p->next;
		j++;
	}
	if(p == NUll)
		return false;
	LNode *s = (LNode *) malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

按位序插入(不带头结点) ------ 时间复杂度 O(n)

cpp 复制代码
bool ListInsert(LinkList &L, int i, int e){
	if(i < 1)
		return false;
	if(i == 1){
		LNode *s = (LNode *) malloc(sizeof(LNode));
		s->data = e;
		s->next=> L;
		s = L;
		return true;
	}
	LNode *p;  // 指针p指向当前扫描到的结点
	int j = 0;  // 当前p指向的是第几个结点
	p = L; // L指向头结点
	while(p != NULL && j < i-1){
		p = p->next;
		j++;
	}
	if(p == NUll)
		return false;
	LNode *s = (LNode *) malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

以上代码都有一段重复逻辑:在指定结点的后插操作,因此可以将重复逻辑封装起来
指定结点的后插操作 ------ 时间复杂度 O(1)

cpp 复制代码
bool InsertNextNode(LNode *p, int e){
	if(p == NUll)
		return false;
	LNode *s = (LNode *) malloc(sizeof(LNode));
	if(s == NULL)
	    return false;
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

指定结点的前插操作------ 时间复杂度 O(1)

cpp 复制代码
bool InsertNextNode(LNode *p, int e){
	if(p == NUll)
		return false;
	LNode *s = (LNode *) malloc(sizeof(LNode));
	if(s == NULL)
	    return false;
	s->next = p->next;
	p->next = s;
	s->data = p->data;  // 同样是后插操作,改变数据的位置,完美实现前插操作
	p->data = e;
	return true;
}

按位序删除(带头结点) ------ 时间复杂度 O(n)

cpp 复制代码
bool ListInsert(LinkList &L, int i, int e){
	if(i < 1)
		return false;
	LNode *p;  // 指针p指向当前扫描到的结点
	int j = 0;  // 当前p指向的是第几个结点
	p = L; // L指向头结点
	while(p != NULL && j < i-1){
		p = p->next;
		j++;
	}
	if(p == NUll)
		return false;
	if(p->next == NULL)
		return false; 
	LNode *q = p->next;  // 令q指向被删除结点
	e = q->data;
	p->next = q->next;
	free(q); 
	return true;
}

按位序查找(带头结点) ------ 时间复杂度 O(n)

cpp 复制代码
LNode * GetElem(LinkList L, int i){
	if(i < 0)
		return false;
	LNode *p;
	int j = 0;
	p = L;
	while(p != NULL && j < i){
		p = p->next;
		j++;
	}
	return p;
}

按值查找(带头结点) ------ 时间复杂度 O(n)

cpp 复制代码
LNode * LocateElem(LinkList L, int e){
	LNode *p = L->next; // 从第一个结点开始寻找
	while(p != NULL && p->data != e){
		p = p->next;
	}
	return p;
}
尾插法建立单链表
cpp 复制代码
LinkList List_TailInsert(LinkList &L){
	int x;
	L = (LNode *) malloc(sizeof(LNode));  // 建立头结点
	LNode *s,*r=L;  // r为表尾指针
	scanf("%d",&x);
	while(x!=9999){  // 表示结束
		s = (LNode *) malloc(sizeof(LNode));
		s=>data = x;
		r->next = s;
		r = s;    // r指向新的表尾结点
		scanf("%d",&x);
	}
	r->next = NULL;
	return L;
}
头插法建立单链表
cpp 复制代码
LinkList List_HeadInsert(LinkList &L){
    LNode *s;
	int x;
	L = (LNode *) malloc(sizeof(LNode));  // 建立头结点
	L->next = NULL;
	scanf("%d",&x);
	while(x!=9999){  // 表示结束
		s = (LNode *) malloc(sizeof(LNode));
		s=>data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d",&x);
	}
	return L;
}
双链表
双链表的定义
cpp 复制代码
typedef struct DNode{
	int data;
	struct DNode *prior,*next;
}DNode,*DLinkList;

双链表的初始化(带头结点)

cpp 复制代码
bool InitDLinkList(DLinkList &L){
	L = (DNode *) malloc(sizeof(DNode));
	if(L == NULL)
		return false;
	L->prior = NULL;   // 头结点的 prior 永远指向null
	L->next = NULL;
	return true;
}

双链表的插入

cpp 复制代码
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	if(p == NULL || s == NULL)
		return false;
	s->next = p->next;
	if(p->next != NULL)
		p->next->prior = s;
	s->prior = p;
	p->next = s;
	return true;
}

双链表的删除

cpp 复制代码
// 删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
	if(p = NULL)
		return false;
	DNode *q = p->next;
	if(q == NULL)
		return false;
	p->next = q->next;
	if(q->next != NULL)
		q->next->prior = p;
	free(q);
	return true;
}

双链表的销毁

cpp 复制代码
void DestroyList(DLinkList &L){
	while(L->next != NULL){
		DeleteNextDNode(L);
	}
	free(L);
	L = NULL;
}

双链表的遍历 ------ 时间复杂度 O(n)

cpp 复制代码
// 后向遍历
while(p != NULL)
	p = p->next;

//前向遍历
while(p != NULL)
	p = p->prior;
	
//前向遍历(跳过头结点)
while(p->prior != NULL)
	p = p->prior;
循环链表
循环单链表

初始化循环单链表

cpp 复制代码
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode,*LinkList;

bool InitList(LinkList &L){
	L = (LNode *) malloc(sizeof(LNode));
	if(L == NULL)
		retun false;
	L->next = L;   // 头结点的next指向头结点
	return true;
}

判断循环单链表是否为空

cpp 复制代码
bool Empty(LinkList L){
	if(L->next == L)
		return true;
	else
		return false;
}

判断结点p是否为循环单链表的表尾结点

cpp 复制代码
bool isTail(LinkList L, LNode *p){
	if(p->next == L)
		return true;
	else
		return false;
}
循环双链表

初始化循环单链表

cpp 复制代码
typedef struct DNode{
	int data;
	struct DNode *prior,*next;
}DNode,*DLinkList;

bool InitDLinkList(DLinkList &L){
	L = (DNode *) malloc(sizeof(DNode));
	if(L == NULL)
		return false;
	L->prior = L; 
	L->next = L;
	return true;
}

判断循环双链表是否为空

cpp 复制代码
bool Empty(DLinkList L){
	if(L->next == L)
		return true;
	else
		return false;
}

判断结点p是否为循环双链表的表尾结点

cpp 复制代码
bool isTail(DLinkList L, LNode *p){
	if(p->next == L)
		return true;
	else
		return false;
}

判断结点p是否为循环双链表的表尾结点

cpp 复制代码
bool isTail(DLinkList L, LNode *p){
	if(p->next == L)
		return true;
	else
		return false;
}

双链表的插入

cpp 复制代码
// 在p结点之后插入s结点
bool InsertNextDNode(DNode *p, DNode *s){
	s->next = p->next;
	p->next->prior = s;
	s->prior = p;
	p->next = s;
}
静态链表

静态链表:分配一整片连续的内存空间,各个结点集中安置

定义静态链表

cpp 复制代码
#define MaxSize 10
typedef struct{
	ElemType data;   // 存储数据元素
	int next;        // 下一个元素的数组下标
}SLinkList[MaxSize];

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

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

顺序表与链表的区别

1、逻辑结构

都是属于线性表,都是线性结构

2、存储结构

顺序表:

优点:支持随机存取,存储密度高

缺点:大片连续空间分配不方便,改变容量不方便
链式表

优点:离散的小的空间分配方便,改变容量方便

缺点:不可以随机存取,存储密度低
用顺序表 or 链表?

表长难以预估、经常要增加、删除元素------------链表

表长可预估,查询(搜索)操作比较多------------顺序表

相关推荐
Jerry1 小时前
LeetCode 459. 重复的子字符串
算法
海石4 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石4 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry6 小时前
LeetCode 151. 反转字符串中的单词
算法
gugucoding7 小时前
31. 【C语言】堆栈与队列的实现
c语言·开发语言·数据结构·链表
β添砖java7 小时前
2025最新快速构建企业级虚拟专用网络环境学习笔记
笔记·学习
qianpeng47 小时前
IDA PRO笔记
笔记
ChaoZiLL8 小时前
我的数据结构3——链表(link list)
数据结构·链表
a1117769 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜9 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai