当前位置:   article > 正文

链表(用数组模拟单链表和双链表)_用指针数组单链表

用指针数组单链表

·单链表——>邻接表——>存储树和图·

·双链表——>优化某些问题

用数组模拟链表会更快             

单链表 = 数据 + 指针

单链表上的操作
初始化
 通常会用头指针来标识一个单链表,头指针为NULL时表示一个空表。但是,为了操作方便,会在单链表的第一个结点之前附加一个结点,称为头结点。头结点的数据域可以不设任何信息,也可以记录表长等信息。头结点的指针域指向线性表的第一个元素结点。如下图所示:

 头结点和头指针的区分:不管带不带头结点,头指针始终指向单链表的第一个结点,而头结点是带头结点的单链表中的第一个结点,结点内通常不存储信息。
那么单链表的初始化操作就是申请一个头结点,将指针域置空

  1. // 初始化
  2. void init()
  3. {
  4. head = -1; //申请一个头结点,将指针域置空
  5. idx = 0;
  6. }

一、用数组模拟单链表

模板:

  1. // head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
  2. int head, e[N], ne[N], idx;
  3. // 初始化
  4. void init()
  5. {
  6. head = -1;
  7. idx = 0;
  8. }
  9. // 在链表头插入一个数a
  10. void insert(int a)
  11. {
  12. e[idx] = a, ne[idx] = head, head = idx ++ ;
  13. }
  14. // 将头结点删除,需要保证头结点存在
  15. void remove()
  16. {
  17. head = ne[head];
  18. }

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

  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. 1 ≤ M ≤ 100000
  2. 所有操作保证合法。
输入样例:
  1. 10
  2. H 9
  3. I 1 1
  4. D 1
  5. D 0
  6. H 6
  7. I 3 6
  8. I 4 5
  9. I 4 5
  10. I 3 4
  11. D 6
输出样例:
6 4 6 5

AC代码

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 100010;
  4. // head 表示头结点的下标
  5. // e[i] 表示节点i的值
  6. // ne[i] 表示节点i的next指针是多少
  7. // idx 存储当前已经用到了哪个点
  8. int head, e[N], ne[N], idx;
  9. // 初始化
  10. void init()
  11. {
  12. head = -1; //申请一个头结点,将指针域置空
  13. idx = 0;
  14. }
  15. // 将x插到头结点
  16. void add_to_head(int x)
  17. {
  18. e[idx] = x, ne[idx] = head, head = idx ++ ; //head = idx ++ 等价于head = idx; idx ++;
  19. 因为 head = idx ++ 也是head先等于idx,然
  20. 后idx再 + 1
  21. }
  22. // 将x插到下标是k的点后面
  23. void add(int k, int x)
  24. {
  25. e[idx] = x, ne[idx] = ne[k], ne[k] = idx ++ ;
  26. }
  27. // 将下标是k的点后面的点删掉
  28. void remove(int k)
  29. {
  30. ne[k] = ne[ne[k]];
  31. }
  32. int main()
  33. {
  34. int m;
  35. cin >> m;
  36. init();
  37. while (m -- )
  38. {
  39. int k, x;
  40. char op;
  41. cin >> op;
  42. if (op == 'H')
  43. {
  44. cin >> x;
  45. add_to_head(x);
  46. }
  47. else if (op == 'D')
  48. {
  49. cin >> k;
  50. if (!k) head = ne[head]; //删除头节点,等价于if(k == 0)....
  51. else remove(k - 1);
  52. }
  53. else
  54. {
  55. cin >> k >> x;
  56. add(k - 1, x);
  57. }
  58. }
  59. for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ';
  60. cout << endl;
  61. return 0;
  62. }

注意:

对于删除头节点:head=ne[head]的操作?
删除头结点是head指向的结点,也就是链表中的第一个结点,【head指向可能是一个空结点(e数组不存值),或者是非空结点。指向的空结点叫头结点,指向的非空结点叫首元结点,即后者并没有头结点这种概念】
(这句只针对学过单链表的同学,没学过的同学不用理会这句话)。但是y总并没有区分这个概念,所以删除头结点就是删除链表的第一个有值的结点,即首元结点,head指向ne[head]是因为head本来就指向它的下一个结点,所以ne[head]就是头结点的下一个结点。

 二、用数组模拟双链表

双链表(双向链表)

模板:

  1. // e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
  2. int e[N], l[N], r[N], idx;
  3. // 初始化
  4. void init()
  5. {
  6. //0是左端点,1是右端点
  7. r[0] = 1, l[1] = 0;
  8. idx = 2;
  9. }
  10. // 在节点a的右边插入一个数x
  11. void insert(int a, int x)
  12. {
  13. e[idx] = x;
  14. l[idx] = a, r[idx] = r[a];
  15. l[r[a]] = idx, r[a] = idx ++ ;
  16. }
  17. // 删除节点a
  18. void remove(int a)
  19. {
  20. l[r[a]] = l[a];
  21. r[l[a]] = r[a];
  22. }

例题:827. 双链表 - AcWing题库

        

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

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 k 个插入的数删除;
  4. 在第 k 个插入的数左侧插入一个数;
  5. 在第 k 个插入的数右侧插入一个数

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

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

输入格式

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

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

  1. L x,表示在链表的最左端插入数 x。
  2. R x,表示在链表的最右端插入数 x。
  3. D k,表示将第 k 个插入的数删除。
  4. IL k x,表示在第 k 个插入的数左侧插入一个数。
  5. IR k x,表示在第 k 个插入的数右侧插入一个数。
输出格式

共一行,将整个链表从左到右输出。

数据范围
1 ≤ M ≤ 100000


所有操作保证合法。

输入样例:
  1. 10
  2. R 7
  3. D 1
  4. L 3
  5. IL 2 10
  6. D 3
  7. IL 2 7
  8. L 8
  9. R 9
  10. IL 4 7
  11. IR 2 2
输出样例:
8 7 7 3 2 9

        

  1. // 0是左端点,1是右端点
  2. r[0] = 1, l[1] = 0;
  3. idx = 2;

(2)在节点k的右边插入一个数x

  1. // 在节点a的右边插入一个数x
  2. void insert(int k, int x)
  3. {
  4. e[idx] = x; //先赋值
  5. l[idx] = k, r[idx] = r[k];
  6. l[r[k]] = idx, r[k] = idx ++ ;
  7. }

注意:要先写 l [r [k]] = idx;再 r [k] = idx ++ ;不然会导致错误

此外 ,若想在 k 的左边插入 x,即在 l[k] 的右边插入 x ,可以写成:

void insert(int l[k], int x)

(3)删除节点k

  1. void remove(int k)
  2. {
  3.     l[r[k]] = l[k];
  4.     r[l[k]] = r[k];
  5. }

AC代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int N = 100010;
  4. int m;
  5. int e[N], l[N], r[N], idx;
  6. // 在节点a的右边插入一个数x
  7. void insert(int a, int x)
  8. {
  9. e[idx] = x;
  10. l[idx] = a, r[idx] = r[a];
  11. l[r[a]] = idx, r[a] = idx ++ ;
  12. }
  13. // 删除节点a
  14. void remove(int a)
  15. {
  16. l[r[a]] = l[a];
  17. r[l[a]] = r[a];
  18. }
  19. int main()
  20. {
  21. cin >> m;
  22. // 0是左端点,1是右端点
  23. r[0] = 1, l[1] = 0;
  24. idx = 2;
  25. while (m -- )
  26. {
  27. string op;
  28. cin >> op;
  29. int k, x;
  30. if (op == "L")
  31. {
  32. cin >> x;
  33. insert(0, x);
  34. }
  35. else if (op == "R")
  36. {
  37. cin >> x;
  38. insert(l[1], x);
  39. }
  40. else if (op == "D")
  41. {
  42. cin >> k;
  43. remove(k + 1);
  44. }
  45. else if (op == "IL")
  46. {
  47. cin >> k >> x;
  48. insert(l[k + 1], x);
  49. }
  50. else
  51. {
  52. cin >> k >> x;
  53. insert(k + 1, x);
  54. }
  55. }
  56. for (int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
  57. cout << endl;
  58. return 0;
  59. }

邻接表

邻接表就是n个单链表,比如但链表head[i]存储点i的所有临边

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/556393
推荐阅读
相关标签
  

闽ICP备14008679号