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

带头双向链表的结构

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

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

  • 数据域和指针域
  • 两个指针,一个指向前驱结点,一个指向后继结点
  • 给定两个构造函数,有参和无参,分别对结点的指针域和数据域进行初始化
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) 只要改变指针指向
插入数据 要考虑扩容,会带来一定的空间消耗 没有容量这个概念,可以按需申请和释放
缓存利用率
posted @ 2022-11-26 19:07  一只少年AAA  阅读(223)  评论(0编辑  收藏  举报