链表
链表被用于许多库/应用程序,这是有充分理由的。以下是它相对于其他容器的优点,间接来自 cplusplus.com 上的参考页面
- 在容器中的任何位置高效地插入和删除元素(恒定时间)。
- 以正向顺序迭代元素(线性时间)。
- 在容器内甚至在不同容器之间高效地移动元素和元素块(恒定时间)。
以下内容仅适用于双向链表,我稍后会解释
参考资料中没有解释的是它是如何实现的。你*为什么*会想知道这些?对我来说,仅仅是好奇心。对其他人来说,也许他们可能会创建自己的链表容器类型。无论如何,这是*有人*最终会用到的知识,希望如此。
设计
链表通常被描述为以某种方式连接在一起的线性节点列表。在 C/++ 中,你通常会有一个结构,其中包含数据和指向下一个结构容器的指针,该容器包含数据和指向下一个结构容器的指针……依此类推。链表的主要优点是它不以连续的方式包含数据,而是一种灵活的方式。这允许快速插入和更好的整体迭代。链表通常甚至用作其他容器(例如队列或堆栈容器)的基础。
链表有几种变体。实际上,“链表”一词并不真正指实现(因此,是一个抽象术语),而只是指容器的数据的保存方式(通过引用链接)。链表最常见的实现可能是双向链表。双向链表是一个包含节点的列表,这些节点包含对列表中前一个*和*下一个链接的引用。这允许快速节点删除、更快地获取尾部节点以及更灵活的迭代。
以双向链表的灵活性为代价,它通常需要使用更多的内存。在大型列表中,考虑一个节点的大小是正常大小的两倍。这会严重影响应用程序。如果您没有理由向后迭代,则可以认为双向链表效率低下,仅仅是因为设计。 std::list 是一个双向链表。因此,如果我需要单向链表,我会自己实现一个。单向链表只能向前迭代,仅仅因为它所持有的节点仅包含对列表中下一个节点的引用,而不包含对前一个节点的引用,但其优点是少一个引用。
另一种不太常用的链表类型是循环链表。所有这些都是一个单向/双向链表,其中尾部节点向前迭代到起始节点(也可能反之亦然)。这没有太多用途,因为它通常在迭代列表时存在问题,但例如,一个列表会迭代节点直到收到给定的信号。此外,它实际上是一种与链表一起使用的技术……并不总是在实现中使用,尽管特殊的列表实现可以比其他实现更好地处理循环链表:TODO:添加示例...
在嵌入式系统中,使用链表可能很昂贵。每个引用的保存可能非常繁重,以至于不希望将链表与仅保存一个数据位置的节点一起使用。相反,他们通常使用所谓的展开链表。在展开链表中,节点像往常一样保存对下一个和前一个的引用,但每个节点都保存数据数组。当您迭代每个节点时,您线性地迭代数组中的数据,然后移动到下一个节点。这样,我们可以在一个中拥有三到四个数据节点,同时减少节点总数(从而节省内存)。但是,这通常使用连续的内存来保存节点,因此很难移动数组中的节点。TODO:给出一个例子。
维基百科有很棒的图片:http://en.wikipedia.org/wiki/Linked_list#Linear_and_circular_lists
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
template <typename T>
struct SNode //Singly-linked list node
{
SNode () : _next(0) {}
SNode (T data) : _data(data), _next(0) {}
SNode (T data, Node<T>* next) : _data(data), _next(next){}
SNode (Node<T>* next) : _next(next) {}
T data;
Node<T>* next; //Only the next reference.
}
template <typename T>
struct Node : public SNode<T>
{
Node<T>* prev; //This is the only difference in structure!
}
|
这可能是最基本的列表结构形式。但是,这并没有显示列表在任何方面都是有用的。我们可以通过增加实现的复杂性来大大简化接口。 std::list 使用父类,同时在内部使用抽象方法(迭代器)控制节点,以访问节点。我认为 std::list 提供的接口很好,所以我将创建一个类似于它的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
#include <iostream>
template <typename T>
class List;
template <class TNode>
class Iterator
{
/* Helper class to provide pointer like facilities around a node */
friend class List<typename TNode::value_type>;
TNode* pNode; //The node oriented with this instance of iterator.
Iterator(TNode* _pNode) : pNode(_pNode) {}
public:
void operator++(){ pNode = pNode->_next; }
void operator++(int){ pNode = pNode->_next; }
bool operator!=(Iterator<TNode> rval){ return !(pNode == rval.pNode); }
bool operator==(Iterator<TNode> rval){ return (pNode == rval.pNode); }
typename TNode::value_type operator*(){ return pNode->_data; }
Iterator<TNode> operator+(int _i)
{
Iterator<TNode> iter = *this;
for (int i = 0; i < _i; ++i)
{
if (iter.pNode) //If there's something to move onto...
++iter;
else
break;
}
return iter; //Return regardless of whether its valid...
}
};
template <typename T>
class Node
{
friend class List<T>;
friend class Iterator<Node<T> >;
Node () : _next(0) {}
Node (T data) : _data(data), _next(0) {}
Node (T data, Node<T>* next) : _data(data), _next(next){}
Node (Node<T>* next) : _next(next) {}
T _data;
Node<T>* _next;
public:
typedef T value_type;
};
template <typename T>
class List
{
Node<T>* first;
public:
typedef Iterator<Node<T> > iterator;
typedef T value_type;
List () : first(0) {}
~List()
{
if (first)
{
Node<T> *iter = first;
while (iter != 0)
{
Node<T>* tmp = iter;
iter = iter->_next;
delete tmp;
}
}
}
void push_back(T data)
{
if (first)
{
Node<T> *iter = first;
for (; iter->_next != 0; iter = iter->_next); //Iterate until we reach the end of our linked list.
iter->_next = new Node<T>(data);
}
else
first = new Node<T>(data);
};
void push_front(T data)
{
if (first)
{
Node<T> * tmp = new Node<T>(data);
tmp->_next = first;
first = tmp;
}
else
first = new Node<T>(data);
}
iterator begin(){ return iterator(first); }
iterator end(){ return iterator(0); }
bool erase(iterator& _iNode) //True for success, vice versa
{
/* This is rather inneffecient. Maybe a better way to do this? */
/* Even then, it's *still* more effecient than a contiguous container */
if (_iNode.pNode == first)
{
first = first->_next;
delete _iNode.pNode;
return true;
}
else
{
for (Node<T>* iter = first; iter->_next; iter = iter->_next)
{
if (iter->_next == _iNode.pNode) //Find our node.
{
iter->_next = _iNode.pNode->_next;
delete _iNode.pNode;
return true;
}
}
}
return false;
}
};
int main(void)
{
List<int> list;
list.push_back(3);
list.push_back(4);
list.push_front(2);
list.push_front(1);
/*Print all elements*/
for (List<int>::iterator iter = list.begin();
iter != list.end(); ++iter)
{
std::cout << (*iter) << std::endl;
}
/*Delete second element and reprint*/
List<int>::iterator tmp = list.begin() + 1;
list.erase(tmp);
for (List<int>::iterator iter = list.begin();
iter != list.end(); ++iter)
{
std::cout << (*iter) << std::endl;
}
/*Now delete first node and print again*/
tmp = list.begin();
list.erase(tmp);
for (List<int>::iterator iter = list.begin();
iter != list.end(); ++iter)
{
std::cout << (*iter) << std::endl;
}
std::cin.ignore();
//List object takes care of deletion for us.
return 0;
}
|
就功能而言,这是一个巨大的改进。我们现在为我们的节点提供了(基本的)内存管理,以及易于使用的迭代器来迭代我们的节点,而没有指针的危险。你还想要什么?
上面是单向链表的快速实现。如果你查看代码,它相对简单明了(通过逻辑)。没有的是已经被注释掉了。
在上面,我们可以为我们的特定需求更改和定制*很多*东西。例如,(*)类的迭代器可以同时包含前一个节点和当前节点,以帮助更有效地进行删除,但会牺牲内存和迭代时间。例如,如果您有一个很大的列表并且一直移动并且必须重新迭代列表,那么由于重新分配多个节点引用的额外常量,这可能不是很有效。如果您不断地删除和/或交换列表中的元素,那么这将非常有效,因为您需要更改将被交换或删除的元素的先前节点所持有的下一个引用。
还有一种方法可以通过一种称为 XOR 链接的双向链表来降低内存成本,该方法使用 XOR 加密指针来缩小使用的内存大小。TODO:提供示例。