数据结构之用数组模拟单链表【分步图文详解&附例题及AC代码】

知识背景与相应介绍

单链表的特点

单链表可以在任意位置插入,但如果想在 O(1)的复杂度内实现,就只能在某一个点的后面一个点插入。单链表只往后看,不往前看,可以在 O(1)的时间找到下一个节点的位置,但是不能知道上一个节点的位置。因此要找某些点只能从头开始遍历 。其实不管是插入还是删除操作,都是改变指针指向指针的一个操作。

传统的用指针+结构体的实现方式

struct Node
{
    
    
    int val;
	Node *next; 
}; //在面试题比较多,笔试题中比较少

这种实现方式的 特点:每次创建一个新的链表的时候就要调用一下new函数去new一个新的节点【称这种链表为动态链表】 。但是 new Node();操作是非常慢的【链表的大小一般都是10/100w级别,往往会TLE】 。不过改进(直接先初始化n个节点)之后时间效率会得到优化,就可以用了,但本质上和数组模拟差不多。

用数组模拟单链表的特点

用数组模拟一个单链表,单链表用的最多的是存储 邻接表(n个链表),邻接表最主要的应用是存储图和树。

用数组模拟一个双链表(优化某些问题)

数组实现的链表可以做指针实现的链表的所有事,用数组去模拟的原因:速度快

用数组模拟单链表的准备工作

在这里插入图片描述

一开始 head->null,每次会往里插入一个新的元素:head->元素->元素->…->null,每一个点上会存一个val值和next指针。数组e[N]、ne[N]分别模拟每个点的val和next,两者通过下标联系。比如标号 0 1 2 … -1,设节点对应的值为 3 5 7 。所有的点都是可以用下标来索引的。

用数组模拟单链表的几大核心步骤

初始化操作
设head的下标为1,idx的初始下标为0

// 初始化
void init()
{
    
    
	head=-1;
	idx=0;
} 

将 x 插到头结点的操作
在这里插入图片描述

第一步:先把 idx 的指针指向 head指向的那个值
第二步:把 head 指向 idx 的指针
第三步:把 x 的值存到 idx 指针里
第四步:当前的 idx 已经被用过了,移到下一个值

// 将x插到头节点
void add_to_head(int x)
{
    
    
	e[idx] = x; //赋值 
	ne[idx] = head; //节点idx的指针指向头节点所指向的值 
	head = idx; //将头节点指向idx 
	idx++; // 存储的节点序号+1 
} 

将 x 插到下标是 k 的点的后面的操作
在这里插入图片描述

// 将x插到下标是k的点的后面
void add(int k,int x) // 相当于用 ne[]指针替换了head 
{
    
    
	e[idx] = x; 
	ne[idx] = ne[k];
	ne[k] = idx;
	idx++;
}

删除下标是 k 的点的后面的点的操作
在这里插入图片描述

//  将下标是k的点的后面的点删掉
void remove(int k)
{
    
    
	ne[k] = ne[ne[k]];
}

补充说明

写工程一般用动态链表,删掉一个地址时就直接把它释放掉了
算法题不需要考虑内存泄漏的问题
尾插法把 tail记录下来就变成 O(1)了

例题及AC代码

题目

实现一个单链表,链表初始为空,支持三种操作:
(1) 向链表头插入一个数;
(2) 删除第k个插入的数后面的数;
(3) 在第k个插入的数后插入一个数
现在要对该链表进行M次操作,进行完所有操作后,从头到尾输出整个链表。

注意:题目中第k个插入的数并不是指当前链表的第k个数。例如操作过程中一共插入了n个数,则按照插入的时间顺序,这n个数依次为:第1个插入的数,第2个插入的数,…第n个插入的数。

输入格式
第一行包含整数M,表示操作次数。

接下来M行,每行包含一个操作命令,操作命令可能为以下几种:

(1) “H x”,表示向链表头插入一个数x。

(2) “D k”,表示删除第k个输入的数后面的数(当k为0时,表示删除头结点)。

(3) “I k x”,表示在第k个输入的数后面插入一个数x(此操作中k均大于0)。

输出格式
共一行,将整个链表从头到尾输出。

数据范围
1≤M≤100000
所有操作保证合法。

输入样例:
10
H 9
I 1 1
D 1
D 0
H 6
I 3 6
I 4 5
I 4 5
I 3 4
D 6
输出样例:
6 4 6 5

AC代码(有注释)

#include <bits/stdc++.h>
#pragma GCC optimize(3 , "Ofast" , "inline")
using namespace std;
const int N = 100010;

/*head不是节点,只是指向某个节点的指针,
存的是链表第一个点的下标,形象地看就好像是指向了头结点*/ 
// head 表示头节点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针是多少
// idx 存储当前已经用到了哪个节点 
int head,e[N],ne[N],idx;

// 初始化
void init()
{
    
    
	head=-1;
	idx=0;
} 

// 将x插到头节点
void add_to_head(int x)
{
    
    
	e[idx] = x; //赋值 
	ne[idx] = head; //节点idx的指针指向头节点所指向的值 
	head = idx; //将头节点指向idx 
	idx++; // 存储的节点序号+1 
} 

// 将x插到下标是k的点的后面
void add(int k,int x) // 相当于用ne[]指针替换了head 
{
    
    
	e[idx] = x; 
	ne[idx] = ne[k];
	ne[k] = idx;
	idx++;
}

//  将下标是k的点的后面的点删掉
void remove(int k)
{
    
    
	ne[k] = ne[ne[k]];
}
 
int main()
{
    
    
	int m;
	cin>>m;
	init();
	
	while(m--)
	{
    
    
		int k,x;
		char op;
	    cin>>op;
	    
	    if(op=='H') 
	    {
    
    
	    	cin>>k;
	    	add_to_head(x);
		}
		else if(op=='D')
		{
    
    
			cin>>k;
			if(!k) head=ne[head];
			remove(k-1);
		}
		else 
		{
    
    
			cin>>k>>x;
			add(k-1,x);
		}
	}
	
	for(int i=head; i!=-1; i=ne[i]) cout<<e[i]<<' ';
	cout<<endl;
	return 0 ;
}

猜你喜欢

转载自blog.csdn.net/Luoxiaobaia/article/details/108652098