C / C++ 数据结构与算法 (线性表)

只是我自己看书、视频的总结 可能不适合其他人看。

一、线性表

线性表的抽象类型

ADT 线性表(List)
Data 
线性表的数据对象集合为{a1,a2,.....an},每个元素的类型均为DataType。
其中,除第一个元素a1之外,每一个元素有且仅有一个直接前驱元素。
除了最后一个元素an外,每一个元素有且仅有一个直接后继元素。数据元素之间的关系是一对一的关系。

Operation
	InitList (*L);  //初始化操作,建立一个空大线性表L。
	ListEmpty(L);  //若线性表为空,返回ture,否则返回false。
	ClearList(*L); //将线性表清空。
	GetELem(L, i, *e); //将线性表L中的第i个位置元素返回给e。
	LocateElem (L, e); //将线性表L中查找与给定值e相等的元素,如果查找成功,
	//返回该元素在表中的序列号表示成功;否则返回0表示失败。
	ListInsert (*L, i, e); //在线性表L中的第i个位置插入e。
	ListDelete(*L, i, *e); //删除线性表L中的第i个位置元素,并用e返回其值。
	ListLength(L); //返回线性表L的元素个数。
endADT

顺序线性表

获得元素操作
#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0
typedef int Status;
Status GetElem (SqList L, int i, ElemType *e)
{
	if(L.length == 0 || i<1 || i>L.length)
	return ERROR;
	*e = L.data[i-1];
	return OK;
}

注意这里返回值类型Status是一个整形,返回OK代表1,ERROR代表0。

插入操作

算法思路:
1、如果插入位置不合理,抛出异常。
2、如果线性表长度大于等于数组长度,则抛出异常或动态增加容量。
3、从最后一个元素开始向前遍历到第 i 个位置,分别将他们都向后移动一个位置。
4、将要插入元素填入位置 i 处。
5、表长加1。

/* 初始条件:顺序线性表L已经存在, 1 <= i <=ListLength(L)*/
/* 操作结果:在 L 中第 i 个位置之前插入新的数据元素e, L的长度加1. */
Status ListInsert (SqList *L, int i, ElemType e)
{
	int k;
	if (L->length == MAXSIZE)
	return ERROR;
	if (i<1 || i>L->length+1)
	return ERROR;
	if (i<=L->length)
	{
		for (k=L->length-1; k>=i-1; k--) // 把要插入位置之后的元素向后移动一个位置
		L->data[k+1] = L->data[k];
	}
	L->data[i-1] = e;
	L->length++;
	return OK;
}
删除操作

算法思路:
1、如果删除位置不合理,抛出异常;
2、取出删除元素;
3、从删除元素位置开始遍历到最后一个元素位置,分别将他们都向前移动一个位置;
4、表长减1.

Status ListDelete (SqList *L, int i, ElemType *e)
{	
	int k;
	if (L->length == 0)
	return ERROR;
	if (i < 1 || i>L->length)
	return ERROR;
	*e = L->data[i-1];
	if (i<L->length)
	{
		for (k=i;k<L->length; k++)
		L->data[k-1]= L->data[k];
	}
	L->length--;
	return OK;
}

线性表顺序存储结构的优缺点
优点:

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间。
  • 可以快速地存取表中任一位置的元素。

缺点:

  • 插入和删除操作需要移动大量元素。
  • 当线性表长度变化较大时,难以确定存储空间的容量。
  • 造成存储空间的“碎片”。

链式线性表

头节点的头指针存放的地址第一个节点的地址,数据域可以为空,可以存线性表长度等公共数据。链表的尾指针存放的地址是空的。
若线性表为空,则头指针为空。

元素的获取

算法思路:

  1. 声明一个结点p指向链表的第一个结点,初始化 j 从1 开始;
  2. 当 j < 1时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j ++
  3. 若到链表末尾p为空,则说明第i个元素不存在;
  4. 否则查找成功,返回结点p的数据。

Status GetElem (LinkList L, int i, ElemType *e)
{
	int j;
	LinkList p; //声明一结点
	p = L->next; //让p指向链表的第一个结点
	j = 1;
	while (p&& j<i)
	{
		p = p->next;
		++j;
	}
	if (!p || j>i)
	{
		return ERROR;
	]
	*e = p->data;
	return OK;
}
单链表的插入

要插入的结点为s。
让p的后继结点改成s的后继结点,再把结点s变成p的后继结点。

s ->next = p->next; p->next = s;

上述语句不可相反!
算法思路:

  1. 声明一结点p指向链表的第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j++;
  3. 若到链表末尾p为空,则说明第i个元素不存在;
  4. 否则查找成功,在系统中生成一个空结点s;
  5. 将数据元素e赋值给s->data;
  6. 单链表的插入标准语句:s->next = p->next; p->next =s;
  7. 返回成功。
Status GetElem (LinkList *L, int i, ElemType *e)
{
	int j;
	LinkList p, s; 
	p = *L;
	j = 1;
	while (p&& j<i)
	{
		p = p->next;
		++j;
	}
	if (!p || j>i)
	{
		return ERROR;
	]
	s=(LinkList)malloc(sizeof(Node)); //生成新结点
	s->data = e;
	s->next = p->next;
	p->next = s;
	return OK;
}
单链表删除

删除结点为q。

q = p->next; p->next = q->next;

算法思路:

  1. 声明一个结点p指向链表的第一个结点,初始化j从1开始;
  2. 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j++;
  3. 若到链表末尾p为空,则说明第i个元素不存在;
  4. 否则查找成功,将欲删除的结点p->next赋值给q;
  5. 单链表的删除标准语句p->next = q->next;
  6. 将q结点中的数据赋值给e,作为返回;
  7. 释放q结点
  8. 返回成功
Status GetElem (LinkList *L, int i, ElemType *e)
{
	int j;
	LinkList p,q; 
	p = *L;
	j = 1;
	while (p->next && j<i)
	{
		p = p->next;
		++j;
	}
	if (!(p->next) || j>i)
	{
		return ERROR;
	]
	q = p->next;
	p->next = q->next;
	*e = q->data;
	free(q);
	return OK;
}

free函数的作用就是让系统回收一个Node结点,释放内存。
对于插入或者删除数据越频繁的操作,单链表的效率优势明显。

单链表的创建

顺序结构的创建时一个数组初始化的过程,即声明一个类型和大小的数组并赋值的过程。
单链表的创建过程就是一个动态生成链表的过程,即从“空表”的初始状态起,依次建立各元素结点,并逐个插入链表。

算法思路:

  1. 声明一结点p和计数器变量i;
  2. 初始化一空链表L;
  3. 让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
  4. 循环:1、生成一新结点赋值给p;2、随机生成一数字
    赋值给p的数据域p->data; 3、将p插入到头结点与前一新结点之间。

始终让新结点在第一的位置,叫头插法

/*随机产生n个元素的值,建立带表头结点的单链线性表L(头插法)*/
void CreateListHead (LinkList *L, int n)
{
	LinkList p;
	int i;
	srand(time(0)); //初始化随机数种子
	*L = (LinkList)malloc(sizeof(Node)); //为整个线性表
	(*L)->next = NULL; //建立一个带头结点的单链表
	for(i = 0; i < n; i++)
	{
		p = (LinkList)malloc(sizeof(Node)); // 生成新结点;
		p -> data = rand()%100 +1; //随机生成100以内的数字
		p->next = (*L)->next;
		(*L)->next = p; //插到表头
	}
}	

尾插法:

void CreateListHead (LinkList *L, int n)
{
	LinkList p, r;
	int i;
	srand(time(0)); //初始化随机数种子
	*L = (LinkList)malloc(sizeof(Node)); //为整个线性表
	r = *L;
	for(i = 0; i < n; i++)
	{
		p = (Node*)malloc(sizeof(Node)); // 生成新结点;
		p -> data = rand()%100 +1; //随机生成100以内的数字
		r-> next = p;
		r = p; //插到表尾
	}
	r -> next = NULL;
}	

L是指整个单链表,而r是指向尾结点的变量,r会随着循环不断地变化结点,而L则是随着循环增长为一个多结点的链表。
r -> next = p; 就是将表尾终端结点r的指针指向新结点p
r = p; 本来r是尾结点,插进来一个p,所以p是尾结点,要将p结点这个最后的结点赋值给r,此时r又是最终的尾结点。
循环结束后,要让链表的指针域置空,所以r->next = NULL;

单链表的整表删除

算法思路:

  1. 声明一结点p和q;
  2. 将第一个结点赋值给p;
  3. 循环:1、将下一个结点赋值给q;2、释放p;3、将q赋值给p。
Status ClearList(LinkList *L)
{
	LinkList p,q;
	p = (*L)->next;
	while (p)
	{
		q = p-next;
		free(p);
		p = q;
	}
	(*L)->next = NULL;
	return OK;
与顺序链表比较

若线性表需要频繁查找,很少进行插入和删除操作时,宜采用顺序存储结构。若需要频繁插入和删除时,或者元素个数变化较大、根本不知道有几个,宜采用单链表结构


静态链表

用数组描述的链表叫静态链表
数组元素都是由两个数据域组成:datacur。数组的每个下标都对应着一个data和一个cur。数据域data用来存放数据元素,游标cur相当于单链表中的next指针,存放该元素的后继在数组中的下标

#define MAXSIZE 1000
typedef struct
{
	ElemType data;
	int cur;
}
Component, StaticLinkList[MAXSIZE];

对数组第一个和最后一个元素作特殊元素处理,不存数据。
把未被使用的数组元素称为备用链表。
数组的第一个元素,即下标为0的元素的cur就存放备用链表的第一个结点的下标;
而数组的最后一个元素的cur则存放第一个有数值元素的下标,相当于单链表的头结点作用。
在这里插入图片描述
代码实现:

/*space[0].cur 为头指针,“0”表示空指针*/
Status InitList (StaticLinkList space)
{
	int i; 
	for (i=0; i<MAXSIZE-1;i++)
		space[i].cur = i+1;
	space[MAXSIZE-1].cur = 0;//目前静态链表为空,最后一个元素的cur为0
	return OK;

在这里插入图片描述

插入操作

在动态链表中,结点的申请和释放分别借助malloc()free()两个函数来实现。
在静态链表中,操作是数组,不存在像动态链表的结点申请和释放问题,需要我们自己去实现是这个函数,才可以作插入和删除的操作。
为了辨明数组中哪些分量未被使用,解决办法是将所有未被使用过的及已被删除的分量用游标链成一个备用的链表,每当进行插入时,便可以从备用链表中取得第一个结点作为待插入的新结点
算法实现:

int Malloc_SLL(StaticLinkList space)
{
	int i = space[0].cur;/*当前数组第一个元素的cur存的值,
	                     就是要返回的第一个备用链表的下标*/
    if (space[0].cur)
    	space[0].cur = space[i].cur; /* 由于要拿出一个分量来使用了,所以就得把它的下一个分量来用作备用*/
    return i;
}
删除操作

删除第一个元素:

Status ListDelete (StaticLinkList L, int i)
{
	int j, k;
	if (i < 1 || i > ListLength(L))
		return ERROR;
	k = MAX_SIZE -1;
	for (j = 1; j <= i-1; j++)
		k = L[k].cur;
	j = L[k].cur;
	L[k].cur = L[j].cur;
	Free_SSL(L, j);
	return OK;
	void Free_SSL (StacticLinkList space, int k)
	{
		space[k].cur = space[0].cur; /*把第一个元素cur赋值给要删除的分量cur*/
		space[0].cur = k; /* 把要删除的分量下标赋值给第一个第一个元素的cur*/
}

因为i=1;所以for循环不操作,j=k[999].cur=1; L[k].cur=L[1].cur,也就是L[999].cur=L[1].cur=2。
在这里插入图片描述


循环链表

将单链表中的终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环。即循环链表。
在这里插入图片描述
代码实现:

p=rearA->next; /*保存A表的头结点*/
rearA->next = rearB->next->next; /*将本是指向B表的第一个结点(不是头结点)赋值给rearA->next*/
rearB->next = p;
free(p);
双向链表

双向链表是在单链表的每个结点中再设置一个指向其前驱结点的指针域。
所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

/* 线性表的双向链表存储结构*/
typedef struct DulNode
{
	ElemType data;
	struct DulNode *prior;
	struct DulNode *next;
}
DulNode, *DuLinkList;

双向链表也可以是循环链表:
对于链表中的某一个结点p,他的后继的前驱、前驱的后继是他自己

p->next->prior = p = p->prior->next
插入操作

在这里插入图片描述

s->prior = p; //把p赋值给s的前驱
s->next = p->next //把p->next赋值给s的后继
p->next->prior = s;
p->next = s
删除操作

在这里插入图片描述

p->prior->next = p->next;
p->next->prior = p->prior;
free(p);

总结

线性表
顺序存储结构
链式存储结构
单链表
静态链表
循环链表
双向链表
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值