链式存储结构线性表
顺序表的问题:
1、顺序表的访问和修改的时间复杂度是常数级的,非常快。但是顺序表的插入和删除的缺不好,这两种操作的时间复杂读是O(N),看上去挺好,但是顺序表的插入和删除是通过大量数据的移动形成的。考虑最坏的情况,一个顺序表很长,在第一个位置插入元素,或者删除一个很长的顺序表的第一个元素,需要移动大量的元素,当元素的类型是复杂的类类型时,这种情况就更糟了。
2、顺序表是定长的,如果我们申请的长度不够用时,可以使用动态的顺序表,这样是可以实现,但是,又遇到了上述的问题,需要拷贝很多元素,而且元素类型是复杂的类类型时,就和上述问题差不多了
链表可以完美的解决上述的两点问题。
链表:
线性表中的元素在物理空间上不再是连续的,而在逻辑上是连续的。为了实现在逻辑上是连续的描述,链表中的元素由两部分构成,一部分是数据域,另一部分是指针域。这两部分结合起来,我们称之为结点。这时因为逻辑上的连续,所以依然是一种线性表。
基本概念(单链表):
头结点,数据域不写任何数据,指针域是第一个数据结点的地址,只是起到了一个辅助操作的作用
数据结点:数据域和指针域都用到了,包含了数据和后继结点的地址信息
尾结点:链表中的最后一个结点,指针域为空
尾结点是一个特殊的数据结点,因为它大体上可以提现一个链表的类型。
怎么解决开始提出的两个问题:
Node* position(int i) const
{
Node* ret = reinterpret_cast<Node*>(&m_header);
for(int p=0; p<i; p++)
{
ret = ret->next;
}
return ret;
}
bool insert(int i, const T& e)
{
bool ret = ((i >= 0) && (i <= m_length));
if( ret )
{
Node* node = new Node;
if(node != NULL)
{
Node* current = position(i);
node->value = e;
node->next = current->next;
current->next = node;
m_length++;
}
else
{
THROW_EXCEPTION(NoEnoughMemoryException,"No enough memory to insert element!");
}
}
return ret;
}
这个插入的时间复杂读也是O(N),定义一个指向头结点的指针,循环N次,指针指向了N-1号结点,此时的指针域即是我们想要操作的结点的地址。此时申请一个结点,将我们的数据写入数据域,然后先把申请的结点的指针域指向N号结点的地址,然后再把N-1号几点的指针域修改为我们申请的结点内存,这样就插入了一个元素。插入的顺序一定不能颠倒,我们那样操作,保证了始终是一个完整的链表。这个操作的顺序决定了你能不能构造一个好的链表。可以发现,链表的插入虽然时间复杂读和顺序表相同,但是没有大量元素的移动,只是指针的操作,非常简单方便。
删除也是一样的倒立,都是指针的操作,不涉及元素的大量移动。得到-1号结点的指针后,用一个指针变量得到0号结点的地址,即-1号结点的指针域,然后把-1好的指针域指向+1号的结点地址,这样0号结点就从链表上消失了,但是为了不发生内存泄露,我们此时去释放0号结点地址的内存,因为上述我们已经用变量提前保存了信息,所以不会不知道被移走的结点的地址。
bool remove(int i)
{
bool ret = ((i >= 0) && (i < m_length));
if( ret )
{
Node* current = position(i);
Node* toDel = current->next;
current->next = toDel->next;
delete toDel;
m_length--;
}
return ret;
}