数组模拟实现链表

目录

一.单链表的模拟实现

1.1.定义-创建-初始化

1.2.头插 

1.3.遍历链表

1.4.按值查找

1.5.在任意位置之后插入元素

1.6.删除任意位置之后的元素

1.7.完整代码

 二.双向链表的模拟实现

2.1.定义-创建

2.2. 头插

2.3. 遍历链表

2.4. 按值查找

2.5. 在任意位置之后插入元素

2.6.在任意位置之前插入元素

2.7. 删除任意位置的元素

2.8.所有测试代码


链表的实现⽅式分为动态实现和静态实现两种。 (ppt演⽰两种实现⽅式的区别)

  • 动态实现是通过new申请结点,然后通过delete释放结点的形式构造链表。这种实现⽅式最能体 现链表的特性;
  • 静态实现是利⽤两个数组配合来模拟链表。第⼀次接触可能⽐较抽象,但是它的运⾏速度很快,在 算法竞赛中会经常会使⽤到。 

一.单链表的模拟实现

1.1.定义-创建-初始化

  •  两个⾜够⼤的数组,⼀个⽤来存数据,⼀个⽤来存下⼀个结点的位置
  • 变量 h ,充当头指针,表⽰头结点的位置
  • 变量 id ,为新来的结点分位置

 注意我们这里是有一个头结点的(即h和id所指的位置)

const int N = 1e5 + 10;
int h; // 头指针
int id; // 下⼀个元素分配的位置
int e[N], ne[N]; // 数据域和指针域

// 下标0位置作为哨兵位
// 其中ne数组全部初始化为0,其中ne[i] = 0就表⽰空指针,后续没有结点
// 当然,也可以初始化为- 1作为空指针,看个⼈爱好

/*
 e[i]和 ne[i]是绑定在⼀起使⽤的,也有⼀种写法是定义⼀个结构体,把这两个变量放在⼀起,⽐如:
struct node
{
 int e, ne;
 }list[N];
但是,定义成结构体之后,代码书写不⽅便。我们只要知道e[i]和ne[i]是绑定在⼀起使⽤的即可
*/

1.2.头插 

这是链表中最常⽤也是使⽤最多的操作,后续树和图的存储中的邻接表以及链式前向星就会⽤到这个 操作。 因此必须要掌握的操作。

// 头插
void push_front(int x)
{
	// 先把x放在⼀个格⼦⾥⾯
	id++;
	e[id] = x;

	// 修改指针,顺序不能颠倒!
	// 1. x 的右指针指向哨兵位的后继
	// 2. 哨兵位的右指针指向x
	ne[id] = ne[h];
	ne[h] = id;
}

时间复杂度:只涉及指针的修改,时间复杂度为O(1)

1.3.遍历链表

// 打印链表
void print()
{
	// 定义⼀个指针从头结点开始
		// 通过ne数组逐渐向后移动
		// 直到遇到空指针
		for (int i = ne[h]; i; i = ne[i])
		{
			cout << e[i] << " ";
		}
	cout << endl << endl;
}

 时间复杂度:遍历整个链表,时间复杂度为O(N)

1.4.按值查找

解法⼀:遍历整个链表即可。

 解法⼆:如果存储的值数据范围不⼤,可以⽤哈希表优化。(不知道哈希表是什么没有关系,只要知 道mp数组的作⽤,把它当成⼀个标记数组即可。)

int mp[N]; // mp[i] 表⽰i在这个元素存放的位置
/*
	push_front和insert的时候,打上标记
mp[x] = id; // x 这个元素存放的位置是id
erase的时候,消除标记mp[x] = 0;
*/

// 查询值为x的元素存储的位置
int find(int x) // 注意,这⾥传⼊的是元素的值

{
	// 策略⼀:先找到x,然后返回x后⾯的元素
	for (int i = ne[h]; i; i = ne[i]) // 遍历链表
	{
		if (e[i] == x) // 如果找到
		{
			return i;
		}

		// 找不到就返回0
		return 0;
	}
}

// 策略⼆:使⽤哈希表优化
int find(int x) // 注意,这⾥传⼊的是元素的值
{
	return mp[x]; // mp[x] ⾥⾯就存着x这个元素存储的位置下标
}

时间复杂度:

  • 遍历整个链表查询:时间复杂度 O(N) ;
  • 使⽤标记数组优化:时间复杂度 O(1)。

1.5.在任意位置之后插入元素

// 在存储位置为 p 的元素后面,插入一个元素 x
void insert(int p, int x) {
    id++;  // x 这个元素分配的位置
    e[id] = x;  // 将 x 放在 id 位置处

    ne[id] = ne[p];  // x 指向 p 的后面
    ne[p] = id;  // p 指向 x
}

时间复杂度: 只有常数级别的操作,时间复杂度为 O(1) 。

1.6.删除任意位置之后的元素

// 删除存储位置为p后⾯的元素
void erase(int p) // 注意p表⽰元素的位置
{
	if (ne[p])
	{
		mp[e[ne[p]]] = 0; // 将p后⾯的元素从mp中删除
		ne[p] = ne[ne[p]]; //指向下⼀个元素的下⼀个元素
	}
}

时间复杂度: 只⽤修改指针,时间复杂度为 O(1) 。

1.7.完整代码

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// 创建
int e[N], ne[N], h, id;
int mp[N]; // mp[i] 表⽰i这个数存储的位置

// 遍历链表
void print()
{
	for (int i = ne[h]; i; i = ne[i])
	{
		cout << e[i] << " ";
		cout << endl << endl;
	}
}

// 头插
void push_front(int x)
{
	id++;
	e[id] = x;
	mp[x] = id; //标记x存储的位置
		//先让新结点指向头结点的下⼀个位置
		//然后让头结点指向新来的结点

		ne[id] = ne[h];
	ne[h] = id;
}
// 按值查找
int find(int x)
{
	// //解法⼀:遍历链表
		// for(int i = ne[h]; i; i = ne[i])
		 // {
		 //if (e[i] == x) return i;
	// }
	// return 0;
	// 解法⼆:⽤mp数组优化
		return mp[x];
}
// 在任意位置p之后,插⼊⼀个新的元素x
void insert(int p, int x)
{
	id++;
	e[id] = x;
	mp[x] = id;
	ne[id] = ne[p];
	ne[p] = id;
}
// 删除任意位置p后⾯的元素

void erase(int p)
{
	if (ne[p]) //当p不是最后⼀个元素的时候
	{
		mp[e[ne[p]]] = 0; // 把标记清空
		ne[p] = ne[ne[p]];
	}
}

int main()
{
	for (int i = 1; i <= 5; i++)
	{
		push_front(i);
		print();
	}
	//cout << find(1) << endl;
	//cout << find(5) << endl;
	//cout << find(6) << endl;
	// insert(1, 10);
	// print();
	// insert(2, 100);
	// print();
	erase(2);
	print();
	erase(4);
	print();
	return 0;
}

 二.双向链表的模拟实现

2.1.定义-创建

依旧采⽤静态实现的⽅式。

双向链表⽆⾮就是在单链表的基础上加上⼀个指向前驱的指针,那就再来⼀个数组,充当指向前驱的 指针域即可。

const int N = 1e5 + 10;
int h; // 头结点
int id; // 下⼀个元素分配的位置

int e[N]; // 数据域

int pre[N], ne[N]; // 前后指针域

// h 默认等于0,指向的就是哨兵位

// 此时链表为空,没有任何⼏点,因此ne[h]=0

2.2. 头插

// 头插
void push_front(int x)
{
    id++;
    e[id] = x;
    mp[x] = id; // 存一下 x 这个元素的位置
    // 左指向哨兵位,右指向哨兵位的下一个位置,也就是头结点
    pre[id] = h;
    ne[id] = ne[h];
    // 先修改头结点的指针,再修改哨兵位,顺序不能颠倒
    pre[ne[h]] = id;
    ne[h] = id;
}

时间复杂度:只涉及指针的修改,时间复杂度为 **O(1)**。

2.3. 遍历链表

直接无视 `pre` 数组,与单链表的遍历方式一致。  

// 打印链表
void print()
{
    for (int i = ne[h]; i; i = ne[i])
    {
        cout << e[i] << " ";
    }
    cout << endl << endl;
}

时间复杂度:遍历整个链表,时间复杂度为 **O(N)**。

2.4. 按值查找

直接使用 `mp` 数组优化。  

// 查找元素 x 在链表中存储的位置
int find(int x)
{
    // 用 mp 优化
    return mp[x];
}

时间复杂度:直接拿值,时间复杂度为 **O(1)**。

2.5. 在任意位置之后插入元素

// 在存储位置为 p 的元素后面,插入一个元素 x
void insert_back(int p, int x)
{
    id++;
    e[id] = x;
    mp[x] = id; // 存一下 x 这个元素的位置
    // 先左指向 p,右指向 p 的后继
    pre[id] = p;
    ne[id] = ne[p];
    // 先让 p 的后继的左指针指向 id
    // 再让 p 的右指针指向 id
    pre[ne[p]] = id;
    ne[p] = id;
}

时间复杂度:只涉及指针的修改,时间复杂度为 **O(1)**。

2.6.在任意位置之前插入元素

// 在存储位置为 p 的元素前面,插入一个元素 x
void insert_front(int p, int x)
{
    id++;
    e[id] = x;
    mp[x] = id; // 存一下 x 这个元素的位置
    // 先左指针指向 p 的前驱,右指针指向 p
    pre[id] = pre[p];
    ne[id] = p;
    // 先让 p 的前驱的右指针指向 id
    // 再让 p 的左指针指向 id
    ne[pre[p]] = id;
    pre[p] = id;
}

时间复杂度:只涉及指针的修改,时间复杂度为 **O(1)**。

2.7. 删除任意位置的元素

// 删除下标为 p 的元素
void erase(int p)
{
    mp[e[p]] = 0; // 从标记中移除
    ne[pre[p]] = ne[p];
    pre[ne[p]] = pre[p];
}


时间复杂度:只涉及指针的修改,时间复杂度为 **O(1)**。

2.8.所有测试代码
 

#include <iostream>
using namespace std;
const int N = 1e5 + 10;
// 创建双链表
int e[N], ne[N], pre[N], id, h;
int mp[N]; // mp[i] 表示:i 这个值存储的位置

// 遍历链表
void print()
{
    for (int i = ne[h]; i; i = ne[i])
    {
        cout << e[i] << " ";
    }
    cout << endl << endl;
}

// 头插
void push_front(int x)
{
    id++;
    e[id] = x;
    mp[x] = id;
    // 先修改新来结点的左右指针
    pre[id] = h;
    ne[id] = ne[h];
    // 修改哨兵位下一个结点的左指针
    pre[ne[h]] = id;
    ne[h] = id;
}

int find(int x)
{
    return mp[x];
}

// 在任意位置 p 的后面插入新的元素 x
void insert_back(int p, int x)
{
    id++;
    e[id] = x;
    mp[x] = id;
    pre[id] = p;
    ne[id] = ne[p];
    pre[ne[p]] = id;
    ne[p] = id;
}

// 在任意位置 p 的前面插入新的元素 x
void insert_front(int p, int x)
{
    id++;
    e[id] = x;
    mp[x] = id;
    pre[id] = pre[p];
    ne[id] = p;
    ne[pre[p]] = id;
    pre[p] = id;
}

// 删除任意位置 p 的元素
void erase(int p)
{
    mp[e[p]] = 0; // 把标记清空
    ne[pre[p]] = ne[p];
    pre[ne[p]] = pre[p];
}

int main()
{
    for (int i = 1; i <= 5; i++)
    {
        push_front(i);
        print();
    }
    // cout << find(3) << endl;
    // cout << find(5) << endl;
    // cout << find(0) << endl;
    // insert_front(2, 22);
    // print();
    // insert_front(3, 33);
    // print();
    // insert_front(4, 44);
    // print();
    erase(2);
    print();
    erase(4);
    print();
    return 0;
}
```

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值