數據結構是初學編程時候的一個天坑,去年大一的時候學的那叫一個慘烈,雖然C++提供標準的list類庫,但是自己動手實現這些常用的數據結構,不僅能對數據結構的認識更深,也對C++中深淺複製,重載,複用等內容更好的理解。
線性表的鏈式存儲結構是用一組任意的存儲單元存儲線性表的數據元素(這組存儲單元可以是連續的也可以是不連續的),因此爲了表示每個數據元素與其直接後繼的邏輯關係,除了存儲本身的信息之外,還需要一個存儲一個直接後繼的位置信息。這兩部分組成的存儲映射,稱爲結點(node).
一個結點包含數據域和指針域兩個部分,一個鏈表由若干個結點依次鏈接構成,其中第一個結點爲頭結點,最後一個節點的直接後繼爲NULL。
那麼實現一個單鏈表的第一步就是選擇一個合適的結構來實現node。
這裏有很多種方法,比如將node聲明爲struct可直接被LinkList類使用,或者將node聲明爲類,LinkList類public繼承node類或是用嵌套類定義等方法。這裏好像沒什麼優劣之分吧,根據個人喜好選擇吧。
using namespace std;
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next; //struct構造函數
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL)
//函數參數表中的形參允許有默認值,但是帶默認值的參數需要放後面
{
next = ptr;
data = item;
}
};
看到這裏,可能有朋友會問了,這玩意是啥?結構還是類,在上述LinkNode結構中,居然出現了模板類的定義方法和構造函數,其實結構可以看做爲一種默認爲public的類。這裏的構造函數提供的默認值爲NULL可以省去檢測鏈表尾結點是否指向NULL的麻煩。
好了,現在結點有了,那我們來看看怎麼定義LinkList這個模板類吧,先給出來,在慢慢解釋。
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next;
LinkNode(LinkNode<Temp> *ptr = NULL){ next = ptr; } //struct構造函數
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL) //函數參數表中的形參允許有默認值,但是帶默認值的參數需要放後面
{
next = ptr;
data = item;
}
};
template <class Temp>
class LinkList
{
public:
LinkList(){
Head = new LinkNode<Temp>;
if (NULL == Head)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
};
//無參構造函數
LinkList(const Temp &item);
//含參構造函數
LinkList(const LinkList ©);
//拷貝構造函數
LinkList<Temp>& operator= (LinkList<Temp> &List);
~LinkList();
//析構函數
void CreateLink(int nLinkList);
//創建長度爲nLinkList的單鏈表
void DestoryList();
//摧毀線性表
void ClearList();
//清空單鏈表
bool ListIsLoop() const;
//鏈表是否有環
bool ListEnpty() const;
//判斷鏈表是否爲空,爲空返回true,否則返回false
int ListLength() const;
//返回鏈表中數據元素個數
bool GetElem(int pos, Temp &item);
//用item獲取第pos個數據元素的值,false表示失敗
void SetHead(LinkNode<Temp> *p);
//設置鏈表頭結點
LinkNode<Temp>* Find(Temp &item);
// 返回第一個找到的滿足該數據元素的結點對應的指針
LinkNode<Temp>* Locate(int pos);
//返回第pos個數據元素對於的指針
bool LinkInsert(Temp item, int pos);
//將數據元素item插入到第i個結點的位置,成功返回true,否則返回false
bool LinkDelete(Temp &item,int pos);
//將第pos個結點刪除,並用item返回該數據對象
void Print() const;
//打印鏈表
void Sort();
//鏈表排序
void Reverse();
void Reverse(int nStart, int nEnd);
//鏈表逆置
void LinkSwap(int pos1, int pos2);
void LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2);
//交換兩個結點內容
bool LinkIsPalindrome();
//單鏈表是不迴文
private:
LinkNode<Temp> *Head;
};
好了我們的鏈表完成了,整個鏈表的數據有什麼?只有一個類型爲LinkNode的Head指針!!!
曾經看到不少對於單鏈表的提問是這樣的
不管是用類的繼承或是嵌套類,創建一個單鏈表只有唯一的一個LinkList對象,它會鏈接多個LinkNode對象。
①構造函數
I.無參構造函數
LinkList(){
try
{
Head = new ListNode<Temp>;
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
};
沒什麼好說的,用new從堆分配一個結點的空間給我們都頭結點指針,注意檢查堆是否滿是一個很好的習慣。
II.含參構造函數
這裏是函數通過參數列表複用,可以將頭結點的數據域利用起來保存一些例如鏈表長度等信息。
template <class Temp>
LinkList<Temp>::LinkList(const Temp &item)
{
try
{
Head = new ListNode<Temp>(item);
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
此處定義是放在類定義之外的,所有要再聲明模板類型,並提供名稱空間。
②創建單鏈表
按照大多數數據結構的書會先介紹某個位置節點的插入和刪除,再在鏈表的創建中調用這些方法,但是連就一個頭結點你就來介紹插入、刪除某個結點了,我接受不了,雖然代碼複用性會降低,但我們還是一步一步的來。
首先定義一個指向Head的指針q;
然後不斷重複以下三個步驟完成單鏈表的構造:
①用new申請一個LinkNode的空間返回指針p,並輸入數據元素;
②q->next=p,將q指針對應結點的後繼修改爲p;
③q=q->next,將指針指向其直接後繼,這樣一個結點就被鏈接進了鏈表之中。
void LinkList<Temp>::CreateLink(int n)
{
//輸入n個元素的值,建立包含頭結點的單鏈表
LinkNode<Temp> *q = Head;
cout << "Enter the element: ";
int *nodetemp = new int[n];
for (size_t i = 0; i < n; i++)
{
cin >> nodetemp[i];
}
for (size_t i = 0; i < n; i++)
{
try
{
LinkNode<Temp> * p = new LinkNode<Temp>;
p->data = nodetemp[i];
p->next = q->next;
q->next = p;
q = q->next;
}
catch (const bad_alloc &e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
delete[] nodetemp;
}
這裏就有個更大的問題了!!!你new了這麼多次,都不釋放,這不是會導致內存泄露嗎?
new發生內存泄露的原因是當指針的生命週期結束後,會自動消亡,而再內存池中分配的內存卻無法被釋放,但是對於鏈表,雖然原來指向每一個結點對應地址的指針都沒了,但在LinkList的生命週期內,只要頭結點存在,且鏈路正常,就可以依次找到所有的存儲空間並釋放它。所有我們只需要在鏈表對象生命週期結束時釋放全部內存即可。
③析構函數(單鏈表的刪除)
單鏈表的刪除很簡單用兩個指針,從頭結點開始,一前一後依次釋放申請的內存即可。
template <class Temp>
void LinkList<Temp>::DestoryList()
{
LinkNode<Temp> *p=Head;
LinkNode<Temp> *t;
while (p)
{
t = p->next;
delete p;
p = t;
}
}
析構函數直接調用了鏈表的刪除操作即可
template <class Temp>
LinkList<Temp>::~LinkList()
{
DestoryList();
}
好了寫到這裏,雖然還有很多的操作沒寫但是基本上單鏈表的框架有了啊,我們來測試一下吧。
#include <iostream>
#include "LinkList.h"
int main()
{
using namespace std;
LinkList<int> text;
text.CreateLink(5);
LinkList<int> c=text;
cin.get();
return 0;
}
雖然無法輸,但是運行應該沒問題吧!
咕~~(╯﹏╰)b
什麼鬼,深淺拷貝的問題,一張圖說明什麼是深淺拷貝
淺拷貝只是將對象B也指向A存儲的內容,那麼問題來了,A對象析構之後,B對象怎麼辦?
而深拷貝就是分配了資源,數據有自己獨立不同的存儲地址和空間,兩個對象之間的析構不會互相干擾。
因此好的習慣的顯示定義類的構造函數,拷貝構造函數,和賦值函數。
我們首先來看看拷貝賦值函數。
④拷貝構造函數和賦值函數
操作都很簡單,依次分配內存拷貝鏈接即可,類似於鏈表的構建。區別在於拷貝構造還沒有LinkList對象,需要創建,而賦值已經有了LinkList對象,需要將其鏈表刪除再重新構造。
template <class Temp>
LinkList<Temp>::LinkList(const LinkList ©)
{
LinkNode<Temp> *p = copy.Head->next;
try
{
Head = new LinkNode<Temp>;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
try
{
LinkList<Temp> temp(*this);
temp.DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = temp.Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
swap(temp);
return *this;
}
catch (const bad_alloc & e)
{
cerr << "error !" << endl;
return *this;
}
}
這裏要注意對=的重載返回的是類的引用。
LinkList c=text ; 會調用拷貝構造函數
LinkList c;
c=text; 會調用賦值函數
⑤插入和刪除
插入
將前一節點的後繼指向新結點,在將新結點的後繼指向前一結點的後繼即可;
template <class Temp>
bool LinkList<Temp>::LinkInsert(Temp item, int pos)
{
using std::cerr;
using std::endl;
LinkNode<Temp> *p = Locate(pos - 1);
if (p == NULL)
{
return false;
}
try
{
LinkNode<Temp> *node = new LinkNode<Temp>(item);
node->next = p->next;
p->next = node;
return true;
}
catch (const bad_alloc &e)
{
cerr << "Error!";
return false;
}
}
刪除
將上一結點的後繼指向後繼的後繼,在釋放待刪除結點
template <class Temp>
bool LinkList<Temp>::LinkDelete(Temp &item, int pos)
{
LinkNode<Temp> *p = Locate(pos-1);
if (NULL == p || NULL == p->next)
return false;
LinkNode<Temp> *del = p->next;
p->next = del->next;
item = del->data;
delete del;
return true;
}
其他不在單獨介紹了,需要注意的是單鏈表的翻轉需要三個指針才能完成(可能是比較蠢的方法就不多說了),完整的單鏈表文件如下:
//ListNode.h
#ifndef LISTNODE_H_
#define LISTNODE_H_
//單鏈表的結點定義
using namespace std;
namespace Link
{
template <class Temp>
struct LinkNode
{
Temp data;
LinkNode<Temp> *next;
LinkNode(LinkNode<Temp> *ptr = NULL){ next = ptr; } //struct構造函數
LinkNode(const Temp &item, LinkNode<Temp> *ptr = NULL) //函數參數表中的形參允許有默認值,但是帶默認值的參數需要放後面
{
next = ptr;
data = item;
}
};
template <class Temp>
class LinkList
{
public:
LinkList(){
try
{
Head = new ListNode<Temp>;
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
};
//無參構造函數
LinkList(const Temp &item);
//含參構造函數
LinkList(const LinkList ©);
//拷貝構造函數
LinkList<Temp>& operator= (LinkList<Temp> &List);
~LinkList();
//析構函數
void CreateLink(int nLinkList);
//創建長度爲nLinkList的單鏈表
void DestoryList();
//摧毀線性表
void ClearList();
//清空單鏈表
bool ListIsLoop() const;
//鏈表是否有環
bool ListEnpty() const;
//判斷鏈表是否爲空,爲空返回true,否則返回false
int ListLength() const;
//返回鏈表中數據元素個數
bool GetElem(int pos, Temp &item);
//用item獲取第pos個數據元素的值,false表示失敗
void SetHead(LinkNode<Temp> *p);
//設置鏈表頭結點
LinkNode<Temp>* Find(Temp &item);
// 返回第一個找到的滿足該數據元素的結點對應的指針
LinkNode<Temp>* Locate(int pos);
//返回第pos個數據元素對於的指針
bool LinkInsert(Temp item, int pos);
//將數據元素item插入到第i個結點的位置,成功返回true,否則返回false
bool LinkDelete(Temp &item, int pos);
//將第pos個結點刪除,並用item返回該數據對象
void Print() const;
//打印鏈表
void Sort();
//鏈表排序
void Reverse();
void Reverse(int nStart, int nEnd);
//鏈表逆置
void LinkSwap(int pos1, int pos2);
void LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2);
//交換兩個結點內容
bool LinkIsPalindrome();
//單鏈表是不迴文
void swap(LinkList<Temp> & other);
private:
LinkNode<Temp> *Head;
};
template <class Temp>
LinkList<Temp>::LinkList(const Temp &item)
{
try
{
Head = new ListNode<Temp>(item);
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
template <class Temp>
LinkList<Temp>::LinkList(const LinkList ©)
{
LinkNode<Temp> *p = copy.Head->next;
try
{
Head = new LinkNode<Temp>;
LinkNode<Temp> *h = Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
}
catch (const bad_alloc & e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
template <class Temp>
LinkList<Temp> & LinkList<Temp>::operator=(LinkList<Temp> &List)
{
try
{
LinkList<Temp> temp(*this);
temp.DestoryList();
LinkNode<Temp> *p = List.Head;
LinkNode<Temp> *h = temp.Head;
while (p != NULL)
{
LinkNode<Temp> *t = new LinkNode<Temp>;
h->next = t;
t->data = p->data;
p = p->next;
h = h->next;
}
swap(temp);
return *this;
}
catch (const bad_alloc & e)
{
cerr << "error !" << endl;
return *this;
}
}
template <class Temp>
LinkList<Temp>::~LinkList()
{
DestoryList();
}
template <class Temp>
void LinkList<Temp>::CreateLink(int n)
{
//輸入n個元素的值,建立包含頭結點的單鏈表
LinkNode<Temp> *q = Head;
cout << "Enter the element: ";
int *nodetemp = new int[n];
for (size_t i = 0; i < n; i++)
{
cin >> nodetemp[i];
}
for (size_t i = 0; i < n; i++)
{
try
{
LinkNode<Temp> * p = new LinkNode<Temp>;
p->data = nodetemp[i];
p->next = q->next;
q->next = p;
q = q->next;
}
catch (const bad_alloc &e)
{
cerr << "分配內存失敗!" << endl;
exit(1);
}
}
delete[] nodetemp;
}
template <class Temp>
void LinkList<Temp>::DestoryList()
{
LinkNode<Temp> *p = Head;
LinkNode<Temp> *t;
while (p)
{
t = p->next;
delete p;
p = t;
}
}
template <class Temp>
void LinkList<Temp>::ClearList()
{
LinkNode<Temp> *p = Head;
while (p)
{
p->data = NULL;
p = p->next;
}
}
template <class Temp>
void LinkList<Temp>::Print() const
{
using std::cout;
using std::endl;
LinkNode<Temp> *p = Head->next;
while (p)
{
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
template <class Temp>
bool LinkList<Temp>::ListEnpty() const
{
LinkNode<Temp> *p = Head->next;
while (p)
{
if (p->data == NULL)
{
return true;
}
p = p->next;
}
return false;
}
template <class Temp>
int LinkList<Temp>::ListLength() const
{
LinkNode<Temp> *p = Head->next;
int nLink = 0;
while (p)
{
nLink++;
p = p->next;
}
return nLink;
}
template <class Temp>
bool LinkList<Temp>::GetElem(int pos, Temp &item)
{
LinkNode<Temp> *p = Locate(pos);
if (p == NULL)
{
return false;
}
item = p->data;
return true;
}
template <class Temp>
void LinkList<Temp>::SetHead(LinkNode<Temp> *p)
{
Head->data = p->data;
Head->next = p->next;
}
template <class Temp>
LinkNode<Temp>* LinkList<Temp>::Find(Temp &item)
{
LinkNode<Temp> *p = Head->next;
while (p)
{
if (p->data == item)
{
break;
}
p = p->next;
}
return p;
}
template <class Temp>
LinkNode<Temp>* LinkList<Temp>::Locate(int pos)
{
LinkNode<Temp> *p = Head;
while (pos--)
{
p = p->next;
}
return p;
}
template <class Temp>
bool LinkList<Temp>::LinkInsert(Temp item, int pos)
{
using std::cerr;
using std::endl;
LinkNode<Temp> *p = Locate(pos - 1);
if (p == NULL)
{
return false;
}
try
{
LinkNode<Temp> *node = new LinkNode<Temp>(item);
node->next = p->next;
p->next = node;
return true;
}
catch (const bad_alloc &e)
{
cerr << "Error!";
return false;
}
}
template <class Temp>
bool LinkList<Temp>::LinkDelete(Temp &item, int pos)
{
LinkNode<Temp> *p = Locate(pos - 1);
if (NULL == p || NULL == p->next)
return false;
LinkNode<Temp> *del = p->next;
p->next = del->next;
item = del->data;
delete del;
return true;
}
template<class Temp>
void LinkList<Temp>::Reverse()
{
LinkNode<Temp> *pre = Head->next;
LinkNode<Temp> *curr = pre->next;
LinkNode<Temp> *next = NULL;
Head->next->next = NULL;
while (curr)
{
next = curr->next;
curr->next = pre;
pre = curr;
curr = next;
}
Head->next = pre;
}
template<class Temp>
void LinkList<Temp>::Reverse(int nStart, int nEnd)
{
LinkNode<Temp> *start = Locate(nStart - 1);
LinkNode<Temp> *end = Locate(nEnd);
LinkNode<Temp> *pre = Locate(nStart);
LinkNode<Temp> *curr = pre->next;
LinkNode<Temp> *next = NULL;
pre->next = end->next;
while (pre != end)
{
next = curr->next;
curr->next = pre;
pre = curr;
curr = next;
}
start->next = pre;
}
template<class Temp>
void LinkList<Temp>::LinkSwap(int pos1, int pos2)
{
LinkNode<Temp> *p1 = Locate(pos1);
LinkNode<Temp> *p2 = Locate(pos2);
Temp t = p1->data;
p1->data = p2->data;
p2->data = t;
}
template<class Temp>
void LinkList<Temp>::LinkSwap(LinkNode<Temp> * p1, LinkNode<Temp> * p2)
{
Temp t = p1->data;
p1->data = p2->data;
p2->data = t;
}
template<class Temp>
void LinkList<Temp>::Sort()
{
LinkNode<Temp> *p = Head;
for (int i = 1; i <ListLength(); i++)
{
p = Locate(1);
for (int j = 1; j <= ListLength() - i; j++)
{
if (p->data >(p->next->data))
{
LinkSwap(p, p->next);
}
p = p->next;
}
}
}
template<class Temp>
bool LinkList<Temp>::ListIsLoop() const
{
LinkNode *slow = Head;
LinkNode *fast = Head;
while (fast->next != NULL)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
return true;
}
}
return false;
}
template<class Temp>
bool LinkList<Temp>::LinkIsPalindrome()
{
int len = ListLength();
if (len % 2 == 0)
{
Reverse((len / 2) + 1, len);
LinkNode<Temp> *p1 = Locate(1);
LinkNode<Temp> *p2 = Locate((len / 2) + 1);
while (p2->next != NULL)
{
if (p1->data != p2->data)
{
return false;
}
p2 = p2->next;
p1 = p1->next;
}
return true;
}
else
{
Reverse((len / 2) + 2, len);
LinkNode<Temp> *p1 = Locate(1);
LinkNode<Temp> *p2 = Locate((len / 2) + 2);
while (p2->next != NULL)
{
if (p1->data != p2->data)
{
return false;
}
p2 = p2->next;
p1 = p1->next;
}
return true;
}
}
template<class Temp>
void LinkList<Temp>::swap(LinkList<Temp> & other)
{
using std::swap;
swap(Head, other.Head);
}
template<typename Temp>
void swap(LinkList<Temp> & a, LinkList<Temp> & b)
{
a.swap(b);
}
}
#endif
因爲也是初學者,力求直觀簡單了,可能很多地方不夠優化
(^U^)ノ~YO