考研数据结构——线性表(顺序表&链表)最全!含代码!

本文围绕顺序表和链表展开,介绍了顺序表的插入、删除、查找操作及时间复杂度。详细阐述了单链表、双链表、循环链表和静态链表的定义、操作及代码实现,如单链表的插入、删除、查找和建立方法。最后对顺序表和链表进行比较,给出不同场景下的选择建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

前言

一、顺序表的基本操作

1.1 插入

1.2 删除

1.3 查找

1.3.1 按位查找

1.3.2 按值查找

二、链表

2.1 单链表

2.1.1定义

2.1.2 代码实现

2.1.3 创建单链表

2.1.3.1 不带头结点

2.1.3.2 带头结点

2.1.4 插入操作

2.1.4.1 带头结点的插入

2.1.4.2 不带头结点的插入

2.1.4.3 后插操作

2.1.4.4 前插操作

2.1.5 删除操作

2.1.5.1 按位序删除(带头结点)

2.1.5.2 指定结点删除(带头结点)

2.1.6 查找操作

2.1.6.1 按位查找

2.1.6.2 按值查找

2.1.7 单链表的建立

2.1.7.1 尾插法建立单链表

2.1.7.2 头插法建立单链表

考点:链表的逆置

2.2 双链表

2.2.1 双链表的定义

2.2.2 初始化双链表

2.2.3 双链表的插入

2.2.4 双链表的删除&清空双链表

2.2.5 双链表的遍历

2.3 循环链表

2.3.1 循环单链表

2.3.2 循环双链表

2.3.2.1 插入操作与双链表的差别

2.4 静态链表(考点较少,较少考察代码实现)

2.4.1 静态链表的定义

2.4.2 代码定义静态链表

2.4.3 基本操作的实现

2.4.3.1 初始化

2.4.3.1 查找

2.4.3.2 插入

2.4.4 优缺点

三、顺序表和链表的比较

3.1 对比

3.2 总结


本文所有图片均来自25王道考研数据结构课程截图。如需要pdf版本的笔记可以私我。

前言

        线性表是具有相同数据类型的n个数据元素的有限序列,其中n为表长,当n=0时为空表。

        基本操作由:创销(创建&销毁)增删改查。

一、顺序表的基本操作

        顺序表是用顺序存储方式实现线性表顺序存储。把逻辑上相邻的元素存储在物理位置上也相邻的存储单元中,元素之间的关系由存储单元的临街关系来体现。

1.1 插入

#include<iostream>
using namespace std;
#define MAXSIZE 10		//定义最大长度

typedef struct {
	int data[MAXSIZE];	//顺序表数组定义
	int length;			//顺序表的长度
}SqList;				//顺序表类型定义

void ListInsert(SqList& L, int i, int e) {
	for (int j = L.length; j >= i; j--)
		L.data[j] = L.data[j - 1];		//第i个元素之后的元素全部往后移一位,从最后一位开始移动
	L.data[i - 1] = e;					//第i个元素的下标是i-1
	L.length++;							//链表长度+1
}

//做一个测试
int main() {
	SqList L;		//声明一个顺序表
	L.length = 0;	//在声明一个顺序表的时候要把长度置为0,否则L.length可能是随机数。
    //向顺序表中插入数据元素,每插入一个元素要记得把表长+1
	for (int i = 0; i < 5; i++) {
		L.data[i] = i + 100;
		L.length++;
	}
	ListInsert(L, 3, 4);	//在位序3处插入元素4
    //输出结果
	for (int i = 0; i < L.length; i++)
		cout << L.data[i] << " ";
	return 0;
}

        顺序表的插入需要先将第i个节点之后的元素往后移一位,然后将要插入的元素插入进去。注意位序从1开始,数组下标从0开始,所以在for循环内的代码的数组内是“j-1”。

        插入元素的时候应该判断表是否已满,以及插入的位置是否合法。改进代码如下:

bool ListInsert(SqList& L, int i, int e) {
	if (i<1 || i>L.length)			//判断i的范围是否有效(i是位序,而位序从1开始)
		return false;
	if (L.length > MAXSIZE)			//数组长度大于最大长度,即数组已满
		return false;
	for (int j = L.length; j >= i; j--)
		L.data[j] = L.data[j - 1];		//第i个元素之后的元素全部往后移一位,从最后一位开始移动
	L.data[i - 1] = e;					//第i个元素的下标是i-1
	L.length++;							//链表长度+1
	return true;
}

时间复杂度:

  • 最好:O(1)

  • 最坏:O(n)

  • 平均:O(n)

1.2 删除

#include<iostream>
using namespace std;
#define MAXSIZE 10		//定义最大长度

typedef struct {
	int data[MAXSIZE];	//顺序表数组定义
	int length;			//顺序表的长度
}SqList;	

bool ListDelete(SqList& L, int i, int& e) {
	if (i<1 || i>L.length)			//输入的删除位序不合法
		return false;
	e = L.data[i-1];					//用变量e存储要删除的元素
	for (int j = i - 1; j < L.length; j++) {
		L.data[j] = L.data[j + 1];	//后面的元素移动到前面覆盖
	}
	L.length--;						//循环结束后顺序表长度-1
	return true;
}

int main() {
	SqList L;		//声明一个顺序表
	L.length = 0;
    //给顺序表一些初始元素
	for (int i = 0; i < 5; i++) {
		L.data[i] = i + 100;
		L.length++;
	}
	int e = -1;				//e用来带回删除的元素
	ListDelete(L, 1, e);
	cout << "删除位序为1的元素后" << endl;
	for (int i = 0; i < L.length; i++) {
		cout << L.data[i] << " ";
	}
	return 0;
}

函数解析:

  • 删除实际上是把位序 i 之后的元素全部往前依次移一位,覆盖掉位序 i 的元素;

  • 在 ListDelete 中,传入顺序表 L ,要删除的元素位序 i ,存放删除元素的空间 e;

  • 变量e是在ListDelete 函数外定义好的一个变量,主要是申请一片内存空间用来存放删除的元素,赋初值为 -1 ,并且变量 e 是引用类型,因为该变量要带到ListDelete 函数外作打印输出(实际操作中也需要告诉函数调用者这次删除的是哪个元素);

  • 在函数内部先判断位序是否合法,然后再进行后续操作。

时间复杂度:

  • 最好:O(1)

  • 最坏:O(n)

  • 平均:O(n)

1.3 查找

1.3.1 按位查找

        GetElem(L, i):按位查找操作。获取表L中第 i 个位置(位序为 i )的元素的值。

静态分配:

#define MAXSIZE 10		//定义最大长度
typedef struct {
	int data[MAXSIZE];	//顺序表数组定义
	int length;			//顺序表的长度
}SqList;				//顺序表类型定义
//按位查找
int GetInt(SqList& L, int i) {		//传入顺序表L和要查询的元素的位序i
	return L.data[i - 1];
}

动态分配

        data的数据类型决定了data的一个数据在内存中占用多少内存空间;data是一个指向data在内存中存放位置的一大片连续存储空间的首位置,即data是一个指针。比如data的数据类型是“int *”类型,一个data数据占4个字节,而data刚开始指向内存中2000的位置,那么data[0]就是2000-2003这四个字节所存放的数据,data[1]就是2004-2007这四个字节所存放的数据。

        时间复杂度:O[1]

1.3.2 按值查找

        LocateElem(L,e):按值查找操作,在表L中查找等于e的元素。

typedef struct {
	int *data;			//只是动态分配数组的指针
	int MaxSize;		//顺序表的最大容量
	int length;			//顺序表的长度
}SqList;

//按值查找(在顺序表L中查找第一个元素值等于e的元素的位序)
int LocateInt(SqList& L, int e) {
	for (int i = 0; i < L.length; i++) {
		if (L.data[i] == e)
			return i + 1;
	}
	return 0;
}

        因为返回的是所查找元素的位序,而i是数组下标,所以return i+1。只有基本数据类型可以使用“==”判断,如果是结构体等其他类型,不能用“==”直接判断。

时间复杂度:

  • 最好:O(1)

  • 最坏:O(n)

  • 平均:O(n)

二、链表

2.1 单链表

2.1.1定义

        和顺序表相比,单链表是链式存储而不是顺序存储,即在内存中是离散存储的,每个结点除了存放数据元素外还要存放指向下一个节点的指针;不需要大片连续空间,改变容量方便;但是不可随机存取,要耗费一定空间存放指针。

2.1.2 代码实现

typedef struct LNode {			//定义单链表的结点类型
	int data;					//每个结点存放一个数据元素
	struct LNode* next;			//指针指向下一个结点
}LNode, * LinkList;

        要表示一个单链表的时候,只需要声明一个头指针 L ,指向单链表的第一个结点,即头结点,就可以表示整个单链表。

        举例:

typedef struct LNode {			//定义单链表的结点类型
	int data;			//每个结点存放一个数据元素
	struct LNode* next;	//指针指向下一个结点
}LNode, * LinkList;

LNode* GetElem(LinkList L, int i) {
	int j = 1;
	LNode* p = L->next;
	if (i == 0) 
		return L;
	if (i < 1) 
		return NULL;
	while (p != NULL && j < i) {
		p = p->next;
		j++;
	}
	return p;
}

        这里面在定义函数GetElem的时候用了LNode *,而在传入参数的时候使用了 LinkList,这两个本质上是一样的,但是LNode *强调是一个节点,而LinkList强调是一个链表。由于该函数返回的是一个节点p,所以在定义函数返回类型的时候使用LNode *;而传参L是一个链表,所以使用LinkList。

2.1.3 创建单链表

2.1.3.1 不带头结点
typedef struct LNode {			//定义单链表的结点类型
	int data;			//每个结点存放一个数据元素
	struct LNode* next;	//指针指向下一个结点
}LNode, * LinkList;

//初始化一个简单的空表
bool InitList(LinkList& L) {
	L = NULL;
	return true;
}

void test() {
	LinkList L;		//声明一个指向单链表的指针
	//初始化一个空表
	InitList(L);
}

        在test函数中只是声明了一个指向单链表的指针,并没有创建一个结点;

        InitList函数中设置 L=NULL 是为了防止 L 创建的区域有遗留的脏数据。

2.1.3.2 带头结点
typedef struct LNode {			//定义单链表的结点类型
	int data;			//每个结点存放一个数据元素
	struct LNode* next;	//指针指向下一个结点
}LNode, * LinkList;

bool InitList(LinkList& L) {
	L = (LNode*)malloc(sizeof(LNode));	//分配一个头结点
	if (L == NULL)						//内存不足,分配失败
		return false;
	L->next = NULL;						//头结点之后暂时没有节点
	return true;
}

void test() {
	LinkList L;		//声明一个指向单链表的指针
	//初始化一个空表
	InitList(L);
}

2.1.4 插入操作

2.1.4.1 带头结点的插入

        要想在第 i 个位置上插入元素e,首先要找到第 i-1 个结点的位置,将新结点插入其后。图示如下:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值