(二)数据结构——线性表

本文详细介绍了线性表的概念,包括线性表的顺序表示和链式表示。顺序表使用一组地址连续的存储单元存储元素,支持随机访问,但插入和删除操作可能涉及大量元素的移动。链表则允许快速插入和删除,但不能直接访问任意元素。此外,文章还列举了各种操作如插入、删除、查找的实现,并提供了示例代码。
摘要由CSDN通过智能技术生成

第二章 线性表

2.1 线性表的定义和基本操作

2.1.1 线性表的定义

​ 具有相同数据类型的n个数据元素的有限序列

​ 线性表的特点:

  • 表中元素的个数是有限的。

  • 表中元素都有逻辑上的顺序性,表中元素有其现有次序。

  • 表中元素都是数据元素,每个元素都是单个的元素。

  • 表中元素的数据类型都相同,也就是每个元素占有相同大小的存储空间。

  • 表中元素具有抽象性,仅讨论元素之间的逻辑关系,不考虑元素表示什么内容。

    线性表是一种逻辑结构,表示元素之间的一对一的相邻关系,顺序表和链表是指存储结构,两者属于不同的层面的概念。

2.1.2 线性表的进本操作

​ i表示位置,e表示元素

结构含义
InitList(&L)初始化表,构造一个空的线性表
Length(L)求表长,返回线性表的长度,L中数据元素的个数
LocateElem(L,e)按值查找操作
GetElem(L,i)按位查找操作
ListInsert(&L,i,e)插入操作
ListDelete(&L,i,&e)删除操作,并用e返回删除的元素的值
PrintList(L)输出操作,按照先后顺序输出线性表L的所有值
Empty(L)判空操作,L为空的话,返回true,否则返回false
DestoryList(&L)销毁线性表,并释放L所占用的内存空间
  • ​ 基本操作的实现取决于采用哪种存储结构,存储结构不同,算法的实现也不同

2.2 线性表的线性表示

2.2.1 顺序表的定义

​ 线性表的顺序存储又称为顺序表,采用的是一组地址连续的存储单元一次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。

​ 顺序表的特点是表中元素的逻辑顺序与物理顺序相同。

​ 线性表中元素的位序都可以随机存取的,所以线性表的顺序存储结构是一种随机的存储结构。

​ 线性表中的元素的位序是从1开始的,而数组中元素的下标是从0开始的。

​ 一堆数组可以是静态分配的,也可以是动态分配的

​ 在静态分配时,由于数组的大小和空间已经固定,一旦空间占满,再加入新的数据就会产生数据溢出,从而导致程序崩溃。

​ 在动态分配的时候,存储数组的空间是在程序执行过程中通过动态存储分配语句分配的,一旦数据空间占满,就会另外开辟一块更大的存储空间,用来替换原来的存储空间。

​ 动态分配不是链式分配,同样属于顺序存储结构,物理结构没有变化,依然是随机存取方式,知识分配的空间大小可以在运行时间时动态决定。

​ 顺序表最主要的特点就是随机访问,即通过首地址和元素序号可以在O(1)内找到指定的元素。顺序表的存储密度高,每个结点只存储数据元素。

​ 顺序表逻辑上相邻的元素物理上也相邻,所以插入和删除操作需要移动大量的元素。

2.2.2 顺序表上基本操作的实现

​ (1)插入操作

​ 在顺序表L的第i个位置插入新元素e,若i的输入不合法,则返回false,表示插入失败,否则,将第i个位置及其之后的所有元素往后移动一个位置,腾出一个位置用来插入新的元素e,顺序表的长度加1,插入成功,返回true。

bool Listinsert(SqList &L,int i,ElemType e){
    if(i < 1 || i > L.legnth + 1){	//判断范围
        return false;
    }
    if(L.length >= MaxSize){	//当前存储空间已经满
        return false;
    }
    for(int j = L.length; j >= i; j--){		//第i个元素之后的元素依次后移
        L.data[j] = L.data[j - 1];
    }
    L.data[i - 1] = e;	//在第i个位置将e元素放入
    L.length++;		//长度加1
    return true;
}

最好时间复杂度:O(1)

最坏时间复杂度:O(n)

平均时间复杂度:O(n)

​ (2)删除操作

bool ListDelete(SqList &L, int i , Elemtype &e){
    if(i < 1 || i > L.legnth){	//判断范围
        return false;
    }
    e = L.data[i - 1];		//将被删除的元素赋值给e
    for(int j = i; j < L.length ; j++){
        L.data[j - 1] = L.data[j];
    }
    return true;
}

最好时间复杂度:O(1)

最坏时间复杂度:O(n)

平均时间复杂度:O(n)

(3)按值查找(顺序查找)

int LocateElem(SqList L,ElemType e){
    for(int i = 0; L.length; i++){
        if(L.data[i] == e){
            return i + 1;
        }
        return 0;
    } 
}

最好时间复杂度:O(1)

最坏时间复杂度:O(n)

平均时间复杂度:O(n)

2.2.3 综合应用题目
//从顺序表中删除具有最小值的元素并由函数返回被删元素的值。空出的位置由最后一个元素填补,若顺序表为空,则显示出错信息并退出运行
//算法思想:搜索整个顺序表,查找最小值并记录该值的位置,将最后一个元素填充到删除的元素之上。
bool Del_Min(sqList &L, Elemtype &value) {
	if (L.length == 0)
	{
		return false;	//如果数组的长度为0的话则直接返回false
	}

	value = L.data[0];	//令第一个元素为最小值
	int pos = 0;	//用来记录位置

	for (int i = 1; i < L.length; i++)
	{
		if (L.data[i] < value)
		{
			value = L.data[i];
			pos = i;
		}
	}

	L.data[pos] = L.data[L.length - 1];
	L.length--;
	return true;
}
//设计一个高效算法,将顺序表L的所有元素逆置,要求算法的空间复杂度为O(1)
//算法思想:设置一个中间变量,将顺序表的前几个元素和后几个元素进行交换。
void Reverse(SqList &L) {
	Elemtype = temp; //辅助变量
	for (int i = 0; i < L.length / 2; i++)
	{
		temp = L.data[i];
		L.data[i] = L.data[L.length - i - 1];
		L.data[L.length - 1 - i] = temp;
	}
}
//对长度为n的顺序表L,编写一个时间复杂度为O(n),空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据。
//算法思想:查找对应的元素并删除,后面的元素往前排。
void del_x(SqList &L , Elemtype X) {

	int k = 0;
	
	for (int i = 0; i < L.length; i++)
	{
		if (L.data[i] != x)
		{
			L.data[k] = L.data[i];
			K++
		}
	}
	L.length++;
}
//从<有序>顺序表中删除其值在给定S与t之间的所有元素(s<t),若s或t不合理或者顺序表为空,则显示出错信息
//算法思想:首先判断是否正确,然后再进行循环顺序表,符合条件的筛选出来重新对顺序表赋值
bool Del_2(SqList &L , Elemtype s, Elemtype t) {

	int k = 0;
	int step = 0;

	if (s < 0 || L.length == 0 || s >= t)
	{
		return false;
	}

	for (int i = 0; i < L.length; i++)
	{
		if (L.data[i] == t)
		{
			k = i;
		}
	}

	for (int i = 0; i < L.length; i++)
	{
		if (L.data[i] == s)
		{
			step = k - i;
		}
		L.data[i] = L.data[i + step];
	}
	L.length -= step;
}
//从有序顺序表中删除其值在给定S与t之间的所有元素(s<t),若s或t不合理或者顺序表为空,则显示出错信息
//算法思想:首先判断是否正确,然后再进行循环顺序表,符合条件的筛选出来重新对顺序表赋值
bool Del_2(SqList &L , Elemtype s, Elemtype t) {

	int k = 0;

	if (s < 0 || L.length == 0 || s >= t)
	{
		return false;
	}

	for (int i = 0; i < L.length; i++)
	{
		if (L.data[i] < s || L.data[i] > t)
		{
			L.data[k] = L.data[i];
			k++;
		}
	}
	L.length = k;
}
//从有序顺序表中删除所有其值重复的元素,使表中所有元素的值均不同,要求时间复杂度为O(n)
//算法思想:
bool Delete_Same(SqList &L) {
	if (L.length == 0)
	{
		return false;
	}
	int j = 1;
	for (int i = 0; i < L.length; j++) {
		if (L.data[i] != L.data[j])
		{
			L.data[++i] = L.data[j];
		}
	}
	L.length = i + 1;
	return true;
}
//将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。
bool Merge(SqList A, SqList B, SqList &C) {
	if (A.length + B.length > C.maxSize)	//大于顺序表的最大长度
	{
		return false;
	}
	int i = 0, j = 0, k = 0;	//分别对应顺序表A,B,C的下标
	while (i < A.length && j < B.length) {
		if (A.data[i] <= B.data[j]) {
			C.data[K++] = A.data[i++];
		}
		else {
			C.data[K++] = B.data[j++];
		}
	}

	while (i < A.length) {
		C.data[K++] = A.data[i++];
	}

	while (j < B.length) {
		C.data[K++] = B.data[j++];
	}

    C.length = k;
	return true;
}
//已知在一堆数组A[m + n]中依次存放两个线性表(a1,......am)和 (b1,........bm),编写一个函数
//将数组中两个顺序表的位置互换,即将(n1,.....bn)放在(a1,.......am)的前面。
//算法思想:首先将数组的全部元素逆置,再对前m个数组和后n个数组进行逆置。
void Reverse(SqList &L, int m, int n, int arraysize) {
	if (m + n != L.length || m < 0 || n < 0) {
		return;
	}
	ElemType temp;	//用来交换顺序表的连个元素的临时变量

	for (int i = 0; i < L.length / 2; i++) {
		temp = L.data[i];
		L.data[i] = L.data[L.length - i - 1];	//交换顺序表前面的部分
		L.data[L.length - i - 1] = temp;
	}

	for (int i = 0; i < m / 2; i++) {
		temp = L.data[i];
		L.data[i] = L.data[m - i - 1];	//交换顺序表前面的部分
		L.data[m - i - 1] = temp;
	}

	for (int i = 0; i < n / 2; i++) {
		temp = L.data[i];
		L.data[i] = L.data[n - i - 1];	//交换顺序表后面的部分
		L.data[n - i - 1] = temp;
	}
}

2.3 线性表的链式表示

2.3.1 单链表的定义

​ 单链表是非随机存取的,不能直接找到表中某个特定的结点。查找某个特定的结点的时候,需要从表头开始进行遍历。

​ 通常用头指针来标识一个单链表,头指针为NULL时表示一个空表,单链表的第一个结点之前附加一个结点称为头结点。

​ 头节点的优点:

​ ①第一个数据结点的位置被存放在头结点的指针域中,因此在链表的第一个位置上的操作和表的其他位置上的操作一致,无需进行特殊处理。

​ ②无论链表是否为空,头指针都指向头结点的非空指针。

2.3.2 单链表的基本操作的实现

1.头插法建立单链表

算法如下:

LinkList List(LinkList &L) {
	LNode *s;
	int x;
	L = (LinkList)malloc(sizeof(LNode));	//建立头节点
	L -> next = NULL;	//初始为空链表
	scanf("%d", &x);	//输入结点的数值
	while (x != 9999) {	//输入9999表示结束
		s = (LNode*)malloc(sizeof(LNode));	//创建新结点
		s -> data = x;
		s -> next = L -> next;
		L -> next = s;	//将新的结点插入表中,L为头指针
		scanf("&d", &x);
	}
	return L;
}

2.尾插法建立单链表

算法如下:

LinkList List(LinkList &L) {
	int x;
	L = (LinkList)malloc(sizeof(LNode));
	LNode *s, *r = L;	//r为表尾指针
	scanf("%d", &x);
	while (x != 9999) {
		s = (LNode *)malloc(sizeof(LNode));
		s -> data = s;
		r -> next = s;
		r = s;	//指向新的表尾结点
		scanf("%d", &x);
	}
	r -> next = NULL;
	return L;
}

3.按序号查找结点值

//查找i结点的值
LNode *GetElem(LinkList L,int i){
	int j = 1;	//初始值为1
	LNode *p = L -> next;	//第一个结点指针赋值给p

	if(i == 0){
		return L;	//若i等于0,则返回头结点
	}

	if(i < 1){
		return NULL;	//若i值无效,则返回NULL
	}

	while(p && j < i){	//从第一个结点开始找,查找第i个结点
		p = p -> next;
		j++;
	}
	return p;
}

4.按值查找表结点

//按值查找表结点的算法:
LNode *LocateElem(LinkList L,ElemType e){
	LNode *p = L -> next;
	while(p != NULL && p -> data != e){	//从第一个结点开始查找data域或e的结点。
		p = p -> next;
	}
	return p;	//找到后返回该结点指针,否则返回NULL
}

5.插入结点操作

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

6.删除结点操作

//删除结点
p = GetElem(L, i - 1);	//找到被删除结点的前驱结点
q = p -> next;
p -> next = q -> next;	//前驱结点指向被删除结点的下一个结点
free(q);

7.求表长操作

​ 链表长度是不包括头结点的,求表长的思路就是循环整个链表,设置计数器,+1

2.3.3 双链表

​ 双链表有两个指针,分别是prior和next,分别指向该结点的前驱结点和后继结点。

1.双链表的插入操作

//双链表的插入操作,在p结点后插入s结点
s -> next = p -> next;
p -> next -> prior = s;
p -> next = s;
s -> prior =p;

2.双链表的删除操作

//双链表的删除操作,删除p结点后的一个结点q
p -> next = q -> next;
q -> next -> prior = p;
free(q);
2.3.4 循环链表

1.循环单链表

​ 循环单链表和单链表的区别在于,表中最后一个结点的指针不是NULL,而是改为指向头结点,从而整个链表形成一个环。

​ 循环单链表的判空条件是判断它的尾指针是否等于头指针。

2.循环双链表

​ 头节点的指针指向表尾结点。

​ 当循环双链表为空的时,其头结点的prior和next都等于L

2.3.5 静态链表

​ 静态链表借助数组来描述线性的存储结构,结点也有数据域data和指针域next,与前面所讲的链表中的指针不同的是,这里的指针是结点的相对地址(数组下标),又称为游标。和顺序表一样,静态链表也是要预先分配一块连续的内存空间。

​ 静态链表以next == -1作为结束的标志。

2.3.6 顺序表和链表的比较

1.存取(读取)方式

​ 顺序表可以顺序存取,也可以随机存取,链表只能从表头顺序存取。

2.逻辑结构与物理结构

​ 采用顺序存取时,逻辑上相邻的元素,物理上也相邻。

​ 链式存储时,逻辑上相邻的元素,物理上不一定相邻。,

3.查找,插入和删除操作

​ 对于按值查找,顺序表无序时,两者的时间复杂度都是O(n),顺序表有序时,可以采用折半查找,此时的时间复杂度是O(log2n)

​ 按序号查找的话顺序表的时间复杂度是O(1),而链表的时间复杂度是O(n),顺序表的插入,删除操作平均需要移动半个表长的元素。链表只需要查找指针即可。

4.空间分配

​ 顺序表在静态分配的情况下,一旦空间存满,便不能进行扩充,否则就会出现溢出的现象。动态存储的话可以扩充但是操作效率比较慢。

​ 链式存储比较快。

2.3.7 综合应用题目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值