List数据结构的实现

Posted Sunnix

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了List数据结构的实现相关的知识,希望对你有一定的参考价值。

这几天在学习数据结构(C++描述)的知识,为了把基础打的牢固一些,想想把一种数据结构自己动手写代码实现以下,感觉是个不错的方法。那样就不会只停留在会用的程度。首先整理一下要实现的山寨list要提供的接口。

push_back() 末端插入元素

back() 返回末端元素的值

push_front() 顶端插入元素

front() 返回顶端元素的值

find() 查找特定元素的地址

insert() 特定的位置插入元素

size() 返回list当中元素的个数

begin() 返回一个游标,指向顶端元素

end() 返回一个游标,指向末端元素

实现山寨list,底层的数据结构肯定选双向链表,在任何位置插入元素的复杂度都为o(1),双向链表是由一个个节点链接起来的,节点包括三个成员:指向后继节点的指针、指向前驱节点的指针、当前节点的节点值。先实现这个类:

template <typename T>
class dnode
public:
	T nodeValue;//节点值
	dnode<T> *next;//后继节点
	dnode<T> *prev;//前驱节点
	dnode(const T& value=T())
		nodeValue=value;
		next=NULL;
		prev=NULL;
	;//构造函数,初始化节点值、前驱和后继节点
;

然后将一个个节点串接起来就形成了双向链表,将双向链表的首位相接,就得到一个list,就像用一个个珠子串起来串成项链一样。实现miniList这个类:

#include "dnode.h"
#include <string>
using namespace std;
template <typename T>
class miniList
public:
	miniList();/*三个版本的构造函数
	/第一个生成一个空list,第二个初始化list为n个item值的list
	第三个用[first,last]初始化list*/
	miniList(int n,const T& item=T());
	miniList(T *first,T *last);
	~miniList();//析构函数
	/*游标的声明*/
	class iterator
		public:
			iterator();//构造函数,生成一个空游标
			iterator(dnode<T> *p);//构造函数,将dnode类型的指针转换为游标
			/*重载"=="和"!="运算符,比较两个游标所指的值*/
			bool operator ==(const iterator& rhs) const;
			bool operator !=(const iterator& rhs) const;
			/*重载"*"运算符,返回游标所指的值*/
			T& operator *();
			/*重载"++"和"--"运算符,因为这两个运算符有前缀和后缀的形式,所以后缀运算符多加一个
			int参数,好让编译器判断用户使用的是哪种形式。游标的本质其实还是指针,所以++或者--
			只是让私有成员node成为自身的后继或者前驱*/
			iterator& operator ++()
				node=node->next;
				return *this;
			;
			iterator& operator ++(int)
				iterator temp=*this;
				node=node->next;
				return temp;
			;
			iterator& operator --()
				node=node->prev;
				return *this;
			;
			iterator& operator --(int)
				iterator temp=*this;
				node=node->prev;
				return temp;
			;
		private:
			//游标的本质还是指针,指针的类型就是实现list的基石--dnode
			dnode<T> *node;
	;	
	void push_back(const T& item);//在list末端插入值
	T& back();//返回list末端的值
	void push_front(const T& item);//在list顶端插入值
	T& front();//返回list顶端的值
	/*在固定的位置插入值,返回新的新节点的地址,因为dnode指针是对外不可见的,所以要和find函数搭配使用*/
	dnode<T>* insert(const T& item,dnode<T> *curr);
	dnode<T>* find(const T& item);//查找一个元素的位置,返回这个元素的指针
	int size();//返回当前list的元素个数
	typename miniList<T>::iterator begin();//返回一个游标对象,指向list的顶端值
	typename miniList<T>::iterator end();//返回一个游标对象,指向list的末端值
private:
	dnode<T> *header;//头结点
	int listSize;//元素个数
;
template <typename T>//构造函数,生成一个空list,元素个数为0
miniList<T>::miniList()
	header=new dnode<T>;
	listSize=0;

template <typename T>//构造函数,生个n个item元素的list
miniList<T>::miniList(int n,const T& item=T())
	header=new dnode<T>(item);
	dnode<T> *temp;//存储n个item值的动态节点
	dnode<T> *curr;//当前节点
	curr=header;
	unsigned int i;
	for(i=0;i<n;i++)
		temp=new dnode<T>(item);//生成一个节点
		curr->next=temp;//当前节点的后继节点就是这个新生成的节点
		temp->prev=curr;//新节点的前驱节点为当前节点
		curr=temp;//更新当前节点,继续生成下一个节点
	
	curr->next=header;//结束之后将当前节点的后继节点更新为头结点
	header->prev=curr;//头结点的前驱节点为当前这个节点,这样整个list就串起来
	listSize=n;//元素个数更新为n

template <typename T>
miniList<T>::miniList(T *first,T *last)
	/*先计算出first到last这段地址的元素数,其他的与上面类似,
	区别只是生成新节点时的节点值为当前地址中保存的值*/
	int n=last-first;
	header=new dnode<T>;
	dnode<T> *temp;
	dnode<T> *curr;
	curr=header;
	unsigned int i;
	for(i=0;i<n;i++)
		temp=new dnode<T>(*first);
		curr->next=temp;
		temp->prev=curr;
		curr=temp;
		first++;
	
	curr->next=header;
	header->prev=curr;
	listSize=n;

template <typename T>//析构函数,遍历整个list,逐个释放内存
miniList<T>::~miniList()
	dnode<T> *curr;
	curr=header->next;
	while(curr!=header)
		delete curr;
		curr=curr->next;
	
	delete header;

template <typename T>//在list的末端插入值
void miniList<T>::push_back(const T& item)
	dnode<T> *temp,*curr;
	temp=new dnode<T>(item);
	if(listSize==0)//表示当前list中只有一个header
		header->next=temp;
		temp->next=header;
		header->prev=temp;
		temp->prev=header;
	else if(listSize>0)
		//元素数不为0,在末端插入值其实就是在header前面插入值,因为list是串起来的环,先找到header的前驱
		curr=header->prev;
		//打开header的前驱和header,在中间插入新生成的temp
		curr->next=temp;
		temp->next=header;		
		header->prev=temp;
		temp->prev=curr;
	
	listSize++;

template <typename T>
T& miniList<T>::back()
	if(listSize>0)//元素个数不为0,返回末端元素的值,其实就是header前驱节点的值
		return header->prev->nodeValue;

template <typename T>//在顶端和在末端插入元素的方法类似,区别只是在顶端插入值时,插入的位置为header和header后继节点的中间。
void miniList<T>::push_front(const T& item)
	dnode<T> *curr,*temp;
	temp=new dnode<T>(item);
	if(listSize==0)
		header->next=temp;
		temp->prev=header;
		temp->next=header;
		header->prev=temp;
	else if(listSize>0)
		curr=header->next;
		header->next=temp;
		temp->next=curr;
		temp->prev=header;
		curr->prev=temp;
	
	listSize++;

template <typename T>
T& miniList<T>::front()
	if(listSize>0)//元素个数不为0,返回顶端元素,其实就是header后继节点的节点值
		return header->next->nodeValue;

template <typename T>
int miniList<T>::size()
	return listSize;//返回当前元素的个数

template <typename T>//返回一个游标,指向顶端元素,
typename miniList<T>::iterator miniList<T>::begin()
	return iterator(header->next);//调用iterator的构造函数,将dnode类型的指针转换为游标

template <typename T>
typename miniList<T>::iterator miniList<T>::end()
	return iterator(header);//与begin函数类似,区别是end返回的游标指向list的header,而不是一个具体的元素

template <typename T>//在固定的位置插入一个值
dnode<T>* miniList<T>::insert(const T& item,dnode<T> *curr)
	dnode<T> *tempNode,*tempPrev;
	tempNode=new dnode<T>(item);//新生成一个节点
	tempPrev=curr->prev;//找到插入位置的前驱节点

	tempPrev->next=tempNode;//插入这个新节点
	tempNode->next=curr;
	curr->prev=tempNode;
	tempNode->prev=tempPrev;
	listSize++;
	return tempNode;

template <typename T>//查找一个元素,返回它的地址,不在list当中则返回NULL
dnode<T>* miniList<T>::find(const T& item)
	dnode<T> *temp=header->next;
	//查找元素,直到找到该元素或者遍历完整个list
	while(temp->nodeValue!=item&&temp!=header)
		temp=temp->next;
	
	if(temp!=header)//找到的该元素,返回它的地址
		return temp;
	return NULL;//遍历完整个list,说明不在list当中,返回NULL

template <typename T>//游标的两个构造函数,第一个生成一个空游标,第二个将dnode类型的指针转换为游标
miniList<T>::iterator::iterator()
	node=NULL;

template <typename T>
miniList<T>::iterator::iterator(dnode<T> *p)
	node=p;

template <typename T>//重载的运算符,比较两个游标所指元素的元素值
bool miniList<T>::iterator::operator==(const iterator& rhs)const
	return node->nodeValue==rhs.node->nodeValue;

template <typename T>
bool miniList<T>::iterator::operator!=(const iterator& rhs)const
	return node->nodeValue!=rhs.node->nodeValue;

template <typename T>//重载的"*"运算符,返回游标所指元素的值
T& miniList<T>::iterator::operator*()
	return node->nodeValue;


测试一下能不能正常的工作:

void main()
	unsigned int i;	
	int a[]=1,6,2,5,6,7;
	miniList<int> list1(a,a+6);
	miniList<int>::iterator it;
	dnode<int> *temp;
	temp=list1.find(2);
	list1.insert(3,temp);
	list1.push_back(9);
	it=list1.begin();
	while(it!=list1.end())
		cout<<*it<<" ";
		it++;
		
	while(1);
先用数组初始化一个list,生成一个空游标,并用list的end函数初始化,找到2的地址,在2前面插入3,在末端插入9,最后输出list的全部元素,测试结果:

按照预定的方式运行了,由于想让代码看起来简洁,没有进行异常的捕捉,也假设测试输入都是正确的。






以上是关于List数据结构的实现的主要内容,如果未能解决你的问题,请参考以下文章

数据结构练习题

线性表的概念与实现

LeetCode203. Remove Linked List Elements

数据结构 已知P指向双向循环链表中的一个结点,其结点结构为datapriornext三个域,写出算法change(p),交换 p所指向的结点和它的前缀结点的顺序。

List双向链表容器

线性表-顺序结构存储