数据结构初阶–双向循环链表(讲解+类模板实现)

带头双向链表的结构

看下面的图,就是我今天要给大家分享有结构——带头双向循环链表。这里的头是不存放任何数据的,就是一个哨兵卫的头结点。

用代码来表示每一个节点就是这样的:

  • 数据域和指针域
  • 两个指针,一个指向前驱结点,一个指向后继结点
  • 给定两个构造函数,有参和无参,分别对结点的指针域和数据域进行初始化
template <class DateType> struct LinkNode {       //数据域       DateType data;       //两个指针       LinkNode<DateType>* prev;       LinkNode<DateType>* next;       LinkNode(DateType _data, LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :data(_data), prev(_prev), next(_next){}       LinkNode(LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :prev(_prev), next(_next){} }; 

数据结构初阶--双向循环链表(讲解+类模板实现)

带头双向链表的接口实现

要实现的接口

LinkList();//构造函数,初始化头节点 LinkList(const LinkList<DateType>& list2);//拷贝构造,进行两个链表的拷贝 ~LinkList();//析构函数,用来清除链表,释放结点空间 int Length();//求双向循环链表的长度 void CreateList(int n);//常见n个结点的双向循环链表 bool GetElem(int pos,DateType& data);//得到pos位置结点的元素值 LinkNode<DateType>* Locate(int i ,int back_pos);//定位元素,当back_pos=0的时候,从头节点向前查询第i个元素,back_pos!=0的时候,从头节点后查询第i个元素 bool Insert(int pos, const DateType& data, int back_pos);//在pos的位置插入元素,当back_pos!=0的时候,在pos位置后插入元素,当back_pos=0的时候,在pos位置前插入元素 void PrintList(int sign);//输出双向循环链表所有结点的元素值,当sign!=0时,正序打印元素值,当sign=0时,逆序打印 bool Delete(int pos, DateType& data,int back_pos);//删除pos位置的结点 

双向链表的小框架

template<class DateType> class LinkList { public: private: 	LinkNode<DateType>* head;//头节点 }; 

初始化双向链表

在初始化双链表的过程中,我们要开好一个头节点,作为哨兵卫的头节点,然后返回这个节点的指针,接口外面只要用一个节点指针接受这个返回值就好了,具体实现如下:

数据结构初阶--双向循环链表(讲解+类模板实现)

//构造函数,初始化一个循环双链表 	LinkList() 	{ 		head = new LinkNode<DateType>; 		if (head == NULL) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		head->data = 0; 		head->next = head; 		head->prev = head; 	} 

拷贝构造

在拷贝构造中,要注意一件事情,就是最后一个结点的next需要指向头节点,头节点的prev需要指向最后一个结点,形成双向循环链表

//拷贝构造 	LinkList(const LinkList<DateType>& list2) 	{ 		LinkNode<DateType>* p = list2.head->next; 		if (p == NULL) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		head = new LinkNode<DateType>; 		LinkNode<DateType>* h = head; 		while (p!=list2.head) 		{ 			LinkNode<DateType>* t = new LinkNode<DateType>; 			h->next = t; 			t->prev = h; 			t->data = p->data; 			p = p->next; 			h = h->next; 		} 		h->next = this->head; 		this->head->prev = h; 	} 

定位结点

因为后面的在指定插入删除元素,需要定位pos位置结点的地址,所以这里旧封装一个函数,直接获取pos位置结点的地址

//定位元素,back_pos=0时从头节点向前查第i个元素,d!=0时,从头节点向后找第i个元素 	LinkNode<DateType>* Locate(int i ,int back_pos) 	{ 		if (head->next == head || i == 0) { 			return head; 		} 		int j = 0; 		LinkNode<DateType>* p = head; 		//从头节点往后找第i个元素 		if (back_pos) 		{ 			while (p->next != head && j != i) 			{ 				p = p->next; 				++j; 			} 			if (p->next == head && j != i) 			{ 				return NULL; 			} 			else 			{ 				return p; 			}  		}//向前找 		else 		{ 			while (p->prev != head && j != i) 			{ 				p = p->prev; 				++j; 			} 			if (p->prev == head && j != i) 				return NULL; 			else 				return p; 		} 	} 

创建双向链表

//创建双循环链表 	void CreateList(int n) 	{ 		DateType* nodetemp = new DateType[n]; 		for (rsize_t i = 0; i < n; i++) 		{ 			cout << "Enter the element:  " << endl; 			cin >> nodetemp[i]; 			Insert(i, nodetemp[i], 1); 		} 		delete[] nodetemp; 		 	} 

打印双向循环链表

因为是双向循环链表,可以很简单的实现正序打印和逆序打印,所以这里用一个标志sign,来指定正序还是逆序打印链表元素

//输出双循环链表所有结点的元素值,分为正序打印和逆序打印 	void PrintList(int sign) 	{ 		//正序打印 		if (sign) 		{ 			cout << "head " ; 			LinkNode<DateType>* p = head; 			while (p->next != head) 			{ 				p = p->next; 				cout << "-> " << p->data; 			} 			cout << "->over" << endl; 		}//逆序打印 		else 		{ 			cout << "head " << endl; 			LinkNode<DateType>* p = head; 			while (p->prev != head) 			{ 				p = p->prev; 				cout << "-> " << p->data; 			} 			cout << "->over" << endl; 		} 	} 

指定位置插入结点

任意位置插入首先要开辟一个节点,然后就是按照所个位置,改变指针的指向来把这个节点连接上去,看具体代码实现如下:

//在pos的位置插入元素 	bool Insert(int pos, const DateType& data, int back_pos) 	{ 		LinkNode<DateType>* p = Locate(pos, back_pos); 		if (!p) 			return false; 		LinkNode<DateType>* new_node = new LinkNode<DateType>(data); 		if (NULL == new_node) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		//p结点后插入 		if (back_pos) 		{ 			p->next->prev = new_node; 			new_node->prev = p; 			new_node->next = p->next; 			p->next = new_node; 		}//p结点前插入 		else 		{ 			p->prev->next = new_node; 			new_node->next = p; 			new_node->prev = p->prev; 			p->prev = new_node; 		}return true; 	} 

指定位置删除结点

删除就要考虑链表是否为空,防止删除头节点

//删除pos位置的结点 	bool Delete(int pos, DateType& data,int back_pos) 	{ 		LinkNode<DateType>* p = Locate(pos, back_pos); 		if (!p) 		{ 			return false; 		} 		if (p == head ) 		{ 			cout << "请不要删除头节点" << endl; 			return false; 		} 		data = p->data; 		p->prev->next = p->next; 		p->next->prev = p->prev; 		delete p; 		return true; 	} 

获取链表长度

int Length() 	{ 		LinkNode<DateType>* p = head; 		int i = 0; 		while (p->next != head) 		{ 			++i; 			p = p->next; 		} 		return i;  	} 

销毁链表

在析构函数中实现链表的销毁

//析构函数 	~LinkList() 	{ 		LinkNode<DateType>* p, * q = head->next; 		while (q != head) 		{ 			p = q; 			q = q->next; 			delete p; 		} 	} 

整体代码以及测试

#define _CRT_SECURE_NO_WARNINGS #include<iostream> //引入头文件 #include<string>//C++中的字符串 using namespace std; //标准命名空间 /* 双向循环链表 */ template <class DateType> struct LinkNode { 	//数据域 	DateType data; 	//两个指针 	LinkNode<DateType>* prev; 	LinkNode<DateType>* next; 	LinkNode(DateType _data, LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :data(_data), prev(_prev), next(_next) 	{} 	LinkNode(LinkNode<DateType>* _prev = NULL, LinkNode<DateType>* _next = NULL) :prev(_prev), next(_next) 	{} }; template<class DateType> class LinkList { public: 	//构造函数,初始化一个循环双链表 	LinkList() 	{ 		head = new LinkNode<DateType>; 		if (head == NULL) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		head->data = 0; 		head->next = head; 		head->prev = head; 	} 	//拷贝构造 	LinkList(const LinkList<DateType>& list2) 	{ 		LinkNode<DateType>* p = list2.head->next; 		if (p == NULL) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		head = new LinkNode<DateType>; 		LinkNode<DateType>* h = head; 		while (p!=list2.head) 		{ 			LinkNode<DateType>* t = new LinkNode<DateType>; 			h->next = t; 			t->prev = h; 			t->data = p->data; 			p = p->next; 			h = h->next; 		} 		h->next = this->head; 		this->head->prev = h; 	} 	//析构函数 	~LinkList() 	{ 		LinkNode<DateType>* p, * q = head->next; 		while (q != head) 		{ 			p = q; 			q = q->next; 			delete p; 		} 	} 	//求双循环链表的长度 	int Length() 	{ 		LinkNode<DateType>* p = head; 		int i = 0; 		while (p->next != head) 		{ 			++i; 			p = p->next; 		} 		return i;  	} 	//创建双循环链表 	void CreateList(int n) 	{ 		DateType* nodetemp = new DateType[n]; 		for (rsize_t i = 0; i < n; i++) 		{ 			cout << "Enter the element:  " << endl; 			cin >> nodetemp[i]; 			Insert(i, nodetemp[i], 1); 		} 		delete[] nodetemp; 		 	} 	//得到位置为pos的结点元素值 	bool GetElem(int pos,DateType& data) 	{ 		LinkNode<DateType>* p = head; 		if (pos<0 || pos>Length()) 		{ 			cout << "输入的位置不合法" << endl; 			return false; 		} 		else { 			p = Locate(pos, 1); 			data = p->data; 		} 		return true; 	} 	//定位元素,back_pos=0时从头节点向前查第i个元素,d!=0时,从头节点向后找第i个元素 	LinkNode<DateType>* Locate(int i ,int back_pos) 	{ 		if (head->next == head || i == 0) { 			return head; 		} 		int j = 0; 		LinkNode<DateType>* p = head; 		//从头节点往后找第i个元素 		if (back_pos) 		{ 			while (p->next != head && j != i) 			{ 				p = p->next; 				++j; 			} 			if (p->next == head && j != i) 			{ 				return NULL; 			} 			else 			{ 				return p; 			}  		}//向前找 		else 		{ 			while (p->prev != head && j != i) 			{ 				p = p->prev; 				++j; 			} 			if (p->prev == head && j != i) 				return NULL; 			else 				return p; 		} 	} 	//在pos的位置插入元素 	bool Insert(int pos, const DateType& data, int back_pos) 	{ 		LinkNode<DateType>* p = Locate(pos, back_pos); 		if (!p) 			return false; 		LinkNode<DateType>* new_node = new LinkNode<DateType>(data); 		if (NULL == new_node) 		{ 			cout << "内存分配失败" << endl; 			exit(-1); 		} 		//p结点后插入 		if (back_pos) 		{ 			p->next->prev = new_node; 			new_node->prev = p; 			new_node->next = p->next; 			p->next = new_node; 		}//p结点前插入 		else 		{ 			p->prev->next = new_node; 			new_node->next = p; 			new_node->prev = p->prev; 			p->prev = new_node; 		}return true; 	} 	//输出双循环链表所有结点的元素值,分为正序打印和逆序打印 	void PrintList(int sign) 	{ 		//正序打印 		if (sign) 		{ 			cout << "head " ; 			LinkNode<DateType>* p = head; 			while (p->next != head) 			{ 				p = p->next; 				cout << "-> " << p->data; 			} 			cout << "->over" << endl; 		}//逆序打印 		else 		{ 			cout << "head " << endl; 			LinkNode<DateType>* p = head; 			while (p->prev != head) 			{ 				p = p->prev; 				cout << "-> " << p->data; 			} 			cout << "->over" << endl; 		} 	} 	//删除pos位置的结点 	bool Delete(int pos, DateType& data,int back_pos) 	{ 		LinkNode<DateType>* p = Locate(pos, back_pos); 		if (!p) 		{ 			return false; 		} 		if (p == head ) 		{ 			cout << "请不要删除头节点" << endl; 			return false; 		} 		data = p->data; 		p->prev->next = p->next; 		p->next->prev = p->prev; 		delete p; 		return true; 	} private: 	LinkNode<DateType>* head;//头节点 }; int main() { 	LinkList<int> list; 	list.CreateList(5); 	list.PrintList(1); 	cout << "-----------------------" << endl; 	LinkList<int> list2(list); 	list2.PrintList(1); 	cout << "-----------------------" << endl; 	list.Insert(0, 10, 1); 	list.PrintList(1); 	cout << list.Length() << endl; 	cout << "-----------------------" << endl; 	int b = 0; 	list.Delete(0, b, 1); 	cout << b << endl; 	list.PrintList(1); 	cout << list.Length() << endl; 	cout << "-----------------------" << endl; 	list.GetElem(3, b); 	cout << b << endl; 	cout <<"---------------------------" << endl; 	system("pause"); 	return EXIT_SUCCESS; } 

链表和顺序表的对比

参考下表:

不同点 顺序表 链表
存储空间上 物理上连续 逻辑上连续
随机访问 支持 不支持
任意位置插入删除 要移动元素,O(N) 只要改变指针指向
插入数据 要考虑扩容,会带来一定的空间消耗 没有容量这个概念,可以按需申请和释放
缓存利用率
发表评论

相关文章