第三章 链表和list 单链表部分

第三章 链表和list 单链表部分

本章涉及链表和list的基本操作,因为个人感觉理解较为复杂,在此分为多部分进行总结,本篇涉及其中的单链表的创建、插入、删除、查找等。

链表的概念

链表的定义

线性表的链式存储就是链表。

它是将元素存储在物理上的任意存储单元中,因此无法像顺序表一样通过下标来保证数据元素之间的逻辑关系,链式存储除了要保存数据元素外,还需要额外维护数据元素之间的逻辑关系,这两部分信息合称结点。每个结点存在两个域:保存数据元素的数据域和保存下一个结点地址的指针域

我们注意到,链表是通过指针来维护数据元素之间的逻辑关系的,因此在本节利用数组模拟单链表时,数组中的下标仅代表其物理地址,而不代表其逻辑地址,务必进行区分。

链表的分类

链表根据指针域的连接方式可以主要分为单链表双向链表

而其中,根据其是否带有头结点,又可以分为带头结点的单/双向链表不带头结点的单/双向链表。还可以根据是否循环,分为循环单/双向链表非循环单/双向链表

根据以上分类,我们可以得到8种链表的组合:

链表
不带头结点的单链表
不带头结点的单向循环链表
带头结点的单链表
带头结点的单向循环链表
不带头结点的双向链表
不带头结点的双向循环链表
带头结点的双向链表
带头结点的双向循环链表

链表的种类繁多,我们只需掌握单向链表双向链表循环链表即可。

单链表的模拟实现

实现方式

链表的实现方式分为动态实现静态实现

  • 动态实现时通过new申请结点,delete释放结点的形式构造链表。这种实现方式最能够体现链表的特征。
  • 静态实现是利用两个数组配合来模拟链表。运行速度很快,在算法竞赛中经常会使用到。

定义-创建-初始化

  1. 两个足够大的数组,e[]表示结点的值,ne[]表示结点的next指针,两者下标(即物理地址)是相同且一一对应的,一定注意:下标代表的是结点的物理地址,而不是逻辑地址。
  2. 变量h充当头指针,表示头结点的位置。
  3. 变量id用于为新结点分配地址。

示例:

const int N = 100010;

int h;       /* 头指针 */
int e[N];    /* 数据域 */
int ne[N];   /* 指针域 */
int id;      /* 分配地址给新结点 */

头插

代码实现:

void insert_head(int x) {
    id++;
    e[id] = x;
    ne[id] = ne[h];     /* 先将新结点的next指针指向原头结点 */
    ne[h] = id;         /* 再将头指针指向新结点 */

}

时间复杂度:O(1)

遍历

代码实现:

void print() {
    /* 从头结点开始遍历直到遇到空指针 */
    for (int i = ne[h]; i ; i = ne[i]) {
        cout << e[i] << " ";
    }
    cout << endl;
}

时间复杂度:O(n)

按值查找

方法一:遍历整个链表。

代码实现:

int find(int x) {
    for (int i = ne[h]; i ; i = ne[i]) {
        if (e[i] == x) {
            return i;
        }
    }
    return -1;
}

时间复杂度:O(n)

方法二:当存储范围较小且不含重复元素时,利用哈希表,可以理解为一个标记数组,将值映射到下标。

代码实现:

int mp[100010];

/*在插入操作时,将值映射到下标
mp[x] = id;
在删除操作时,消除标记
mp[x] = 0;*/

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

时间复杂度:O(1)

在任意位置 后 插入元素

代码实现:

/*
    在p结点后插入元素x
    即p结点的next指针指向新结点,新结点的next指针指向p结点的原next指针*/
void insert(int p, int x) {
    id++;
    e[id] = x;
    ne[id] = ne[p]; 
    ne[p] = id;
}

时间复杂度:O(1)

  • 我们注意到这里的插入操作是针对于指定节点后面插入的,而不是指定位置插入,因为我们这里模拟的单向列表仅能找到指定节点后面的元素,而无法找到前序元素进行插入,后面的删除操作原理相同。

删除任意位置之后的元素

代码实现:

/*
    删除p结点的后一个结点
    即p结点的next指针指向p结点的后一位(原next指针指向的元素)的next指针*/
void del(int p) {
    if(ne[p]){
        ne[p] = ne[ne[p]];
    }
}

时间复杂度:O(1)

  • 当删除最后一给元素时,倒数第二个元素的next指针指向空指针,即ne[ne[p]] = 0。所以这里务必进行判断。

其他问题

上文提到,我们不会去实现尾插、尾删、删除任意位置的元素等操作。在实际应用中,单向链表不会像在模拟数组中一样十分方便的找到最后一个元素,即无法通过id–和id++直接实现尾删或尾插。
我们需要通过遍历才能找到最后一个元素,所需时间过长。

同时,因为无法定位到前序元素,无法将后续元素接上,所以无法实现删除任意位置的元素。

一更:请注意:e[]ne[]括号中的元素都是地址而不是存储元素,虽然在示例中其与存储数据相同,但请注意区分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值