C++: 实现双向链表(例题讲解)

来源:互联网 发布:简单的聊天软件 编辑:程序博客网 时间:2024/06/11 01:13

C++: 实现双向链表(例题讲解)

标签: C++ 双向链表

by 小威威


这一周的实验题比较水,我就不总结了,而作业题中最有意思的就是实现双向链表这一道题。

这道题和我们之前做的单向链表是同一种类型的,只是这一次是双向链表,需要实现的操作较多,可能产生的bug也比之前多。我个人觉得,打出这道题的代码不难,难是难在调试。

(入门链表的可以参照我以前的一篇文档入门:链表的基本操作)

题目如下:(Author: 叶嘉祺(TA))
Introduction

Well, this time we will be a little difficult for you to do. We are going to design a link list class in c++. Again, the knowledge of pointer in c++ in widely used this time and you have to focus when you are coding. List is not easy for you.

Knowledge

This time, you are going to finish a advanced list which is known as doubly linked list. Each node of the list have three members: data which is use as the real storage, next pointer which is used to linked the next node of the list, prev pointer which is use to linked the previous node.

Requirements

Finish the member functions below.

Notice that for output functions, the format is as below:

toString() [1,2,3,4,5]

NULL<-1<->2<->3<->4<->5->NULL
toString() []

NULL
toString() [1]

NULL<-1->NULL
No new line is needed;

void split(int position, list* des1, list* dest2) 2 [1,2,3,4]

[1,2] [3,4]

list& merge(const list& src1, const list& src2) [1,2,3,4] [5,6,7,8]

[1,2,3,4,5,6,7,8]

list& remove_if(bool (*condition)(listPointer)); [1,2,3,4,5,6] condition = odd number

[2,4,6]

list& unique(void) [1,2,2,3,4,4,5,6]

[1,2,3,4,5,6]

list& reverse(void) [1,2,3,4,5,6]

[6,5,4,3,2,1]


下面是双向链表的简要模型:
双向链表

题目的main.cpp 与 List.hpp 已经给出。

// main.cpp#include "List.hpp"#include <iostream>#include <string>using std::cin;using std::cout;using std::endl;using std::string;bool condition1(list::listPointer p) { return true; }bool condition2(list::listPointer p) {  if (p->data % 2 == 0) {    return false;  }  return true;}bool condition3(list::listPointer p) {  if (p->data > 5) {    return false;  }  return true;}void outputList(const list& li) {  cout << li << " size:" << li.size();  if (&li.front() == NULL) {    cout << " front:NULL";  } else {    cout << " front:" << li.front();  }  if (&li.back() == NULL) {    cout << " back:NULL";  } else {    cout << " back:" << li.back();  }  cout << endl;}int main() {  int n, m;  cin >> n >> m;  int* a = new int[n]();  for (int i = 0; i < n; i++) {    cin >> a[i];  }  if (true) {    list li1(a, n);    li1.insert(2, 111);    li1.push_front(150);    list li2(li1);    outputList(li1);    outputList(li2);  }  cout << endl;  if (true) {    list li1;    for (int i = 0; i < n; i++) {      li1.insert(i, a[i]);    }    for (int i = 0; i < m; i++) {      li1.erase(i);    }    outputList(li1);  }  cout << endl;  if (true) {    list li1(a, n), li2, li3;    li1 = li2 = li3 = li1;    outputList(li1);    li1.split(0, &li2, &li3);    outputList(li1);    outputList(li2);    outputList(li3);    li1.split(li1.size(), &li2, &li3);    outputList(li1);    outputList(li2);    outputList(li3);    li1.split(li1.size() / 2, &li2, &li3);    cout << li2.toString() << endl;    cout << li3.toString() << endl;    li1 += (li2 += li1).merge(li1, li1);    outputList(li1);    li1 += li3;    li2.merge(li1, li3);    for (int i = 0; i < li1.size(); i++) {      cout << li1[i] << " ";    }    cout << endl;    outputList(li2);  }  cout << endl;  cout << endl;  if (true) {    list li1(a, n);    li1.remove_if(condition1);    cout << li1 << " " << endl;    li1.assign(a, n);    li1.remove_if(condition2);    cout << li1 << endl;    li1.assign(a, n);    li1.remove_if(condition3);    outputList(li1);  }  cout << endl;  if (true) {    list li(a, n);    li.merge(li, li).merge(li, li).unique();    outputList(li);  }  delete[] a;  return 0;}// list.hpp #ifndef LIST#define LIST#include <string>#include <iostream>class list { public:  typedef int data_type;  struct node {   public:    data_type data;    node* next;    node* prev;    node(data_type data = 0, node* next = NULL, node* prev = NULL)        : data(data), next(next), prev(prev){};  };  typedef node listNode;  typedef node* listPointer;  typedef unsigned int size_type; private:  listPointer head;  listPointer tail;  size_type _size;  inline listPointer at(int index) {    if (index >= 0 && index < this->_size) {      if (index <= this->_size / 2) {        int counter = 0;        listPointer p = this->head;        while (counter != index) {          counter++;          p = p->next;        }        return p;      } else {        int counter = 0;        listPointer p = this->tail;        while (counter != this->_size - 1 - index) {          counter++;          p = p->prev;        }        return p;      }    }    return NULL;  } public:  list();  // construct a list from an exist array  list(const data_type[], int length);  list(const list&);  list& operator=(const list&);  ~list();  // Capacity  bool empty(void) const;  size_type size(void) const;  // Element access  data_type& front(void) const;  data_type& back(void) const; public:  // output  std::string toString(void) const;  // Modifiers  void assign(const list&);  void assign(const data_type datas[], int length);  void push_front(const data_type&);  void push_back(const data_type&);  void pop_front(void);  void pop_back(void);  void insert(int position, const data_type& data);  void erase(int position);  void clear(void);  // Operations  // split this list into to lists at the given position  void split(int position, list* des1, list* dest2);  // merge two list to this list from src1 and src2  list& merge(const list& src1, const list& src2);  // remove the elements who satisfy the condition  list& remove_if(bool (*condition)(listPointer));  // remove duplicate elements  list& unique(void);  // reverse the list  list& reverse(void);  // operators  data_type& operator[](int index);  list& operator+=(const list&);  friend std::ostream& operator<<(std::ostream& os, const list& li);};#endif

因为实现这些操作不难,况且有些操作在16.03.04实验课总结是类似的,我就不详细一一介绍了。下面就简单地介绍split, merge, remove_if, unique, reverse操作.

split操作指将链表根据输入的position拆成两段,这个很简单,就用一个外层的while循环对原链表进行遍历,然后在内部弄两个小的while循环形成两段目标链表。

merge操作指将两段链表连接在一起,这个很简单,就将其中一个链表复制给原链表,然后再通过push_back操作将第二个链表的内容压入。

remove_if这个有点小难度,很容易导致内存错误。传入remove_if的参数是一个函数指针,如bool (*condition)(listPointer)就是定义一个函数指针,其中bool是函数的返回类型,condition是指针的名字,listPointer是指传入参数的类型。那么通过函数指针来调用函数的操作如下:

// listPointer是用typedef定义的指向链表结点的指针类型listPointer p1;if ((*condition)(p1))  {}

也就是说,在(*condition)()的括号里加入我们传入的参数就可以调用这个函数了。

remove_if是对链表中各个结点进行检测的,如若不满足条件就删去。因此在遍历时,要用一个“中介”指针去存储遍历指针指向的对象的下一个结点,然后在执行完if ((*condition)(p1)) 后再将“中介”指针里的内容赋给遍历指针。原因是遍历指针指向的对象可能已经被删除了,因此不能访问。

unique操作也是要注意的,你的程序会不会超时取决于这个操作的实现。我起初想到的方法是将链表里的内容存到数组里,然后对数组进行操作(毕竟对数组的操作比较熟悉),后来发现有一组测试样例超时了,于是我就改进了我的代码,把数组去了,然后直接对链表进行操作。其实很简单,就是用一个外层的while循环遍历数组里的内容,然后在弄一个内层的while循环遍历之前的内容,如果没有重复,就将遍历到的对象储存在一个新的链表里。

最后是reverse操作,我发现标程写的挺麻烦的,直接把head与tail的内容交换不久可以了么。

好的,下面开始讲解我debug的内容。

1.构造函数
我此处强调的是带有参数的构造函数,这类函数往往很容易忘记给类成员进行初始化。

例子1:

list :: list(const data_type a[], int length);

这是一个带有参数的构造函数。在实现将数组内容输入链表之前,最好对类的成员进行初始化。如这个函数没有先对成员函数size置0,那么用size++就会造成内存非法访问。再如没有在一开始对头指针与尾指针置空,那么当输入数组的长度为0时,将不执行数组内容输入链表的操作,便会导致类成员没有被初始化,在接下来的代码中将有可能会引发错误。

例子2:

list :: list(const list& list) {    head = tail = NULL;    _size = 0;    *this = list;}list& list :: operator=(const list& list) {    clear();    head = tail = NULL;    _size = 0;    if (list.head != NULL) {        head = new listNode(list.head->data);        _size++;        listPointer p1 = head;        listPointer p2 = list.head->next;        tail = head;        while (_size < list._size) {            p1->next = new listNode(p2->data);            p1->next->prev = p1;            p1 = p1->next;            p2 = p2->next;            _size++;            if (_size == list._size) {                tail = p1;            }        }    }    return *this;}void list :: clear(void) {    if (head != NULL) {        listPointer p1;        while (head != NULL) {            p1 = head->next;            delete head;            head = p1;        }    }}

这是一个复制构造函数,根据重载的”=”实现类的对象的复制。但是这里也是要注意要在一开始给类的成员初始化。我们可以看重载”=”的代码,在我上一篇写链表的文章里我有提到,实现赋值之前要将原链表指向的内存清空。好的,那我们现在来看clear()函数。不难发现clear不会对头指针为空的链表进行内存释放,也就是说如若在复制构造函数中没有提前初始化成员,会导致head不为NULL,会执行clear()操作,而显然这个链表对象中并没有动态内存可以释放,所以会引发错误。

2.尾指针的问题

对于初学者来说,接触到的链表大多数是单向链表,所以在写双向链表时很容易采用单向链表的思想从而忽略了一些小细节。如尾指针的复制。在单向链表中,我们只给头指针复制,而在双向链表中,我们不仅要考虑头指针,还要考虑尾指针。也就是说,当链表建立完毕以后,不要忘了将最后一个结点的地址赋给尾指针。如果没有给尾指针赋值,会导致链表长度计算错误以及栈溢出等后果。

3.push_back操作要考虑链表为空的情况

push_back操作是指在链表末端增加结点。这个操作比较容易忽略的就是没有考虑链表为空的情况。这个情况很重要,因为我们可以通过不断调用push_back来实现数据输入链表,所以一定要记得考虑链表为空的情况。

与此同时,push_front,pop_front, pop_back也要记得考虑链表为空的情况,虽然有些操作不考虑链表为空的操作不会出错,但是为了使代码更加“天衣无缝”,还是要把一些可能使程序崩溃的情况考虑下去。

4.插入与删除操作

插入与删除操作都有传入一个代表位置的参数,我们要先对这个参数进行判断,判断其是否合法。对于插入操作,其插入的位置参数必须大于等于0且小于等于链表的大小。而对于删除操作,其删除的位置参数必须大于等于0且小于链表的大小(注意一个是小于等于一个是小于)。

5.merge操作

对于merge操作有一点要注意,其传入的参数可以是自己。一般来说,调用成员函数将两个链表连接起来赋值到该成员之前要先清空原成员链表中的内容以避免内存泄漏,但是此处不同,因为传入的参数是本身,倘若先清空,便会导致传入的两个参数也变成空链表,最终连接的结果就是一个空链表,这显然是错误的。因此在这里应该再定义一个list对象来存储链表连接的结果,然后再用”=”赋值给原成员的链表(“=”会先清空原成员的链表里的内容)。

6.if_remove操作

这个在前文我已经讲解过了,所以就这样代过了。

7.unique操作超时

这个在前文我已经讲解过了,所以就这样代过了。

我的代码还有可以简化的地方:
1.重载[]运算符时可以用已经定义的at函数,没有必要自己去定义;
2.重载+=运算符时可以用merge函数,没有必要自己去定义。
3._size置0与头指针尾指针置空可以放到clear函数里。
以上三点就是我觉得我的代码里还可以简化的地方。

以下是我的代码:

# include "List.hpp"# include <string># include <sstream># include <iostream>using std::string;using std::stringstream;typedef unsigned int size_type;typedef int data_type;struct node {   public:    data_type data;    node* next;    node* prev;    node(data_type data = 0, node* next = NULL, node* prev = NULL)        : data(data), next(next), prev(prev) {}};typedef node listNode;typedef node* listPointer;list :: list(): head(NULL), tail(NULL), _size(0) {}list :: list(const data_type a[], int length) {    head = tail = NULL;    _size = 0;    if (length > 0) {        head = new listNode(a[0]);        _size++;        listPointer p1 = head;        int i = 1;        tail = p1;        while (_size < length) {            p1->next = new listNode(a[i]);            p1->next->prev = p1;            p1 = p1->next;            i++;            _size++;            if (_size == length) {                tail = p1;            }        }    }}list :: list(const list& list) {    head = tail = NULL;    _size = 0;    *this = list;}list& list :: operator=(const list& list) {    clear();    head = tail = NULL;    _size = 0;    if (list.head != NULL) {        head = new listNode(list.head->data);        _size++;        listPointer p1 = head;        listPointer p2 = list.head->next;        tail = head;        while (_size < list._size) {            p1->next = new listNode(p2->data);            p1->next->prev = p1;            p1 = p1->next;            p2 = p2->next;            _size++;            if (_size == list._size) {                tail = p1;            }        }    }    return *this;}list :: ~list() {    clear();}bool list :: empty(void) const {    return _size ? false : true;}size_type list :: size(void) const {    return _size;}data_type& list :: front(void) const {    return head->data;}data_type& list :: back(void) const {    return tail->data;}std::string list :: toString(void) const {    string result = "NULL";    if (_size == 0) {        return result;    }    result += "<-";    listPointer p1 = head;    while (p1 != NULL) {        stringstream ss;        string str1;        ss << p1->data;        ss >> str1;        if (p1->next != NULL) {            result += str1 + "<->";        } else {            result += str1 + "->";        }        p1 = p1->next;    }    result += "NULL";    return result;}void list :: assign(const list& list) {    *this = list;}void list :: assign(const data_type datas[], int length) {    clear();    *this = list(datas, length);}void list :: push_front(const data_type& data) {    if (head == NULL) {        listPointer p1 = new listNode(data);        head = tail = p1;        _size++;        return;    }    listPointer p1 = new listNode(data);    p1->next = head;    head->prev = p1;    head = p1;    _size++;}void list :: push_back(const data_type& data) {    if (tail == NULL) {        listPointer p1 = new listNode(data);        head = tail = p1;        _size++;        return;    }    listPointer p1 = new listNode(data);    p1->prev = tail;    tail->next = p1;    tail = p1;    _size++;}void list :: pop_front(void) {    if (head == NULL) {        return;    }    listPointer p1 = head->next;    if (p1 == NULL) {        delete head;        head = tail = NULL;        _size = 0;        return;    }    p1->prev = NULL;    delete head;    head = p1;    _size--;}void list :: pop_back(void) {    if (tail == NULL) {        return;    }    listPointer p1 = tail->prev;    p1->next = NULL;    delete tail;    tail = p1;    _size--;}void list :: insert(int position, const data_type& data) {    if (position < 0 || position > _size) {        return;    }    if (position == 0) {        push_front(data);        return;    }    if (position == _size) {        push_back(data);        return;    }    listPointer p1 = new listNode(data);    listPointer p2 = head;    while (position--) {        p2 = p2->next;    }    listPointer p3 = p2->prev;    p1->next = p2;    p2->prev = p1;    p1->prev = p3;    p3->next = p1;    _size++;}void list :: erase(int position) {    if (position < 0 || position >= _size) {        return;    }    if (position == 0) {        pop_front();        return;    }    if (position == _size-1) {        pop_back();        return;    }    listPointer p1 = head;    while (position--) {        p1 = p1->next;    }    listPointer p2 = p1->prev;    listPointer p3 = p1->next;    delete p1;    p2->next = p3;    p3->prev = p2;    _size--;}void list :: clear(void) {    if (head != NULL) {        listPointer p1;        while (head != NULL) {            p1 = head->next;            delete head;            head = p1;        }    }}void list :: split(int position, list* des1, list* dest2) {    des1->clear();    des1->head = des1->tail = NULL;    des1->_size = 0;    dest2->clear();    dest2->head = dest2->tail = NULL;    dest2->_size = 0;    listPointer p1 = head;    int i = 0;    while (p1 != NULL) {        if (i < position) {            des1->push_back(p1->data);            i++;        } else {            dest2->push_back(p1->data);            i++;        }        p1 = p1->next;    }}list& list :: merge(const list& src1, const list& src2) {    list list1;    listPointer p1 = src1.head;    while (p1 != NULL) {        list1.push_back(p1->data);        p1 = p1->next;    }    listPointer p2 = src2.head;    while (p2 != NULL) {        list1.push_back(p2->data);        p2 = p2->next;    }    clear();    head = tail = NULL;    _size = 0;    *this = list1;    return *this;}list& list :: remove_if(bool (*condition)(listPointer)) {    if (head == NULL) {        return *this;    }    listPointer p1 = head;    while (p1 != NULL) {        if (p1->prev == NULL) {            if (p1->next == NULL) {                if ((*condition)(p1)) {                    pop_front();                }                return *this;            } else {                listPointer p2 = p1->next;                if ((*condition)(p1)) {                    pop_front();                }                p1 = p2;            }        } else if (p1->next == NULL) {            if ((*condition)(p1)) {                pop_back();            }            p1 = NULL;        } else {            listPointer p = p1->next;            if ((*condition)(p1)) {                listPointer p2 = p1->prev;                listPointer p3 = p1->next;                delete p1;                p2->next = p3;                p3->prev = p2;                _size--;            }            p1 = p;        }    }    return *this;}list& list :: unique(void) {    list list1;    if (head == NULL) {        return *this;    }    if (head->next == NULL) {        return *this;    }    listPointer p1 = head;    while (p1 != NULL) {        int flat = 0;        listPointer p2 = head;        while (p2 != p1) {            if (p1->data == p2->data) {                flat = 1;            }            p2 = p2->next;        }        if (flat == 0) {            list1.push_back(p1->data);        }        p1 = p1->next;    }    *this = list1;    return *this;}list& list :: reverse(void) {    listPointer p1 = head;    head = tail;    tail = p1;    return *this;}data_type& list :: operator[](int index) {    if (index < 0 || index >= _size) {        return head->data;    }    listPointer p1 = head;    while (index--) {        p1 = p1->next;    }    return p1->data;}list& list :: operator+=(const list& list) {    listPointer p1 = list.head;    while (p1 != NULL) {        push_back(p1->data);        p1 = p1->next;    }    return *this;}std::ostream& operator<<(std::ostream& os, const list& li) {    os << li.toString();    return os;}

以上内容皆为本人观点,欢迎大家提出批评和指导,我们一起探讨!


0 0
原创粉丝点击