1. 程式人生 > >資料結構實現 2.1:連結串列(C++版)

資料結構實現 2.1:連結串列(C++版)

1. 概念及基本框架

連結串列 是一種 線性結構 ,而且儲存上屬於 鏈式儲存(即記憶體的物理空間是不連續的),是線性表的一種。連結串列結構如下圖所示:
連結串列
下面以一個我實現的一個簡單的連結串列類來進一步理解連結串列。

template <class T>
class Node{
public:
	Node(T num = NULL, Node *next = NULL){
		m_data = num;
		this->next = next;
	}
public:
	T m_data;
	Node *next;
};

首先設計一個 結點類 ,這個結點類包含 資料指向下一個結點的指標

。結點類的建構函式可以直接對結點賦值,然後利用結點類物件來建立一個連結串列。連結串列類的設計如下:

template <class T>
class LinkedList{
public:
	LinkedList(){
		head.m_data = NULL;
		head.next = NULL;
		m_size = 0;
	}
	...
private:
	Node<T> head;
	int m_size;
};

這裡為了避免重複設計就可以相容更多資料型別,引入了 泛型 ,即 模板 的概念。(模板的關鍵字是 classtypename
這裡的 m_size

表示 連結串列長度 。為了保護資料,這個變數以及 結點資料 都設定為 private
與動態陣列不同,動態陣列的“動態”含義是可以自動擴容(縮容),但實質還是靜態的,而連結串列則實現了真正意義上的動態。因為只有需要一個結點,才會新增一個結點,不需要就會刪除。在這裡,為了下面程式程式碼編寫方便、統一,引入了 虛擬頭結點(也稱 哨兵結點 )的概念。這個結點本身不存放資料,使用者也不知道它的存在。
實現了前面的程式之後,接下來就是一個連結串列的增、刪、改、查以及一些其他基本操作,接下來利用程式碼去實現。

2. 基本操作程式實現

2.1 增加操作

template <class T>
class LinkedList{ public: ... //增加操作 void add(int index, T num); void addFirst(T num); void addLast(T num); ... };

首先,在類體內進行增加操作函式的原型說明。這裡包括三個函式:
add(新增到任意位置)
addFirst(新增到頭部)
addLast(新增到尾部)
然後分別去實現它們。

template <class T>
void LinkedList<T>::add(int index, T num){
	if (index < 0 || index > m_size){
		cout << "新增位置非法!" << endl;
		return;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	node->next = new Node<T>(num, node->next);
	m_size++;
}
template <class T>
void LinkedList<T>::addFirst(T num){
	add(0, num);
}
template <class T>
void LinkedList<T>::addLast(T num){
	add(m_size, num);
}

由於這些函式在類體外,所以每個函式頭部必須新增一行程式碼:

template <class T>

表示該函式使用模板,下面同理。
如果不使用虛擬頭結點,程式碼編寫就要區分第一個結點和其他結點,從這裡可以看出引入虛擬頭結點的好處,統一了程式碼編寫形式,下面的同理。

2.2 刪除操作

template <class T>
class LinkedList{
public:
	...
	//刪除操作
	T remove(int index);
	T removeFirst();
	T removeLast();
	void removeElement(T num);
	...
};

同理,在類體內進行刪除函式的原型說明。這裡包括四個函式:
remove(刪除任意位置元素):返回刪除元素的值。
removeFirst(刪除頭部元素):返回刪除元素的值。
removeLast(刪除尾部元素):返回刪除元素的值。
removeElement(刪除特定元素):這裡刪除的是第一個這樣的元素,如果想把這樣的元素都刪掉,可以寫一個新的函式來實現。
然後分別去實現它們。

template <class T>
T LinkedList<T>::remove(int index){
	if (index < 0 || index >= m_size){
		cout << "刪除位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	Node<T> *p = node->next;
	T res = p->m_data;
	node->next = p->next;
	delete p;
	m_size--;
	return res;
}
template <class T>
T LinkedList<T>::removeFirst(){
	return remove(0);
}
template <class T>
T LinkedList<T>::removeLast(){
	return remove(m_size - 1);
}
template <class T>
void LinkedList<T>::removeElement(T num){
	if (!head.next){
		return;
	}
	Node<T> *node = &head;
	Node<T> *p;
	while(node){
		p = node->next;
		if (p->m_data == num){
			node->next = p->next;
			delete p;
			m_size--;
			return;
		}
		node = p;
	}
}

這裡刪除操作的“刪除位置非法”後面返回的 NULL 也可以用 throw 拋異常來實現,這裡只是為了方便。

2.3 修改操作

template <class T>
class LinkedList{
public:
	...
	//修改操作
	void set(int index, T num);
	...
};

修改操作只有一個函式
set(修改指定位置的值)
同理,在類體內進行修改函式的原型說明,然後在類體外實現。

template <class T>
void LinkedList<T>::set(int index, T num){
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	node->m_data = num;
}

2.4 查詢操作

template <class T>
class LinkedList{
public:
	...
	//查詢操作
	T get(int index);
	T getFirst();
	T getLast();
	bool contains(T num);
	...
};

查詢函式有四個:
get(返回特定位置元素)
getFirst(返回第一個元素)
getLast(返回最後一個元素)
contains(返回是否包含特定元素)
這裡並沒有實現 find 函式,因為即使獲得了元素位置索引,也不能像陣列一樣方便的再次通過位置索引獲得資料,依然需要遍歷連結串列來獲得資料。
分別對它們進行實現。

template <class T>
T LinkedList<T>::get(int index){
	if (index < 0 || index >= m_size){
		cout << "訪問位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	return node->m_data;
}

template <class T>
T LinkedList<T>::getFirst(){
	return get(0);
}
template <class T>
T LinkedList<T>::getLast(){
	return get(m_size - 1);
}
template <class T>
bool LinkedList<T>::contains(T num){
	Node<T> *node = head.next;
	while(node){
		if (node->m_data == num){
			return true;
		}
		node = node->next;
	}
	return false;
}

同理,這裡 get 函式的“訪問位置非法”後面返回的 NULL 也可以用 throw 拋異常來實現,這裡只是為了方便。

2.5 其他操作

連結串列還有一些其他的操作,這些函式我在類體內進行了實現。
包括 連結串列長度 的查詢,還有 連結串列的列印 等操作。

template <class T>
class LinkedList{
public:
	...
	int size(){
		return m_size;
	}
	bool isEmpty(){
		return m_size == 0;
	}
	void print(){
		cout << "LinkedList: ";
		cout << "Size = " << m_size << endl;
		Node<T> *node = head.next;
		while(node){
			cout << node->m_data << "->";
			node = node->next;
		}
		cout << "NULL" << endl;
	}
	...
};

3. 演算法複雜度分析

3.1 增加操作

函式 最壞複雜度 平均複雜度
add O(n) O(n/2) = O(n)
addFirst O(1) O(1)
addLast O(n) O(n)

這裡的時間複雜度與陣列相反,原因在於,引起陣列時間複雜度增加的是元素的移動,而引起連結串列時間複雜度增加的是元素的遍歷。

3.2 刪除操作

函式 最壞複雜度 平均複雜度
remove O(n) O(n/2) = O(n)
removeFirst O(1) O(1)
removeLast O(n) O(n)

同理,刪除操作的時間複雜度與陣列也相反。

3.3 修改操作

函式 最壞複雜度 平均複雜度
set O(n) O(n/2) = O(n)

3.4 查詢操作

函式 最壞複雜度 平均複雜度
get O(n) O(n/2) = O(n)
getFirst O(1) O(1)
getLast O(n) O(n)
contains O(n) O(n/2) = O(n)

總體情況:

操作 時間複雜度
O(n)
O(n)
O(n)
O(n)

因為連結串列需要遍歷,所以操作的複雜度都是 O(n) 級別的,但是,如果只針對頭結點操作,那麼操作時間複雜度就會變成 O(1) 級別。

4. 完整程式碼

程式完整程式碼(這裡使用了標頭檔案的形式來實現類)如下:

#ifndef __LINKEDLIST_H__
#define __LINKEDLIST_H__

using namespace std;

template <class T>
class Node{
public:
	Node(T num = NULL, Node *next = NULL){
		m_data = num;
		this->next = next;
	}
public:
	T m_data;
	Node *next;
};
template <class T>
class LinkedList{
public:
	LinkedList(){
		head.m_data = NULL;
		head.next = NULL;
		m_size = 0;
	}
	int size(){
		return m_size;
	}
	bool isEmpty(){
		return m_size == 0;
	}
	void print(){
		cout << "LinkedList: ";
		cout << "Size = " << m_size << endl;
		Node<T> *node = head.next;
		while(node){
			cout << node->m_data << "->";
			node = node->next;
		}
		cout << "NULL" << endl;
	}
	//增加操作
	void add(int index, T num);
	void addFirst(T num);
	void addLast(T num);
	//刪除操作
	T remove(int index);
	T removeFirst();
	T removeLast();
	void removeElement(T num);
	//修改操作
	void set(int index, T num);
	//查詢操作
	T get(int index);
	T getFirst();
	T getLast();
	bool contains(T num);
private:
	Node<T> head;
	int m_size;
};

template <class T>
void LinkedList<T>::add(int index, T num){
	if (index < 0 || index > m_size){
		cout << "新增位置非法!" << endl;
		return;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	node->next = new Node<T>(num, node->next);
	m_size++;
}
template <class T>
void LinkedList<T>::addFirst(T num){
	add(0, num);
}
template <class T>
void LinkedList<T>::addLast(T num){
	add(m_size, num);
}

template <class T>
T LinkedList<T>::remove(int index){
	if (index < 0 || index >= m_size){
		cout << "刪除位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = &head;
	for (int i = 0; i < index; ++i, node = node->next);
	Node<T> *p = node->next;
	T res = p->m_data;
	node->next = p->next;
	delete p;
	m_size--;
	return res;
}
template <class T>
T LinkedList<T>::removeFirst(){
	return remove(0);
}
template <class T>
T LinkedList<T>::removeLast(){
	return remove(m_size - 1);
}
template <class T>
void LinkedList<T>::removeElement(T num){
	if (!head.next){
		return;
	}
	Node<T> *node = &head;
	Node<T> *p;
	while(node){
		p = node->next;
		if (p->m_data == num){
			node->next = p->next;
			delete p;
			m_size--;
			return;
		}
		node = p;
	}
}

template <class T>
void LinkedList<T>::set(int index, T num){
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	node->m_data = num;
}

template <class T>
T LinkedList<T>::get(int index){
	if (index < 0 || index >= m_size){
		cout << "訪問位置非法!" << endl;
		return NULL;
	}
	Node<T> *node = head.next;
	for (int i = 0; i < index; ++i, node = node->next);
	return node->m_data;
}

template <class T>
T LinkedList<T>::getFirst(){
	return get(0);
}
template <class T>
T LinkedList<T>::getLast(){
	return get(m_size - 1);
}
template <class T>
bool LinkedList<T>::contains(T num){
	Node<T> *node = head.next;
	while(node){
		if (node->m_data == num){
			return true;
		}
		node = node->next;
	}
	return false;
}

#endif