数组模拟单链表你会了吗?

链表

实现链表的方式

 struct Node
 {
 	int val;
 	Node *next;
 };// 不讲——竞赛不常用

每次创建一个新的链表的时候,就会调用一次new函数来创建新的节点(动态创建链表),这个操作是非常慢的

单链表:算法题中单链表用的最多的是邻接表(n个链表)。应用:存储树和图

双链表:优化某些问题

模拟单链表

1.使用数组来模拟单链表

// head 表示头节点的下标
// e[i] 表示节点i的值
// ne[i] 表示节点i的next指针(下一个节点的位置)是多少
// idx 存储当前已经用到的节点(当前已经用到哪个节点了)
int head, e[N], ne[N], idx;

image

2.初始化单链表

初始化:默认-1是代表空节点 head 初始值为-1,idx 初始值为0;

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

image

3.插入操作(头插法)

  • 创建这个节点的值(e[idx] = x)
  • head的赋值给当前节点next[i]指针(ne[idx] = head)
  • 让head重新指向头节点(head = idx)
  • 将idx后移(idx++)为下次插入操作做好准备
//将x插到头节点(用的最多!)
void add_to_head(int x)
{
	e[idx] = x;// 将插入节点的value值存下
	ne[idx] = head;// 操作1:将新插入节点的指针指向后一个节点(这里的后一个节点说的就是头节点!)
	head = idx;// 操作2:更新头节点的指针(head为指向第一个节点的指针(第一个节点的下标!))
	idx++; // 新节点插入之后(节点被使用之后)idx++后移(为下一次插入做准备)
}

image

4.在下标为k的节点后插入x

  • 创建这个节点的值(e[idx]=val)
  • head的值赋给当前新节点next[i]指针(ne[idx]=ne[k])
  • 将下标为k的节点指向当前节点(ne[k]=idx)
  • 将idx向后移动(idx++)
void add(int k, int x)
{
	e[idx] = x;
	ne[idx] = ne[k]; //02 :新插入节点的指针指向k位置节点的后一个节点
	ne[k] = idx; // 03:k位置的节点的指针指向新插入的节点
	idx++;
}

image

5.删除下标为k的节点的后一个节点

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

image

6.删除头节点

head = ne[head];

image

7.例题

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 k 个插入的数后面的数;
  3. 在第 k 个插入的数后插入一个数。

现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。

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

输入格式

第一行包含整数 M,表示操作次数。

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

  1. H x,表示向链表头插入一个数 xx。
  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

【参考代码】

#include<iostream>

using namespace std;

const int N = 100000+10;
int e[N], ne[N], idx, head;

//初始化单链表
void init()
{
   head = -1;
   idx = 0; 
}
// 向表头插入数据
void add_to_head(int x)
{
    e[idx] = x;
    ne[idx] = head;
    head = idx;
    idx++;
}
// 在下标为k的节点后插入x
void add(int k, int x)
{
    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 opt;
        cin>>opt;
        if(opt == 'H')
        {
            cin>>x;
            add_to_head(x); // 头插
        }
        
        else if(opt == 'D')
        {
            cin>>k;
            if(k == 0) head = ne[head];// 当 k 为 0 时,表示删除头结点
            
            remove(k - 1); // 删除第 k 个插入的数后面的数(第k个对应下标:k - 1)
        }
        
        else if(opt == 'I')
        {
            cin>>k>>x;
            add(k - 1, x); // 在第 k 个插入的数后面插入一个数 x
        }
    }
    
    // 遍历单链表  i = ne[i]使得i往后走(ne[i]是当前节点的下一个节点的位置)
    for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
    cout << endl;
    
    return 0;
}

总结

一开始在头插法那里卡了好久,其主要原因还是对head, e[N], ne[N], idx;的含义没有理解透,从而导致不理解具体操作的代码是怎么得来的,通过几次手动模拟画图之后就能更好的理解啦!

注:如果文章有任何错误或不足,请各位大佬尽情指出,评论留言留下您宝贵的建议!如果这篇文章对你有些许帮助,希望可爱亲切的您点个赞推荐一手,非常感谢啦

posted @ 2021-10-31 20:45  时间最考验人  阅读(222)  评论(0编辑  收藏  举报