1. 程式人生 > >資料結構與演算法筆記(三) 線性表(鏈式描述) 連結串列

資料結構與演算法筆記(三) 線性表(鏈式描述) 連結串列

在鏈式描述中,線性表元素的位置在記憶體中是隨機的,每個元素都有一個明確的指標指向線性表的下一個元素的位置。

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()方法: