数据结构练手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&;
好的,直接上代码:
先是头文件,注意最后一行;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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"
接着是实现文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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
最后是测试文件:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
结果如下图所示:
补充: 其实还可以重载更多的操作符,有心情的朋友可以自己添加下,比如++(int), ++操作等。甚至,可以加入个迭代器类,这样更方便使用,有时间可以实现下哦。
另外,心细,心细,手别抖。 自勉下!