数据结构——链表(单链表、循环链表、双向链表、静态链表)

来源:互联网 发布:淘宝买家查号 编辑:程序博客网 时间:2024/05/29 02:08

线性表的顺序存储结构(简称“顺序表”)不仅逻辑有序,还在物理上有序存储。对于查找操作,顺序表的速度是很快的,但是对于插入和删除操作,顺序表需要移动大量元素,相当费时。而线性表的链式存储结构(简称“链表”)通过增加一个指针域来维持线性表仅在逻辑上有序,弥补了顺序表在插入和删除操作上的不足,但代价是查找需要遍历整个链表。顺序表和链表各有优缺点,适合不同的应用场合。

下面对单链表、循环链表、双向链表以及静态链表4种链表进行介绍。

 

1.单链表

单链表中每个节点都包含一个指向后继节点的指针,通过节点的指针域将数据元素按照逻辑次序进行排列。


基本操作:

void MakeEmpty();//置空链表
int Length(); //获取链表长度
ListNode<Type> *Find(Type value, int n);        //获取数据元素为value的节点指针
ListNode<Type> *Find(int n);        //获取第n个节点指针
bool Insert(Type item, int n = 0);        //在第n个位置插入数据元素为item的节点
Type Remove(int n = 0);//删除第n个节点
bool RemoveAll(Type item);//删除所有数据元素为item的节点
Type Get(int n);//获取第n个节点的数据元素
void Print(); //打印单链表

为了达到和STL中各容器相同的使用效果,本文将使用类模板对所有数据结构进行泛化。

对单个节点,我们用一个类模板进行封装:

//ListNode.h 对单个节点进行封装,包括数据域和指向下一个节点的指针域template<typename Type> class SingleList;template<typename Type> class ListNode{private:friend typename SingleList<Type>;ListNode() :m_pnext(NULL){}ListNode(const Type item, ListNode<Type> *next = NULL) :m_data(item), m_pnext(next){}~ListNode(){m_pnext = NULL;}public:Type GetData();//获取数据成员friend ostream& operator<< <Type>(ostream&, ListNode<Type>&);//<<运算符重载private:Type m_data;ListNode *m_pnext;};template<typename Type> Type ListNode<Type>::GetData(){return this->m_data;}template<typename Type> ostream& operator<<(ostream& os, ListNode<Type>& out){os << out.m_data;return os;}
同理,我们也可以对单链表进行类模板封装。

1.1 置空操作

/* 置空单链表*  从头节点开始删除后续节点直到后续节点为空*/template<typename Type> void SingleList<Type>::MakeEmpty(){ListNode<Type> *pdel;while (head->m_pnext != NULL){pdel = head->m_pnext;head->m_pnext = pdel->m_pnext;delete pdel;}}

1.2 查找操作

/* 查找第n个节点指针*  从头结点开始遍历*/template<typename Type> ListNode<Type>* SingleList<Type>::Find(int n){if (n < 0){cout << "The n is out of boundary" << endl;return NULL;}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n&&pmove; i++){pmove = pmove->m_pnext;}if (pmove == NULL){cout << "The n is out of boundary" << endl;return NULL;}return pmove;}/* 查找第n个数据元素为value的节点指针*  从头结点开始遍历*/template<typename Type> ListNode<Type>* SingleList<Type>::Find(Type value, int n){if (n < 1){cout << "The n is illegal" << endl;return NULL;}ListNode<Type> *pmove = head;int count = 0;while (count != n&&pmove){pmove = pmove->m_pnext;if (pmove->m_data == value){count++;}}if (pmove == NULL){cout << "can't find the element" << endl;return NULL;}return pmove;}

1.3 插入操作

单链表的插入操作不需要移动整个链表,只需要改动插入位置前后指针的指向即可。

/* 插入操作:在位置n插入元素*  先从头结点开始遍历到第n个节点,再进行插入操作*/template<typename Type> bool SingleList<Type>::Insert(Type item, int n){if (n < 0){cout << "The n is illegal" << endl;return 0;}ListNode<Type> *pmove = head;ListNode<Type> *pnode = new ListNode<Type>(item);if (pnode == NULL){cout << "Application error!" << endl;return 0;}for (int i = 0; i < n&&pmove; i++){pmove = pmove->m_pnext;}if (pmove == NULL){cout << "the n is illegal" << endl;return 0;}//注意顺序pnode->m_pnext = pmove->m_pnext;pmove->m_pnext = pnode;return 1;}

1.4 删除操作

删除操作和插入操作类似,详见代码。
/* 删除操作:删除位置n处的节点,并返回该节点的数据元素*  先从头结点开始遍历到第n个节点,再进行删除操作*/template<typename Type> Type SingleList<Type>::Remove(int n){if (n < 0){cout << "can't find the element" << endl;exit(1);}ListNode<Type> *pmove = head, *pdel;for (int i = 0; i < n&&pmove->m_pnext; i++){pmove = pmove->m_pnext;}if (pmove->m_pnext == NULL){cout << "can't find the element" << endl;exit(1);}pdel = pmove->m_pnext;pmove->m_pnext = pdel->m_pnext;Type temp = pdel->m_data;delete pdel;return temp;}


完整代码:

//单链表类模版#include "ListNode.h"template<typename Type> class SingleList{public:SingleList() :head(new ListNode<Type>()){}~SingleList(){MakeEmpty();delete head;}public:void MakeEmpty();//置空链表int Length();//获取链表长度ListNode<Type> *Find(Type value, int n);//获取数据元素为value的节点指针ListNode<Type> *Find(int n);//获取第n个节点指针bool Insert(Type item, int n = 0);//在第n个位置插入数据元素为item的节点Type Remove(int n = 0);//删除第n个节点bool RemoveAll(Type item);//删除所有数据元素为item的节点Type Get(int n);//获取第n个节点的数据元素void Print();//打印单链表private:ListNode<Type> *head;};/* 置空单链表*  从头节点开始删除后续节点直到后续节点为空*/template<typename Type> void SingleList<Type>::MakeEmpty(){ListNode<Type> *pdel;while (head->m_pnext != NULL){pdel = head->m_pnext;head->m_pnext = pdel->m_pnext;delete pdel;}}template<typename Type> int SingleList<Type>::Length(){ListNode<Type> *pmove = head->m_pnext;int count = 0;while (pmove != NULL){pmove = pmove->m_pnext;count++;}return count;}/* 查找第n个节点指针*  从头结点开始遍历*/template<typename Type> ListNode<Type>* SingleList<Type>::Find(int n){if (n < 0){cout << "The n is out of boundary" << endl;return NULL;}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n&&pmove; i++){pmove = pmove->m_pnext;}if (pmove == NULL){cout << "The n is out of boundary" << endl;return NULL;}return pmove;}/* 查找第n个数据元素为value的节点指针*  从头结点开始遍历*/template<typename Type> ListNode<Type>* SingleList<Type>::Find(Type value, int n){if (n < 1){cout << "The n is illegal" << endl;return NULL;}ListNode<Type> *pmove = head;int count = 0;while (count != n&&pmove){pmove = pmove->m_pnext;if (pmove->m_data == value){count++;}}if (pmove == NULL){cout << "can't find the element" << endl;return NULL;}return pmove;}/* 插入操作:在位置n插入元素*  先从头结点开始遍历到第n个节点,再进行插入操作*/template<typename Type> bool SingleList<Type>::Insert(Type item, int n){if (n < 0){cout << "The n is illegal" << endl;return 0;}ListNode<Type> *pmove = head;ListNode<Type> *pnode = new ListNode<Type>(item);if (pnode == NULL){cout << "Application error!" << endl;return 0;}for (int i = 0; i < n&&pmove; i++){pmove = pmove->m_pnext;}if (pmove == NULL){cout << "the n is illegal" << endl;return 0;}//注意顺序pnode->m_pnext = pmove->m_pnext;pmove->m_pnext = pnode;return 1;}template<typename Type> bool SingleList<Type>::RemoveAll(Type item){ListNode<Type> *pmove = head;ListNode<Type> *pdel = head->m_pnext;while (pdel != NULL){if (pdel->m_data == item){pmove->m_pnext = pdel->m_pnext;delete pdel;pdel = pmove->m_pnext;continue;}pmove = pmove->m_pnext;pdel = pdel->m_pnext;}return 1;}/* 删除操作:删除位置n处的节点,并返回该节点的数据元素*  先从头结点开始遍历到第n个节点,再进行删除操作*/template<typename Type> Type SingleList<Type>::Remove(int n){if (n < 0){cout << "can't find the element" << endl;exit(1);}ListNode<Type> *pmove = head, *pdel;for (int i = 0; i < n&&pmove->m_pnext; i++){pmove = pmove->m_pnext;}if (pmove->m_pnext == NULL){cout << "can't find the element" << endl;exit(1);}pdel = pmove->m_pnext;pmove->m_pnext = pdel->m_pnext;Type temp = pdel->m_data;delete pdel;return temp;}template<typename Type> Type SingleList<Type>::Get(int n){if (n < 0){cout << "The n is out of boundary" << endl;exit(1);}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n; i++){pmove = pmove->m_pnext;if (NULL == pmove){cout << "The n is out of boundary" << endl;exit(1);}}return pmove->m_data;}template<typename Type> void SingleList<Type>::Print(){ListNode<Type> *pmove = head->m_pnext;cout << "head";while (pmove){cout << "--->" << pmove->m_data;pmove = pmove->m_pnext;}cout << "--->over" << endl << endl << endl;}//测试代码#include <iostream>using namespace std;#include "SingleList.h"int main(){SingleList<int> list;for (int i = 0; i < 20; i++){list.Insert(i * 3, i);}for (int i = 0; i < 5; i++){list.Insert(3, i * 3);}cout << "the Length of the list is " << list.Length() << endl;list.Print();cout << *list.Find(3, 2) << endl;list.Remove(5);cout << "the Length of the list is " << list.Length() << endl;list.Print();list.RemoveAll(3);cout << "the Length of the list is " << list.Length() << endl;list.Print();cout << "The third element is " << list.Get(3) << endl;list.Find(100);list.MakeEmpty();cout << "the Length of the list is " << list.Length() << endl;list.Print();return 0;}


输出结果:


2 循环链表

单链表每次遍历都需要从头结点开始,到尾节点终止。如果将单链表的尾节点指针由空改为指向头结点,整个单链表就构成了一个环,可以从链表中任何节点开始遍历整个链表,而不需要每次从头结点开始。这种首尾相连的单链表就是循环链表。

单链表:


循环链表:


可以再改进一下,将链表头指针改为尾指针,这样访问头结点和尾节点都只需要O(1)的时间。


循环链表除了将单链表遍历时节点是否为空的条件改为是否为头结点之外,和单链表操作基本相同。

完整代码:

//ListNode.htemplate<typename Type> class CircularList;template<typename Type> class ListNode{private:friend class CircularList<Type>;ListNode() :m_pnext(NULL){}ListNode(const Type item, ListNode<Type> *next = NULL) :m_data(item), m_pnext(next){}~ListNode(){m_pnext = NULL;}private:Type m_data;ListNode *m_pnext;};//CircleList.h#include "ListNode.h"template<typename Type> class CircularList{public:CircularList() :head(new ListNode<Type>()){head->m_pnext = head;}~CircularList(){MakeEmpty();delete head;}public:void MakeEmpty();//clear the listint Length();//get the lengthListNode<Type> *Find(Type value, int n);//find the nth data which is equal to valueListNode<Type> *Find(int n);//find the nth databool Insert(Type item, int n = 0);//insert the data into the nth data of the listType Remove(int n = 0);//delete the nth databool RemoveAll(Type item);//delete all the datas which are equal to valueType Get(int n);//get the nth datavoid Print();//print the listprivate:ListNode<Type> *head;};template<typename Type> void CircularList<Type>::MakeEmpty(){ListNode<Type> *pdel, *pmove = head;while (pmove->m_pnext != head){pdel = pmove->m_pnext;pmove->m_pnext = pdel->m_pnext;delete pdel;}}template<typename Type> int CircularList<Type>::Length(){ListNode<Type> *pmove = head;int count = 0;while (pmove->m_pnext != head){pmove = pmove->m_pnext;count++;}return count;}template<typename Type> ListNode<Type>* CircularList<Type>::Find(int n){if (n < 0){cout << "The n is out of boundary" << endl;return NULL;}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n&&pmove != head; i++){pmove = pmove->m_pnext;}if (pmove == head){cout << "The n is out of boundary" << endl;return NULL;}return pmove;}template<typename Type> ListNode<Type>* CircularList<Type>::Find(Type value, int n){if (n < 1){cout << "The n is illegal" << endl;return NULL;}ListNode<Type> *pmove = head;int count = 0;while (count != n){pmove = pmove->m_pnext;if (pmove->m_data == value){count++;}if (pmove == head){cout << "can't find the element" << endl;return NULL;}}return pmove;}template<typename Type> bool CircularList<Type>::Insert(Type item, int n){if (n < 0){cout << "The n is out of boundary" << endl;return 0;}ListNode<Type> *pmove = head;ListNode<Type> *pnode = new ListNode<Type>(item);if (pnode == NULL){cout << "Application error!" << endl;exit(1);}for (int i = 0; i < n; i++){pmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;return 0;}}pnode->m_pnext = pmove->m_pnext;pmove->m_pnext = pnode;return 1;}template<typename Type> bool CircularList<Type>::RemoveAll(Type item){ListNode<Type> *pmove = head;ListNode<Type> *pdel = head->m_pnext;while (pdel != head){if (pdel->m_data == item){pmove->m_pnext = pdel->m_pnext;delete pdel;pdel = pmove->m_pnext;continue;}pmove = pmove->m_pnext;pdel = pdel->m_pnext;}return 1;}template<typename Type> Type CircularList<Type>::Remove(int n){if (n < 0){cout << "can't find the element" << endl;exit(1);}ListNode<Type> *pmove = head, *pdel;for (int i = 0; i < n&&pmove->m_pnext != head; i++){pmove = pmove->m_pnext;}if (pmove->m_pnext == head){cout << "can't find the element" << endl;exit(1);}pdel = pmove->m_pnext;pmove->m_pnext = pdel->m_pnext;Type temp = pdel->m_data;delete pdel;return temp;}template<typename Type> Type CircularList<Type>::Get(int n){if (n < 0){cout << "The n is out of boundary" << endl;exit(1);}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n; i++){pmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;exit(1);}}return pmove->m_data;}template<typename Type> void CircularList<Type>::Print(){ListNode<Type> *pmove = head->m_pnext;cout << "head";while (pmove != head){cout << "--->" << pmove->m_data;pmove = pmove->m_pnext;}cout << "--->over" << endl << endl << endl;}//测试代码#include <iostream>#include "CircleList.h"using namespace std;int main(){CircularList<int> list;for (int i = 0; i < 20; i++){list.Insert(i * 3, i);}cout << "the Length of the list is " << list.Length() << endl;list.Print();for (int i = 0; i < 5; i++){list.Insert(3, i * 3);}cout << "the Length of the list is " << list.Length() << endl;list.Print();list.Remove(5);cout << "the Length of the list is " << list.Length() << endl;list.Print();list.RemoveAll(3);cout << "the Length of the list is " << list.Length() << endl;list.Print();cout << "The third element is " << list.Get(3) << endl;list.MakeEmpty();cout << "the Length of the list is " << list.Length() << endl;list.Print();return 0;}

输出结果:



3 双向链表

STL中的list容器就是双向链表,顾名思义,相较于单链表,双向链表可以从正反两个方向进行遍历,每个节点的指针域不仅有指向后节点的指针还有指向前驱节点的指针。相应地增加了数据的存储空间和操作的复杂度。


节点的类模板:

//ListNode.htemplate<typename Type> class DoublyList;template<typename Type> class ListNode{private:friend class DoublyList<Type>;ListNode():m_pprior(NULL),m_pnext(NULL){}ListNode(const Type item,ListNode<Type> *prior=NULL,ListNode<Type> *next=NULL):m_data(item),m_pprior(prior),m_pnext(next){}~ListNode(){m_pprior=NULL;m_pnext=NULL;}public:Type GetData();private:Type m_data;ListNode *m_pprior;ListNode *m_pnext;};template<typename Type> Type ListNode<Type>::GetData(){return this->m_data;}


需要特别强调的是,双向链表的插入和删除操作需要同时改变两个指针变量,且操作的顺序很重要。策略是:先将待插入节点和该位置的前后节点单向连接,再切断原位置处节点的前后关系:先搞定后节点的前驱,再搞定前节点的后继。


1、将s的前驱指针指向ps->prior = p2、将s的后继指针指向p+1(即p->next)s->next = p->next3、切断p和p+1的关系,p+1的前驱指向sp->next->prior = s4、最后再将p的后继指向sp->next = s


完整代码:

//ListNode.htemplate<typename Type> class DoublyList;template<typename Type> class ListNode{private:friend class DoublyList<Type>;ListNode() :m_pprior(NULL), m_pnext(NULL){}ListNode(const Type item, ListNode<Type> *prior = NULL, ListNode<Type> *next = NULL):m_data(item), m_pprior(prior), m_pnext(next){}~ListNode(){m_pprior = NULL;m_pnext = NULL;}public:Type GetData();private:Type m_data;ListNode *m_pprior;ListNode *m_pnext;};template<typename Type> Type ListNode<Type>::GetData(){return this->m_data;}//DoubleList.h#include "ListNode.h"template<typename Type> class DoublyList{public:DoublyList() :head(new ListNode<Type>()){    //the head node point to itselfhead->m_pprior = head;head->m_pnext = head;}~DoublyList(){MakeEmpty();delete head;}public:void MakeEmpty();   //make the list emptyint Length();       //get the length of the listListNode<Type> *Find(int n = 0);  //find the nth dataListNode<Type> * FindData(Type item);   //find the data which is equal to itembool Insert(Type item, int n = 0);     //insert item in the nth dataType Remove(int n = 0);   //delete the nth dataType Get(int n = 0);      //get the nth datavoid Print();           //print the listprivate:ListNode<Type> *head;};template<typename Type> void DoublyList<Type>::MakeEmpty(){ListNode<Type> *pmove = head->m_pnext, *pdel;while (pmove != head){pdel = pmove;pmove = pdel->m_pnext;delete pdel;}head->m_pnext = head;head->m_pprior = head;}template<typename Type> int DoublyList<Type>::Length(){ListNode<Type> *pprior = head->m_pprior, *pnext = head->m_pnext;int count = 0;while (1){if (pprior->m_pnext == pnext){break;}if (pprior == pnext&&pprior != head){count++;break;}count += 2;pprior = pprior->m_pprior;pnext = pnext->m_pnext;}return count;}template<typename Type> ListNode<Type>* DoublyList<Type>::Find(int n = 0){if (n < 0){cout << "The n is out of boundary" << endl;return NULL;}ListNode<Type> *pmove = head->m_pnext;for (int i = 0; i < n; i++){pmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;return NULL;}}return pmove;}template<typename Type> bool DoublyList<Type>::Insert(Type item, int n){if (n < 0){cout << "The n is out of boundary" << endl;return 0;}ListNode<Type> *newnode = new ListNode<Type>(item), *pmove = head;if (newnode == NULL){cout << "Application Erorr!" << endl;exit(1);}for (int i = 0; i < n; i++){   //find the position for insertpmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;return 0;}}//insert the datanewnode->m_pnext = pmove->m_pnext;newnode->m_pprior = pmove;pmove->m_pnext = newnode;newnode->m_pnext->m_pprior = newnode;return 1;}template<typename Type> Type DoublyList<Type>::Remove(int n = 0){if (n < 0){cout << "The n is out of boundary" << endl;exit(1);}ListNode<Type> *pmove = head, *pdel;for (int i = 0; i < n; i++){   //find the position for deletepmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;exit(1);}}//delete the datapdel = pmove;pmove->m_pprior->m_pnext = pdel->m_pnext;pmove->m_pnext->m_pprior = pdel->m_pprior;Type temp = pdel->m_data;delete pdel;return temp;}template<typename Type> Type DoublyList<Type>::Get(int n = 0){if (n < 0){cout << "The n is out of boundary" << endl;exit(1);}ListNode<Type> *pmove = head;for (int i = 0; i < n; i++){pmove = pmove->m_pnext;if (pmove == head){cout << "The n is out of boundary" << endl;exit(1);}}return pmove->m_data;}template<typename Type> void DoublyList<Type>::Print(){ListNode<Type> *pmove = head->m_pnext;cout << "head";while (pmove != head){cout << "--->" << pmove->m_data;pmove = pmove->m_pnext;}cout << "--->over" << endl << endl << endl;}template<typename Type> ListNode<Type>* DoublyList<Type>::FindData(Type item){ListNode<Type> *pprior = head->m_pprior, *pnext = head->m_pnext;while (pprior->m_pnext != pnext && pprior != pnext){ //find the data in the two directionif (pprior->m_data == item){return pprior;}if (pnext->m_data == item){return pnext;}pprior = pprior->m_pprior;pnext = pnext->m_pnext;}cout << "can't find the element" << endl;return NULL;}//测试代码#include <iostream>#include "DoubleList.h"using namespace std;int main(){DoublyList<int> list;for (int i = 0; i < 20; i++){list.Insert(i * 3, i);}cout << "the Length of the list is " << list.Length() << endl;list.Print();for (int i = 0; i < 5; i++){list.Insert(3, i * 3);}cout << "the Length of the list is " << list.Length() << endl;list.Print();list.Remove(5);cout << "the Length of the list is " << list.Length() << endl;list.Print();cout << list.FindData(54)->GetData() << endl;cout << "The third element is " << list.Get(3) << endl;list.MakeEmpty();cout << "the Length of the list is " << list.Length() << endl;list.Print();return 0;}


输出结果:



4 静态链表

虽然静态链表用的比较少,但最后还是说说静态链表是怎么回事,说白了就是用数组存储数据元素的链表,乍一听这不是顺序表吗?其实,就是拿数组当链表用,顺序表中数组只存储数据,而静态链表中数组元素包括两部分:数据和指向指下一个数据的指针。从根本上来说也没有解决数组连续内存分配问题(元素个数难以确定),反而还是去了数组快速存取的特性。真是吃力不讨好。


数组的第一个即下标为0的元素的指针指向接下来要使用的第一个节点的下标,上面的链表为空,所以位置1为即将使用的空间。而数组最后一个元素的指针指向第一个使用到的数组下标,相当于头结点的作用。


假如我想在节点“乙”和“丁”之间插入“丙”,在这里并不会移动“丁”后面的所有节点,只需要将“丙”放在数组下标为7的位置,然后修改“乙”和“丙”的指针指向即可。插入后,在逻辑上节点是按照“甲乙丙丁...”的顺序排列,而实际的物理位置却不是彼此顺序相邻,从而实现了链表的功能。


插入操作完成的最后一步是将下标为0的节点指针指向下一个数组的空闲位置,同样的,删除操作不仅会修改该节点前后节点的指针指向,还会将删除后的空间进行回收,以供下一次优先使用。只要记住使用数组元素的顺序永远是按照下标从小到大分配即可,每次插入和删除都要更新开始位置0和末尾位置的节点指向。


以上。


参考书籍:《大话数据结构》

0 0
原创粉丝点击