C++中的鏈表


何謂鏈表,為何使用鏈表的數據結構

鏈表是一種線性表,也就是說,它還是有順序的,比如下標1, 2, 3...通常情況下,提起線表自然想到的存儲方式就是連續的一片內存,比如C++中的數組或者STL的vector,這種存儲方式便於連續讀取和存儲,訪問也很方便,只要知道第一個元素的位置,再走n步(步長為數據長度),就是第n+1個元素的位置。對於鏈表,其存儲位置並不是連續的。所以為了把隨機位置的數據連起來,鏈表需要存儲數據,還要存儲數據之間是怎么連在一起的,也就是相鄰元素的地址信息。

可見鏈表的存儲是比較麻煩的,同時,鏈表的與遍歷有關的運行往往會很慢。設想,如果想要獲取第5000個元素,數組只要在第一個位置地址基礎上延后5000;而對於鏈表,你可能需要從第一個位置一直找下去,直到走5000步。但是鏈表在增刪元素上是靈活的,只要隨機划一片內存,加一個元素,然后后改變相鄰元素的指針關系就可以了,增刪元素的耗時都是一個常數;相形之下,數組要笨重地多,你要把數組從中間剖開,后續的元素都要受到影響,需要復制重建。

鏈表的實現

鏈表並不是一個全新的概念,事實上,在STL標准庫里面就有list鏈表容器了。但是為了便於理解,自己來親手寫一個鏈表的結構也很有意思。

template <class T>
// 鏈表節點結構體
struct listNode {
	listNode(const T& t) { 
		data = t;
		previous = NULL;
		next = NULL;
	}
	T data;
	listNode<T>* previous;
	listNode<T>* next;
};

如上,即為一個雙向鏈表的一個節點的內容,包括數據,向前指針和向后指針。值得注意的是,鏈表和鏈表節點是不同結構,這里我們定義的並不是鏈表,而是描述一個鏈表節點的結構體。而對於鏈表類,我們有如下的類的定義。

template <class T>
class List {
public:
	List();
	List(const List& L);
	~List();
	void print();
	void push_back(T t);
	void insert(T t, int position);
	void delete(int position);

protected:
	listNode<T>* firstNode;
	listNode<T>* lastNode;
	int listSize;
};

這個簡單的鏈表的成員包括第一個節點位置firstNode,最后一個節點位置lastNode, 以及鏈表總長度listSize. 同時該鏈表還包括最基本的成員函數,比如構造,復制析構等。還有將所有鏈表內容打印的print函數,在尾部加入元素的push_back函數,隨機位置增刪的insert以及delete函數。

現在我們就來看一下這幾個是怎么實現的吧!

//構造函數
template <class T>
List<T>::List() {
	firstNode = NULL;
	lastNode = NULL;
	listSize = 0;
}
//復制構造函數
template <class T>
List<T>::List(const List<T>& L) {
	listNode<T>* tempNode1;
	listNode<T>* sourceNode;
	listNode<T>* duplicateNode;

	//如果為空
	if (L.listSize == 0) {
		firstNode = NULL;
		lastNode = NULL;
		listSize = 0;
		return;
	}
	//如果非空
	firstNode = new listNode<T>(L.firstNode->data);
	// sourceNode復制來源
	sourceNode = L.firstNode;
	// duplicateNode新建節點
	duplicateNode = firstNode;
	while (sourceNode->next != NULL) {
		sourceNode = sourceNode->next;
		duplicateNode->next = new listNode<T>(sourceNode->data);
		tempNode1 = duplicateNode;
		duplicateNode = duplicateNode->next;
		duplicateNode->previous = tempNode1;
	}
	lastNode = duplicateNode;
	listSize = L.listSize;
}
// 析構函數
template <class T>
List<T>::~List() {
	// 從頭到尾逐步刪除節點
	while (firstNode != NULL) {
		listNode<T>* tempNode = firstNode->next;
		delete firstNode;
		firstNode = tempNode;
	}

}
// push_back()函數, 在尾部加一個節點
template <class T>
void List<T>::push_back(T t) {
	listNode<T>* newNode;
	newNode = new listNode<T>(t);
	if (listSize == 0) {
		lastNode = firstNode = newNode;
	}
	else {
		lastNode->next = newNode;
		lastNode = newNode;
	}
	listSize++;
}
// insert函數, 在鏈表中加一個節點
template <class T>
void List<T>::insert(T t, int position) {
	listNode<T>* newNode;
	listNode<T>* tempNode;
	newNode = new listNode<T>(t);
	if (position == 0) {
		firstNode->previous = newNode;
		newNode->next = firstNode;
		firstNode = newNode;
		listSize++;
		return;
	}
	else if (position == listSize) {
		lastNode->next = newNode;
		newNode->previous = lastNode;
		lastNode = newNode;
		listSize++;
		return;
	}

	if (position > listSize / 2) {
		tempNode = lastNode;
		for (int i = 0; i < listSize - position; i++) {
			tempNode = tempNode->previous;
		}
	}
	else {
		tempNode = firstNode;
		for (int i = 0; i < position - 1; i++) {
			tempNode = tempNode->next;
		}
	}
	listSize++;
	newNode->next = tempNode->next;
	tempNode->next->previous = newNode;
	newNode->previous = tempNode;
	tempNode->next = newNode;
	return;
}

// delete 函數
template <class T>
void List<T>::del(int position) {
	listNode<T>* tempNode;
	if (position == 0) {
		tempNode = firstNode->next;
		tempNode->previous = NULL;
		delete firstNode;
		firstNode = tempNode;
		listSize--;
		return;
	}
	else if (position == listSize - 1) {
		tempNode = lastNode->previous;
		tempNode->next = NULL;
		delete lastNode;
		lastNode = tempNode;
		listSize--;
		return;
	}
	// 依據位置靠近頭部還是尾部選擇
	if (position < listSize / 2) {
		tempNode = firstNode;
		for (int i = 0; i < position - 1; i++) {
			tempNode = tempNode->next;
		}
	}
	else {
		tempNode = lastNode;
		for (int i = 0; i < listSize - position - 1; i++) {
			tempNode = tempNode->previous;
		}
	}
	listSize--;
	tempNode->previous->next = tempNode->next;
	tempNode->next->previous = tempNode->previous;
	delete tempNode;
}
// print 函數, 打印鏈表的全部內容
template <class T>
void List<T>::print() {
	listNode<T>* tempNode;
	tempNode = firstNode;
	for (int i = 0; i < listSize; i++) {
		std::cout << std::to_string(tempNode->data) << std::endl;
		tempNode = tempNode->next;
	}
	return;
}

鏈表的測試

對上述構建的鏈表類進行了簡單測試, 基本實現了上述目標功能. 現在回看,上述鏈表實現存在着一些不足。比如頭結點,加入頭結點使得代碼更加簡潔, 就不需要再判斷鏈表是否為空或者特殊操作位置是否在鏈表的頭或者尾了。

新手指南

對於新手而言, 通過C++實現數據結構里面的鏈表,最難理解的可能就是指針, 而不能理解指針就無法理解鏈表.

我們知道,程序運行過程中,數據存儲在內存中,而指針就是這些內存的門牌號. 比如上述實現方法里面, listNode * 就是指針,指向listNode類型的變量。指針的復制,代表復制了這個門牌號,按着門牌號找,找到的還是同一間房,同樣的數據。改變了指針,也即改變了門牌號,可能找不到房間,但是房間內存儲的數據還在原來的位置。

上面程序中有指向結構體的指針,通過該指針能夠訪問結構體的成員,但是不能用"a.b", 而是使用了"a->b". 二者的用法是非常類似的.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM