C++ List的泛型实现

List的泛型实现

一、介绍链表

​ 之前的文章中讲过了动态数组的实现,但是分析得知动态数组无论如何巧妙,增删复杂度最差都是 o(n) ,如果我们有这样一个使用场景,对于一个停车场,经常有人进出,所以需要频繁修改数据。如果停车场确实数量少,使用数组还可以,但是数量多了,肯定不可以使用数组了,因为比较慢。(ps:当然有更好的选择,后续介绍AVL树和RB树时再介绍)

链表的结构

​ 那么链表是一个什么样的结构呢?代码如下

/*可以用泛型实现,也可以特化
一个节点中包含:
	1、一个指向下一个节点的指针
	2、一个数据元素
*/
template<typename T>
class listnode
{
    listnode* next;
    //T data;
    T* data;
};

​ 可以参考下面的图片,链表基础模型就是这样的结构

在这里插入图片描述

链表的特点

​ 链表使用指针的特点让它可以很快的调整结构,因为只需要改变指针指向就可以了。

//如何删除 p 的下一个节点
void earse(listnode* p)
{
    //如果是动态内存,需要释放
    //auto tmp = p->next;
	p->next = p->next->next;	//让p节点指向p节点的下一个的下一个节点
    //delete tmp;
}

//如何添加数据元素
void insert(listnode* p,listnode* newnode)
{
	newnode->next = p->next;	//让新节点,指向p的下一个节点
    p->next = newnode;			//让p指向新节点
}

链表的缺点

​ 优点明显,当然缺点也很明显,如果我们想在一个链表中找的某一个位置的元素怎么办?

listnode& find(listnode* p , size_t n)
{
    //不负责溢出检测,秉承着溢出也是为了客户体验,且让客户享受到原汁原味的溢出体验
	for(int i=0;i<n;++i)
      	p = p->next; 
    return p;
}

​ 可以看到找到一个节点,需要进行n次遍历 ,时间复杂度为 o(n)。相较于vector支持随机访问,非常慢。所以我们应该选择正确的结构。我们可以对比一下两种基础结构的区别,打好基础是最重要的,如果基础不牢,后面学习一些复杂结构就会非常困难。

vector list
添加 o(n),遍历 o(1),结构调整
删除 o(n),遍历 o(1),结构调整
修改 o(1),支持随机访问 o(n),遍历
访问 o(1),支持随机访问 o(n),遍历

二、泛型实现的list

结点

​ 这个链表是一个双向链表,其实这个我还想着作为一个轮子用,但是确实写的时候遇到很多问题,比如如何支持类对象,对于基础类型如何做优化。因为支持类对象就设计构造析构,或许出现用户设计的类不完善的情况。这些情况可能在后续有优化。

​ 因为调整结构非常频繁,所以直接写个可以复用的接口

template<typename T>
struct ListNode
{
	ListNode(T data = 0, ListNode* p = nullptr, ListNode* n = nullptr) : _data(data), _prev(p), _next(n) {};	//构造函数
	ListNode* _prev;		//前驱
	ListNode* _next;		//后继
	T _data;				//数据成员

	//修改前驱为新前驱,自动调整节点关系(也就是说,对于前驱后继都不匹配的,都可以指定当前位置正确的前驱后继关系)
	ListNode* changePrev(ListNode* newPrev)
	{
		//newPrev作为新前驱
		newPrev->next = this;
		newPrev->prev = this->prev;
		this->prev->next = newPrev;
		this->prev = newPrev;
		return newPrev;
	}
	
	//修改后继为新后继,自动调整节点关系
	ListNode* changeNext(ListNode* newNext)
	{
		newNext->_next = this->_next;
		newNext->_prev = this;
		this->_next->_prev = newNext;
		this->_next = newNext;
		return newNext;
	}
};

链表

template<typename T>
class List
{
public:
	List() { init(); }					//默认构造
	List(const List&);		//拷贝构造
	List<T>& operator=(const List&);		//拷贝构造运算符
	~List() { clear(); };

public:
	bool empty() { return !_size; }						//判空
	size_t size() { return _size; }						//大小

	virtual void init();		//初始化基础资源
	virtual void clear();		//释放全部动态资源,回归初始状态

	//遍历器
	void traverse() {
		ListNodePtr<T> tmp = _head->_next;
		while (tmp->_next != nullptr) {
			func(*tmp);
			tmp = tmp->_next;
		}
	};							//传入遍历器进行遍历
	//增删改查
	virtual ListNodePtr<T> insert(size_t index,const T&& data);			//根据指定下标,在节点元素后,插入该目标元素
	virtual ListNodePtr<T> operator[](size_t index);				//访问运算符
	virtual void earse(size_t index) { earse(index, index); };		//特化
	virtual void earse(size_t start,size_t end);					//泛化:删除元素
	virtual void setFunction(void p(ListNode<T>)) { func = p; }		//设置遍历器

private:
	//遍历器
	std::function<void(ListNode<T>)> func;
	/*	类似迭代器操作,方便标识边界	*/
	ListNodePtr<T> _head;		//头哨兵
	ListNodePtr<T> _tail;		//尾哨兵
	/*	维护一个size是有意义的,大多数情况下可以减少遍历次数	*/
	size_t _size;

};

​ 只介绍重要接口

  1. ​ 遍历器:可以用function或者函数指针。初始化遍历器,然后调用traverse,就可以遍历每个元素。STL中有算法库,所以STL很方便;

  2. ​ 插入操作:指定位置后面插入元素;

    template<typename T>
    ListNodePtr<T> List<T>::insert(size_t index,const T&& data)
    {
    	++_size;		//修改元素数量
    	return this->operator[](index-1)->changeNext(new ListNode<T>(data));	//这里下标-1传入和下标运算符策略有关。	
    }
    
    
  3. ​ 删除:支持范围删除。支持删除元素自动释放;

    template<typename T>
    void List<T>::earse(size_t start, size_t end)
    {
    	ListNodePtr<T> p = this->operator[](start)->_prev;
    	ListNodePtr<T> tmp;
    	for (int i = 0; i < (end - start)+1; ++i)
    	{
    		tmp = p->_next;
    		tmp->_next->_prev = p;
    		p->_next = tmp->_next;
    		delete tmp;
    		p = p->_next;
    	}
    	_size -= end-start+1;
    }
    
  4. ​ 重载了下标运算符,支持 o(n) 的下标访问。

    /*	复杂度分析 :		
    因为是遍历,故时间复杂度为 o(n);
    使用一个辅助空间,空间复杂度为 o(1)
    */
    template<typename T>
    ListNodePtr<T> List<T>::operator[](size_t index)
    {
    	ListNodePtr<T> tmp = _head;
    	for (int i = 0; i < index+1; ++i)
    	{
    		tmp = tmp->_next;
    	}
    
    	return tmp;
    }
    

​ 学习中造的轮子,以后有机会还会继续完善,目前属于是能使用。可以直接继承并直接重写,后续会介绍我在httpserver中使用这个轮子做的定时器队列。后续想要提升轮子的泛型能力,就需要深入模板技术了。

三、github:

​ 如果有后续,我会持续更新我的轮子。

}

return tmp;

}




​	学习中造的轮子,以后有机会还会继续完善,目前属于是能使用。可以直接继承并直接重写,后续会介绍我在httpserver中使用这个轮子做的定时器队列。后续想要提升轮子的泛型能力,就需要深入模板技术了。

### 三、github:[](https://github.com/yqm-307/yqm_data_structure)

​	如果有后续,我会持续更新我的轮子。

​	博主是正在学习中的大三学生。C++内卷群:672372860

Guess you like

Origin blog.csdn.net/green_healthy/article/details/121332098