數據結構之雙鏈表——c++模板元編程

想想自己好像並沒有真正實現過數據結構裏面的各類結構和算法,便打算動手用c++實現數據結構。這一篇是實現雙鏈表,雙鏈表較之於單鏈表,前者可以雙向連接,既可以向前,也可以向後;而單鏈表的方向是單一的、固定的。好的,話不多說,直接上乾貨吧。


 首先,聲明雙鏈表節點 DNode,每個節點都有一個字段表示當前節點的值、有一個前驅字段來指向上一個節點、有一個後繼字段指向下一個節點。默認構造和拷貝構造的區別是,如果初始化 DNode 時,如果沒有指定value、prev、next 的話就使用默認構造函數 DNode();如果初始化時指定了 value 、prev、next 的值,就會使用拷貝構造函數DNode(T t, DNode * prev, DNode * next)。

//聲明雙鏈表節點的模板類結構體
template<class T>
struct DNode
{
public:
	T value;
	DNode * prev;
	DNode * next;
public:
	//默認構造函數
	DNode(){}
	//拷貝構造函數
	DNode(T t, DNode * prev, DNode * next){
		this->value = t;
		this->prev = prev;
		this->next = next;
	}
};

 template<class T>是 C++ 模板編程的標識,T表示數據類型,進行實例化時可以自己定義爲int,float,char等數據類型,也可以是結構體等自定義數據類型。

模板編程,顧名思義,是一個模板,可以給多個類或結構體套用的模板。我們在使用函數或類或結構體是時,都必須知名數據類型是什麼,比如void print(int a,int b)這個函數只能傳入int數據類型的參數;而void print(T a,T b)可以是任意類型。

定義好雙鏈表節點結構後,繼續定義雙鏈表結構

//聲明雙鏈表的模板類
template<class T>
class DoubleLink{
public:
	DoubleLink();
	~DoubleLink();
	int size();	
	int is_empty();

	T get(int index);
	T get_first();
	T get_last();

	int insert(int index, T t);
	int insert_first(T t);
	int append_last(T t);

	int del(int index);
	int delete_first();
	int delete_last();

private:
	int count;
	DNode<T> * phead;
	DNode<T> * get_node(int index);
};

雙鏈表時由每一個一開始定義的 DNode 節點連接起來的,count 表示雙鏈表中 DNode 節點的數量,phead表示表頭結點,get_node(int index)函數用來獲取 index 位置的節點,返回的是 DNode * 類型。

 

  • DoubleLink():構造函數,對DoubleLink()的定義如下:
template<class T>
DoubleLink<T>::DoubleLink():count(0){	
	//創建表頭
	phead = new DNode<T>();
	phead->prev = phead->next = phead;
}

DoubleLink<T>::DOubleLink():count(0):使用了Lambda表達式來初始化 count,賦值爲0。

調用拷貝構造函數,即實例化對象時,會自動創建一個表頭節點phead,phead的前驅和後繼都是它本身。

 

  • ~DoubleLink():析構函數,用來銷燬對象
template<class T>
DoubleLink<T>::~DoubleLink(){
	//刪除所有節點
	DNode<T> * ptmp;
	DNode<T> * pnode = phead->next;
	while (pnode != phead){
		ptmp = pnode;
		pnode = pnode->next;
		delete ptmp;
	}
	delete phead;
	phead = NULL;
}

析構函數的定義與構造函數相反,用來銷燬對象,銷燬每一個節點。

 

  • int size():獲取雙鏈表中節點的數量
//獲取雙鏈表節點數
template<class T>
int DoubleLink<T>::size(){
	return count;
}
  • int is_empty():判斷是否爲空函數
//判斷雙鏈表是否爲空
template<class T>
int DoubleLink<T>::is_empty(){
	return count == 0;
}

如果count不爲0,則返回0,否則返回1。

 

  • T get_node(int index):獲取 index 位置的節點,返回的是節點類型
//獲取index位置的節點
template<class T>
DNode<T>* DoubleLink<T>::get_node(int index){
	if (index < 0 || index >= count){
		std::cout << "get node failed! the index in out of bound!" << std::endl;
		return NULL;
	}
	//正向查找
	if (index <= count / 2){
		int i = 0;
		DNode<T> * pindex = phead->next;
		for (i; i < index; i++){
			pindex = pindex->next;
		}
		return pindex;
	}
	//反向查找
	DNode<T> *rpindex = phead->prev;
	for (int j = 0; j < count - index - 1; j++){
		rpindex = rpindex->prev;
	}
	return rpindex;
}

首先判斷 index 是否爲有效位置,如果小於0或者超過雙鏈表實際長度,輸出錯誤信息。

若 Index 爲有效位置,如果Index在鏈表的前半部分也就是在鏈表中間節點位置之前,就從鏈表頭部開始遍歷,一直到index位置;

如果index 在鏈表後半部分,也就是在鏈表中間節點位置之後,就從鏈表尾部反向遍歷,一直到index位置。

之所以要先判斷index在中間節點位置之前還時之後,主要是爲能夠減少遍歷節點的數量,減少開支。

 

T get(int index):獲取index位置節點的值:

//獲取index位置的節點的值
template<class T>
T DoubleLink<T>::get(int index){
	return get_node(index)->value;
}

 

T get_first():獲取鏈表第一個結點的值(不是頭節點)。

//獲取第一個節點的值
template<class T>
T DoubleLink<T>::get_first(){
	return get_node(0)->value;
}

 

T get_last():獲取鏈表最後一個節點的值。直接調用get_node(int index)函數獲取最後一個節點的值

//獲取最後一個節點的值
template<class T>
T DoubleLink<T>::get_last(){
	return get_node(count - 1)->value;
}

 

int insert(int index,T t):將 t 插入到鏈表index的位置上。

//將節點插入到第index位置之前
template<class T>
int DoubleLink<T>::insert(int index, T t){
	if (index == 0){
		return insert_first(t);
	}
	DNode<T>*pindex = get_node(index);
	DNode<T>*pnode = new DNode<T>{ t, pindex->prev, pindex };

	pindex->prev->next = pnode;
	pindex->prev = pnode;
	count++;
	return 0;
}

 

int insert_first(T t):將 t 插入到鏈表的一個節點位置。

//將節點插入到第一個節點處
template<class T>
int DoubleLink<T>::insert_first(T t){	
	DNode<T> *pnode = new DNode<T>{ t, phead, phead->next };
	phead->next->prev = pnode;
	phead->next = pnode;
	count++;
	return 0;
}

 

int append_last(T t):將 t 加到鏈表尾部。注意鏈表連接的變化。

//將節點插入到最後一個節點處
template<class T>
int DoubleLink<T>::append_last(T t){
	DNode<T>*pnode = new DNode<T>{ t, phead->prev, phead };
	phead->prev->next = pnode;
	phead->prev = pnode;
	count++;
	return 0;
}

int del(int index):刪除 index位置的節點:

//刪除index位置的節點
template<class T>
int DoubleLink<T>::del(int index){
	DNode<T> * pindex = get_node(index);
	pindex->next->prev = pindex->prev;
	pindex->prev->next = pindex->next;
	delete pindex;
	count--;
	return 0;
}

每次插入或者刪除鏈表節點,count 都要加1或者減1.

 

int delete_first():刪除第一個節點。

//刪除第一個節點
template<class T>
int DoubleLink<T>::delete_first(){
	return del(0);
}

 

int delete_last():刪除最後一個節點。

//刪除最後一個節點
template<class T>
int DoubleLink<T>::delete_last(){
	return del(count - 1);
}

 

鏈表的聲明和定義要在同一個文件裏,聲明和定義分文件的話,會報錯的,因爲模板編程不支持這樣,具體可以參考

爲什麼C++編譯器不能支持對模板的分離式編譯

 

 

下面是頭文件的全部代碼:

// DoubleLink.h

#include <iostream>

//聲明雙鏈表節點的模板類結構體
template<class T>
struct DNode
{
public:
	T value;
	DNode * prev;
	DNode * next;
public:
	//默認構造函數
	DNode(){}
	//拷貝構造函數
	DNode(T t, DNode * prev, DNode * next){
		this->value = t;
		this->prev = prev;
		this->next = next;
	}
};

//聲明雙鏈表的模板類
template<class T>
class DoubleLink{
public:
	DoubleLink();
	~DoubleLink();
	int size();	
	int is_empty();

	T get(int index);
	T get_first();
	T get_last();

	int insert(int index, T t);
	int insert_first(T t);
	int append_last(T t);

	int del(int index);
	int delete_first();
	int delete_last();

private:
	int count;
	DNode<T> * phead;
	DNode<T> * get_node(int index);
};


//定義雙鏈表的模板類
template<class T>
DoubleLink<T>::DoubleLink():count(0){	
	//創建表頭
	phead = new DNode<T>();
	phead->prev = phead->next = phead;
}
template<class T>
DoubleLink<T>::~DoubleLink(){
	//刪除所有節點
	DNode<T> * ptmp;
	DNode<T> * pnode = phead->next;
	while (pnode != phead){
		ptmp = pnode;
		pnode = pnode->next;
		delete ptmp;
	}
	delete phead;
	phead = NULL;
}
 //獲取雙鏈表節點數
template<class T>
int DoubleLink<T>::size(){
	return count;
}

//判斷雙鏈表是否爲空
template<class T>
int DoubleLink<T>::is_empty(){
	return count == 0;
}

//獲取index位置的節點
template<class T>
DNode<T>* DoubleLink<T>::get_node(int index){
	if (index < 0 || index >= count){
		std::cout << "get node failed! the index in out of bound!" << std::endl;
		return NULL;
	}
	//正向查找
	if (index <= count / 2){
		int i = 0;
		DNode<T> * pindex = phead->next;
		for (i; i < index; i++){
			pindex = pindex->next;
		}
		return pindex;
	}
	//反向查找
	DNode<T> *rpindex = phead->prev;
	for (int j = 0; j < count - index - 1; j++){
		rpindex = rpindex->prev;
	}
	return rpindex;
}

//獲取index位置的節點的值
template<class T>
T DoubleLink<T>::get(int index){
	return get_node(index)->value;
}

//獲取第一個節點的值
template<class T>
T DoubleLink<T>::get_first(){
	return get_node(0)->value;
}

//獲取最後一個節點的值
template<class T>
T DoubleLink<T>::get_last(){
	return get_node(count - 1)->value;
}

//將節點插入到第index位置之前
template<class T>
int DoubleLink<T>::insert(int index, T t){
	if (index == 0){
		return insert_first(t);
	}
	DNode<T>*pindex = get_node(index);
	DNode<T>*pnode = new DNode<T>{ t, pindex->prev, pindex };

	pindex->prev->next = pnode;
	pindex->prev = pnode;
	count++;
	return 0;
}

//將節點插入到第一個節點處
template<class T>
int DoubleLink<T>::insert_first(T t){	
	DNode<T> *pnode = new DNode<T>{ t, phead, phead->next };
	phead->next->prev = pnode;
	phead->next = pnode;
	count++;
	return 0;
}

//將節點插入到最後一個節點處
template<class T>
int DoubleLink<T>::append_last(T t){
	DNode<T>*pnode = new DNode<T>{ t, phead->prev, phead };
	phead->prev->next = pnode;
	phead->prev = pnode;
	count++;
	return 0;
}


//刪除index位置的節點
template<class T>
int DoubleLink<T>::del(int index){
	DNode<T> * pindex = get_node(index);
	pindex->next->prev = pindex->prev;
	pindex->prev->next = pindex->next;
	delete pindex;
	count--;
	return 0;
}

//刪除第一個節點
template<class T>
int DoubleLink<T>::delete_first(){
	return del(0);
}

//刪除最後一個節點
template<class T>
int DoubleLink<T>::delete_last(){
	return del(count - 1);
}

 

下面做個測試:

#include "DoubleLink.h"
#include <string>
void int_test(){
	DoubleLink<int> *pdlink = new DoubleLink<int>();
	pdlink->insert_first(20);
	pdlink->append_last(10);
	pdlink->insert(1, 90);

	std::cout << "雙鏈表第一個節點爲:" << pdlink->get_first() << std::endl;
	std::cout << "雙鏈表最後一個節點爲:" << pdlink->get_last() << std::endl;
	
	pdlink->delete_first();
	pdlink->delete_last();
	pdlink->del(0);
	if (pdlink->is_empty()){
		std::cout << "DoubleLink is empty" << std::endl;
	}
	else{
		std::cout << "DoubleLink is not empty" << std::endl;
	}
	for (int i = 0; i < pdlink->size(); i++){
		std::cout << pdlink->get(i) << std::endl;
	}
}


void string_test(){
	DoubleLink<std::string> *pslink = new DoubleLink<std::string>();
	//pslink->append_last("Hello world! key!");
	pslink->insert_first("Hello world! key!");
	pslink->insert(0, "My name is zky");
	pslink->append_last("呵呵");
	for (int i = 0; i < pslink->size(); i++){
		std::cout << pslink->get(i) << std::endl;
	}
}


void object_test(){
	struct stu{
		int id;
		char name[20];
	};

	static struct stu arr_stu[] = {
		{0,"小強"},
		{ 1, "小李" },
		{2,"小剛"}
	};

	DoubleLink<stu> *polink = new DoubleLink<stu>();
	polink->insert(0, arr_stu[0]);
	polink->insert_first(arr_stu[2]);

	for (int i = 0; i < polink->size(); i++){
		std::cout << polink->get(i).id << "	" << polink->get(i).name << std::endl;
	}

}


int main(){
	//int_test();
	//string_test();
	object_test();
	std::cin.get();
	return 0;
}

好了,雙鏈表的學習就到這了,後續會學習其他數據結構。

發佈了4 篇原創文章 · 獲贊 4 · 訪問量 8676
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章