线性表的链接存储结构及实现——单链表

1.单链表的存储方法:

单链表是用一组任意的存储单元存放线性表的元素,这组存储单元可以连续也可以不连续。
为了表明元素之间的逻辑关系,每个元素需要存储自身数据(data)的同时,还必须存储其后继元素的地址信息,这个地址信息称为指针(next),这两部分(data和next)组成了每个数据元素的存储形式,称为结点。

用C++描述单个结点的结构:
template<class T>
struct Node
{
	T data;
	Node<T> *next ;
}

显然,单链表中每个结点的存储地址存放在其前驱结点的next域中,而第一个元素无前驱,所以设头指针(head)指向第一个元素所在结点(称为开始结点),整个单链表的存取必须从头指针开始进行,因而头指针具有标识一个单链表的作用;同时,由于最后一个元素无后继,故最后一个元素的next域为空(NULL),也称为单链表的结尾标志。

除了开始结点之外,其他每个结点的存储地址都存放在其前驱结点的next域中,而开始结点是由我们所设的头指针直接指示的,这个特例需要在单链表实现时特殊处理。因此,通常在单链表的开始结点之前再附设一个类型相同的结点(其data域为空,他也可以存放一些数据,但并不介意这样做)称为头结点,加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了。
如下图所示:
带头结点的单链表

2.单链表的实现:

将线性表的抽象数据类型定义在单链表存储结构下用C++中的类实现。由于线性表的数据元素类型不确定,所以采用C++的模板机制。

//头文件LinkList.h:

template <class T>  
struct Node    //定义结点的结构
{
  T data;
  Node<T> *next;  //此处<T>也可以省略
};

template <class T>
class LinkList
{
  public:
    LinkList( );           //建立只有头结点的空链表
    LinkList(T a[ ], int n);  //建立有n个元素的单链表
    ~LinkList();           //析构函数
    int Length();          //求单链表的长度
    T Get(int i);           //取单链表中第i个结点的元素值
    int Locate(T x);       //求单链表中值为x的元素序号
    void Insert(int i, T x);   //在单链表中第i个位置插入元素值为x的结点
    T Delete(int i);        //在单链表中删除第i个结点
    void PrintList( );       //遍历单链表,按序号依次输出各元素
 private:
   Node<T> *first;  //单链表的头指针
};

1.无参构造:
也就是生成只有头结点的空链表,算法如下:

template <class T>
LinkList<T>:: LinkList( )
{
 first=new Node<T>; 
 first->next=NULL;
}

2.有参构造 LinkList:
建立有n个元素的单链表(尾插法):
每次将新申请的结点插在终端结点的后面。

template<class T>
LinkList<T>::LinkList(T a[],int n)
{
	first=new Node<T>;  //生成头结点
	Node<T> *r,*s;   //*s用于创建结点,通过*r将新申请的结点不断放在最后
	r=first;    //尾指针初始化  
	for(int i=0;i<n;i++)
	{
		s=new Node<T>;
		s->data=a[i];  //为每个数组元素建立一个结点
		r->next=s;
		r=s;    //插入到终端结点之后
	}
	r->next=NULL:   //单链表建立完毕,将终端结点的指针域置空
}

3.析构函数~LinkList:
单链表类中的结点是用运算符new申请的,在释放单链表类的对象时无法自动释放这些结点的存储空间,所以,析构函数应将单链表中结点(包括头结点)的存储空间释放,算法如下:

template<class T>
LinkLIst<T>::~LinkList()
{
	while(first!=NULL)  //释放单链表的每一个结点的存储空间
	{
		q=first;   //暂存被释放结点
		first=first->next;   //first指向被释放结点的下一个结点
		delete q;
	}
}

4.求单链表长度Length:
由于单链表类中没有存储线性表的长度,因此,不能直接求得线性表的长度。可以设置一个工作指针p依次指向各结点,当指针p指向某结点时求出其序号,然后再将p修改为指向其后继结点并将其序号加1,则最后一个结点的序号即为表中结点个数(即线性表长度)。即在单链表中求线性表的长度需要将单链表扫描一遍,算法如下:

伪代码:
1.工作指针p初始化;累加器count初始化;
2.重复执行下述操作,直到p为空:
	2.1 工作指针p后移;
	2.2 count++;
3.返回累加器count的值;
template <class T>
int LinkList<T>::length()
{
	Node<T>*p=first->next;  //工作指针p
	int count=0;  //累加计算长度count
	while(p)
	{
		p=p->next;
		count++;
	}
	return count;
}

5.按位查找Get:(基本操作)
查找算法的基本语句是工作指针p后移,该语句执行的次数与被查结点在表中的位置有关。算法如下:

template <class T>
T LinkList<T>::Get(int i)
{
	Node<T>*p;
	int count;   
	p=first->next;
	count=1;  //工作指针p和累加器count的初始化
	//或p=first; count=0;
	while(p&&count<i)
	{
		p=p->next;
		count++;
	}
	if(!p)throw"位置异常";
	else return p->data;
}

6.按值查找Locate:
在单链表中实现按值查找操作,需要对单链表中的元素依次进行比较,如果查找成功,返回元素的序号,如果查找不成功,返回0表示查找失败。算法如下:

template <class T>
int LinkList<T>::Locate(T x)
{
	p=first->next;
	count=1;   //工作指针p和累加计count初始化
	while(p!=NULL)
	{
		if(p->data==x)
			return count;    //查找成功,结束函数并返回序号
		else
			p=p->next;
			count++;
	}
	return 0;   //退出循环表明查找失败
}

7.插入操作Insert:
单链表的插入操作是将值为x的新结点插入到单链表的第i个位置,即插入到a[i-1]与a[i]之间。因此,必须先扫描单链表,找到a[i-1]的存储地址p,然后生成一个数据域为x的新结点s,将结点s的next域指向结点a[i],将结点p的next域指向新结点s(注意指针的链接顺序),从而实现三个结点a[i-1] , x和 a[i]之间逻辑关系的变化,由于单链表带头结点,所以在表头,表中间,表尾插入的情况的操作语句一致,不用特殊处理。算法如下:

伪代码:
1.工作指针p初始化;
2.查找第i-1个结点并使工作指针p指向该结点;
3.若查找不成功,说明插入位置不合理,抛出插入位置异常;
   否则, 3.1 生成一个元素值为x的新结点s;
   		  3.2 将新结点s插入到结点p之后;
template <class T>
void LinkList<T>::Insert(int i,T x)
{
	Node<T>*p=first;	//工作指针p应指向头结点
	int count=0;
	while(p==NULL&&count<i-1)    //查找第i-1个结点
	{
		p=p->next;    //工作指针p后移
		count++;
	}
	if(p==NULL)
		throw"插入位置异常";   //没有找到第i-1个结点
	else
	{
		Node<T>*s;
		s=new Node<T>;
		s->data=x;   //申请一个结点s,其数据域为x
		s->next=p->next;
		p->next=s;	//将结点s插入到结点p之后
	}
}

8.删除操作Delete:
删除操作是将单链表的第i个结点删去。因为在单链表中结点a[i]的存储地址在其前驱结点a[i-1]的指针域中,所以必须首先找到a[i-1]的存储地址p,然后令p的next域指向a[i]的后继结点,即把结点a[i]从链上摘下,最后释放结点a[i]的存储空间。需要注意表尾的特殊情况,此时虽然被删结点不存在,但其前驱结点却存在。因此仅当被删除结点的前驱结点p存在且p不是终端结点时,才能确定被删除结点存在。

伪代码描述如下:
1.工作指针p初始化;累加计count初始化;
2.查找第i-1个结点并使工作指针p指向该结点;
3.若p不存在或p的后继结点不存在,则抛出位置异常;
  否则,3.1 暂存被删结点和被删元素值;
  	   3.2 摘链,将结点p的后继节点从链表上摘下;
  	   3.3 释放被删结点;
  	   3.4 返回被删元素值;
template <class T>
T LinkList<T>::Delete(int i)
{
	Node<T>*p=first;  //注意工作指针p要指向头结点
	int count=0;  
	while(p!=NULL&&count<i-1)  //查找第i-1个结点
	{
		p=p->next;
		count++;
	}
	if(p==NULL||p->next==NULL)  //结点p不存在或p的后继结点不存在
		throw"位置异常";
	else
	{
		Node<T>*q=p->next;
		int x=q->data;  //暂存被删结点
		p->next=q->next;   //摘链
		delete q;
		return x;
	}
}

9.遍历操作PrintList:
所谓遍历操作单链表是指按序号依次访问单链表中的所有结点且仅访问一次。可以设置一个工作指针p依次指向各结点,当指针p指向某结点是输出该结点的数据域。

伪代码:
1.工作指针p初始化;
2.重复执行下述操作,直到p为空:
	2.1 输出结点p的数据域;
	2.2 工作指针p后移;
template <class T>
{
	Node<T>*p=p->first;   //工作指针p初始化
	while(p!=NULL)
	{
		cout<<p->data;
		p=p->next;    //工作指针p后移
	}
}

参考至-----《数据结构(C++版)(第二版)》/王红梅,胡明,王涛编著.

猜你喜欢

转载自blog.csdn.net/qq_28840013/article/details/85109424