1、鏈表和順序表
鏈表是很常見的數據結構,鏈表總是會和線性順序表來比較。
1.1、順序表
- 具有隨機存儲的特性,給定位置就能通過計算找到數據所在位置,因為在分配存儲空間的時候,是連續分配的,所以讀取很快。
- 因為事先要開辟一塊空間,過大會造成空間浪費,過小會溢出,所以在存儲位置長度的數據時,表現不是很好。
- 在插入和刪除元素時,涉及到元素的移動,最壞情況下要移動表里所有元素,效率不高。
1.2、鏈表
- 通過指向下一結點的指針,來找到下一個元素,每個節點要存儲數據信息和指向下一結點的指針,從空間利用率上來說,不如順序表。
- 不具有隨機存儲的特點,例如想找到下標為5的元素,順序表可以直接訪問,而鏈表必須逐個調整,最后才能找到這個元素。
- 插入、刪除效率高,相較于順序表的移動元素,鏈表只需調整指針指向的元素。
1.3、比較
- 對于隨機讀取次數多,且對插入、刪除需求小的時候,可以用順序表來存儲,更合理。
- 對于插入、刪除需求頻繁的時候,可以用鏈表來存儲,更合理。
- 如果內存中碎片多,此時可能無法開辟一塊連續的大空間,從而無法創建順序表,此時可以使用鏈表。
2、單鏈表
2.1、兩種類型
有許多種鏈表,單鏈表就是很常見的一種鏈表類型。即只能從前向后遍歷,無法從后向前遍歷。其中單鏈表又有帶頭和不帶頭之分,聽起來怎么有點嚇人……
- 不帶頭鏈表中第一個結點就是我們所存儲的元素的結點。
- 帶頭鏈表中第一個結點存儲的元素為空,這個就是所謂的頭,第二個結點才是我們存儲的元素。
2.2、比較
- 不帶頭鏈表在第一個結點之前插入和刪除第一個結點的時候,會出現特殊情況,使得操作不方便。
- 帶頭鏈表,因為第一個有效的結點實際上是第二結點,所以避免了上述的情況。
3、基于C++的代碼
3.1、給出類的基本構造
#include <iostream>
using namespace std;
template<class T>
class Node
{
private:
Node* link;
T element;
public:
Node* getLink(){return link;}
T getElement(){return element;}
void setElement(T x){element = x;}
void setLink(Node<T>* x){link = x;}
};
template<class T>
class SingleList
{
public:
SingleList() { first = new Node<T>; first->setLink(NULL); length = 0; };
~SingleList();
bool isEmpty() const;
int Length() const;
bool Find(int i, T& x)const;
Node<T>* Find(int i)const;
int Search(T x)const;
bool Insert(int i, T x);
bool Delete(int i);
bool Update(int i, T x);
void Clear();
void Output(ostream& out)const;
private:
Node<T>*first;
int length;
};
- Node類是結點類
- isEmpty返回bool類型,判斷表是否為空
- Length返回表長度
- Find(int i, T& x),找到第i個結點,并將結點內的值放在x中保存
- Find(int i) 找到第i個結點并返回
- Search找到指定元素,并返回其位置
- Insert向指定位置插入元素
- Delete刪除指定位置元素
- Update更新指定位置元素
- Clear清除表
- Output輸出表中元素
3.2、類的實現
template<class T>
void SingleList<T>::Output(ostream& out) const
{
if (length==0)
{
out<<"The SingeList is null";
return;
}
Node<T>*p = first->getLink();
while (p != NULL)
{
out << p->getElement() << " ";
p = p->getLink();
}
out << endl;
}
template<class T>
void SingleList<T>::Clear()
{
Node<T>*p = first->getLink();
while (p != NULL)
{
Node<T>* q = p;
p = p->getLink();
delete q;
}
length = 0;
}
template<class T>
bool SingleList<T>::Update(int i, T x)
{
Node<T>* p = Find(i);
if (p == NULL) return false;
p->setElement(x);
return true;
}
template<class T>
bool SingleList<T>::Delete(int i)
{
Node<T>*p;
if (i == 0)
p = first;
else
{
p = Find(i - 1);
if (p == NULL)return false;
}
//找到第i-1個結點
Node<T>*q = p->getLink();//q是第i個結點
p->setLink(q->getLink());//第i-1個結點指向第i+1個結點
delete q;//刪除第i個結點
length--;//減小長度
return true;
}
template<class T>
bool SingleList<T>::Insert(int i, T x)
{
Node<T>*p;
if (i==0)
p = first;
else
{
p = Find(i - 1);
if (p == NULL)return false;
}
//以上是找到第i-1個結點,越界判斷在Find函數里
Node<T>* newNode = new Node<T>;//生成新插入的結點
newNode->setElement(x);//賦值
newNode->setLink(p->getLink());//新結點指向原來的第i個結點
p->setLink(newNode);//第i-1個結點指向新結點
length++;//表長度增加
return true;
}
template<class T>
int SingleList<T>::Search(T x) const
{
if (length == 0)//如果表里沒有元素,則直接返回
{
cout << "The SingleList is null!";
return -1;
}
Node<T>*p = first->getLink();
int position = 0;//位置信息
while (p != NULL)
{
if (p->getElement() == x) return position;//找到元素,返回位置
p = p->getLink();
position++;
}
cout << "Don't find the element!";
return -1;
}
template<class T>
Node<T>* SingleList<T>::Find(int i) const
{
if (i > length - 1 || i < 0)//越界檢查
{
cout << "Out of Bounds!";
return NULL;
}
Node<T>* p = first->getLink();//從第一個有效元素開始查找
for (int j = 0; j < i; j++)
{
p = p->getLink();//逐個調整,知道指向第i個元素
}
return p;
}
template<class T>
bool SingleList<T>::Find(int i, T& x) const
{
Node<T>* p = Find(i);
if (p == NULL) return false;
x = p->getElement();
return true;
}
template<class T>
int SingleList<T>::Length() const
{
return length;
}
template<class T>
bool SingleList<T>::isEmpty() const
{
if (length == 0) return true;
else return false;
}
template<class T>
SingleList<T>::~SingleList()
{
Node<T>*p;
while (first != NULL)
{
p = first->getLink();//找到當前刪除結點的下一個結點
delete first;//刪除當前結點
first = p;
}
}
除了刪除和插入以外,其他的方法實現起來都不困難,唯一有些復雜的就是插入和刪除,要想好先后執行的操作,從老師的PPT里找到的圖片
插入和刪除.png
- 插入時,先將新結點指向原來的第i個結點,再讓第i-1個結點指向新結點。
- 刪除時,找到第i-1個結點和第i個結點,讓第i-1個結點指向第i+1個結點,刪除第i個結點。
至此整個基本的帶頭單鏈表就完成了,讓我們來測試一下吧!
4、測試
測試代碼如下
#include "SingleList.h"
#include "iostream"
using namespace std;
int main()
{
SingleList<int> newlist;
newlist.Insert(0, 1);
newlist.Insert(0, 2);
newlist.Insert(0, 3);
newlist.Insert(0, 4);
newlist.Insert(2, 20);
newlist.Output(cout);
newlist.Delete(1);
newlist.Output(cout);
cout<<newlist.Length()<<endl;
cout << newlist.isEmpty() << endl;
cout << newlist.Search(1)<<endl;
cout << newlist.Search(5)<<endl;
int n;
newlist.Find(1, n);
cout << n << endl;
cout << newlist.Find(2)->getElement()<<endl;
newlist.Update(2, 55);
newlist.Output(cout);
newlist.Clear();
newlist.Find(0);
cin >> n;
return 0;
}
我們先預測一下輸出,應該是
4 3 20 2 1
4 20 2 1
4
0
3
don't find the element!
20
2
4 20 55 1
out of bounds!
運行程序
測試.JPG
運行結果符合我們的預期,這樣就基本實現了單鏈表