帶頭雙向連結串列的結構
看下面的圖,就是我今天要給大家分享有結構——帶頭雙向迴圈連結串列。這裡的頭是不存放任何資料的,就是一個哨兵衛的頭結點。
用程式碼來表示每一個節點就是這樣的:
- 資料域和指標域
- 兩個指標,一個指向前驅結點,一個指向後繼結點
- 給定兩個建構函式,有參和無參,分別對結點的指標域和資料域進行初始化
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) | 只要改變指標指向 |
插入資料 | 要考慮擴容,會帶來一定的空間消耗 | 沒有容量這個概念,可以按需申請和釋放 |
快取利用率 | 高 | 低 |