链表的内涵简单,使用不简单
链表就是一段内存颗粒的连接,如同珍珠项链。珠子就是存放的内容,线就是单向或双向指针。
链表相对于c数组,好处是内存全是散的,不需要整块内存,只处理头部或尾部数据很简单,中间加减数据不复杂。
但好处就是坏处,散在的内存就无法随机定位,对于随机操作就成了弟弟。而且对于巨大的数据量,析构都是问题。
想起一个故事,韩信要与项羽决战,但刘邦怎么催都不出战,因为缺少一个工具,战车。
战车不难造,又快又能抗打的没有,但是如果不想死,必须造出这种战车。铁车抗造,但马拉不动,只能挨打。木车轻快,但一箭穿心,车里的人都玩完。
怎么办?
结合,取其精华,去其糟粕,将木车防箭的地方镶上铁板,整体下来,依然没有铁车结实,没有木车轻快,但吸收了二者的优点,去除了二者的缺陷,已经可以一战了。
故事归故事,瞎说的,但道理是实在的。数组加链表的组合体,因该是对付大数据最合适的战车。
以下是纯单向链表实现的队列和栈,非常简单,不过我照着《算法4》这本书扣了半天,愣是没弄出来,最大的问题是java和C++的变量差的太远,其实java的变量更像C++的指针。怪不得C语言满天飞指针,因为在实现底层数据结构的时候,所有的玩法都是内存的玩法。
动态数组,说白了就是一段连续内存。链表就是不连续内存。对付内存,只能用指针,否则实现难度直接升一个量级,一点不夸张。
多说无益,见代码:再多说两句,链表析构比构造要慢太多了。C++写类的时候,一定给基础变量初始值,指针是nullptr,数值是0,否则就等着找bug吧。
#ifndef STACK
#define STACK
#include <assert.h>
template <typename T>
struct ListStack;
template <typename T>
struct ListQueue;
template <typename T>
struct Node
{
private:
T item;
Node *next = nullptr;
friend class ListStack<T>;
friend class ListQueue<T>;
};
template <typename T>
struct ListStack
{
bool isEmpty() { return N == 0; }
int size() { return N; }
void push(const T &item)
{
Node<T> *oldfirst = first;
first = new Node<T>();
first->item = item;
first->next = oldfirst;
++N;
}
T pop()
{
assert(first);
T item = first->item;
Node<T> *temp = first;
first = first->next;
--N;
delete temp;
return item;
}
~ListStack()
{
while (first)
{
Node<T> *temp = first;
first = first->next;
delete temp;
}
}
private:
Node<T> *first = nullptr;
unsigned N = 0;
};
template <typename T>
struct ListQueue
{
bool isEmpty() { return N == 0; }
int size() { return N; }
void enqueue(const T &item)
{
Node<T> *oldlast = last;
last = new Node<T>();
last->item = item;
if (first)
{
oldlast->next = last;
}
else
{
first = last;
}
++N;
}
T dequeue()
{
assert(first);
Node<T> *df = first;
T item = first->item;
first = first->next;
delete df;
--N;
return item;
}
~ListQueue()
{
while (first)
{
Node<T> *df = first;
first = first->next;
delete df;
}
last = nullptr;
}
private:
Node<T> *first = nullptr;
Node<T> *last = nullptr;
unsigned N = 0;
};
#endif