003线性表(完)

概述

  1. 线性表:零个或多个具有相同特性数据元素的有限序列
  2. 在一个复杂的线性表中,数据元素可以由若干个数据项组成。
  3. 特点:
    (1)均匀性: 同一线性表的各数据元素必定具有相同的数据类型和长度
    (2)有序性: 各数据元素都有自己的位置且数据元素之间的相对位置是线性的

在这里插入图片描述

分类

  • 按存储结构划分:
  1. 顺序表: 按索引值从小到大存放在一片相邻的连续区域,结构紧凑,存储密度为1。
  2. 链表: 单链表,双链表、循环链表。
  • 按操作划分:
  1. 线性表: 不限制操作,先进后出、后进先出。
  2. 栈: 限制在同一端操作。
  3. 对列: 限制在两端操作,先进先出,后进后出。

在这里插入图片描述

在这里插入图片描述

抽象数据类型

在这里插入图片描述

1. 以上所提及的运算,是逻辑结构上定义的运算,只要给出这些运算的功能是“做什么”,至于“如何做”等实现细节,只有待确定了存储结构之后才考虑的。
2. 复杂的个性化操作,是将简单的基本操作组合而成的。

顺序存储结构

  • 定义: 用一段地址连续的存储单元依次存储线性表的数据元素。
#define  MAXSIZE 20   /* 存储空间初始分配量 */
typedef int ElemType;  /* ElemType 类型根据实际情况而定,这里假设为int */
typedef struct
{
	ElemType data[MAXSIZE];   /* 数组存储数据元素,最大值为MAXSIZE */
	int length;             /* 线性表的当前长度 */
}SqList;

地址计算

假设每个数据元素占用的是c个存储单元,那么线性表中的第i+1个元素的存储位置满足下列关系:(LOC表示获得存储位置的函数)
L O C ( a i + 1 ) = L O C ( a i ) + c LOC(a_{i+1}) = LOC(a_i) + c LOC(ai+1)=LOC(ai)+c
因此对于第i个数据元素ai的存储位置可以有a1推算得出:
L O C ( a i ) = L O C ( a i ) + ( i − 1 ) ∗ c LOC(a_{i}) = LOC(a_i) + (i - 1) * c LOC(ai)=LOC(ai)+(i1)c

在这里插入图片描述
在这里插入图片描述

获取元素

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
/* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L) */
/*操作结果:用e返回L中第i个数据元素的值*/
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;
}

插入操作

在这里插入图片描述

/* 初始条件:顺序线性表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. 表长减一
/* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L) */
/* 操作结果:在L中第i个位置之前插入新的元素e,L的长度加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. 数据域: 存储数据元素信息的域
  2. 指针域: 存储直接后继位置的域

在这里插入图片描述
在这里插入图片描述

  1. 单链表: 链表中的每个结点中只包含一个指针域
  2. 头指针: 链表中第一个结点的存储位置
  3. 头节点: 设立在第一个结点前的一个结点,头指针的数据域可以不存储任何信息,也可以存储线性表的长度等附加信息

在这里插入图片描述
在这里插入图片描述

/* 线性表的单链表存储结构 */
typedef struct Node
{
	ElemType data;
	struct Node * next;
}Node;
typedef struct Node * LinkList;  /* 定义LinkList */

在这里插入图片描述
在这里插入图片描述

获取元素

  • 算法思路:
  1. 声明一个指针p指向链表的第一个节点,初始化j1开始。
  2. j<i时,就遍历链表,让p的指针向后移动,不断指向下一个结点,j累加1
  3. 若到链表末尾p为空,说明第i个元素不存在
  4. 否则查表成功,返回节点p的数据
/* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L) */
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType *e)
{
	int j;
	LinkList p;  /* 声明一指针p */
	p = L->next;  /* 让p指向链表L的第一个结点 */
	j = 1;    /* j为计数器 */
	while(p && j < i)  /* p不为空且计数器j还没有等于i时,循环继续 */
	{
		p = p->next;   /* 让p指向下一个结点 */
		++j;
	}
	if( !p || j > i)
		return ERROR;
	*e = p->data;
	return OK;
}

插入操作

  • 算法思路:
  1. 声明一指针p指向链表头结点,初始化j1开始;
  2. j<i时,就遍历链表,让指针p向后移动,不断指向下一个结点,j累加1
  3. 若到链表末尾即p为空,则说明第i个节点不存在, 否则查找成功;
  4. 在系统中生成一个空节点s,将数据e赋值给s->data
  5. 单链表的插入标准语句s->next=p->next; p->next=s;
  6. 返回成功。

在这里插入图片描述
在这里插入图片描述

/* 初始条件:顺序线性表L已存在,1<=i<=ListLength(L),  */
/* 操作结果:在L中第i个结点位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList * L, int i, ElemType e)
{
	int j;
	LinkList p,s;
	p = *L;
	j = 1;
	while(p && j < i)  /目标:使p指向第i-1个结点/
	{
		p = p->next;
		++j;
	}
	if(!p || j > i)
		return ERROR;    /* 第i个结点不存在 */
	/*j>i是为了防止误输入为i<1的整数*/
	
	s = (LinkList)malloc(sizeof(Node));  /* 生成新结点 */
	s->data = e;
	s->next = p->next;   /* 将p的后继结点赋值给s的后继 */
	p->next = s;		/* 将s赋值为p的后继 */
	return OK;
}

删除操作

在这里插入图片描述

  • 算法思想:
  1. 声明一指针p指向链表的头结点,初始化j1开始
  2. j<i时,就遍历链表,让p的指向向后移动,不断指向下一个结点,j累加1
  3. 若到链表末尾即p为空,则说明第i个结点不存在
  4. 否则查找成功,将欲删除的节点p->next赋值给q
  5. 单链表的删除标准语句p->next=q->next
  6. q结点中的数据赋值给e,作为返回
  7. 释放q结点
  8. 返回成功
/* 初始条件:顺序线性表L(包含头结点)已存在,1<=i<=ListLength(L) */
/* 操作结果:删除L的第i个结点,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList * L, int i, ElemType * e)
{
	int j;
	LinkList p, q;
	p = *L;
	j = 1;
	while(p->next && j < i)  /* while结束后p会指向第i-1个结点 */
	{
		p = p->next;
		++j;
	}
	if(!(p->next) || j > i)
		return ERROR;    /* 第i个结点不存在 */
	q = p->next;
	p->next = q->next;	/* 将q的后继赋值给p的后继 */
	*e = q->data;   
	free(q);     
	return OK; 
}

整表创建

  • 算法思路:
  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));
	p = *L;
	*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;   /* 插入到表头 */
	}
}
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(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;
		r->next = p;
		r = p;
	}
	r->next = NULL;
}

循环链表

  1. 将单链表的终端结点的指针域由NULL改为头结点,即可形成循环链表
  2. 循环链表的循环的判断条件:p->next != L,即p->next不等于头结点,则循环未结束

在这里插入图片描述

  • 循环链表中不用头指针,而是用指向终端结点的尾指针来表示循环链表。
    (1) 尾指针rear指向终端结点,则查找终端结点是O(1)
    (2)rear->next->next指向开始结点,则查找开始结点也是O(1)

在这里插入图片描述

  • 合并循环链表
    在这里插入图片描述
    在这里插入图片描述
p = rearA->next;     /* 保存A表的头结点 */
rearA->next = rearB->next->next;  /* 将本是指向B表的第一个结点(不是头结点) */
									/* 赋值给rearA->next */
q=rearB->next;
rearB->next = p;     /* 将原A表的头结点赋值给rearB->next */
free(q);

双向链表

  • 在单链表的基础上,再设置一个指向其前驱结点的指针域
/* 线性表的双向链表结构 */
typedef struct DulNode
{
	ElemType data;
	struct DuLNode * prior;   /* 直接前驱指针 */
	struct DuLNode * next;    /* 直接后继指针 */
}DulNode, * DuLinkList;

在这里插入图片描述

插入操作

在这里插入图片描述

s->prior = p;  /* 把p赋值给s的前驱,如图1*/
s->next = p->next;  /* 把p->next赋值给s的后继 如图2*/
p->next->prior = s;  /* 把s赋值给p->next的前驱,如图3 */
p->next = s;     /* 把s赋值给p的后继 */

删除操作

在这里插入图片描述

p->prior->next = p->next;    /* 把p->next赋值给p->prior的后继,如图中1 */
p->next->prior = p->prior;   /* 把p->prior赋值给p->next的前驱,如图中2 */
free(p);      /* 释放结点 */

静态链表

/* 线性表的静态链表存储结构 */
/* 假设链表的最大长度是1000 */
#define MAXSIZE 1000
typedef struct 
{
	ElemType data;   /* 游标(Cursor),为0时表示无指向 */
	int cur;	
}Component,StaticLinkList[MAXSIZE];
  1. 用数组表示链表,元素是由两数据域组成:datacur
    (1)data:存放数据元素
    (2)cur:相当于单链表的next指针,存放该元素的后继元素在数组中的下标

在这里插入图片描述

  1. 未被使用的数组元素称为备用链表
  2. 数组第一个元素的cur存放备用链表的第一个结点的下标
  3. 数组最后一个元素的cur则存放第一个有数值的元素的下标,相当于单链表中头结点作用。

初始化

/* 将一维数组space中各分量链成一备用链表 */
/* 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;
}

插入操作

/* 若备用空间链表非空,则返回分配的结点下标,否则返回0 */
int Malloc_SLL(StaticLinkList space)
{
	int i = space[0].cur;  /* 当前数组第一个元素的cur存的值 */
						   /* 就是要返回的第一个备用空闲的下标 */	
	if(space[0].cur)
		space[0].cur = space[i].cur;  /* 由于要拿出一个分量来使用了,所以我们 */
									 /* 就得把它的下一个分量用来做备用 */	
	return i;
}
/* 在L中第i个元素之前插入的数据元素e */
Status ListInsert(StaticLinkList L, int i, ElemType e)
{
	int j, k, l;
	k = MAX_SIZE - 1;   /* 注意k首先是最后一个元素的下标 */
	if(i < 1 || i > ListLength(L) + 1)
		return ERROR;
	j = Malloc_SSL(L);   /* 获得空闲分量的下标 */
	if(j)
	{
		L[j].data = e;  /* 将数据赋值给此分量的data */
		for(l = 1; l <= i - 1; l++)   /* 找到第i个元素之前的位置 */
			k = L[k].cur;
		L[j].cur = L[k].cur;  /* 把第i个元素之前的cur赋值给新元素的cur */
		L[k].cur = j;  /* 把新元素的下标赋值给第i个元素之前元素的cur */
		return OK;
	}
	return ERROR;
}

问题: 该插入操作当数组中除第一个元素和最后一个元素外均插满时,再插入元素会产生错误。

在这里插入图片描述

删除操作

/* 删除在L中第i个数据元素e */
Status LinkDelete(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;	/* 找到第i-1个结点在数组中的下标 */
	j = L[k].cur;     /*找到第i个结点在数组中的下标*/
	L[k].cur = L[j].cur;
	Free_SSL(L, j);
	return OK;
}
/* 将下标为k的空闲结点收回到备用链表 */
void Free_SSL(StaticLinkList space, int k)
{
	space[k].cur = space[0].cur;   /*把第一个元素cur值赋给要删除的分量cur*/
	space[0].cur = k;			/* 把要删除的分量下标赋值给第一个元素的cur */
}

优缺点

在这里插入图片描述

广义表

定义

n个元素a1,a2,...,ai,...,an的有限序列,其中ai或者是原子项,或者是一个广义表,通常记作LS=(a1,a2,...,ai,...an)。

  • 线性表 vs 广义表:
数据类型区别
线性表元素仅限于原子项
广义表元素容许具有其自身结构

原子是作为结构上不可分割的成分,它可以是数或是一个结构

举例

  1. A = () :A为一个空表,长度为0
  2. B = (e) :表B只有一个原子e,B的长度为1
  3. C = (a,(b,c,d)):表C的长度为2,2个元素分别为原子a和子表(b,c,d)
  4. D = (A, B, C):表D的长度为3,三个元素都是广义表,将上述的5. A、B、C带入,则D=((), (e),(b,c,d))。
  5. E = (E):递归的表,长度为2,E相当于一个无限的广义表E=(a,(a,(a,(a,…))))。

广义表的元素可以是子表,而子表的元素还可以是子表

  • 区分广义表()和(())
区别()(())
是否为空表空表非空表
长度01

表示

  1. 由于广义表难以用顺序存储结构表示,因此通常采用链式存储结构
  • 表结点:
    在这里插入图片描述
  • 原子结点
    在这里插入图片描述
  • 头尾链表存储表示:
typedef enum {ATOM,LIST } ElemTag;  //ATOM==0:表示原子,LIST==1:表示子表
typedef struct GLNode {
    ElemTag  tag;  //公共部分,用以区分原子部分和表结点
    union {       //原子部分和表结点的联合部分
      AtomType  atom; //atom是原子结点的值域,AtomType由用户定义
      struct { struct GLNode *hp, *tp;} ptr;
             // ptr是表结点的指针域,ptr.hp 和ptr.tp分别指向表头和表尾
    };
} *Glist;  //广义表类型 

在这里插入图片描述

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计,皆可应用在项目、毕业设计、课程设计、期末/期/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_46427273

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值