I think There is no need to translate lesson 4 5 6 So..Let's continue with This 链表
虽然链表听起来好像很难很吓人的样子,不过不要担心,当你亲自有一些小实践后你就会发现原来链表也不过如此。当我第一次学到这种奇怪的数据存储方式的时候,我真的在默想我以后一定不会使用这种方式。现在我们观念完全的改变了。在我的游戏中,链表是许多数据储存的基础。
当你不知道你到底需要一种数据类型的多少对象的时候,且你不想浪费空间,链表的确是一种很良好的选择。当程序运行的时候,链表的大小能够动态的改变。 在我还没把你搞迷惑之前,我们来看好好的看看链表吧。
链表是由结构或是记录(它们称为节点)组成的一个链。每一个节点至少有两个成员,其中的一个成员指向下一个节点!!这种链表称为单链表,因为节点中只指向下一个节点,由不是前一个。还有一种被称为双向链表或是循环链表,它们节点即指向前一个节点也指向后一个节点。要注意的是:双向链表和循环链表是有区别的。在这点上我不打算解释更多,这个教程只打算介绍单向链表。由链表的定义可知,我们可以在节点中存放任何东西。只有一种要注意的是,链表中各个节点必须是同一种结构类型。这意味着,我们无法使一个存放CHAR型的节点指向一个存放SHORT型的节点。链表还有一个很重要特性在于,每个结点在内存中的位置是任何的,不必一定得是线性相连在一起的。
typedef struct List
{
long Data;
List* Next;
List()
{
Next=NULL;
Data=0;
}
}; (C++来表达一个结构,List()是这个结构的构造函数)
这里要注意的是我们写了一个默认构造函数来设置Next=NULL.这么做的原因在于当我们遍历链表的时候,我们需要判定是否已达链尾。当Next!=NULL时意味着指向本链的下一个节点;若为NULL,则表明我们已遍历了整张表了。
Starting UP
1、首先,我们先把指针指向已申请的内存。我们定义一个空指针,申请一块内存(以节点的数据类型),然后对指针赋值。
SLList::SLList()
{
Head=new List();
Tail=Head;
CurrentPtr=Head;
}
在这样初始化之后我们可以什么也不过。Head指针向一直指向第一个内存块直到它被释放。就像我们上们讨论的一样,我们定义一个空指针,申请一块内存,然后使Head Tail都指向这块内存。这个初始化的过程是非常之重要的。我们必须创建至少一个节点实例以即于我们定义的指针是指向一个真正的内存块地址。OK。。我们现在有一个最短的链表了,Head及Tail都指向同一个元素,整个表中只有一个节点。指针的使用使得初次学习链表有点难,但是当你想明白的时候一切看起来就那么的顺畅了。既然现在我们的两个指针(实际三个嘛)都指向一个真正的内存地址,那么我们就进一步来讨论如何往链表里加入一些新的节点。
Adding a Node
实际上我们可以在两个可能的位置上增加一个节点,在表头或是表尾,虽说看起来好像在表尾增加加更Standard一些。这使得我们的链表像一个队列那样,表头是最老的元素表尾是最新的元素。这同时也带来了一个很有趣的问题,那就是我们将要如何来使用链表?正是链表有不同的使用方法才使得它如此的强大。我们可以把它当成一个优先级列表,最老的元素有最先被处理的优先级,直到它被删除。我们也可以把它当成一个项目的控制列表,在任何需要的时候删除其中的节点而不必去考虑优先级的问题。下面的代码展示怎样在链尾增加一个节点并使Tail正确的指向链尾。
void SLList::AddANode()
{
Tail->Next=new List;
Tail=Tail->Next;
}
这里,我们在链尾增加一个结点并使得Tail指向这个新的结点以便Tail永远指向链尾。调用了此函数以后, 我们可以通过Tail指针来访问这个新的结点。
Traversing the List
实际上这是单向链表处理中最困难的部分了。因为我们不能马上不加判断的去访问一个结点的前结点,比如在下面这种情况下我们就不能这么做:当我们要删除一个结点,然后要把结点后的结点连到该结点的前结点上以便重新构造一条链。去判断一个结点是否还有前结点的一个方法是:
写个以要删除的结点的下标为输入参数的函数来遍历链表到这个下标为止,然后函数返回指向该结点前结点的指针。
ListPtr SLList::Previous(long Index)
{
ListPtr temp=Head;
for(long count=0;count<Indx-1;count++)
{ temp=temp->Next; }
return temp;
}
ListPtr SLList::Previous(ListPtr index)
{
ListPtr temp=Head;
if(index==Head)
return Head;
while(temp->Next!=index)
temp=temp->Next;
return temp;
}
如果我们已经处于链表中的一个结点时,我们可以以这个结下的下标来调用函数来获得指向其前结点的指针。这方可行,但是在有些情况下可能会发生越界问题。此函数的另一个版本是传入一个当前结点的指针,此版函数可以确保我们一定可以获得前结点的指针。分析如下,此版本会错误检查,如果我们当前指向链表头,那么函数只返回指向表头的指针而不返回一个无意义的值(如NULL)
下面再来看下如何实际的在链表中来回移动当前结点的指针:
void SLList::Advance()
{ if (CurrentPtr->Next!=NULL)
CurrentPtr=CurrentPtr->Next;
}
void SLList::Rewind()
{
if (CurrentPtr!=Head)
CurrentPtr=Previous(CurrentPtr);
}
如果当前结点已处于链尾,则不移动此指针。同样的,如果当前结点在链表头,可同样不进行任何的操作。
Deleting a Node
要删除链表中的结点,要分成三种情况。删除的结点是表头、表尾、除表头与表尾外的其它结点。对于各种情况需要分别考虑处理,现在来详细的看一下如何处理吧。
void SLList::DeleteANode(ListPtr corpse)
{
ListPtr Temp;
if(corpse==Head)
{
Temp=Head;
Head=Head->Next;
Delete Temp;
}
else if (corpse==Tail)
{
Temp=Tail;
Tail=Previous(Tail);
Tail->Next=NULL;
Delete Temp;
}
else
{
Temp=Previous(corpse);
Temp->Next=corpse->Next;
Delete corpse;
}
}
情况1: corpse=Head Node
这种情况下,我们要删除的结点是头结点,这是一种特殊的情况因为头结点前已无结点了,处理的方法是很简单的,记下头结点的位置Temp=Head,使头结点指向下一个结点Head=Head->next;然后删除;
情况2: corpse=End Node
删除的结点是链尾,这也是一种特殊的情况,链尾意味着其后没有结点了,所以处理的时候用如下的方法::
把链尾保存Temp=Tail;并使Tail指向新的链尾Tail=Previous(Tail);然后Tail->Next=NULL;最后就可以删除了了
情况3:corpse=somewhere in between
此情况下,结点有前结点也有后结点,我们要做的是使前结点指向后结点。如下 处理: temp=Previous(corpse); 指向前结点;
temp->Next=corpse->Next;使前结点指向下结点.OK...That's simple....hahah.......
Before Exit:
在程序退出之前我们要保证我们所申请的所有动态的内存都以被释放以免造成内存溢出;方法很简单只要遍历链表然后依次删除每个结点:
SLList::~SLListI()
{
ListPtr temp=Head;
CurrentPtr=Head;
While(CurrentPtr!=NULL)
{ Current=Current->Next;
delete temp;
temp=Current;
}