資料結構與演算法筆記(三) 線性表(鏈式描述) 連結串列
在鏈式描述中,線性表元素的位置在記憶體中是隨機的,每個元素都有一個明確的指標指向線性表的下一個元素的位置。
1.單向連結串列:
資料物件的每一個元素都用一個單元或者節點來描述,每個節點都明確包含另一個相關節點的位置資訊。
線性表的鏈式描述圖如下所示:
每個節點只有一個鏈,這種結構稱為單向連結串列
重點: 連結串列的插入與刪除
結構chainNode,資料成員element是節點的資料域,儲存線性表的元素,資料成員next是節點的鏈域,儲存下一個節點的指標。
定義連結串列類chain:
首先定義連結串列的節點的資料結構,結構體chainNode;
template<typename T> struct chainNode // 定義節點的資料結構 { // 資料成員 T element; // 資料域 chainNode<T>* next; // 指標域 //chainNode() {} chainNode(T& element) // 結構體的建構函式 { this->element = element; } chainNode(T& element, chainNode<T>* next) // 結構體的建構函式 { this->element = element; // 這裡的this的作用類似於類裡面的this this->next = next; } };
連結串列類chain的定義:
template<typename T> class chain: public linearList<T> { protected: bool checkIndex(int theIndex) const; // 索引是否有效 chainNode<T>* firstNode; int listSize; public: chain(int init_capacity=10); chain(const chain<T>&); // 拷貝建構函式 ~chain(); // 解構函式 //抽象資料型別ADT bool empty() const; int size() const; T& get(int index); int indexOf(T& element) const; void erase(int index); void insert(int index); void output() const; };
重點掌握的,連結串列的拷貝建構函式實現,解構函式實現,插入函式和刪除函式
拷貝建構函式:
template<typename T>
chain<T>::chain(const chain<T>& new_chain) // 重要
{
listSize = new_chain.listSize;
if(listSize==0) // 要複製的連結串列為空
{
firstNode = NULL;
return; // end
}
// 連結串列非空
chainNode<T>* sourceNode = new_chain.firstNode;
firstNode = new chainNode<T>(sourceNode->element); // 結構體變數的初始化 chainNode結構體有兩個構造方法,這是其中的一個
// firstNode = new chainNode<T>((*sourceNode).element) //解引用
sourceNode = sourceNode->next; // 原來連結串列的指標
chainNode<T>* targetNode = firstNode; // 複製得到的連結串列的節點指標
while(sourceNode!=NULL)
{
targetNode->next = new chainNode<T>(sourceNode->element);
targetNode = targetNode->next;
sourceNode = sourceNode->next;
}
targetNode->next = NULL; // 連結串列結束
}
指標移動細節如下圖所示:
解構函式:
解構函式逐個刪除連結串列的節點,通過重複清除連結串列的首個元素節點,知道連結串列為空。在清除之前用變數儲存第二個元素節點的指標。時間複雜度
template<typename T>
chain<T>::~chain()
{
// 連結串列的解構函式, 刪除連結串列的所有節點
while(firstNode!=NULL)
{
chainNode<T>* nextNode = firstNode->next; // 儲存連結串列第二個節點的指標
delete firstNode; // 刪除釋放第一個節點的記憶體
firstNode = nextNode; // 將第二個節點變為第一個節點
}
}
連結串列類chain的完整程式碼:
1. chain的基類, 抽象類linearList
#ifndef LINEAR_LIST_H
#define LINEAR_LIST_H
#include <iostream>
using namespace std;
template<typename T> // 定義一個抽象類
class linearList
{
public:
// 抽象類中的純虛擬函式
virtual ~linearList() {}; // 解構函式
virtual bool empty() const=0;
virtual int size() const=0;
virtual T& get(int index) const=0;
virtual int indexOf(const T x) const=0; // 這裡定義的是虛擬函式,虛擬函式virtual functiuonName() const=0表示的是定義為純虛擬函式,這個純虛擬函式是隻讀函式
virtual void erase(int index) = 0; // 這裡定義的是虛擬函式,虛擬函式virtual functiuonName()=0表示的是定義為純虛擬函式,這個純虛擬函式不是隻讀函式
virtual void insert(int index, T x) = 0;
// virtual void output(ostream& out) const=0;
};
#endif
2. 類chain的定義和實現:
#ifndef CHAIN_H
#define CHAIN_H
#include <iostream>
#include "E:\back_up\code\c_plus_code\digui\external_file\linearlist.h"
template<typename T>
struct chainNode // 定義節點的資料結構
{
// 資料成員
T element; // 資料域
chainNode<T>* next; // 指標域
//chainNode() {}
chainNode(T& element) // 結構體的建構函式
{
this->element = element;
}
chainNode(T& element, chainNode<T>* next) // 結構體的建構函式
{
this->element = element; // 這裡的this的作用類似於類裡面的this
this->next = next;
}
};
template<typename T>
class chain: public linearList<T>
{
protected:
//bool checkIndex(int theIndex) const; // 索引是否有效
chainNode<T>* firstNode;
int listSize;
public:
chain(int init_capacity=10);
chain(const chain<T>& new_chain); // 拷貝建構函式
~chain(); // 解構函式
//抽象資料型別ADT
bool empty() const;
int size() const;
T& get(int index) const;
int indexOf(T x) const;
void erase(int index);
void insert(int index, T x);
void output() const;
};
/*
template<typename T>
bool chain<T>::checkIndex(int index)
{
return (index>=0&&index<=listSize)?true:false;
}
*/
template<typename T>
chain<T>::chain(int init_capacity) // 建構函式
{
if(init_capacity<1)
cout << "Init capacity should be greater than 0" << endl;
firstNode = NULL; // 連結串列初始化
listSize = 0;
}
#endif
template<typename T>
chain<T>::chain(const chain<T>& new_chain) // 重要
{
listSize = new_chain.listSize;
if(listSize==0) // 要複製的連結串列為空
{
firstNode = NULL;
return; // end
}
// 連結串列非空
chainNode<T>* sourceNode = new_chain.firstNode;
firstNode = new chainNode<T>(sourceNode->element); // 結構體變數的初始化 chainNode結構體有兩個構造方法,這是其中的一個
// firstNode = new chainNode<T>((*sourceNode).element) //解引用
sourceNode = sourceNode->next; // 原來連結串列的指標
chainNode<T>* targetNode = firstNode; // 複製得到的連結串列的節點指標
while(sourceNode!=NULL)
{
targetNode->next = new chainNode<T>(sourceNode->element);
targetNode = targetNode->next;
sourceNode = sourceNode->next;
}
targetNode->next = NULL; // 連結串列結束
}
template<typename T>
chain<T>::~chain()
{
// 連結串列的解構函式, 刪除連結串列的所有節點
while(firstNode!=NULL)
{
chainNode<T>* nextNode = firstNode->next; // 儲存連結串列第二個節點的指標
delete firstNode; // 刪除釋放第一個節點的記憶體
firstNode = nextNode; // 將第二個節點變為第一個節點
}
}
template<typename T>
bool chain<T>::empty() const
{
return listSize==0;
}
template<typename T>
int chain<T>::size() const
{
return listSize;
}
template<typename T>
T& chain<T>::get(int index) const
{
/*
if(checkIndex(index))
{
chainNode<T>* currentNode = firstNode;
for(int i=0; i<index; i++)
{
currentNode = currentNode->next; //先找到index的前一個節點
}
return currentNode->element;
}
else
{
cout << "The index is invalid" << endl;
}
*/
chainNode<T>* currentNode = firstNode;
for(int i=0; i<index; i++)
{
currentNode = currentNode->next; //先找到index的前一個節點
}
return currentNode->element;
}
template<typename T>
int chain<T>::indexOf(T the_element) const
{
// 返回the_element首次出現的索引值
// fouzefanhui -1
chainNode<T>* currentNode = firstNode;
int index_cnt = 0;
while(currentNode!=NULL)
{
if(currentNode->element == the_element)
{
return index_cnt;
}
currentNode = currentNode->next;
index_cnt ++;
}
return -1;
}
template<typename T>
void chain<T>::insert(int index, T x)
{
// 在連結串列中插入元素
//chainNode<T>* currentNode = firstNode;
// 插入元素得考慮是否在表頭插入;
if(index==0) // 在表頭插入
{
firstNode = new chainNode<T>(x, firstNode); // 造結構體構造方法
//firstNode->next = currentNode;
}
else // 不是在表頭插入
{
chainNode<T>* currentNode = firstNode;
for(int i=0; i<index-1; i++)
{
currentNode = currentNode->next;
}
currentNode->next = new chainNode<T>(x, currentNode->next); // 插入元素到連結串列index位置
}
listSize++;
}
template<typename T>
void chain<T>::erase(int index)
{
// 1.檢查index的合法性
// 2.檢查index==0
if(index==0)
{
chainNode<T>* currentNode = firstNode;
firstNode = firstNode->next;
delete currentNode;
listSize--;
}
else
{
chainNode<T>* currentNode = firstNode;
// int node_cnt = 0;
for(int i=0; i<index-1; i++)
{
currentNode = currentNode->next;
}
// currentNode 指向第index-1個節點
chainNode<T>* tmp = currentNode->next; // tmp指向第index個節點
currentNode->next = tmp->next;
delete tmp;
listSize--;
}
}
template<typename T>
void chain<T>::output() const
{
chainNode<T>* currentNode = firstNode;
int node_cnt = 0;
while(currentNode!=NULL) //遍歷連結串列的所有元素
{
cout << currentNode->element << " ";
if((node_cnt+1)%10==0)
{
cout << endl;
}
currentNode = currentNode->next;
node_cnt++;
}
}
3。測試函式:
main.cpp
#include <iostream>
#include <string>
#include <time.h>
#include "E:\back_up\code\c_plus_code\digui\external_file\linearlist.h"
#include "E:\back_up\code\c_plus_code\digui\external_file\arraylist.h"
#include "E:\back_up\code\c_plus_code\digui\external_file\chain.h"
using namespace std;
// 實現友元函式
int main(int argc, char *argv[])
{
/*
arrayList<double> array(3);
for(int i=0; i<10; i++)
{
array.insert(i, i);
}
//array.insert(0, a);
//array.insert(1, b);
array.output();
array.insert(9, 9.9);
array.output();
*/
chain<int> chain_1;
for(int i=0; i<10; i++)
{
chain_1.insert(i, i*i);
}
cout << "The size of chain is " << chain_1.size() << endl;
chain_1.output();
// cout << "The size of chain is " << chain_1.get() << endl;
chain_1.insert(3, 520);
cout << "The size of chain is " << chain_1.size() << endl;
chain_1.output();
chain_1.erase(9);
cout << "The size of chain is " << chain_1.size() << endl;
chain_1.output();
cout << "The " << 3 << " element in chain is " << chain_1.get(3) << endl;
return 0;
}
測試結果:
------------------------------------------------------------------------------------------------
給類chain新增clea()功能:
template<typename T>
void chain<T>::clear()
{
while(firstNode!=NULL)
{
chainNode<T>* nextNode = firstNode->next;
delete firstNode;
firstNode = nextNode;
}
listSize = 0;
}
main.cpp
#include <iostream>
#include <string>
#include <time.h>
#include "E:\back_up\code\c_plus_code\digui\external_file\linearlist.h"
#include "E:\back_up\code\c_plus_code\digui\external_file\arraylist.h"
#include "E:\back_up\code\c_plus_code\digui\external_file\chain.h"
using namespace std;
// 實現友元函式
int main(int argc, char *argv[])
{
/*
arrayList<double> array(3);
for(int i=0; i<10; i++)
{
array.insert(i, i);
}
//array.insert(0, a);
//array.insert(1, b);
array.output();
array.insert(9, 9.9);
array.output();
*/
chain<int> chain_1;
for(int i=0; i<10; i++)
{
chain_1.insert(i, i*i);
}
cout << "The size of chain is " << chain_1.size() << endl;
chain_1.output();
chain_1.clear();
if(chain_1.empty())
{
cout << "The chain is empty" << endl;
}
else
{
cout << "The chain is not empty" << endl;
}
return 0;
}
測試結果:
效能比較:
1.記憶體比較:
在陣列描述的線性表中,陣列滿的時候,陣列的長度需要加倍,,當線性表的元素個數不及陣列長度的1/4時,陣列元素減半。
n個元素的線性表可以儲存在n~4n長度的陣列中。 假設每個元素需要s個位元組,則所佔的空間為ns~4ns.對於連結串列,n個元素分配n個節點,每個指標4位元組大小,則所佔的空間的小為n(s+4),因此在選擇線性表的描述方法時,空間上的差異不是決定因素。
2.時間比較
例如get(int index)操作,連結串列的時間複雜度是 ,陣列的時間複雜度是 ,其他操作的時間如下:
--------------------------------------------------f分割線--------------------------------------------------------
給連結串列新增一些新的方法
1.實現 setSize()方法: