算法 {双链表(单链表)}

算法 {双链表(单链表)}

@LOC: 2;

双链表(单链表)

定义

由若干节点组成, 每个节点拥有 {id: 唯一性标识, previous_id: 前驱节点的id号, next_id: 后驱节点的id号};

头节点: 从头节点开始, 不断通过next_id, 可以遍历完整个链表;

尾节点: 从尾节点开始, 不断通过previous_id, 可以遍历完整个链表;

性质

# {链表的连通性, 删除节点的2种方式}; MAKR: @LOC_1;

从算法角度 (或数据结构角度), 链表结构里 只能有1条链表, 即H->...->T, H1->...->T1 这是错误的 因为有2条链表;
即链表 一定是连通的, 为什么要谈这个问题呢?
. 因为如果你只有插入操作 那么这个链表一定始终是连通的, 但是一旦有删除操作 那么你要如何定义这个删除操作?

假如你定义的删除操作是: H->...->p->c->n->...->T, 删除c节点后 变成了H->...->pn->...->T, 那么原来的链表 就不再连通了, 就变成2条链表了;
. 那么, 根据上面讲的链表的连通性, 这就不符合了, 因此 这种删除方式, 在链表了 是不被允许的;

因此, 你要做到: 既删除节点, 还要继续保持链表的连通性; 要满足这两点, 有2种删除策略:

## 删除节点–方式1;

原链表: H->...->p->c->n->...->T, 删除c后 变成: H->...->p->n->...->T;

这个经典做法 也是链表默认的删除节点方法, 即将要删除节点的前驱和后驱 连接起来;
. 如果你认为这种方式 头尾节点不会改变, 这是错误的; 因为如果c = H (或c = T), 那么头尾节点是会改变的; (如果c不是{头/尾}节点, 那么头尾节点就不会改变);

## 删除节点–方式2;

原链表: H->...->p->c->n->...->T, 删除c后 变成: n->...->T->H->...->p;

这种删除策略, 即如果c = {H/T} 则和方式1一样 直接删除该点即可;
但当c != {H, T}时 此时他的做法是: [删除当前点, 连接T->H], 然后头尾节点一定会改变 (这和方式1不同, 方式1的头尾节点 在这种情况下 是不会改变的), 新的头是后驱n 而新的尾是前驱p;
. 注意, 连接T->H 和 连接H->T 是完全不同的, 只有连接T->H (即next[ T] = H, previous[ H] = T) 才能保证链表是连通的, 否则如果是H->T 那这个链表是无效的 就不是链表了;

例题: LINK: @LOC_0;

@DELIMITER

# 节点的id号;

每个节点 有3部分组成: {id号, previous_id, next_id前驱后驱节点id号, data数据} (比如保证所有节点的id号都不同, 因为id号是唯一标识一个节点的依据), 很重要的是 这id号 不是用户来指定设定的, 而是链表内部自己维护的;
. 假如让用户来指定id号, 对于插入删除操作 就无法做到O(1)时间了; 比如用户指定的id号为-123, 那么根据这个id号 要想得到他的节点, 只能通过O(n)的依次遍历去找这个id号;
. 因此, 必须由系统自己来设定这个id号, 具体做法是: 所有节点都是在一个数组Arr里, 每次往链表里插入一个节点, 这个节点 其实就是数组里的一个元素 比如是Arr[ x], 那么x就是这个节点的id号;

这也有点矛盾, 因为一切操作(插入,删除) 这肯定是用户调用的, 他们都需要依据id号来进行操作 (比如删除id=x的节点, 在id=x的节点的后面 插入一个节点);
. 可是, 这id号 是系统内部自己指定的, 用户怎么知道呢?
. 这其实并不矛盾, 2点可以说明:
. 第一, 每个插入操作的函数 都会返回一个id号 表示新插入这个节点的id号;
. 第二, 其实id号 你自己也可以计算, 系统指定id号的规则 就是有一个id_counter, 根据你插入过多少节点, 比如你插入过x个节点 (即调用了x次有关插入节点的函数), 那么 如果你再插入一个节点 他的id号就会是x; (注意, 插入过的节点数 不等于 当前链表里节点数, 因为可能会有删除过节点; 即新插入的节点的id号 不仅和当前链表里的节点的id不同 而且和已经删除的节点的id也不同);
. 假如你就是要自己去指定id号, 那么将你的id号 和 你插入的这个节点的系统id号, 你自己去做一个关联;
. . 一般来说, 题目就是通过第几次插入的节点 作为id号, 比如题目说 删除第x次插入的节点 (看到这个题目, 基本就是链表的题目), 那么第x次插入的节点 他的id号 就是 x − 1 x-1 x1;

@DELIMITER

# 数组与链表;

我们可能会拿数组来和链表做类比, 认为链表是一种特殊的数组, 其实这是完全错误的;
. 因为链表就是从头节点到尾节点 比如H->a->b->T, 他对应到数组 也就是: [H, a, b, T], 那么 比如对于插入操作 插入c, 链表会变成H->a->c->b->T 对应数组是[H, a, c, b, T], 看似好像是合理的 其实这是错误的;
. . 错误原因: 不管数组还是链表, 都必须有一个id 来标识元素, 数组是通过下标 而链表 他节点里面会有一个id号 (所有节点的id号 一定是独一无二的); 此时再对比数组和链表, 插入前后 H,a,b,T这些节点 他的id号肯定是相同的, 但是 如果对应到数组 原来b,T的下标是2,3, 可是 插入之后 他俩的下标变成了3,4;
. . 因此 用数组来模拟链表 是不正确的, 最本质的差异是 链表是通过让每个节点拥有previous_id, next_id指向前驱后驱节点 来确定节点的相对位置, 而数组是通过固定的线性下标 因此数组的元素的相对位置 就是绝对位置 是固定死了的; 当然 他俩还有很多其他不同 比如插入删除的时间度问题;

@DELIMITER

# 方向是唯一的;

不管是单链表 还是双链表 (其实他俩本质上是一样的), 单链表就是没有previous_id的双链表;
. 单链表肯定是有方向的 这很容易判断, 因为每个节点 都只能往一个方向走 即走next_id这个方向;
. . 但你容易以为 双链表是没有方向的, 因为每个节点 可以往2个方向走 即previous_id, next_id都可以走; 但严格讲 这种说法是错误的, 虽然每个节点有2个方向 但对于这个链表 他的方向 是独一无二的, 即next_id是他的方向;
. . . 之所以再加一个previous_id 其实只是一个附加功能 记录下前驱节点;
. 不管{单/双}链表 他都有唯一的head_id, tail_id头和尾节点, 从head_id通过next_id遍历到tail_id 这个方向 就是链表的方向; 也就是, 头和尾 2个节点, 就确定了链表的方向;
. 因此, 给定链表里的两个邻接点a<->b, 虽然从图的角度看 好像这是一条无向边 无法说谁指向谁, 但这是错误的, 他一定是有方向的;
. . 如果是next_id[ a] = b 则说明方向是a->b, 否则一定是next_id[ b] = a 那么方向是b->a; 明白这一点, 对理解双链表非常非常重要, 虽然是双向, 但其实他是有方向的! 而是方向是唯一的;
. . 这里又引入一个概念, 即连接2个节点操作, 比如给定2个节点a,b 你要将他俩连接起来, 那么 你必须要指定方向, 即是a->b 还是 b->a;
. . 千万不要双链表的边 理解为是无向边 这是错误的; 如果是a->b 那么对应的是next[ a] = b, previous[ b] = a; 而如果是b->a 则对应的是next[ b] = a, previous[ a] = b;
. . 也就是, next是唯一用来表明方向的; 所以不能看做是无向边 因为一个是next 另一个是previous, 并不是说 next[ a] = b, next[ b] = a 不是这样的;

假如双链表没有方向, 那么是无法进行插入操作 的, 下面介绍下插入操作;

## 插入操作;

对于链表H-a-T, 插入操作有几种情况:
. ?-H-a-T (在链表的头部插入);
. H-?-a-T (在a节点的前面插入);
. H-a-?-T (在a节点的后面插入);
. H-a-T-? (在链表的尾部插入);

即可以总结为2种操作: {操作1: 在链表的头部/尾部插入, 操作2: 在x节点的前面/后面插入};
. 你可能认为: 操作1 可以归纳到 操作2里面 (比如在链表头部插入, 不就是在head节点的前面插入嘛), 这是不对的; 当链表为时, 此时没有节点 (即x节点是不存在的), 那么操作2此时就不可行了, 但是 操作1是可行的 (即当链表为空时, 通过操作1来插入);

@DELIMITER

模板

//{ `LinkedList`--声明;
template< class _Type_Data>
class __LinkedList{
public:
    struct Node_{
    public:
        int Previous_id, Next_id;
        _Type_Data Data;
    };
    //--
    const Node_ * Nodes;
    //--
    __LinkedList( int);
    void Initialize();
    int Add_head( const _Type_Data &);
    int Add_tail( const _Type_Data &);
    int Add_back_byId( int, const _Type_Data &);
    int Add_front_byId( int, const _Type_Data &);
    void Delete_type1( int);
    void Delete_type2( int);
    void Modify( int, const _Type_Data &);
    bool Exist( int);
    //--
    int Get_nodesCount() const{ return nodes_count;}
    int Get_headId() const{ return head_id;}
    int Get_tailId() const{ return tail_id;}
private:
    int lengthMaximum;
    Node_ * nodes;
    int head_id, tail_id;
    int id_counter;
    int nodes_count;
    bool * id_exist;
};
//} `LinkedList`--声明;
//{ `LinkedList`--实现;
//<
// @BRIFE 
// . 虽然是双链表 但他是有方向的 而且方向是唯一的 即从`head_id->...->tail_id`;
//   . 链表一定是连通的, 即从任意节点开始遍历 一定可以遍历到所有*未删除的节点(即`is_exist=true`)*;
// @NAME( id_counter): 因为最初链表一定为空 你一定需要进行添加节点操作, 每个添加过的节点 都有一个独一无二的`id`号;
// . 注意是`添加过的` 因为节点可能被删除, 所有节点 不管是当前链表里的 还是已经被删除了的, 他们的`id`号 都是不同的;
// . 其实这个`id`号 就对应该节点的`插入时序` (即 调用`Add_?`函数的次序), 比如最先第一个插入的节点 id号一定为`0`;
//   . `id = 5`的节点 说明是他第`6`个插入进来的;
// @NAME( nodes): `nodes[ x]`这个节点, 他里面是不存储`id`号的 (链表的节点由3部分组成: {id号, 前驱后驱节点的id号, 数据})
// . 这里不存储id号, 因为数组下标`x` 就是他的id号, 再存储就重复了;
template< class _Type_Data> __LinkedList< _Type_Data>::__LinkedList( int _lengthMaximum)
        :
        lengthMaximum( _lengthMaximum){
    nodes = new Node_[ lengthMaximum];  id_exist = new bool[ lengthMaximum];
    //--
    Nodes = nodes;
    //--
    Initialize();
}
template< class _Type_Data> void __LinkedList< _Type_Data>::Initialize(){
    head_id = tail_id = -1;  id_counter = 0;  nodes_count = 0;
    memset( id_exist, false, sizeof( lengthMaximum));
}
template< class _Type_Data> int __LinkedList< _Type_Data>::Add_head( const _Type_Data & _data){
//< 在链表头插入; 返回值为*新插入的这个节点的id号*;
    ASSERT_( id_counter < lengthMaximum);
    //--
    nodes[ id_counter].Data = _data;  id_exist[ id_counter] = true;
    //--
    nodes[ id_counter].Previous_id = -1;  nodes[ id_counter].Next_id = head_id;
    //--
    if( head_id != -1) nodes[ head_id].Previous_id = id_counter;
    //--
    head_id = id_counter;
    if( tail_id == -1) tail_id = id_counter;
    //--
    ++ nodes_count;  ++ id_counter;
    //--
    return id_counter - 1;
}
template< class _Type_Data> int __LinkedList< _Type_Data>::Add_tail( const _Type_Data & _data){
//< 在链表尾部插入; 返回值为*新插入的这个节点的id号*;
    ASSERT_( id_counter < lengthMaximum);
    //--
    nodes[ id_counter].Data = _data;  id_exist[ id_counter] = true;
    //--
    nodes[ id_counter].Previous_id = tail_id;  nodes[ id_counter].Next_id = -1;
    //--
    if( tail_id != -1) nodes[ tail_id].Next_id = id_counter;
    //--
    tail_id = id_counter;
    if( head_id == -1) head_id = id_counter;
    //--
    ++ nodes_count;  ++ id_counter;
    //--
    return id_counter - 1;
}
template< class _Type_Data> int __LinkedList< _Type_Data>::Add_back_byId( int _id, const _Type_Data & _data){
//< 在`id`号节点的`后面` 插入节点;  返回值为*新插入的这个节点的id号*;
    ASSERT_( _id >= 0 && _id < lengthMaximum && id_exist[ _id] && id_counter < lengthMaximum);
    //--
    nodes[ id_counter].Data = _data;  id_exist[ id_counter] = true;
    //--
    nodes[ id_counter].Previous_id = _id;  nodes[ id_counter].Next_id = nodes[ _id].Next_id;
    //--
    if( nodes[ _id].Next_id != -1) nodes[ nodes[ _id].Next_id].Previous_id = id_counter;
    nodes[ _id].Next_id = id_counter;
    //--
    if( _id == tail_id) tail_id = id_counter;
    //--
    ++ nodes_count;  ++ id_counter;
    //--
    return id_counter - 1;
}
template< class _Type_Data> int __LinkedList< _Type_Data>::Add_front_byId( int _id, const _Type_Data & _data){
//< 在`id`号节点的`前面` 插入节点;  返回值为*新插入的这个节点的id号*;
    ASSERT_( _id >= 0 && _id < lengthMaximum && id_exist[ _id] && id_counter < lengthMaximum);
    //--
    nodes[ id_counter].Data = _data;  id_exist[ id_counter] = true;
    //--
    nodes[ id_counter].Previous_id = nodes[ _id].Previous_id;  nodes[ id_counter].Next_id = _id;
    //--
    if( nodes[ _id].Previous_id != -1) nodes[ nodes[ _id].Previous_id].Next_id = id_counter;
    nodes[ _id].Previous_id = id_counter;
    //--
    if( _id == head_id) head_id = id_counter;
    //--
    ++ nodes_count;  ++ id_counter;
    //--
    return id_counter - 1;
}
template< class _Type_Data> void __LinkedList< _Type_Data>::Delete_type1( int _id){
//< 删除`id`号 (方式1); 假如当前链表为`head->...->p->id->n->...->tail` 则删除后变成`head->...->p->n->...->tail`;
    ASSERT_( _id >= 0 && _id < lengthMaximum && id_exist[ _id]);
    //--
    id_exist[ _id] = false;  -- nodes_count;
    //--
    if( _id == head_id){
        if( nodes[ _id].Next_id == -1) head_id = tail_id = -1;
        else nodes[ nodes[ _id].Next_id].Previous_id = -1, head_id = nodes[ _id].Next_id, nodes[ _id].Next_id = -1;
    }
    else if( _id == tail_id){
        if( nodes[ _id].Previous_id == -1) head_id = tail_id = -1;
        else nodes[ nodes[ _id].Previous_id].Next_id = -1, tail_id = nodes[ _id].Previous_id, nodes[ _id].Previous_id = -1;
    }
    else{
        nodes[ nodes[ _id].Previous_id].Next_id = nodes[ _id].Next_id;
        nodes[ nodes[ _id].Next_id].Previous_id = nodes[ _id].Previous_id;
        nodes[ _id].Previous_id = nodes[ _id].Next_id = -1;
    }
}
template< class _Type_Data> void __LinkedList< _Type_Data>::Delete_type2( int _id){
//< 删除`id`号 (方式2); 假如当前链表为`head->...->p->id->n->...->tail` 则删除后变成`n->...->tail->head->...->p`;
    ASSERT_( _id >= 0 && _id < lengthMaximum && id_exist[ _id]);
    //--
    id_exist[ _id] = false;  -- nodes_count;
    //--
    if( _id == head_id){
        if( nodes[ _id].Next_id == -1) head_id = tail_id = -1;
        else nodes[ nodes[ _id].Next_id].Previous_id = -1, head_id = nodes[ _id].Next_id, nodes[ _id].Next_id = -1;
    }
    else if( _id == tail_id){
        if( nodes[ _id].Previous_id == -1) head_id = tail_id = -1;
        else nodes[ nodes[ _id].Previous_id].Next_id = -1, tail_id = nodes[ _id].Previous_id, nodes[ _id].Previous_id = -1;
    }
    else{
        nodes[ tail_id].Next_id = head_id;  nodes[ head_id].Previous_id = tail_id;
        head_id = nodes[ _id].Next_id, tail_id = nodes[ _id].Previous_id;
        nodes[ _id].Previous_id = nodes[ _id].Next_id = -1;
    }
}
template< class _Type_Data> void __LinkedList< _Type_Data>::Modify( int _id, const _Type_Data & _data){
//< 修改节点;
    ASSERT_( _id >= 0 && _id < lengthMaximum && id_exist[ _id]);
    //--
    nodes[ _id].Data = _data;
}
template< class _Type_Data> bool __LinkedList< _Type_Data>::Exist( int _id){
//< 判断`id`号节点 是否存在 (即是否已经被删除了);
    ASSERT_( _id >= 0 && _id < lengthMaximum);
    //--
    return id_exist[ _id];
}
//} `LinkedList`--实现;

例题

双链表模板: https://www.acwing.com/problem/content/829/

@DELIMITER

单链表模板: https://www.acwing.com/problem/content/828/

@DELIMITER

MARK: @LOC_0;
删除节点-方式2 的应用;
LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=130464894;

@TODO

{ ___List_
//template< class _T_> struct ___List_{
//struct __Node_{
//    __Node_ * Prev, * Next;
//    _T_ Data;
//};
//    __Node_ * __Head; // 虽然是*双向链表* 即`f-...-b`(注意`f-b`也是有边的) 但我们还是从一维的角度去处理他 即他也是有头的 `f`就是头 `f.Next`就是尾;
//    int Size;
//    void Initialize(){
//        __Head = nullptr;
//        Size = 0;
//    }
//    __Node_ * Insert_Front( __Node_ * _ind, _T_ const& _data){ // `ind.Prev = new Node`;
//        ++ Size;
//        if( _ind == nullptr){
//            ASSERT_(__Head == nullptr);
//            __Head = new __Node_;
//            __Head->Prev = __Head->Next = __Head;
//            __Head->Data = _data;
//            return;
//        }
//        __Node_ * cur = new __Node_;
//        cur->Data = _data;
//        cur->Prev = _ind->Prev;
//        cur->Next = _ind;
//        _ind->Prev = cur;
//        if( _ind == this->__Head){ this->__Head = cur;}
//        return cur;
//    }
//friend std::ostream& operator<<( std::ostream & _cout, ___List_ const& _a){
//    _cout<< "___List_-Debug-Begin\n";
//    _cout<< "Size: "<< _a.Size<< "\n";
//    if( _a.__Head != nullptr){
//        ASSERT_( _a.Size==0, _a.Size);
//        _cout<< "[";
//        _cout<< _a.__Head->Data;

//        _cout<< "]";
//    }
//    _cout<< "___List_-Debug-End\n";
//    return _cout;
//}
//};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值