概述
- 鏈表特點:
1、元素在內存中位置隨機(數組元素的地址是數學公式決定的)
2、每個元素有一個明確的“鏈”指向線性表的下一個元素 - 鏈表分類:
1、單向鏈表:x.firstNode指向鏈表首節點,當x.firstNode=NULL時爲空表,最後節點的鏈指針爲NULL;
2、單向循環鏈表:最後節點的鏈指針指向第一個節點;
3、雙向鏈表:兩個指針域next和previous,最後節點的next指針爲NULL,首節點的previous指針爲NULL;
4、雙向循環鏈表:首節點的previous指向尾節點,尾節點的next指針指向首節點。
注:以上分類均默認沒有頭結點,如果引入頭結點,每個鏈表(包括空表)都至少包括一個節點(頭結點)
單向鏈表性質與實現
1、單向鏈表
- 一個線性表的鏈式描述
- 鏈表的刪除操作
兩個步驟:
(1)找到第二個節點
(2)把第二個節點和第四個節點鏈接起來 - 鏈表的插入操作
兩個步驟:
(1)找到索引爲index-1的節點
(2)改變前後節點的指針
2、C++語言實現
2.1 結構chainNode
存儲數據成員和指針:
template <class T>
struct chainNode
{
// data members
T element;
chainNode<T>* next;
// methods
chainNode() {}
chainNode(const T& element)
{
this->element = element;
}
chainNode(const T& element, chainNode<T>* next)
{
this->element = element;
this->next = next;
}
};
2.2 類chain
聲明代碼:
template<class T>
class chain : public linearList<T>
{
friend linkedDigraph;
friend linkedWDigraph<int>;
friend linkedWDigraph<float>;
friend linkedWDigraph<double>;
public:
// constructor, copy constructor and destructor
chain(int initialCapacity = 10);
chain(const chain<T>&);
~chain();
// ADT methods
bool empty() const { return listSize == 0; }
int size() const { return listSize; }
T& get(int theIndex) const;
int indexOf(const T& theElement) const;
void erase(int theIndex);
void insert(int theIndex, const T& theElement);
void output(ostream& out) const;
protected:
void checkIndex(int theIndex) const;
// throw illegalIndex if theIndex invalid
chainNode<T>* firstNode; // pointer to first node in chain
int listSize; // number of elements in list
};
實現代碼:
template<class T>
chain<T>::chain(int initialCapacity)
{// Constructor.
if (initialCapacity < 1)
{
ostringstream s;
s << "Initial capacity = " << initialCapacity << " Must be > 0";
throw illegalParameterValue(s.str());
}
firstNode = NULL;
listSize = 0;
}
template<class T>
chain<T>::chain(const chain<T>& theList)
{// Copy constructor.
listSize = theList.listSize;
if (listSize == 0)
{// theList is empty
firstNode = NULL;
return;
}
// non-empty list
chainNode<T>* sourceNode = theList.firstNode;
// node in theList to copy from
firstNode = new chainNode<T>(sourceNode->element);
// copy first element of theList
sourceNode = sourceNode->next;
chainNode<T>* targetNode = firstNode;
// current last node in *this
while (sourceNode != NULL)
{// copy remaining elements
targetNode->next = new chainNode<T>(sourceNode->element);
targetNode = targetNode->next;
sourceNode = sourceNode->next;
}
targetNode->next = NULL; // end the chain
}
template<class T>
chain<T>::~chain()
{// Chain destructor. Delete all nodes in chain.
while (firstNode != NULL)
{// delete firstNode
chainNode<T>* nextNode = firstNode->next;
delete firstNode;
firstNode = nextNode;
}
}
template<class T>
void chain<T>::checkIndex(int theIndex) const
{// Verify that theIndex is between 0 and listSize - 1.
if (theIndex < 0 || theIndex >= listSize)
{
ostringstream s;
s << "index = " << theIndex << " size = " << listSize;
throw illegalIndex(s.str());
}
}
template<class T>
T& chain<T>::get(int theIndex) const
{// Return element whose index is theIndex.
// Throw illegalIndex exception if no such element.
checkIndex(theIndex);
// move to desired node
chainNode<T>* currentNode = firstNode;
for (int i = 0; i < theIndex; i++)
currentNode = currentNode->next;
return currentNode->element;
}
template<class T>
int chain<T>::indexOf(const T & theElement) const
{// Return index of first occurrence of theElement.
// Return -1 if theElement not in list.
// search the chain for theElement
chainNode<T>* currentNode = firstNode;
int index = 0; // index of currentNode
while (currentNode != NULL &&
currentNode->element != theElement)
{
// move to next node
currentNode = currentNode->next;
index++;
}
// make sure we found matching element
if (currentNode == NULL)
return -1;
else
return index;
}
template<class T>
void chain<T>::erase(int theIndex)
{// Delete the element whose index is theIndex.
// Throw illegalIndex exception if no such element.
checkIndex(theIndex);
// valid index, locate node with element to delete
chainNode<T>* deleteNode;
if (theIndex == 0)
{// remove first node from chain
deleteNode = firstNode;
firstNode = firstNode->next;
}
else
{ // use p to get to predecessor of desired node
chainNode<T>* p = firstNode;
for (int i = 0; i < theIndex - 1; i++)
p = p->next;
deleteNode = p->next;
p->next = p->next->next; // remove deleteNode from chain
}
listSize--;
delete deleteNode;
}
template<class T>
void chain<T>::insert(int theIndex, const T & theElement)
{// Insert theElement so that its index is theIndex.
if (theIndex < 0 || theIndex > listSize)
{// invalid index
ostringstream s;
s << "index = " << theIndex << " size = " << listSize;
throw illegalIndex(s.str());
}
if (theIndex == 0)
// insert at front
firstNode = new chainNode<T>(theElement, firstNode);
else
{ // find predecessor of new element
chainNode<T>* p = firstNode;
for (int i = 0; i < theIndex - 1; i++)
p = p->next;
// insert after p
p->next = new chainNode<T>(theElement, p->next);
}
listSize++;
}
template<class T>
void chain<T>::output(ostream & out) const
{// Put the list into the stream out.
for (chainNode<T>* currentNode = firstNode;
currentNode != NULL;
currentNode = currentNode->next)
out << currentNode->element << " ";
}
// overload <<
template <class T>
ostream & operator<<(ostream & out, const chain<T> & x)
{
x.output(out); return out;
}
注:鏈表在創建的時候不需要估計元素的最大個數;構造函數有一個表示初試容量的形參initialCapacity,目的是與arrayList相容
3、總結
(1)可以在上述簡單實現的基礎上添加鏈表的成員類Iterator,由於鏈表是單向的,所以只能定義一個向前迭代器
(2)如果想對鏈表操作進行擴充,首先要對抽象數據類型linearList進行擴充,比如添加clear和push_back(添加元素到表尾)等。
例如:爲了快速在表尾插入元素,可以增加數據成員lastNode(指向鏈表結尾點的指針)。需要完成的工作有:聲明一個數據成員lastNode;提供改進的erase和insert代碼;定義在linearList中剩餘的純虛函數;實現clear和push_back方法。
循環列表和頭節點
- 兩條措施可以讓鏈表更加簡潔和高效:
- 單向鏈表尾節點和頭節點鏈接起來構成循環列表;
- 鏈表前面添加頭結點
循環鏈表:
帶頭結點的循環列表:
雙向列表
一個節點存在兩個指針,分別指向前驅節點和後繼節點