
一旦我偶然看到我的同事解决了在C ++中扩展双向链表的初级任务。 在那一刻,令我感到奇怪的不是他是领导者,而且早已不合时宜,而是决定本身。
更确切地说,并非如此。
解决方案是标准的:成千上万的人在它之前写过相同的线性段落,并在每个节点中替换了指针。 没什么异常或复杂的,但是当我看着他时,我有两个问题:
- 为什么默认情况下每个人都以这种方式解决问题?
- 有可能做得更好吗?
简要介绍一下装备。 如果您知道双向链表是什么,则可以跳过下一段。
双向链表是基本数据结构之一;熟悉链表通常发生在编程培训的早期。 这是一个由一系列节点组成的集合,这些节点包含一些数据并且彼此配对-也就是说,如果节点A将B称为下一个,则节点B将A称为上一个。 实际上,您可以在诸如C ++中的std :: list,C#中的LinkedList和Java之类的容器中找到此结构。 您很可能会在其他任何语言的标准库中找到其实现。
进一步在本文中,将描述具有计算复杂性的指示的这种列表上的基本属性和操作。
现在回到前面提出的问题。
我调查了问题的状况,并看到了第一个答案。 这是:
struct node
{
int data;
node* next;
node* prev;
};
. ? :

. ― , . , , .
EBDCA. ― , . .
, , , , O(1), . , . , .
. , , :
- , .
- . , .
- . , .
- .
- (/ //) .
- / , .
- – . , .
― . ― / . , - . , . , - , , , - .
«
? -?» ― : «
!»
, , - - . , : , . Data-Oriented Design: , , , . DOD. . , ,
.
, , . . .
, -, . , «» . :

, . , . , - O(1), .
― (SoA) (AoS), . :

? :
― . data, prev/next.
/ -.
, ― , prev next . - . .
, ?
ACDBE. EBDCA, :

! , , , , .
, prev next , first last , , :
prev <-> next
last <-> first
, . . . - , - .
, , :
template <class T, size_t Cap>
struct doubly_linked_list
{
struct node { size_t index; };
T& get(node n) { return data_[n.index]; }
const T& get(node n) const { return data_[n.index]; }
node first() const { return { first_ }; }
node last() const { return { last_ }; }
node next(node n) const { return { next_[n.index] }; }
node prev(node n) const { return { prev_[n.index] }; }
bool is_valid(node n) const { return n.index < length_; }
node add(T v)
{
auto index = length_;
if (length_ == 0) first_ = 0;
data_[index] = v;
next_[index] = INVALID_INDEX;
prev_[index] = last_;
next_[last_] = index;
last_ = index;
length_++;
return { index };
}
node insert_before(T v, node n)
{
auto index = length_;
data_[index] = v;
next_[index] = n.index;
auto prev = prev_[index] = prev_[n.index];
prev_[n.index] = index;
next_[prev] = index;
length_++;
if (n.index == first_) first_ = index;
return { index };
}
void reverse()
{
std::swap(first_, last_);
std::swap(next_, prev_);
}
private:
static constexpr size_t INVALID_INDEX = SIZE_T_MAX;
T data_[Cap];
size_t indirection_[Cap * 2];
size_t *next_ = indirection_;
size_t *prev_ = indirection_ + Cap;
size_t first_ = INVALID_INDEX;
size_t last_ = INVALID_INDEX;
size_t length_ = 0;
};
:
auto list = doubly_linked_list<int, 10>();
auto pos = list.add(0);
for (int i = 0; i < 5; ++i) pos = list.insert_before(i + 1, pos);
std::cout << "list";
for (auto n = list.first(); list.is_valid(n); n = list.next(n))
std::cout << list.get(n) << " ";
list.reverse();
std::cout << std::endl << "reversed";
for (auto n = list.first(); list.is_valid(n); n = list.next(n))
std::cout << list.get(n) << " ";
, , AoS . , : - , .
, . , - - ― , cache-friendly
DCEL, . , .
:. . . ― , . , , , – , .
Data-Oriented Design: , . . – , . , .
. - .