基于C++的数据结构-3-线性链表

1.线性链表

2.插入、删除算法实现

3.C++实现


线性链表:一组任意的存储单元存储线性表中的数据元素。其节点可以是连续的也可以是不连续的甚至是零散分布在内存中的任意位置上,因此其数据元素的逻辑顺序与其物理顺序不一定相同。包括单向链表、循环链表和双向链表。

        数据结构中的每个数据元素对应一个存储单元,这种单元叫做存储结点,其中用于存放数据元素的值的部分叫做数据域;另一部分用于指向与该结点在逻辑上相连的其他结点的指针叫做指针域。通过结点指针将n个结点按逻辑结构连接到一起的数据存储结构叫做链式存储结构。

 

1、单向链表极其基本操作

定义:用一个专门的指针指向线性表中的第一个结点,每一个结点的指针都指向它的下一个逻辑结点,线性表的最后一个结点的指针为NULL。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5aSW5amG55qE6YW45rGk6bG8,size_13,color_FFFFFF,t_70,g_se,x_16

        对于链式结构,在第一个结点之前如果增加一个头结点,程序代码会很简洁,运行速度也会提高,所以这就是为什么要在头结点之前设置头指针的原因。在头结点之前增加结点只需要将新结点的指针指向头结点,再修改头指针指向新结点即可完成。

        在C++中,我们使用类的声明来表示结点,因为要在主类中操作结点,需要访问结点的内部属性,因此需要把操作的主体类设置为结点类的友元类(以活得对结点属性的访问权),由此我们可以得到存储节点类和单向链表类的C++描述如下:

//线性链表
#include <iostream>
using namespace std;

template<class T>
class LinkList;

template<class T>
class LinkNode{
	friend class LinkList<T>;

	public:
		LinkNode(){
			next=NULL;
		}

	private:
		T data;
		LinkNode<T> *next;
};

template<class T>
class LinkList{
	public:
		LinkList();
		~LinkList();
		LinkList<T>& Insert(int k,const T& x);
		bool IsEmpty() const;
		int GetLength() const;
		bool GetData(int k,T& x);
		bool ModifyData(int k,const T& x);
		int Find(const T& x);
		LinkList<T>& DeleteByIndex(int k,T& x);
		LinkList<T>& DeleteByKey(const T& x,T& y);
		void Output(ostream& out);

	private:
		LinkNode<T> *head;
};

        线性链表的插入算法:

算法描述:n--链表长度;k--插入位置

        1、当k>n+1或k<1时,插入位置不正确,报错

        2、当是空表且K=1时,只需要将头指针的指针指向新结点即可

        3、当不是空表且k=1时,需要将新结点的指针指向第一个元素结点,再将头指针指向新结点即可

        4、当1<k<=n+1时,将新结点的指针指向第k个结点,将第k-1个元素结点的指针指向新结点即可

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5aSW5amG55qE6YW45rGk6bG8,size_15,color_FFFFFF,t_70,g_se,x_16

其算法统一实现代码为:

p=head;
for (int i=1;i<k;i++){
    p=p->next;
    newNode->next = p->next;
    p->next = newNode;
}

 线性链表的删除算法:

算法描述:

        1、当k>n或k<1时,由于没有目标结点,所以删除失败;

        2、当k=1,只需要将头结点的指针指向第二个结点,然后释放第一个结点即可

        3、当1<k<n时,只需要将第k-1个元素结点的指针指向第k+1个结点,然后释放第k个结点即可;

        4、当k=n时,将第n-1个元素的指针赋值为NULL,然后释放第k个结点即可。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5aSW5amG55qE6YW45rGk6bG8,size_14,color_FFFFFF,t_70,g_se,x_16

        上图的删除只是逻辑上的删除,实际上有时候我们还需要物理删除,也就是说逻辑删除只是在当前链表没有这个结点的位置了,但是它依然存在于计算机中的存储器中,对于C++而言,我们会使用new关键字为结点申请物理空间,当我们不需要该结点存在之后,我们可以使用delete关键字将申请的物理空间释放掉,避免程序占用太多的空间对计算机造成运行压力。

        链表删除操作的统一代码实现如下:

p=head;
for(int i=1;i<k;i++){
    p=p->next;
}
q=p->next;        
p->next=q->next;

        据此,对链式存储表的具体增删改查C++实现如下:

template<typename T>
LinkList<T>::LinkList(){
	head=new LinkNode<T>();
}

template<typename T>
LinkList<T>::~LinkList(){
	T x;
	int len=GetLength();
	for(int i=len;i>=1;i--){
		DeleteByIndex(i,x);
	}
}
template<typename T>
LinkList<T>& LinkList<T>::Insert(int k,const T& x){
	LinkNode<T> *p=head;
	LinkNode<T> *newNode=new LinkNode<T>;
	newNode->data=x;
	int len=GetLength();
	if(k<1||k>len+1){
		cout<<"插入位置下标错误,请重新插入"<<endl;
	}
	else{
		for(int i=1;i<k;i++){
			p=p->next;
		}
		newNode->next=p->next;
		p->next=newNode;
	}
	return *this;
}

template<typename T>
bool LinkList<T>::IsEmpty() const{
	return head->next==NULL;
}

template<typename T>
int LinkList<T>::GetLength() const{
	int length=0;
    LinkNode<T> *p=head->next;
    while(p){
    	length++;
    	p=p->next;
	}
	return length;
}
template<typename T>
bool LinkList<T>::GetData(int k, T& x){
	LinkNode<T> *p=head->next;
	int index=1;
	if(k<0||k>GetLength()){
		return false;
	}
	while(p!=NULL&&index<k){
		index++;
		p=p->next;
	}
	if(p==NULL){
		return false;
	}
	else{
		x=p->data;
		return true;
	}
}
template<typename T>
bool LinkList<T>::ModifyData(int k,const T& x){
	LinkNode<T> *p=head->next;
	int index=1;
	if(k<0||k>GetLength()){
		return false;
	}
	while(p!=NULL&&index<k){
		index++;
		p=p->next;
	}
	if(p==NULL){
		return false;
	}
	else{
		p->data=x;
		return true;
	}
}
template<typename T>
int LinkList<T>::Find(const T& x){
	LinkNode<T> *p=head->next;
	int index=1;
	while(p!=NULL&&p->data!=x){
		p=p->next;
		index++;
	}
	if(p!=NULL){
		return index;
	}
	else{
		return 0;
	}
}
template<typename T>
LinkList<T>& LinkList<T>::DeleteByIndex(int k,T& x){
	if(GetData(k,x)){
		LinkNode<T> *p=head;
		LinkNode<T> *q=NULL;
		for(int i=1;i<k;i++){
			p=p->next;
		}
		q=p->next;
		p->next=q->next;
		delete q;
	}
	else{
		cout<<"下标越界,删除失败"<<endl;
	}
	return *this;
}
template<typename T>
LinkList<T>& LinkList<T>::DeleteByKey(const T& x,T& y){
	int index=Find(x);
	if(index!=0){
		return DeleteByIndex(index,y);
	}
	else{
		cout<<"没有此元素"<<endl;
		return *this;
	}
}
template<typename T>
void LinkList<T>::Output(ostream& out){
	LinkNode<T> *p=head->next;
	while(p!=NULL){
		out<<p->data<<endl;
		p=p->next;
	}
}
template<typename T>
ostream& operator<<(ostream& out,LinkList<T>& x){
	x.Output(out);
	return out;
}

【写在最后】

        对于线性顺序表和链式存储表,创建一个表都只需要常数时间O(1),而删除整个表线性顺序表只需要释放整个空间即可故时间复杂度是O(1),而链式顺序表因为需要一个结点一个结点的释放,因此需要O(n)的时间复杂度;但对于增加结点和删除结点而言,明显链式存储表的时间复杂度O(k)和O(k)比线性顺序表的O(n)、O((n-k)s)更为快速,同时由于删、改操作时十分常用的操作,因此在操作大量数据时避免额外的运算,可以节省时间,再者链表不会像线性顺序表为了避免空间不够而申请多余的空间,避免空间的初步浪费,而且可扩展性强,因此线性链表的优势是显然而明确的。

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值