数据结构练手02 双向链表实现

双向链表实现

今天晚上用模板方法把双向链表实现了下,由于有点小粗心,在 insert中手抖了下,把tail->prev 打成了 tail->next,由于错误是发生在drop函数执行时,让我一直去调drop函数,调了半天还是一样错误,最后我系统在vs中监视了下值的变化,终于看到是insert出错了。 看来程序员离不开调试呀。

另外,昨天说的模板输出重载,我说要在友元的实现时加上 <>, 但是今天我在gcc中测试了下,居然说找不到匹配的函数,导致编译不通过,真心蛋疼,vs和g++的区别还真心大,看来改天要好好专研下模板输出重载,知道的朋友希望能够告知下。


对数据结构的实现,其实都很简单,思想就是:

1、定义结点结构类,包含数据域和指针域

2、定义 链表(树,图)结构,其中封装包含几个结点,作为数据接口,因为节点定义指向自身类型的指针,因此而后所有的操作都围绕这个数据接口进行。


对于双向链表来说,结点定义很简单,一个数据域,一个next指针,表示以后他将指向下一个结点; 一个prev指针,表示它将指向前一个结点。

template<typename T>

struct Node{

    T datum;

    Node* next;

    Node* prev;

};


由于链式结构添加的结点都要new出来,且结点类包含了数据域,因此需要对结点类进行构造函数的编写,一般两个,带数据域的(以后new来做链表中结点),默认无参的(用于new给head和tail的);析构函数就不用了,因为其指针域所指向的东西是由表结构new出来的,操作应该留给表。

结点类定义好了后,我们定义下链表类,主要部分就是要包含下 head 指针, tail 指针。

另外,所有的表(树,图结构)最好包含个 “表示长度的数据”,如size, 这样求length()操作只要O(1)的复杂度,要不然就要遍历整个链表,复杂度就是O(n)了。

对于head指针,规定 head->next 指向第一个结点,head->prev 指向自身或NULL;

对于tail指针,规定 tail->prev 指向最后一个结点,tail->next指向自身或NULL;  这样规定了head,tail后,后面的操作就会很顺畅,我们就可以next/prev到底了,哈哈。

空的条件为: size == 0 或者 head->next == NULL 或者 tail->prev == NULL ,看个人喜好。

剩下一些增删改查操作,别手抖写错了指向关系就行,另外可以加上一个位置判断,看看是从头或从尾开始,哪边移动的少。

函数写法可以给的建议是:

若只是访问不修改,成员函数为const;

若需要操作类类型,则用 & 或 const&;

好的,直接上代码:

先是头文件,注意最后一行;

 1 #ifndef MY_CHAIN_H
 2 #define MY_CHAIN_H
 3 #include <ostream>
 4 using namespace std;
 5 template <class T>
 6 struct Node{
 7     T datum;
 8     Node* prev;
 9     Node* next;
10 
11     Node(const T& x);
12     Node();
13 };
14 
15 
16 template<class T>
17 class dList{
18     public:
19         dList();
20         ~dList();
21         bool find(int pos,T& hold) const;
22         int search(const T& x) const;
23         int length() const;
24         bool isEmpty() const;
25         dList<T>& drop(int pos, T& hold);
26         dList<T>& insert(int pos, const T& x);
27         dList<T>& push_back(const T& x);
28         dList<T>& push_front(const T& x);
29         T& operator[](int pos);
30         void show(std::ostream& os) const;
31         friend ostream& operator<< <>(ostream& os, const dList& d);
32     protected:
33         Node<T>* head;
34         Node<T>* tail;
35         int size;
36 };
37 #endif 
38 
39 #include "chain.cpp"
View Code

接着是实现文件:

#ifndef MY_CHAIN_CPP
#define MY_CHAIN_CPP
#include "chain.h"
#include <ostream>
#include <cassert>
using std::ostream;
// 节点类的构造
template<class T>
Node<T>::Node(const T& x) : datum(x){
    next = prev = NULL;
}

template<class T>
Node<T>::Node(){
    next = prev = NULL;
    datum = 0;
}

// 双向链表类的构造
template<class T>
dList<T>::dList()
{
    head = new Node<T>();
//    tail = new Node<T>();

    head->next = NULL;
    head->prev = head;
    tail = head;
    tail->next = tail;
    tail->prev = NULL;
    size = 0;
}
 // 析构
template<typename T>
dList<T>::~dList()
{
    if(head){   // 一个建议,若类中数据成员是指针类型,且指向是通过new出来的,那么最好这里加上一个判断。 当然我这里是多余的,因为构造时必然new了。
        Node<T>* p = head->next;
        Node<T>* t;
        while(p != NULL){
            t = p;
            p = p->next;
            delete t;
        }
        delete head;
        head = NULL;  // 最好删除后指向空
        tail = NULL;
    }
}

template<class T>
bool dList<T>::isEmpty() const
{
    return size == 0;
}

template<class T>
bool dList<T>::find(int pos,T& hold) const   // 访问不修改,成员函数const
{
    if(pos < 1 || pos > size) return false;
    Node<T>* p;
        if(pos <= (size>>1)){      // 从头开始比较快
            p  = head;
        int count = pos;
        while(count--){p = p->next;}    // next 给 head
        }else{
            p = tail;                      // 从尾开始比较快
            int count = size - pos;
            while(count--){p = p->prev;}    // 哈哈,中就是为什么把prev给tail, 代码是不是很顺畅?
        }
    hold = p->datum;
    return true;
}

template<class T>
int dList<T>::search (const T& x) const
{
    Node<T>* p = head->next;
    int count = 1;
    while((p!= NULL) && (p->datum != x)){
        p = p->next;
        count++;
    }
    if (p == NULL) {
        return 0;
    }
    return count;
}
template<class T>
int dList<T>::length ()const
{
    return size;
}
template<class T>
dList<T>& dList<T>::push_front (const T& x)   // 前插
{
    Node<T>* p = new Node<T>(x);
    if (size == 0) {
        head->next = p;
        p->prev = NULL;
        tail->prev = p;
        p->next = NULL;
    }else{
        p->next = head->next;
        head->next->prev = p;
        p->prev = NULL;
        head->next = p;
    }
    ++size;
    return *this;
}

template<class T>
dList<T>& dList<T>::push_back (const T& x)     // 尾插
{
    Node<T>* p = new Node<T>(x);
    if (tail->prev == NULL) {
        head->next = p;
        p->prev = NULL;
        tail->prev = p;
        p->next = NULL;
    }else{
        tail->prev->next = p;
        p->prev = tail->prev;
        p->next = NULL;
        tail->prev = p;
    }
    ++size;
    return *this;
}

template<class T>
dList<T>& dList<T>::insert(int pos, const T& x)    // 指定位置插入,范围[1,size+1]  注意,我的代码中,1为下标开始;除了后边重载的[]操作符。
{
    if (pos == 1) {
        return push_front (x);
    }
    if (pos == (size+1)) {
        return push_back (x);
    }
    Node<T>* in = new Node<T>(x);
    Node<T>* p;
    if (pos <= (size/2)) {
        p = head;
        int t = pos;
        while(t--){p = p->next;}
    }else{
        p = tail;
        int t = size - pos;
        while(t--){p = p->prev;}
    }
    in->next = p;
    in->prev = p->prev;    // 哎,这里就是我万恶粗心的地方,Mark下,下次要心细点。
    p->prev->next = in;
    p->prev = in;
    ++size;
    return *this;
}

template<class T>
dList<T>& dList<T>::drop(int pos, T& hold)
{
    if (pos < 1 || pos > size) {
        exit(1);
    }
    Node<T>* d = NULL;
    if(pos == 1){
        d = head->next;
        hold = d->datum;
        if(size == 1){            
            tail->prev = NULL;
            head->next = NULL;
        }else{
            head->next = d->next;
            d->next->prev = NULL;
        }
        --size;
        delete d;
        d = NULL;
        return *this;
    }
    if(pos == size){
        d = tail->prev;
        hold = d->datum;
        tail->prev = d->prev;
        d->prev->next = NULL;
        size--;
        delete d;
        d = NULL;
        return *this;
    }else{
        if(pos <= (size/2)){
            int c=pos;
            d = head;
            while(c--){d = d->next;}
        }else{
            int c = size - pos;
            d = tail;
            while(c--){d = d->prev;}
        }
        hold = d->datum;
        d->prev->next = d->next;
        d->next->prev = d->prev;
        --size;
        delete d;
        d = NULL;
        return *this;
    }

}

template<class T>
T& dList<T>::operator[](int pos)
{
    pos = pos + 1;
    if(pos<1 || pos> size) {
        exit(1);
    }
    Node<T>* p = NULL;
    if (pos <= (size>>1)) {
        int t = pos;
        p = head;
        while(t--){p = p->next;}
    }else{
        int t = size - pos;
        p = tail;
        while (t--) { p = p->prev;}
    }
    return p->datum;
}

template<class T>
void dList<T>::show (ostream& os) const
{
    Node<T>* p = head->next;
    int t = 0;
    while(p != NULL){
        os << p->datum << " ";
        p = p->next;
        t++;
    }
    assert(t==size);
}

template<class T>
ostream& operator<< <>(ostream& out, const dList<T>& x)
{
    x.show(out);
    return out;
}
#endif
View Code

最后是测试文件:

 1 #include "chain.h"
 2 #include <iostream>
 3 using namespace std;
 4 
 5 int main()
 6 {
 7     dList<int> dl;
 8     int x=0;
 9     dl.insert (1,5);
10     cout << "insert pos1 5: " << dl << endl;
11     cout << "length: " << dl.length() << endl;
12     dl.push_front (3);
13     cout << "push front 3: " << dl << endl;
14     cout << "length: " << dl.length() << endl;
15     dl.push_back (6);
16     cout << "push back 6: " << dl << endl;
17     cout << "length: " << dl.length() << endl;
18     dl.insert(4,8);
19     cout << "insert pos4 8: " << dl << endl;
20     cout << "length=" << dl.length () << endl;
21     dl.find (3,x);
22     dl.drop(2,x);
23     cout << "drop pos 2: " << dl << endl;
24     cout << "length=" << dl.length () << endl;
25     cout << "dl[0]=" << dl[0] << endl;
26     dl[0] = 10;
27     cout << "dl[0]=" << dl[0] << endl;
28     cout << dl << endl;
29 }
View Code

结果如下图所示:

补充: 其实还可以重载更多的操作符,有心情的朋友可以自己添加下,比如++(int), ++操作等。甚至,可以加入个迭代器类,这样更方便使用,有时间可以实现下哦。

另外,心细,心细,手别抖。 自勉下!

posted @ 2013-05-23 00:29  xield  阅读(360)  评论(0编辑  收藏  举报