[关闭]
@cxm-2016 2016-12-15T10:24:50.000000Z 字数 6980 阅读 2567

数据结构:实现LinkedList

数据结构

版本:2
作者:陈小默


如果之前学习过java的话,一定接触过其中的ArrayList和LinkedList。当我们说起其中的区别时,大部分人的回答都是面试宝典的标准答案:一个内部实现是数组,另一个内部实现是链表,一个查找快,另一个增删快。至于为什么是这样的,许多人就回答不上来了。所以,我们现在就是要亲自动手制造一个简易的LinkedList来学习其中的原理。

定义需求

我们仿照Java中的实现,定义以下需求:

作为类

LinkedList应该具有初始化器和析构函数,能够在创建时进行初始化和结束时销毁数据。

  1. LinkedList();
  2. ~LinkedList();

作为List

LinkedList应该具有添加数据,获得数据,移除数据的基本功能。

  1. void add(Any * data);
  2. Any * get(int index);
  3. bool add(int index,Any * data);
  4. void addFirst(Any* data);
  5. void addLast(Any* data);
  6. Any * remove(int index);
  7. Any * removeFirst();
  8. Any * removeLast();
  9. int size();

作为栈stack

LinkedList应该具有入栈和出栈的功能。

  1. Any * pop();
  2. void push(Any * data);

作为队列

LinkedList应该具有入队列和出队列的功能。

  1. void in(Any * data);
  2. Any * out();

定义模版

我们的LinkedList不能只存放固定类型的数据,所以我们需要定义泛型模版,来处理不同的存储需求

  1. template<class Any>

定义数据结构

由于内部实现方式是链表,所以我们需要定义一个双向链表结点Node

  1. template<class Any>
  2. struct Node{
  3. Node<Any> * prior;
  4. Any * data;
  5. Node<Any> * next;
  6. };

其中prior指向前一个结点,data指向存储的数据,next指向下一个结点。

声明类

当我们一切准备就绪之后我们可以写我们的头文件了LinkedList.h

  1. #ifndef LINKED_LIST_H_
  2. #define LINKED_LIST_H_
  3. template<class Any>
  4. struct Node{
  5. Node<Any> * prior;
  6. Any * data;
  7. Node<Any> * next;
  8. };
  9. template<class Any>
  10. class LinkedList{
  11. Node<Any> * head;//头结点
  12. Node<Any> * tail;//尾结点
  13. int length;//链表长度
  14. Node<Any> * getNode(int index){
  15. //获得index位置的结点数据
  16. if(index<0||index>length){
  17. return NULL;
  18. }else{
  19. Node<Any> *temp = head->next;
  20. for(int i=0;i<index;i++,temp=temp->next);
  21. return temp;
  22. }
  23. };
  24. public:
  25. LinkedList();
  26. ~LinkedList();
  27. //list
  28. void add(Any * data);//添加数据
  29. Any * get(int index);//获取index位置上的数据
  30. bool add(int index,Any * data);//向index位置上添加一条数据
  31. void addFirst(Any* data);//向链表头部添加一条数据
  32. void addLast(Any* data);//向链表尾部添加一条数据
  33. Any * remove(int index);//移除index位置上的数据
  34. Any * removeFirst();//移除链表第一个元素
  35. Any * removeLast();//移除链表最后一个元素
  36. int size();//获得链表的长度
  37. //stack
  38. Any * pop();//出栈一条数据
  39. void push(Any * data);//入栈一条数据
  40. //queue
  41. void in(Any * data);//进入队列
  42. Any * out();//出队列
  43. };
  44. #endif

方法实现

这里由于我使用的环境是Visual Studio 2010 ,其中并不支持较高的C++标准。而在较低的版本中我们的模板并不支持与源文件的分离,也就是说如果我在另一个源文件中实现这个类将无法通过编译,但是较高版本的编译环境可以通过给模板添加export 关键字来使声明与定义分离。注意:是给每一个模板添加export关键字。

  1. #ifdef STACKTP_H_
  2. #define STACKTP_H_
  3. export template<class T>
  4. class Stack
  5. {......};
  6. #endif

但是这里我就将实现放在了头文件中,也就是说下面的所有代码均在头文件实现在#endid之前,有条件的可以另起一个源文件。
接下来我们一个方法一个方法的去分析
1,

  1. template<class Any>
  2. LinkedList<Any>::LinkedList(){
  3. head = new Node<Any>;
  4. tail = new Node<Any>;
  5. head->data = NULL;
  6. head->next = tail;
  7. head->prior = NULL;
  8. tail->data=NULL;
  9. tail->prior=head;
  10. tail->next=NULL;
  11. length=0;
  12. }

对于我们的构造方法,其中最主要的作用就是初始化结点。当我们创建列表对象的时候,需要创建一个空的头结点和一个空的尾结点,这两个空结点没有数据。头结点的后继指向尾结点,尾结点的前驱指向头结点。因为头结点只是表示链表的开始,尾结点表示链表的结束。所以该结点不计入长度,并且其中的数据指针也永远不会存储数据。
2,

  1. template<class Any>
  2. LinkedList<Any>::~LinkedList(){
  3. for(int i=0;i<length+2;i++){
  4. Node<Any> * temp = head;
  5. head = head->next;
  6. Any * data = temp->data;
  7. if(data!=NULL){
  8. delete data;
  9. }
  10. delete temp;
  11. }
  12. }

这是这个类的析构函数,其目的就是在销毁链表的时候同时销毁其中存储的数据。以免造成内存泄漏。注意我们销毁之前必须先移动头指针到下一个位置,否则该结点销毁之后我们就无法再取出下一个结点的指针了。同理,我们在销毁结点之前必须先销毁数据。
3,

  1. template<class Any>
  2. void LinkedList<Any>::add(Any * data){
  3. addLast(data);
  4. }

由于我们的插入位置是最后,所以可以直接调用addLast()方法。如果我们不想让新加入的数据存放在最后也可以自定义。
4,

  1. template<class Any>
  2. Any * LinkedList<Any>::get(int index){
  3. Node<Any>* temp = getNode(index);
  4. if(temp==NULL){
  5. return NULL;
  6. }else{
  7. return temp->data;
  8. }
  9. }

这个方法的目的就是获取index结点的数据。这里需要提醒的是,我们仅仅是获取了引用,数据引用仍然在链表中存在,所以我们delete链表的时候一定要小心,以免造成在get出来的数据在delete链表之后发生空指针错误。
5,

  1. template<class Any>
  2. bool LinkedList<Any>::add(int index,Any * data){
  3. if(index<0||index>length){
  4. return false;
  5. }else if(index==0){
  6. addFirst(data);
  7. }else if(index==length){
  8. addLast(data);
  9. }else{
  10. Node<Any> *temp = getNode(index-1);
  11. Node<Any> *node = new Node<Any>;
  12. node->data=data;
  13. node->prior=temp;
  14. node->next=temp->next;
  15. temp->next->prior=node;
  16. temp->next=node;
  17. length++;
  18. }
  19. return true;
  20. }

这个方法的目的是将数据插入index位置。我们先要判断用户传入的index是否合法,不合法返回false。另外我们的判断还有一个作用就是去选择性的调用插入头部或者是尾部。现在我们解释最后一个else的作用:获得我们要插入的位置的前一个结点,然后通过指针移动去将自身节点插入到原链表的中间位置。实现时一定要注意操作指针的顺序,以免指针丢失。
6,

  1. template<class Any>
  2. void LinkedList<Any>::addFirst(Any* data){
  3. Node<Any> * node = new Node<Any>;
  4. node->data=data;
  5. node->prior=head;;
  6. node->next=head->next;
  7. node->next->prior=node;
  8. head->next=node;
  9. length++;
  10. }

通过这个实现我们能够看出头结点的作用仅仅是表示链表的开始,其中有效数据存储在头结点之后,并且在我们增加了头尾结点后,并不需要我们去判断头尾指针是否重合。
7,

  1. template<class Any>
  2. void LinkedList<Any>::addLast(Any* data){
  3. Node<Any> * node = new Node<Any>;
  4. node->data=data;
  5. node->next=tail;
  6. node->prior=tail->prior;
  7. node->prior->next=node;
  8. tail->prior=node;
  9. length++;
  10. }

解释同方法6
8,

  1. template<class Any>
  2. Any * LinkedList<Any>::remove(int index){
  3. if(index<0||index>length){
  4. return NULL;
  5. }else if(index==0){
  6. return removeFirst();
  7. }else if(index==length){
  8. return removeLast();
  9. }else{
  10. Node<Any> *temp = getNode(index);
  11. if(temp==NULL){
  12. return NULL:
  13. }else{
  14. Any * data = temp->data;
  15. delete temp;
  16. length--;
  17. return data;
  18. }
  19. }
  20. }

这里的remove方法在返回数据后删除结点,但是不会删除数据,所以如果我们需要在delete链表之后还能正常使用数据的话,应该在delete链表之前,使用remove方法移出数据。
9,

  1. template<class Any>
  2. Any * LinkedList<Any>::removeFirst(){
  3. if(length==0){
  4. return NULL;
  5. }else{
  6. Node<Any> * temp = head->next;
  7. Any * data = temp->data;
  8. head->next=temp->next;
  9. head->next->prior=head;
  10. delete temp;
  11. length--;
  12. return data;
  13. }
  14. }

这个方法跟remove方法相同,其移除链表中的第一条数据。注意这里移除的是头结点后的第一个结点而不是头结点。
10,

  1. template<class Any>
  2. Any * LinkedList<Any>::removeLast(){
  3. if(length==0){
  4. return NULL;
  5. }else{
  6. Node<Any> * temp = tail->prior;
  7. Any* data = temp->data;
  8. tail->prior=temp->prior;
  9. tail->prior->next=tail;
  10. delete temp;
  11. length--;
  12. return data;
  13. }
  14. }

同上,移除链表的最后一条数据。移除的是尾结点前的第一个结点,而不是尾结点。
11,

  1. template<class Any>
  2. int LinkedList<Any>::size(){
  3. return length;
  4. }

这个方法不用解释了吧。
12,

  1. template<class Any>
  2. Any * LinkedList<Any>::pop(){
  3. return removeLast();
  4. }
  5. template<class Any>
  6. void LinkedList<Any>::push(Any * data){
  7. addLast(data);
  8. }

这是栈实现方法,符合先进后出
13,

  1. template<class Any>
  2. void LinkedList<Any>::in(Any * data){
  3. addLast(data);
  4. }
  5. template<class Any>
  6. Any * LinkedList<Any>::out(){
  7. return removeFirst();
  8. }

这是队列的实现,符合先进先出。

到这里一个能够存储数据的链表就创造完成了。下面我们演示一下能否正常运行。

演示

这里我使用的对象是之前博客创建的学生类。简单了解一下,其中的show()方法会计算平均成绩并打印,被销毁时也会打印一句话。

用作一般数据存储

  1. #include "stdafx.h"
  2. #include "student.h"
  3. #include "LinkedList.h"
  4. int _tmain(int argc, _TCHAR* argv[]){
  5. LinkedList<Student> * list = new LinkedList<Student>();
  6. list->add(new Student("Jack",98,99,100));
  7. list->add(new Student("Sam",99,90,99));
  8. list->add(new Student("Sue",97,99,87));
  9. for(int i=0;i<list->size();i++){
  10. list->get(i)->show();
  11. }
  12. delete list;
  13. return 0;
  14. }

以下是输出结果,从结果中可以看出list结束时,释放了其中的全部内存空间

Jack 同学的语文成绩为98分,数学成绩为100分,英语成绩为99分,平均成绩99.0分
Sam 同学的语文成绩为99分,数学成绩为99分,英语成绩为90分,平均成绩96.0分
Sue 同学的语文成绩为97分,数学成绩为87分,英语成绩为99分,平均成绩94.3分
再见! Jack.
再见! Sam.
再见! Sue.
请按任意键继续. . .

用作栈

  1. #include "stdafx.h"
  2. #include "student.h"
  3. #include "LinkedList.h"
  4. int _tmain(int argc, _TCHAR* argv[]){
  5. LinkedList<Student> * list = new LinkedList<Student>();
  6. list->push(new Student("Jack",98,99,100));
  7. list->push(new Student("Sam",99,90,99));
  8. list->push(new Student("Sue",97,99,87));
  9. list->pop()->show();
  10. list->pop()->show();
  11. list->pop()->show();
  12. delete list;
  13. return 0;
  14. }

以下是输出结果:可以看到其符合先入后出的规则。

Sue 同学的语文成绩为97分,数学成绩为87分,英语成绩为99分,平均成绩94.3分
Sam 同学的语文成绩为99分,数学成绩为99分,英语成绩为90分,平均成绩96.0分
Jack 同学的语文成绩为98分,数学成绩为100分,英语成绩为99分,平均成绩99.0分
请按任意键继续. . .

用作队列

  1. #include "stdafx.h"
  2. #include "student.h"
  3. #include "LinkedList.h"
  4. int _tmain(int argc, _TCHAR* argv[]){
  5. LinkedList<Student> * list = new LinkedList<Student>();
  6. list->in(new Student("Jack",98,99,100));
  7. list->in(new Student("Sam",99,90,99));
  8. list->in(new Student("Sue",97,99,87));
  9. list->out()->show();
  10. list->out()->show();
  11. list->out()->show();
  12. delete list;
  13. return 0;
  14. }

以下是输出结果:符合先进先出

Jack 同学的语文成绩为98分,数学成绩为100分,英语成绩为99分,平均成绩99.0分
Sam 同学的语文成绩为99分,数学成绩为99分,英语成绩为90分,平均成绩96.0分
Sue 同学的语文成绩为97分,数学成绩为87分,英语成绩为99分,平均成绩94.3分
请按任意键继续. . .

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注