資料結構初階--雙向迴圈連結串列(講解+類別範本實現)

2022-11-26 21:01:06

帶頭雙向連結串列的結構

看下面的圖,就是我今天要給大家分享有結構——帶頭雙向迴圈連結串列。這裡的頭是不存放任何資料的,就是一個哨兵衛的頭結點。

用程式碼來表示每一個節點就是這樣的:

  • 資料域和指標域
  • 兩個指標,一個指向前驅結點,一個指向後繼結點
  • 給定兩個建構函式,有參和無參,分別對結點的指標域和資料域進行初始化
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) 只要改變指標指向
插入資料 要考慮擴容,會帶來一定的空間消耗 沒有容量這個概念,可以按需申請和釋放
快取利用率