【数据结构教程】线性表及其逻辑结构

线性表的定义


线性表是具有相同特性的数据元素的一个有限序列

具有以下特性:

  1. 有穷性
  2. 一致性
  3. 序列性

线性表的抽象数据类型描述


ADT List{
	数据对象:
		D={ai|1<=i<=n,n>=0,ai为ElemType类型}		//ElemType是自定义类型标识符
	数据关系:
		R={<ai,ai+1>|ai、ai+1属于D,i=1……n-1}
	基本运算:
		InitList(&L);							//初始化线性表,改造一个空的线性表L
		DestroyList(&L);						//销毁线性表
		ListEmpty(L);							//判断线性表是否为空表
		ListLength(L);							//求线性表的长度
		DispList(L);							//输出线性表
		GetElem(L,i,&e);						//求线性表中某个数据元素值
		LocateElem(L,e);						//按元素值查找
		ListInsert(&L,i,e);						//插入数据元素
		ListDelete(&l,i,&e);					//删除数据元素

线性表的顺序存储结构–顺序表


定义:线性表中逻辑上相邻的两个元素在对应的顺序表中它们的存储位置也相邻。

  • 声明:
typedef int ElemType;

typedef struct{
	ElemType data[MaxSize];		//存放线性表中的元素
	int length;					//存放线性表的长度
}SqList;						//顺序表的类型
  • 建立顺序表
void CreateList(SqList * &L,ElemType a[],int n)		//由a中的n个元素建立顺序表
{
	int i = 0;k = 0;								//k表示L中的元素个数,初始值为0
	L = (SqList * )malloc(sizeof(SqList));			//分配存放线性表空间
	while(i < n)									//i扫描数组a的元素
	{	L->data[k] = a[i];							//将元素a[i]存放在L中
		k++;i++;
	}
	L->length = k;									//设置L的长度k
}

1.需要头文件:<malloc.h>

  • 初始化线性表
void InitList(SqList * &L)
{
	L = (SqList * )malloc(sizeof(SqList));				//分配存放线性表的空间
	L->length = 0;
}
  • 销毁线性表
void InitList(SqList * &L)
{
	free(L);
}
  • 判断线性表是否为空表
bool ListEmpty(SqList * L)
{
	return(L->length == 0);
}
  • 求线性表的长度
int ListLength(SqList * L)
{
	return L->length;
}
  • 输出线性表
void DispList(SqList * L)
{
	for(int i = 0;i<L->length;i++)
		printf("%d",L->data[i]);
	printf("%\n");
  • 求线性表中某个数据的元素值
bool GetElem(SqList * L,int i,ElemType &e)
{
	if(i<1||i>L->length)
		return false;
	e = L->data[i-1];
	return true;
}
  • 按元素值查找
int locateElem(SqList * L,ElemType &e)
{
	int i = 0;
	while(i<L->length && L->data[i]!=e)
		i++
	if(i>=L->length)
		return 0;
	else
		return i+1;				//找到后返回其逻辑符号
}
  • 插入数据元素i
bool ListInsert(SqList * &L,int i,ElemType e)
{	int j;
	if(i<1||i>L->length+1)
		return false;
	i--;						//将顺序逻辑序号转换为物理序号
	for(j = l->Length;j>i;j--)	//将data[j]以及后面的元素后移一个位置
		L->data[j] = L->data[j-1];
	L->data[i] = e;				//插入元素e
	L->length++;
	return true;				//成功插入返回true
}
  • 删除数据元素i
bool ListDelete(SqList * &L,int i,ElemType &e)
{	int j;
	if(i<1||i>L->length+1)
		return false;
	i--;
	e = L->data[i];	
	for(j = i;j<L->length-1;j++)		//将data[i]之后的元素前移一个位置
		L->data[j] = L->data[j+1];
	L->length--;
	return true;
}	
  • 应用示例

例1. 假设一个线性表采用顺序表表示,射界一个算法,删除其中所有值为x的元素,要去算法的时间复杂度为O(n),空间复杂度为O(1)。
解法一:

void delnodel(SqList * &L,ElemType x){
	int k = 0,i;						//k记录不等于x的元素概述,即要插入到L中的元素个数
	for(i = 0;i<L->length;i++){
		if(L->data[i]!=x)			//若当前元素不为x,将其插入L中
		{	L->data[k] = L->data[i];
			k++;					//插入一个元素时元素的个数增1
		}
	L->length = k;					//顺序表L的长度等于k
}

解法二:

void delnode2(SqList * &L,ElemType x){
	int k = 0,i;						//k记录不等于x的元素概述,即要插入到L中的元素个数
	while(i<L->length){
		if(L->data[i]==x)			//若当前元素为x,k增加1
			k++;
		else
			L->data[i-k] = L->data[i];//当前元素不为x时,前移k个单位
		i++;
	}
	L->length-=k;				//顺序表L的长度递减K
}

线性表的链式存储结构–链表


单链表

定义:链表 中每一个元素称为“结点”,每个结点都应包括两个部分,不仅包含元素本身的信息(称为数据域),而且包含表示元素之间逻辑关系的信息,在C/C++语言中采用指针来实现(称为指针域)。

在单链表中增加头结点的优点如下:

  1. 单链表中首结点的插入和删除操作域其他结点一致,无需进行特殊处理
  2. 无论单链表是否为空都有一个头结点,因此统一了空表和非空表的处理过程

注意:在单链表中,由于每个结点只包含有一个真相后继结点的指针,所以当访问过一个结点后只能接着访问它的后续结点,而无法访问它的前驱结点。

  • 单链表的声明
typedef int ElemType;

typedef struct LNode
{	ElemType data;			//存放元素值(数据域)
	struct LNode * next;	//指向后继结点(指针域)
}LinkNode;					//单链表结点类型
  • 插入结点操作
s->next  =p->next;
p->next = s;

//先连接插入结点的后续结点,再连接其头结点
//“=”意思就是两者指向相同
  • 删除结点的操作
q = p->next;					//q临时保存呗删除结点
p->next  =q->next;		//从链表中删除结点q
free(q);						//释放结点q的空间
  • 建立单链表
  1. 头插法

生成一个新结点(由s指向它),将读取的数组元素存放到该结点的数据域中,然后将其插入到当前链表的表头上(即头结点之后),直到数组a的所有元素读完为止。

void CreateListF(LinkNode * &L,ElemType a[],int n)
{
	LinkNode * s;
	L = (LinkNode * )malloc(sizeofLinkNode));
	L->next = NULL;			//创建头结点,其next域置为NULL
	for(int i = 0;i<n;i++)
	{	s = (LinkNode * )malloc(sizeof(LinkNode));
		s->data = a[i];		//创建数据结点s
		s->next = L->next;	//将结点s插入到原首结点之前,头结点之后
		L->next = s;
	}
}
  1. 尾插法

核心思想:插入到当前链表的表尾上

void CreateListR(LinkNode * &L,ElemType a[],int n)
{	LinkNode * s,* r;
	L = (LinkNode * )malloc(size(LinkNode));		//创建头结点
	r  =L;																//r始终指向尾结点,初始时指向头结点
	for(int i = 0;i<n;i++)
	{	s = (LinkNode * )malloc(LinkNode));
		s->data = a[i];
		r->next = s;												//将结点s插入到结点r之后
		r = s;														//更新头结点
	}
	r->next = NULL;											//最后将尾结点的next域 置为空
}
  • 单链表的实现

初始化线性表

void InitList(LinkNode * &L)
{	L = (Linknode * )malloc(sizeof(LinkNode);
	L->next = NULL;							//创建头结点,其next域置为NULL
}

销毁线性表

void DestroyList(ListNode * &L)
{	LinkNode * pre = L,*p = L->next;		//pre指向结点p的前驱结点
	while(p!=NULL)
	{	free(pre)
		pre = p;											//pre、p同步后移一个结点
		p = pre->nest;
	}
	free(pre);												//循环结束时p为NULL,pre指向尾结点,释放它
}

判断线性表是否为空表

bool ListEmpty(LinkNode * L)
{
	return(L->next == NULL);
}

求线性表的长度(求数据结点个数)

int ListLength(LinkNode * L)
{
	int n  =0;
	LinkNode * p = L;			//p指向头结点,n置为0(即头结点的序号为0)
	while(p->next!-NULL)
	{	n++;
		p = p->next;
	}
	return n;					//循环结束,p指向尾结点,所有n为结点个数
}

输出线性表

void DispList(LinkNode * L)
{
	LinkNode *p = L->next;				//p指向首结点
	while(p!=NULL)
	{	printf("%d",p->data);
		p = p->next;
	}
	printf("\n");
}

求线性表中某个数据的元素值

bool GetElem(SqList * L,int i,ElemType &e)
{
	int j = 0;
	LinkNode *p = L;		//p指向头结点,j置为0(头结点序号为0)
	if(i<=0) return false;			
	while(j<i && p!=NULL)
	{	j++;
		p=p->next;
	}
	if(p == NULL)			//不存在第i个数据结点,返回false
		return false;
	else
	{	e = p->data;
		return true;
	}
}

按元素值查找

int LocateElem(LinkNode * L,ElemType e){
	int i = 1;
	LinkNode * p = L->next;		//p指向首结点,i置为1(即首结点序号为1)
	while(p!=NULL && p->data!=e){//查找data值为e的结点,其序号为i
		p = p->next;
		i++; 
	} 
	if(p == NULL)
		return 0;
	else
		return i;
} 

插入数据元素i

bool ListInsert(LinkNode * &L,int i,ElemType e){
	int j = 0;
	LinkNode * p = L,*s;		//p指向头结点,头结点序号置0
	if(i <= 0) return false;
	while(j<i - 1 && p!=NULL) 	//查找第i-1个结点p
	{
		j++;
		p = p->next;
	} 
	if(p == NULL)	return false;	//未找到第i-1个结点,返回false
	else{							//找到第i-1个结点p,插入新结点并返回true
		s = (LinkNode * )malloc(sizeof(LinkNode));
		s -> data = e;				//创建新结点s,其data域置为e
		s -> next =  p->next;		//将结点s插入到结点s之后
		p ->next = s;
		return true; 		
	} 
}

删除数据元素i

bool ListDelete(LinkNode * &L,int i,ElemType &e){
	int j = 0;
	LinkNode * p = L,* q;		//p指向头结点,j置为0(即头结点的序号为0)
	if(i<=0) return false;		//i错误返回false
	while(j<i-1 && p!=NULL){	//查找第i-1个结点
		j++;
		p = p->next; 
	} 
	if(p == NULL) return false;	//未找到第i-1个结点,返回false
	else{						//找到i-1个结点p
		q = p->next;			//q指向第i个结点 
		if(q == NULL)	return false;	//不存在第i个接待你返回false
		e = q->data;
		p->next = q->next;		//从单链表中删除q结点
		free(q);
		return true; 
		
	} 
}
  • 应用

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

void sort(LinkNode * &L){
	LinkNode * p,*pre,* q;
	p = L->next->next;		//p指向L的第2个数据结点
	L->next->next = NULL;	//构造只含一个数据结点的有序单链表(第二个数据结点用于放入插入结点)
	while(p!=NULL){
		q = p->next;		//q保存p结点后继结点的指针
		pre = L;			//从有序单链表开头进行比较,pre指向插入结点的前驱结点
		while(pre->next!=NULL && pre->next->data < p->data)
			pre = pre->next;	//在有序单链表中 找 插入p所指结点的前驱结点 
		//在pre所指结点之后插入p所指结点
		p->next = pre->next;
		pre->next = p;
		
		p = q;					//扫描原单链表余下的结点(向后继续查找) 
	} 
}

双链表

  • 双链表的声明
typedef struct DNode{
	ElemType data;				//存放元素值
	struct DNode * prior;		//指向前驱结点
	struct DNode * next;		//指向后继结点 
}DLinkNode;						//双链表的结点类型 

:就是指结点有两个

  • 双链表的建立

使用头插法建立

void CreateListF(DLinkNode * &L,ElemTyp a[],int n){//采用头插法简历双链表
	//由含有n个元素的数组a创建带头结点的双链表L
	DLinkNode * s;
	L = (DLinkNode *)malloc(sizeof(DLinkNode));		//创建头结点
	L -> prior = L->next = NULL;					//前后指针域置为NULL
	for(int i = 0;i<n;i++){							//循环简历数据结点 
		s = (DLinkNode * )malloc(sizeof(DLinkNode));
		s -> data = a[i];							//创建数据结点s 
		s -> next = L->next;						//将s结点插入到头结点之后
		if(L->next != NULL)							//若L存在数据结点,修改L->next的前驱指针
		 	L->next->prior = s;
		L->next = s;
		s->prior=L;
	} 
	
}

使用尾插法建立

void CreateListR(DLinkNode * &L,ElemType a[],int n){//采用尾插法建立双链表
	//用含有n个元素的数组a创建带头结点的双链表L
	DLinkNode * s,* r;
	L = (DLinkNode * )malloc (sizeof(DLinkNode));	//创建头插法
	r = L;											//r始终指向尾结点,开始时指向结点
	for(int i = 0;i<n;i++){							//循环建立数据结点 
		s = (DLinkNode * )malloc(sizeof(DLinkNode));
		s -> data = a[i];							//创建数结点s
		r -> next = s;s -> prior = r;				//将s结点插入到r结点之后
		r = s;										//r指向尾结点 
	}
	r->next = NULL;									//尾结点的next域置为NULL	 
}
  • 在双链表第i个位置上插入值为e的结点

在双链表中p所指结点之后插入一个结点s,其指针的变化过程如下:

s->next = p->next;	//将s结点插入到p结点之后
p->next->prior=s;
s->prior=p;
p->next=s; 
bool ListInsert(DLinkNode * &L,int i,ElemType e){
	int j = 0;
	DLinkNode * p = L,*s;		//p指向头结点,j置为0
	if(i<=0) return false;		//i错误返回false
	while(j<i-1 && p!=NULL){	//查找第i-1个结点
		j++;
		p-p->next; 
	} 
	if(p == NULL) return false;	//未找到返回false
	
	else{
		s = (DLinkNode * )malloc(sizeof(DLinkNode));
		s -> data = e;			//创建新结点s
		s -> next = p->next;	//在p结点之后插入s结点
		if(p->next!=NULL)		//若p结点存在后继结点,修改其前驱指针 
			p->next->prior = s;
		s->prior = p;
		p->next = s;
	} 
}
  • 删除双链表L中结点p的后继结点
    在这里插入图片描述

算法:

bool ListDelete(DLinkNode * &L,ElemType & e){
	int j = 0;
	DLinkNode * p = L,*q;		//p指向头结点,j设置为0
	if(i <= 0) retun false;		//i错误返回false 
	while(j<i - 1 && p!=NULL){	//查找第i-1个结点
		j++;
		p = p->next; 
	} 
	if(p == NULL) return false;	//未找到第i-1个结点
	else{						//找到 
		q = p->next;			//先纪录下结点和数据 
		if(q == NULL)	return false;
		e = q->data;
		p->next = q->next;		//删除操作 
		if(p->next!=NULL)		//若存在后继结点,修改其前驱指针
			p->next->prior = p;
		free(q);
		return true; 
	} 
}

应用:
在这里插入图片描述

void sort(DLinkNode * &L){	//递增排序
	DLinkNode * p,* pre,* q;
	p = L->next->next;		//p指向L的第2个数据结点
	L->next->next = NULL;	//构造只含应该数据结点的有序表
	while(p != NULL){
		q = p->next;		//q保存p结点的后继结点
		pre = L;			//从有序表开头进行比较,pre指向插入结点p的前驱结点
		while(pre->next != NULL && pre->next->data < p->data)
			pre = pre->next;		//在有序表中找插入结点的前驱结点pre
		//插入操作 
		p->next = pre->next;		//在pre结点之后插入结点p 
		if(pre->next != NULL)
			pre->next->prior = p;
		pre->next = p;
		p->prior = pre;
		p = q;						//对下一个数据结点进行检验 
	} 
}

比起双链表头插法的特点:

  1. 构造了只含一个数据结点的双链表
  2. 增加了一个对递增检验的判断
  3. 为了寻找,同时推动pre和p指针

循环链表

在这里插入图片描述

不同之处:
尾结点的p->next = L

  • 循环单链表
    在这里插入图片描述
int count(LinkNode * L,ElemType x){
	int i = 0;
	LinkNode * p = L->next;		//p指向首结点,i置为0
	while(p != L){				//扫描循环单链表L 
		if(p->data == x)
			i++;				//找到值为x的结点后增1
		p = p->next;			//p指向下一个结点 
	} 
	return i;					//返回值为x的结点个数 
}
  • 循环双链表
    在这里插入图片描述
bool delelem(DLinkNode * &L,ElemType x){
	DLinkNode * p = L->next;		//p指向首结点
	while(p!=L && p->data!=x)		//查找第一个data值为x的结点p
		p = p->next;
		if(p!=L){						//找到了第一个值为x的结点 
			p->next->prior = p->prior;	//删除p结点
			p->prior->next = p->next;
			free(p);
			return true;				
		}
		else{							//没有找到值为x的结点 
			return false;
		}
}
  • 循环链表的优势

在这里插入图片描述

bool Symm(DLinkNode * L){
	bool same = true;		//same表示L是否对称,初始时为true
	DLinkNode *p = L->next;			//p指向首结点
	DLinkNode *q = L->prior;		//q指向尾结点
	while(same){
		if(p->data!=q->data)		//对应结点值不相同,退出循环
			same = false;
		else{
			if(p == q || p == q->prior) break;
			q = q->prior;			//q前移一个结点 
			p = p->next;			//p后移一个结点 
		}
	} 
	return same;
}

有序表

有序表:指所有元素以递增或递减方式有序排列的线性表。

  • 基本运算算法

在这里插入图片描述

void ListInsert(SqList * &L,ElemType e){
	int i = 0,j;
	while(i<L->length && L->data[i]<e)
		i++;						//查找值为e的元素
	for(j = ListLength(L);j>i;j--)	//讲data[i]及后面的元素后移一个位置
		L->data[j] = L->data[j-1];
	L->data[i] = e;
	L->length++; 
}

在这里插入图片描述

void ListInsert(LinkNode * &L,ElemType e){
	LinkNode * pre = L,* p;
	while(pre->next!=NULL && pre->next->data<e)
		pre = pre->next;				//查找插入结点的前驱结点pre
	p = (LinkNode * )malloc(sizeof(LinkNode));
	p->data = e;						//创建存放e的数据结点p
	p->next = pre->next;				//在pre结点之后插入p结点
	pre->next = p; 
}
  • 有序表的并归算法

在这里插入图片描述

void UnionList(SqList *LA,SqList *LB,SqList * &LC){
	int i = 0,j = 0,k = 0;					//i,j分别为LA,LB的下标,k为LC中的元素个数
	LC = (SqList *)malloc(sizeof(SqList));	//建立有序顺序表LC
	while(i<LA->length && j<LB->length){
		if(LA->data[i]<LB->data[j]){
			LC->data[k] = LA->data[i];
			i++;k++;
		}
		else{
			LC->data[k] = LB->data[j];
			j++;k++;
		}
	} 
	while(i<LA->length)						//LA尚未扫描完,将其余元素插入LC中
	{
		LC->data[k] = LA->data[i];
		i++;k++; 
	}
	while(j<LB->length){
		LC->data[k] = LB->data[j];
		j++;k++;
	}
	LC->length = k;
}

在这里插入图片描述

void UnionList(LinkNode * LA,LinkNode *LB,LinkNode * &LC){
	LinkNode * pa = LA->next,*pb=LB->next,*r,*s;
	LC = (LinkNode *)malloc(sizeof(LinkNode));			//创建LC的头结点
	r = LC;												//r始终指向LC的尾结点
	while(pa!=NULL && pb!=NULL){
		if(pa->data < pb->data){
			s = (LinkNode *)malloc(sizeof(LinkNode));	
			s->data = pa->data;							//复制pa所指结点
			r->next = s;r = s;							//将s结点插入到LC中,再让r变为新的尾节点
			pa = pa->next; 
		}
	} 	else{
			s = (LinkNode *)malloc(sizeof(LinkNode));
			s->data = pb->data;							//复制pb所指的结点 
			r->next = s;r = s;							//将s结点插入到LC中
			pa = pa->next;	
		}
		while(pb!=NULL){
			s = (LinkNode *)malloc(sizeof(LinkNode));	
			s->data = pb->data;							//复制pb所指结点
			r->next = s;r = s;							//将s接待你插入到LC中
			pb = pb->next; 
		}
		r->next = NULL;									//尾结点的next域置空	
}
  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值