O(1)时间 实现 双链表逆转 《算法导论》10.2-8

  • 先来思考一个问题,通常双链表每个节点包含当前节点的前驱和后继两个指针属性,这里有没有信息冗余呢,属性个数能否进一步减少?

    分析:假设有一个双链表,保存了2个节点,分别是Node1,Node2,通常在实现中还需要一个哨兵节点sentinel。现在让我们画一画所有节点的属性数据

    Node1:
    前驱 | Sentinel
    --|--
    后继 | Node2

    Node2:
    前驱 | Node1
    --|--
    后继 | Sentinel

    Sentinel:
    前驱 | Node2
    --|--
    后继 | Node1

    然后我们会发现,Node1,Node2,Sentinel在属性数据中都出现过两次,看似是有进一步压缩的潜力的,那么可以如何压缩呢?

    一则,既然是双向链表,就要求每个节点必须保存有其前驱和后继节点相关的信息;二则,要减少属性个数,前驱和后继节点的信息又必须保存在一个节点属性里,那么自然想到了保存前驱和后继两者的异或信息,异或的特性是,已知两者中任何一方加上异或信息,都可以还原出另一方。

    与常见的双指针节点相比,单指针节点的缺点是需要前驱的协助才能找到后继,同样需要后继的协助才能找到自己的前驱,也就是说,它不能像双指针节点那样独立地索引到前驱和后继,也正因为此,它删除节点操作的时间复杂度更高;好处是它的节点信息没有固定的方向性,逆转整个链表时,只需要将两端的节点指针对调,O(1)时间即可实现逆转,不需要向双指针节点那样遍历修改每个节点。

    下面是一个C++实现。

struct Node
{
    Node*   pXOR;    //前驱、后继的异或
    int     key;
};

class DoubleLinkedList_XOR
{
public:
    DoubleLinkedList_XOR();
    ~DoubleLinkedList_XOR();
    void Insert(int key);        //在头部插入,O(1)
    Node* Search(int key);    //O(n)
    void Delete(Node* p);    //O(n)
    void Traverse() const; //遍历打印每个节点的key值 O(n)
    void Invert();                //逆转整个链表 O(1)
private:
    inline Node* XOR(const Node*, const Node*) const;
private:
    Node* m_begin;        //头节点的前驱
    Node* m_end;         //尾节点的后继
};

DoubleLinkedList_XOR::DoubleLinkedList_XOR()
    :m_begin(new Node),m_end(new Node)
{
    m_begin->pXOR = XOR(m_end, m_end);
    m_end->pXOR = XOR(m_begin, m_begin);
}

DoubleLinkedList_XOR::~DoubleLinkedList_XOR()
{
    Node* pPrevious = m_end;
    Node* pCurrent = m_begin;
    Node* pNext = nullptr;
    while(pCurrent != m_end)
    {
        pNext = XOR(pPrevious, pCurrent->pXOR);
        delete pCurrent;
        pPrevious = pCurrent;
        pCurrent = pNext;
    }
    delete m_end;
}

void DoubleLinkedList_XOR::Insert(int key)
{
    //because existing node's pre and next should not change together,so init 2 nodes;
    //为什么不能把m_begin,m_end合并为一个节点呢?
    Node* pFirst = XOR(m_end, m_begin->pXOR);
    Node* pSecond = XOR(m_begin, pFirst->pXOR);

    //在m_begin和pFirst之间插入pNew,
    //除了构造pNew之外,
    Node* pNew = new Node;
    pNew->key = key;
    pNew->pXOR = XOR(m_begin, pFirst);
    //还需要修改m_begin和pFirst,其中
    //根据m_begin的新后继pNew和老前驱m_end计算m_begin->pXOR,这里就必须保证m_begin的老前驱必须在此次Insert之间就确定了的。
    m_begin->pXOR = XOR(m_end, pNew);
    //同样,这里要求pFirst的老后继必须是在此次insert之前就确定了的。
    pFirst->pXOR = XOR(pNew, pSecond);
    //所以为了避免处理Insert的边界条件,初始化时必须有两个哨兵节点,m_begin,m_end
}

Node* DoubleLinkedList_XOR::Search(int key)
{
    m_end->key = key;
    Node* pPrevious = m_begin;
    Node* pCurrent = XOR(m_end, m_begin->pXOR);
    Node* pNext = nullptr;
    while(pCurrent->key != key)
    {
        pNext = XOR(pPrevious, pCurrent->pXOR);
        pPrevious = pCurrent;
        pCurrent = pNext;
    }
    return pCurrent == m_end ? nullptr : pCurrent;
}

void DoubleLinkedList_XOR::Delete(Node *p)
{
    Node* pPrevious = m_begin;
    Node* pCurrent = XOR(m_end, m_begin->pXOR);
    Node* pNext = nullptr;
    while(pCurrent != m_end &&
          pCurrent != p)
    {
        pNext = XOR(pPrevious, pCurrent->pXOR);
        pPrevious = pCurrent;
        pCurrent = pNext;
    }
    if(pCurrent == m_end)
    {
        cerr << "Node Not Found" << endl;
        return;
    }
    pNext = XOR(pPrevious, pCurrent->pXOR);
    Node* pBeforePrevious = XOR(pPrevious->pXOR, pCurrent);
    Node* pAfterNext = XOR(pNext->pXOR, pCurrent);

    pPrevious->pXOR = XOR(pBeforePrevious, pNext);
    pNext->pXOR = XOR(pAfterNext, pPrevious);
    delete pCurrent;
}

void DoubleLinkedList_XOR::Traverse() const
{
    const Node* pPrevious = m_begin;
    const Node* pCurrent = XOR(m_end, m_begin->pXOR);
    const Node* pNext = nullptr;
    while(pCurrent != m_end)
    {
        cout << pCurrent->key << '\t';
        pNext = XOR(pPrevious, pCurrent->pXOR);
        pPrevious = pCurrent;
        pCurrent = pNext;
    }
}

void DoubleLinkedList_XOR::Invert()
{
    swap(m_begin, m_end);
}

Node* DoubleLinkedList_XOR::XOR(const Node* p1, const Node* p2) const
{
    return reinterpret_cast<Node*>(
           reinterpret_cast<long>(const_cast<Node*>(p1)) ^
           reinterpret_cast<long>(const_cast<Node*>(p2)));
}

void TestDoubleLinkedList_XOR()
{
    DoubleLinkedList_XOR* p = new DoubleLinkedList_XOR;

    for(int i = 0; i != 10; ++i)
        p->Insert(i);
    p->Traverse(); cout << endl; //9,8,7...0
    p->Invert();
    p->Traverse(); cout << endl; //  0,1,2...9

    auto pFind = p->Search(0);
    cout << pFind->key << endl;//0
    p->Delete(pFind);
    p->Traverse(); cout << endl; //1,2,3...9

    pFind = p->Search(3);
    cout << pFind->key << endl;//3
    p->Delete(pFind);
    p->Traverse(); cout << endl; //1,2,4,5,6,7,8,9

    pFind = p->Search(10);
    cout << pFind<< endl;//0

    pFind = p->Search(3);
    cout << pFind << endl;//0

    delete p;p = nullptr;
}

//输出:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
0
1 2 3 4 5 6 7 8 9
3
1 2 4 5 6 7 8 9
0
0

转载于:https://www.cnblogs.com/meixiaogua/p/9696103.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值