先来思考一个问题,通常双链表每个节点包含当前节点的前驱和后继两个指针属性,这里有没有信息冗余呢,属性个数能否进一步减少?
分析:假设有一个双链表,保存了2个节点,分别是Node1,Node2,通常在实现中还需要一个哨兵节点sentinel。现在让我们画一画所有节点的属性数据
Node1:
前驱 | Sentinel
--|--
后继 | Node2Node2:
前驱 | Node1
--|--
后继 | SentinelSentinel:
前驱 | 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