数据结构——线性表

线性结构:

除第一个和最后一个之外,集合中的每个元素均只有一个直接前驱和一个直接后继
表的抽象数据类型:
数据对象:ai:代表一个元素,属于数据元素的集合
数据关系:一个学生信息跟着另一个学生的信息,是有序的。
线性表:逻辑结构
顺序表&链表:实现的存储结构

2.1 线性表的定义和特点

在这里插入图片描述
同一线性表中的元素必定有相同特性,数据元素之间是线性关系。
开始结点:没有直接前驱,仅有一个直接后继。
终端结点:没有直接后继,仅有一个直接前驱。
其余内部结点都有且仅有一个直接前驱和直接后继。

2.2 线性表的线性表示和实现

线性表顺序存储结构的图示:
在这里插入图片描述
用一组连续的存储单元依次存储线性表的数据元素。
以物理位置相邻表示逻辑关系相邻,且任意一个元素均可随机存取。
LOC(ai) = LOC(a1)+(i-1)* l
LOC(a1):称为基地址
l: 每个dataelement存储的长度
好处:只要知道起始位置在哪,可以随机访问
在这里插入图片描述
elemType:代表数组中元素的类型。
若数据元素为复杂类型,可以定义一个结构类型。
在这里插入图片描述

类C语言的补充

数组的定义:
在这里插入图片描述
第二个:用内存动态分配的函数来分配内存
c语言中动态存储分配:

SqList L;
L.data = (ElemType*)malloc(sizeof(ElemType)*MaxSize);

在这里插入图片描述
maxSize:元素个数。 elemType:类型所占的字节数。
(Elemtype*):通过此类型划分开辟的空间,强制类型转换运算。转换为指针,获得地址。
需要加载头文件:<stdlib.h>

c++的动态存储分配

int *p1 = new int;
delete p1;

new出来的是一块空间的地址,所以要复制给指针变量。
释放空间:

delete 指针P//p必须是new操作的返回值

c++中参数的传递
在这里插入图片描述
用指针进行交换

#include<iostream>
using namespace std;

void swap (float *m, float *n){
	float t;
	t = *m;
	*m = *n;
	*n = t;
}
int main(){
	float a, b, *p1, *p2;
	cin >> a >> b;
	p1 = &a;
	p2 = &b;
	swap(p1, p2);
	cout << a << " "<< b << endl;	
}

引用类型做参数:
引用:给一个对象提供一个替代的名字。是同一个东西,共用一块空间。

void swap (int &m, int &n);
int main(){
	int a, b;
	cin >> a >> b;
	swap(a, b);
	cout << a << " " << b; 
}
void swap (int &m, int &n){
	int t;
	t = m;
	m = n;
	n = t;
}

几点说明:引用类型做形参是直接对实参操作,形参改变实参也发生变化,是同一个东西。因此当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好。

2.2.1 线性表的初始化及一些基本操作(参数用引用)

typedef struct{
	int *elem;
	int length;
}SqList;

//线性表L的初始化
int InitList_Sq(SqList &L){ //构造一个空的顺序表,
	L.elem = (int*)malloc(MAXSIZE*sizeof(int); //动态分配,为顺序表分配空间返回基地址 
	if(!L.elem) // 异常处理,存储分配失败(内存太小可能分配不成功),返回-2 
		exit(OVERFLOW);
	L.length = 0; //空表长度为0 
	return OK;
} 

//销毁线性表L
void DestroyList(SqList &L){
	if(L.elem) 
		delete L.elem;//若线性表存在,释放存储空间 
} 

//清空线性表L(线性表还在,但要告诉计算机没有元素了) 
void ClearList(SqList &L){
	L.length=0;//将线性表的长度置为0 
}

//求线性表L的长度
int GetLength(SqList L){
	return(L.length);
} 

//判断线性表L是否为空
int IsEmpty(SqList L){
	if(L.length == 0)
		return 1;
	else
		return 0; 
} 

2.2.2 顺序表的取值

int GetElem(SqList L, int i, int &e){
	if(i<1 || i>L.length)
		return ERROR;
	e = L.elem[i-1];
	return OK;
} 

所有代码都只执行一次,时间复杂度为常量阶O(1)

2.2.3 顺序表的查找

按值查找

时间复杂度:
先找循环次数最多的语句:循环体里的。
平均查找长度
在这里插入图片描述
在这里插入图片描述
第二个公式:
1,2……7 查找第i个元素的比较次数,1/7:每个元素被查找的概率
在这里插入图片描述
数量级就是O(n)

2.2.4 顺序表的插入

在这里插入图片描述

Status ListInsert_Sq(SqList &L, int i, ElemType e){
	int j;
	if(i<1 || i>L.length+1)
		return ERROR;
	if(L.length == MAXSIZE)
		return ERROR;
	for(j=L.length-1; j>=i-1; j--){
		L.elem[j+1] = L.elem[j];
	}
	L.elem[i-1]=e;
	L.length++;
	return OK;
}

算法时间:
在这里插入图片描述
概率都是1/n+1,因为有(n+1)个可以插入的位置,时间复杂度为O(n)

2.2.5 顺序表的删除

在这里插入图片描述

Status ListDelete_Sq(SqList &L, int i){
	int j;
	if((i<1) || (i>L.length))
		return ERROR;
	for(j=i; j<L.length-1; j++)
		L.elem[j-1] = L.elem[j];
	L.length--;
	return OK;
} 

时间复杂度,有n个元素可以删除。
O(n)在这里插入图片描述

2.3 链式存储结构

2.3.1基础

在这里插入图片描述 记录第一个元素地址的是头指针H,单链表可以用头指针唯一确定,因此单链表可以用头指针的名字来命名。 在这里插入图片描述
在这里插入图片描述
带头结点的单链表
在这里插入图片描述

2.3.2 单链表,双链表,循环链表

结点只有一个指针域的链表,称为单链表或线性链表。用来存储后继元素
结点有两个指针域的链表,称为双链表。一个存储前驱,一个存储后继。
首尾相接的链表称为循环列表。
在这里插入图片描述
头指针,头节点,首元结点
头指针:是指向链表中第一个结点的指针
首元结点:是指量表中存储第一个数据元素a1的结点
头结点:为了处理方便,在第一个结点之间附加一个结点
在这里插入图片描述
链表的两种形式:
带头结点和不带头结点在这里插入图片描述
表示空表:在这里插入图片描述
设置头结点的好处:
在这里插入图片描述
头结点的数据域可以为空,也可存放线性表长度等附加信息,但此结点不能计入链表的长度值。

2.3.3 链表的特点

节点在存储器中位置是任意的,访问的时候只能透过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间不等。
顺序表:随机存取。

2.3.4 单链表的存储

next是下一个的地址,是指针变量。
在这里插入图片描述
在这里插入图片描述
struct Lnode 是指向这种类型的指针,这种类型又包括指向这两个成员。用自己定义自己,嵌套定义。
Lnode 指向一个结点,Lnode a; a.data。Lnode *L;
*LinkList:指向结点的指针,LinkList L;
在这里插入图片描述
例;
存储学生的学号,姓名,成绩的单链表结点类型定义如下:

typedef Struct student{
	char num[8];
	char name[8];
	int score;
}D;

typedef struct Lnode{
	D data;
	struct Lnode *next;
}Lnode, *LinkList;  
LinkList L;

2.3.5 单链表的基本操作

1、单链表的初始化

构造一个空的单链表
算法步骤:
1、生成的新结点作头结点,用头指针L指向头结点。
2、将头结点的指针域置空
在这里插入图片描述

Status InitList L(LinkList &L){
	L = new Lnode;
	L->next = NULL;
	return OK;
}

new一个结点,从内存中找的这么一块空间,得到指向结点一个指针。将这块空间的地址赋值给L。
补充算法1:判断链表是否为空
思路:判断头结点的指针域是否为空

int ListEmpty (LinkList L){
	if(L->next) //非空
		return 0;
	else
		return 1; 
} 

补充算法2:单链表的销毁
链表销毁后不存在了,头结点,头链表都不存在了。
算法思路:
从头指针开始,一次释放所有结点
一个用来操作当前想要操作的结点:p,让他指向头结点,再把它指向的结点删除掉。

Status DestroyList_L(LinkList &L){
	Lnode *p;
	while(L){
		p = L;
		L = L->next;
		delete p;
	}
	return OK;
}

结束条件:L==NULL
补充算法3:清空链表
链表仍存在,单链表中无元素,成为空链表(头指针和头结点仍然存在)
算法思路:
依次释放所有结点,并将头节点的指针域设置为空
在这里插入图片描述
p指向当前要删除的结点,q来指向释放掉结点的下一个结点

//清空链表 
Status ClearList(LinkList &L){
	Lnode *p, *q;
	p = L->next;
	while(p){
		q = p->next;
		delete p;
		p = q; 
	}
	L->next = NULL; //头结点的指针域为空 
	return OK; 
} 

补充算法4:单链表的表长
算法思路:从首元结点开始,依次计数所有结点
在这里插入图片描述

int ListLength_L(LinkList L){
	Lnode *p;
	int i = 0;
	p = L->next;
	while(p){
		i++;
		p = p->next;
	}
	return i;
}

几个重要操作:

p=L; //p指向头结点
s=L->next; //s指向首元结点
p=p->next; //p指向下一结点

2. 取值——取单链表中第i个元素的内容

算法思路:从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。因此,链表不是随机存储结构。
在这里插入图片描述
算法步骤:
在这里插入图片描述

3. 按值查找——根据指定的数据获取该数据所在的位置

算法步骤:
1、从第一个结点起,依次和e相比较。
2、如果找到一个其值与e相等的数据元素,则返回其在链表中的位置或地址。
3、如果查遍整个链表都没有找到其值与e相等的元素,则返回0或NULL

Lnode *LocateElem_L(LinkList L, Elemtype e){//加*表示函数返回的类型是Lnode的指针类型的
	p = L->next;
	while(p && p->data != e){
		p = p->next;
	}  
	return p;//找到后返回地址
}

// 返回值为e的位置序号
int LocateElem_L(LinkList L, Elemtype e){
	p = L->next;
	int j = 1;
	while(p && p->data != e){
		p = p->next;
		j++;
	}
	if(p){
		return j;
	}
	else
		return 0;
}

如果找到了,即p不为空,返回位置。如果没找到,p为空,返回0,查找失败

4. 插入——在第i个结点前插入值为e的新结点

算法步骤:
在这里插入图片描述
循环:当查找到第i-1的时候停止,即 j=i-1。或者第i-1个元素已经超出了表长,p已经为空了。也不用找了。
异常情况:返回error;i 大于表长+1或者小于1,插入位置非法

5.删除——删除第i个结点

算法步骤:
在这里插入图片描述
可能会存在删除的位置不合理:i > 表长或 i < 1;

Status ListDelete_L(LinkList &L, int i; ElemType &e){
	p = L;
	j = 0;
	while(p && j<i-1){
		p = p->next;
		j++;
	}
	if(!p || j>i-1) //删除位置不合理 
		return ERROR;
	q = new Lnode; 
	q = p->next; //临时保存被删结点的地址以备释放 
	e = q->data; //保存删除节点的数据域 
	p->next = q->next; //删除 
	delete q; //释放删除结点的空间 
	return OK;
} 

6. 单链表的查找,插入,删除算法时间效率分析

查找:
循环体中的那条语句循环次数最多
在这里插入图片描述

7.单链表的建立

头插法

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

void CreateList_H(LinkList &L, int n){
	L = new LNode;
	L->next = NULL; //建立一个带头结点的单链表
	for(i=n; i>0; i--){
		p = new Lnode;
		cin >> p->data;
		p->next = L->next;
		L->next = p;
	} 
} 

时间复杂度O(n)

尾插法:

在这里插入图片描述

void CreateList_R(LinkList &L, int n){//最后通过L可以带回整个链表 
	L = new Lnode;
	L->next = NULL;
	r = new Lnode;
	r = L; //尾指针r指向头结点 
	for(i=0; i<n; i++){
		p = new Lnode; //生成新结点 
		cin >> p->data; //输入元素值 
		p->next = NULL;
		r->next = p; //插入到表尾 
		r = p; //r指向新的尾结点 
	} 
}

2.3.6 循环链表

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
循环链表通常使用尾指针,更方便。
将带尾指针的循环链表合并。
在这里插入图片描述

LinkList Connect(LinkList Ta, LinkList Tb){
	p = Ta->next;
	Ta->next = Tb->next->next;
	delete Tb->next;
	Tb->next = p;
	return Tb;
} 

2.3.7 双向链表

在单链表的每个结点里再增加一个指向其直接前驱的指针域prior,这样链表中就形成了有两个方向不同的链,故称为双向链表。为了在O(1)的时间内找到一个元素的前驱和后继。
双向链表:
在这里插入图片描述
双向循环链表:
在这里插入图片描述
在这里插入图片描述

1. 双向链表的插入

在这里插入图片描述

void ListInsert_Dul(DuLinkList &L, int i, ElemType e){
	if(!(p = GerElemP_DuL(L,i))) //找到第i个元素的函数
		return ERROR
	s->prior = p->prior;
	p->prior->next = s;
	p = s->next;
	p->prior = s;
	return OK;
} 

2.双向链表的删除

在这里插入图片描述

//删除循环链表的第i个元素,并用e返回
void ListDelete_Dul(DuLinkList &L, int i, ElemType &e){
	if(!(p=GetElemP_DuL(L,i)))
		return ERROR;
	e = p->data;
	p->prior->next = p->next;
	p->next->prior = p->prior;
	delete p;
	return OK;
}

2.3.8 时间复杂度的比较

在这里插入图片描述

2.4 顺序表与链表的比较

链式存储优缺点

在这里插入图片描述
存储密度

在这里插入图片描述

比较一下

在这里插入图片描述

2.5 线性表的应用

在这里插入图片描述
线性表合并的算法步骤:
依次取出Lb中的每个元素:在La中查找该元素,如果找不到,则将其插入La的最后

void  union(List &La, List &Lb){
	La_len = ListLength(La);
	Lb_len = ListLength(Lb); 
	for(i=1; i<=Lb_len; i++){
		GetElem(Lb, i, e); //去除每个元素的值 
		if(!LocateElem(La, e))  //如果找到了返回位置,如果没找到返回0,进行插入 
			ListInsert(&La, ++La_len, e)
	}
}

有序表的合并(顺序表):
算法步骤:
在这里插入图片描述
这里++优先级更大,所以*p++ = *(p++)加的是地址

void MergeList_Sq(SqList LA, SqList LB, SqList &LC){
	pa = LA.elem;
	pb = LB.elem; //指针分别指向两个表的第一个元素 
	LC.length = LA.length + LB.length; //新表长度 
	Lc.elem = new ElemType[LC.length]; //为新表分配一个数组空间 
	pc = LC.elem; //指针指向第一个元素 
	pa_last = LA.elem + LA.length-1; 
	pb_last = LB.elem + LB.length-1; //指针指向 最后一个元素
	while(pa<=pa_last && pb<=pb_last){
		if(*pa<=*pb)
			*pc++ = *pa++; //移动的是地址
		else
			*pc++ = *pb++; 
	} 
	while(pa<=pa_last)
		*pc++ = *pa++; //将LA剩余元素加入LC 
	while(pb<=pb_last)
		*pc++ = *pb++;
	}		
}

在这里插入图片描述
有序表合并——链表实现
在这里插入图片描述

void MergeList_L(LinkList &La, LinkList &Lb, LinkLiST &Lc){
	pa = La->next;
	pb = Lb->next;
	pc = Lc = La; //用La的头结点作为Lc的头结点 
	while(pa && pb){
		if(pa->data <= pb->data){
			pc->next = pa;
			pc = pa;
			pa = pa->next; 
	}
	else{
		pc->next = pb;
		pc = pb;
		pb = pb->next; 
	}	
	}
	
	pc->next = pa?pa:pb; //插入剩余段 
	delete Lb; //释放Lb的头结点 	
} 

时间复杂度:
在这里插入图片描述
空间复杂度:不需要额外的空间,因此为O(1)

2.6 案例

案例一 —— 一元多项式的运算:

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

案例二 —— 系数多项式的运算

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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值