线性表(考研复习用)

定义

  • 线性表是n个具有相同数据类型的数据元素的有限序列
  • n=0时,为空表
  • 第一个数据元素只有一个唯一的直接后继
  • 最后一个数据元素只有一个唯一的直接前驱
  • 其他元素有且仅有一个直接前驱和一个直接后继

特点

  • 元素有限;
    • 全部整数组成的序列不是线性表,因为元素不是有限的
  • 元素具有逻辑上顺序性 ,各元素有先后顺序;
  • 表中元素都是数据元素,每个元素都是单个元素;
  • 各元素数据类型相同,每个元素所占存储空间相同

基本操作

InitList(&L):初始化线性表,构造一个空表

DestroyList(&L):销毁线性表,释放线性表所占内存空间

ClearList(&L):清空线性表,置为空表

ListEmpty(L):判断线性表是否为空表

ListLength(L):求表长

GetElem(L, i, &e):按位查找,获取第i个元素的值

LocateElem(L, e):按值查找,在线性表中查找第一个满足值为e的元素

PriorElem(L, cur_e, &pre_e):获取当前元素的前驱

NextElem(L, cur_e, &next_e):获取当前元素的后继

ListInsert(&L, i, e):在线性表的第i个位置插入元素e

ListDelete(&L, i, &e):删除线性表的第i个位置的元素,并用e返回该元素的值

ListTraverse(L, visit()):依次对线性表的每个数据元素调用visit()

线性表的顺序存储结构

  • 线性表的顺序存储需要一组地址连续的存储单元;
  • 顺序表使得逻辑上相邻的元素在物理位置上也相邻;
  • 线性表的第i个数据元素的a_i的存储位置为
    L O C ( a i ) = L O C ( a 1 ) + ( i − 1 ) × l 其 中 l 是 每 个 数 据 元 素 所 占 的 内 存 空 间 LOC(a_i)=LOC(a_1)+(i-1)\times{l} \\ 其中l是每个数据元素所占的内存空间 LOC(ai)=LOC(a1)+(i1)×ll
  • 表中元素的逻辑顺序与其物理顺序相同;
  • 线性表的顺序存储结构是一种随机存取的存储结构。

在这里插入图片描述

顺序表的特点

  • 随机访问,可在O(1)内找到第i个元素;
  • 存储密度高,每个结点只存储数据元素;
  • 扩展容量不方便
  • 插入、删除不方便,需要移动大量数据。

顺序表的描述

// ———————线性表的静态分配顺序存储结构——————
#define MAXSIZE 50
typedef struct{
	ElemType data[MAXSIZE];		// 顺序表的元素
	int length;		// 当前长度
}SqList;

//——————-线性表的动态分配顺序存储结构——————-
#define INIT_SIZE 100
typedef struct{
	ElemType *elem;		// 存储空间的基址
	int length;		// 当前长度
	int MaxSize;		// 数组最大容量
}SeqList;

顺序表的操作

顺序表结构体

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OVERFLOW -1

#define INIT_SIZE 100
#define LIST_INCREASE 10
typedef struct{
	int *data;
	int length;
	int MaxSize;
}SeqList;

初始化顺序表

Status InitList(SeqList &L){
	// malloc申请一片连续的存储空间
	L.data = (int *)malloc(sizeof(int) * InitSize);
	if (! L.data)
		return ERROR;
	L.length = 0;
	L.MaxSize = INIT_SIZE;
	return OK;
}

初始动态分配语句中malloc,申请一整片连续的存储空间,会返回指向这片存储空间开始地址的指针

增加动态数组的空间

Status IncreaseSize(SeqList &L, int len){
	int *p = L.data;
	L.data = (int *)malloc((L.MaxSize + len)*sizeof(int))
	if (! L.data)
		return ERROR;
	for(int i=0; i<L.length; i++){
		L.data[i] = p[i];
	}
	L.MaxSize = L.MaxSize + len;
	free(p);
	return OK;
}

// 使用realloc
Status IncreaseSize(SeqList &L, int len){
	newbase = (int *)realloc(L.data, (L.MaxSize + len)*sizeof(int));
	if (! newbase)
		return ERROR;
	L.data = newbase;
	L.MaxSize += len;
	return OK;
}

顺序表的插入

在顺序表L的第i个位置插入元素e,在最后一个元素插入不需要移动其他元素位置

Status ListInsert(SeqList &L, int i, ElemType e){
	if (i < 1 && i > L.length+1)
		return ERROR;
	if (L.length >= L.MaxSize){
		if (! IncreaseSize(L, LIST_INCREASE))
			return OVERFLOW;
	}
	for (int j=L.length; j>=i; j—){
		L.data[j] = L.data[j-1] ;
	}
	L.data[i-1] = e;
	L.length++;
	return OK;
}
  • 顺序表插入的时间复杂度:
    • 最好:O(1),在表尾插入元素;
    • 最坏:O(n),在表头插入元素,需要移动n次;
    • 平均:O(n+1),平均移动n/2次。

顺序表的删除

删除顺序表L的第i个位置的元素,在删除最后一个元素不需要移动其他元素。

Status ListDelete(SeqList &L, int i, ElemType &e){
	if (i < 1 && i > L.length)
		return ERROR;
	e = L.data[i-1]
	for (int j=i; j<L.length; j++){
		L.data[j-1] = L.data[j];	
	}
	L.length—;
	return OK;
}
  • 顺序表的删除的时间复杂度:
    • 最好:O(1),删除表尾元素;
    • 最坏:O(n),删除表头元素,移动n-1次;
    • 平均:O(n),平均移动(n-1)/2。

顺序表的查找

在顺序表中查找第一个元素值为e的元素

int LocateList(SeqList L, ElemType e){
	for (int j=0; j<L.length; j++){
		if (L.data[j] == e)
			return j+1;
	}
	return 0;
}
  • 顺序表的查找的时间复杂度:
    • 最好:O(1),查找的元素在表头;
    • 最坏:O(n),查找的元素在表尾;
    • 平均:O(n),查找的平均次数(n+1)/2。

线性表的链式存储结构

  • 不需要使用地址连续的存储单元,逻辑上相邻的元素物理位置上不一定相邻
  • 插入和删除元素不需要移动元素,只需修改指针;
  • 不可随机存取。

单链表

定义

  • 通过一组任意的存储单元来存储线性表中的数据元素;
  • 对每个链表结点,需要存放元素自身信息和一个指向其后继的指针

单链表的描述

typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;
// LNode 强调这是结点
// LinkList 强调这是链表
  • 头结点和头指针的区别:
    • 头指针始终指向链表的第一个结点;
    • 头结点是带头结点的链表的第一个结点,结点内通常不存储信息;
  • 引入头结点的优点:
    • 使链表在第一个数据结点上的操作和表的其他位置操作一致;
    • 无论链表是否为空,其头指针都指向头结点的非空指针,空表和非空表的处理得到统一。

单链表的基本操作

单链表的初始化

初始化一个空表

LinkList InitLink(){
	LinkList L;
	L = (LinkList)malloc(sizeof(LNode));
	if (! L)
		return ERROR;
	L->next = NULL;
	return L;
}
单链表的建立
头插法

从空表开始,生成新结点,将新结点插入到当前链表的表头

在这里插入图片描述

Status List_HeadInsert(LinkList &L){
	LNode *s;
	int num, x, i=0;
	printf(“输入链表长度:”);
	scanf(%d”, &num);
	while(i < num){
		s = (LNode *)malloc(sizeof(LNode));
		if (s == NULL)
			return ERROR;				
		printf(“输入第%d个数据:”, i);
		scanf(%d”, x);
		s->data = x;
		s->next = L->next;
		L->next = s;
		i++;
	}
	return OK;
}
  • 读入数据的顺序与生成链表元素的顺序相反
  • 头插法建立链表的时间复杂度:
    • 每个结点插入时间为O(1);
    • 建立的链表表长为n,总时间复杂度为O(n)。
尾插法

建立链表时将新生成的结点插入到表尾

在这里插入图片描述

Status List_TailInsert(LinkList &L){
	LNode *s, *r=L;
	int num, x, i=0;
	printf(“输入链表长度:”); 	
	scanf(%d”, &num);
	while(i < num){
		s = (LNode *)malloc(sizeof(LNode));
		if (s == NULL)
			return ERROR;
		printf(“输入第%d个数据:”, i); 		
		scanf(%d”, x);
		s->data = x;
		r->next = s;
		r = s;
	}
	r->next = NULL;
	return OK;
}
  • 尾插法建立链表的时间复杂度与头插法相同。
单链表的查找
按序号查找

从第一个结点开始,直到找到第i个结点为止

LNode *GetElem(LinkList L, int i){
	int j = 1;
	LNode *p = L->next;
	if (i == 0)
		return L;
	if (i < 0)
		return NULL;
	while (p && j<i){
		p = p->next;
		j++;
	} 
	return p;
}
  • 按序查找操作的时间复杂度是O(n)
按值查找
LNode * LocateElem(LinkList L, ElemType e){
	LNode *p = L->next;
	while (p!=NULL && p->data!=e){
		p = p->next;
	}
	return p;
}
  • 按值查找的时间复杂度是O(n)
单链表的插入
后插

将值为x的结点插入到链表的第i个位置
先找到待插入结点的前驱结点,再在其后面插入新结点

在这里插入图片描述

Status InsertLink(LinkList &L, int i, ElemType e){
	LNode *p, *s;
	s = (LNode *)malloc(sizeof(LNode));
	if (s == NULL)
		return ERROR;
	p = GetElem(L, i);
	s->next = p->next;
	p->next = s;
}
  • 插入结点的主要时间开销在于查找插入位置
  • 平均时间复杂度为O(n),最好的时间复杂度是O(1)
前插

在某个结点前面插入到一个新结点
解法一:找到第i-1个结点,采用后插操作
解法二:找到第i个结点,执行后插操作后,交换前后结点的值

// 解法二
Status InsertPrior(LinkList &L, int i, ElemType e){
	LNode *s, *p;
	int temp;
	s = (LNode *)malloc(sizeof(LNode));
	if (s == NULL)
		return ERROR;
	p = GetElem(L, i);
	s->next = p->next;
	p->next = s;
	temp = p->data;
	p->data = s->data;
	s->data = temp;
	return OK;,
}
单链表的删除

删除第i个结点

在这里插入图片描述

Status DeleteNode(LinkList &L, int i, ElemType &e){
	LNode *p = L;
	LNode *q;
	// 先找到第i-1个结点,p指向第i个结点的前驱
	while (p->next && j<i-1){
		p = p->next;
		++j;
	}
	if (!(p->next) && j>i-1)
		return ERROR;
	q = p->next;
	p->next = q->next;
	e = q->data;
	free(q);
	return OK;
}
求表长

包含头结点的链表在计算表长时不需要将头结点计算进去

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

静态链表

  • 借用数组来描述线性表的链式存储结构
  • 链表的指针是数组的下标
  • 静态链表需要分配一块连续的内存空间

静态链表的描述

#define MaxSize 50
typedef struct{
	ElemType data;
	int next;	// 下一元素的数组下标
}SLinkList[MaxSize];
  • 静态链表以next=-1为链表结束标志
  • 静态链表的增删只需要修改next,不需要移动元素

双链表

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

在这里插入图片描述

双链表的描述

typedef struct DNode{
	ElemType data;
	struct DNode *prior, *next;
}DNode, *DLinkList;

基本操作

插入操作

在这里插入图片描述

Status ListInsert_Dul(DLinkList &L, int i, ElemType e){
	DNode *p, *s;
	// 查找插入位置操作与单链表一致
	if (!p=GetElem_Dul(L, i))
		return ERROR;
	s = (DNode *)malloc(sizeof(DNode));
	if (!s)
		return ERROR;
	s->data = e;
	s->prior = p->prior;
	p->prior->next = s;
	s->next = p;
	p->prior = s;
	return OK;
}
删除操作

在这里插入图片描述

Status ListDelete_Dul(DLinkList &L, int i, ElemType &e){
	DNode *p;
	if (!p=GetElem_D(L, i))
		return ERROR;
	e = p->data;
	p->next->prior = p->prior;
	p->prior->next = p->next;
	free(p);
	return OK;
}

循环链表

在这里插入图片描述

  • 链表表尾结点指针指向表头结点;
  • 从表中任一结点出发都可以到达表中其他结点;
  • 到达表尾结点的标志为p->next = L

循环双链表

  • 循环双链表头结点prior指针需要指向表尾结点;
  • 当循环双链表为空表时,头结点的nextprior都指向头结点自身L。

参照 严蔚敏《数据结构(C语言版)》
王道考研 《数据结构考研复习指导》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值