数据结构之双链表——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;
}

好了,双链表的学习就到这了,后续会学习其他数据结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值