大二下开始学数据结构与算法--06,判断两个节点是否相交,删除链表倒数第K个节点

自习所完成的任务

  1. 完成函数判断单项链表是否相交的代码编写和测试。
  2. 完成函数删除倒数第K个节点的代码编写和测试。

感悟

  1. 其实这篇是昨天晚上写的,但是昨天下午在实验室呆了一下,然后写完这些代码后感觉脑袋昏沉,晚上十点就回宿舍了,想着看会儿书,但是,没看成,还是玩手机了。
  2. 感觉坚持做一件事,还挺难的,老是为自己找逃避的借口,比如说周三晚上跟舍友出去吃,就放下了写代码的每日任务。我在想,是不是应该改变一下观念,以进度为指标,而不是以每日任务为指标,这样会不会行动起来更能坚持?
#include
#include
#include
#include
using namespace std;


//单链表,设置一个头节点,以方便后续的数据的插入;
//可以将链表的每个节点看成一个单元,每个单元都有着当前的数据,以及指向下一个节点的地址(先一个节点的数据类型是定义好的,所以就要用自主定义的数据类型)

//节点类型
struct Node
{
	Node(int date=0):date_(date),next_(nullptr){}
	int date_;
	Node *next_;//为什么可以这么做?如果是这样的话,这个指针的大小是多少?以Node这个为数据类型的变量的大小是多少?(我记得之前在哪看过,好像是只包含构造函数的)
	/*我的理解是,无论是结构体还是类,都是对一类事情的抽象共性的集合。这样的定义一个Node类型的指针就很正常了。*/
};

class Clink
{
	friend void ReverseLink(Clink&link);
	friend bool GetLastKNode(Clink&link,int k,int &KVal);
	friend void Showone(Clink &link,int num);
	friend void MergeLink(Clink &link1,Clink &link2);
	//friend bool IsLinkHaveCircle(Clink &link,int &val);
public:
	Clink()
	{
		//给head_初始化指向头节点
		head_= new Node();//会自动开启一个类型为Node的空间,还会自动初始化head_这个节点//为什么要new出来?
		/*首先head_是一个Node类型的指针,这个指针只记录地址,并不包含其他内容,因此,要实例化一个Node类型的变量,这里使用的无参构造,通过new返回地址,同时,也要注意new开辟在堆区的内存的释放*/
	}
	~Clink()
	{
		//结点的释放
		/*其实这个东西就是"过河拆桥",你需要先找好退路,才能大胆进行断后路,很多思想都能在现实中找到例子,生活经历的多了,也许对编程也会有帮助,同时,也不要将编程想的太高大上,让变成生活化,也许是算法进步的一种途径*/
		Node*p=head_;/*从头部向尾部进行释放,通过直接转移头节点,从而减少变量的生成,是代码变得精简,这一点需要学习;然后就是关于什么时候用head_->next,head_这一点你需要仔细琢磨一下;do while ,while仔细思考一下*/
		while(p!=nullptr)
		{
			head_=head_->next_;
			delete p;
			p=head_;
		}
		head_=nullptr;//为什么一定要将这个定义成空指针?其他的不需要吗?
	}
private:
	Node*head_;//指向链表的头节点的指针
public:
	//链表的尾插法  时间复杂度O(n)./*时间主要浪费在找尾节点上,假若我给出一个尾节点,那么时间复杂度就会降低为O(1)*/
	void insertTail(int val)
	{
		//先找到当前链表的末尾节点。这个尾节点是通过一个新定义的指针进行寻找的。
		Node*p=head_;//算不算拷贝构造函数?如果算的话,这不就是浅拷贝吗?不会出现错误吗?每次尾插都会建立一个Node类型的指针,可以释放吗?/*不算拷贝构造函数,他只是初始化了一个Node类型的指针,指针的内容为head_的地址,这个属于区域变量,会在结束后,自动释放空间。拷贝构造实质上是通过一个类或者结构体来实例化另一个结构体或者类。*/
		while(p->next_!=nullptr)//这里为什么要用p->next_?/*尾插法的核心就是找到尾节点,后者说是当前节点的属性指针指向空节点,因此,用这个方法*/
		{
			p=p->next_;
		}

		//生成新的节点
		Node*node=new Node(val);//这个是怎么释放的?/*这个相当于生成了一个串入链表的节点,后期在析构函数中会逐渐被释放掉*/
		//将新的节点的地址放在原尾节点的后面
		p->next_=node;
		//关于这里各种元素的声明,实例,和调用,我有点不清楚原理。这个好像就是一个框架,之后的东西就是套进去写的。将字体缩小之后,从总的角度来看,变量的所属有点不清晰,比如,Node*p=head_这句话,我就感觉有点没头没尾的?
	}
	//链表的头插法 时间复杂度O(1)
	void insertHead(int val)
	{
		//这一段尾插法的后半段很像,就是通过建立一个新的Node类型指针进行传递
		Node *node=new Node(val);//这里肯定是要释放的,通过谁的析构函数进行释放?
		node->next_=head_->next_;
		head_->next_=node;
	}

	//链表节点的删除(删除值为val的节点)
	void Remove(int val)//AI写的,感觉更好,(看了几遍后,感觉有点麻烦,在存疑)因为我不熟悉do...while,和while,已经修改了一下,个人感觉还挺好。
	{
		Node *node = head_;
		Node *p = head_->next_;
		while (p != nullptr)//有点不明白,如何通过值的转换,来达到改变节点指针指向的操作?/*通过node,p这两个指针来实现,node,p记录的是节点的地址,通过对地址的解引用进而对节点属性进行操作,达到改变节点指针指向的目的*/
		{
			if (p->date_ == val)
			{
				node->next_ = p->next_;
				delete p;/*因为跳过之后,那个节点仍然占据空间,所以,要删除掉*/
				return;/*只删除最先遇到符合条件的话,直接用return就行了*/
				//p = node->next_; /*更新 p 指针,继续遍历,达到持续释放的效果*/
				
			}
			else
			{
				node = p;
				p=p->next_;
			}
		}
	}
	
	//删除所有符合条件的值
	void RemaveAll(int val)
	{
		Node*p=head_->next_;
		Node*node=head_;
		while(p!=nullptr)
		{
			if(p->date_==val)
			{
				node->next_=p->next_;
				delete p;
				p=node->next_;
			}
			else
			{
				node=p;
				p=p->next_;
			}
		}

	}
	
	//搜索(判断是否存在给定值),时间复杂度是O(n),线性搜索
	bool Find(int val)
	{
		Node * p=head_->next_;/*找有效元素,不需要从头节点开始找,从第一个有效节点开始就行;也可以在头节点中添加节点的个数*/
		while(p!=nullptr)
		{
			if(p->date_==val)
			{
				return 1;
			}
			else
			{
				p=p->next_;
			}
		}
		return 0;
	}

	//删除倒数第K个元素
	void RemoveTailK(int k)
	{
		Node *p=head_->next_;
		Node *q=head_->next_;

		for(int i=0;inext_;
		}

		while(p->next_!=nullptr)//这里有点小问题,后续要仔细思考一下
		{
			p=p->next_;
			q=q->next_;
		}
		//p=q->next_;
		q->next_=q->next_->next_;
		delete p->next_;
	}

	//链表打印;
	void Show()
	{
		Node *node=head_->next_;
		while(node!=nullptr)
		{
			cout<date_<<" ";
			node=node->next_;
		}
	}
	//链表打印第k个,通过成员函数实现,还能优化结构;测试一下极端值;倒数第0个?/*倒数第0个感觉没有什么实际意义,提示过后,直接退出程序就行*/
	/*其实我感觉没必要通过成员函数实现,在我的理解里,成员函数是抽象出来的公用功能,像打印第k个元素这样的细节型,通过全局函数进行处理最好*/

	//展示正数第num个元素,成员函数
	void Showone(int num)
	{
		int count=1;
		int val=0;
		Node *p=head_->next_;
		if(num<=0)
		{
			cout<<"节点不存在";
		}
		if(p==nullptr)//改为先判断,后输出,这样比较好
		{
			cout<<"空链表"<date_;
			if(count==num)
			{
				cout<next_;
		}
	}
};

//展示正数第num个元素,全局函数
	void Showone(Clink &link,int num)
	{
		int count=1;
		int val=0;
		Node *p=link.head_->next_;
		if(num<=0)
		{
			cout<<"节点不存在";
		}
		if(p==nullptr)//改为先判断,后输出,这样比较好
		{
			cout<<"空链表"<date_;
			if(count==num)
			{
				cout<next_;
		}
	}


//链表元素逆序,关注复杂度(时间/空间)
/*函数作为友元,来访问类中的私有属性;引用传递;头插法变形以达到逆序目的*/
void ReverseLink(Clink & link)//这里一开始写的是Node *head,在main中对应函数写入(link.head_)会报错;后来修改成的这,引用传递,直接传入参数就行,能够改变参数具体元素的值;那再改成Clink * link,main函数中写&link行不?
{
	Node*p=link.head_->next_;
	link.head_->next_=nullptr;/*作用是,重置初始化头节点,这里还挺关键的*/
	if(p==nullptr)
	{
		return;
	}
	else
	{
		while(p!=nullptr)
		{
			Node*q=p->next_;
			p->next_=link.head_->next_;/*类似于头插法,若为第一次,这p->next_为nullptr,其余情况为上一个节点的地址,这里还挺精巧的*/
			link.head_->next_=p;
			p=q;
		}
	}
}

//通过双指针求倒数第k个元素
bool GetLastKNode(Clink&link,int k,int &KVal)
{
	Node *p=link.head_->next_;
	Node *q=link.head_->next_;
	if(k<1)
	{
		cout<<"k值过小";
		return false;
	}
	for(int i=0;inext_;
		if(p==nullptr)
		{
			cout<<"k值超过节点数";
			return false;
		}
	}
	while(p->next_!=nullptr)
	{	
		q=q->next_;
		p=p->next_;
	}
	KVal=q->date_;
	return true;
}

//合并两个有序单链表,成为一个,小->大;如果从大->到小呢?
void MergeLink(Clink &link1,Clink &link2)
{
	//构建双指针
	Node*p=link1.head_->next_;
	Node*q=link2.head_->next_;
	Node*last=link1.head_;

	while(p!=nullptr && q!=nullptr)
	{
		if(p->date_date_)//为什么last的改变能影响到head_->next_的指向?/*->是解码得到地址所对应的对象,然后按要求操作对象*/
		{
			last->next_=q;
			q=q->next_;
		}
		else//稳定性怎么样?
		{
			last->next_=p;
			p=p->next_;
		}
		 last = last->next_;
		
	}
	if(p==nullptr)
	{
		last->next_=q;
	}
	else
	{
		last->next_=p;
	}

	link2.head_->next_=nullptr;
}

//判断单链表是否存在环,存在环,返回节点
bool IsLinkHaveCircle(Node*head,int &val)
{
	//定义快慢指针
	Node *fast=head;
	Node *slow=head;

	while(fast!=nullptr&&fast->next_!=nullptr)
	{
		fast=fast->next_->next_;
		slow=slow->next_;

		if(slow==fast)
		{
			int count=0;
			fast=head;
			while(fast!=slow)
			{
				fast=fast->next_;
				slow=slow->next_;
				count++;
			}
			val=count;
			return true;
		}
	}

}

//判断两个单链表是否相交,若相交,则返回相交节点的值,并返回再第一个链表节点的位置
/*这东西也要将链表连起来,方便起见,直接声明节点,让节点进行相交*/
bool IsLinkHasMerge(Node*n1,Node*n2,int &val,int &index)
{
	int count1=0,count2=0,diff;
	
	Node*p=n1;
	Node*q=n2;
	while(p->next_!=nullptr)
	{
		count1++;
		p=p->next_;
	}
	while(q->next_!=nullptr)
	{
		count2++;
		q=q->next_;
	}

	p=n1->next_;/*写成更有实际意义的事*/
	q=n2->next_;

	diff=count1-count2;

	if(diff>0)/*能提公因式就提公因式*/
	{
		for(int i=0;inext_;
		}
	}
	else
	{
		while(diff++<0)
		{
			q=q->next_;
		}
	}

	while(p!=nullptr&&q!=nullptr)
	{	
		index++;
		if(p==q)
		{
			val=p->date_;
			return true;
		}
			
		p=p->next_;
		q=q->next_;
	}
	return false;
	//if(diff>0)
	//{
	//	for(int i=0;inext_;
	//	}

	//	index=index+diff;

	//	while(p!=nullptr&&q!=nullptr)
	//	{	
	//		index++;
	//		if(p==q)
	//		{
	//			val=p->date_;
	//			return true;
	//		}
	//		
	//		p=p->next_;
	//		q=q->next_;
	//	}

	//}
	//else if(diff==0)
	//{
	//	while(p!=nullptr&&q!=nullptr)
	//	{
	//		index++;
	//		if(p==q)
	//		{
	//			val=p->date_;
	//			return true;
	//		}
	//		p=p->next_;
	//		q=q->next_;
	//	}
	//}
	//else
	//{
	//	for(int i=0;i<-diff;i++)
	//	{
	//		q=q->next_;
	//	}

	//	while(p!=nullptr&&q!=nullptr)
	//	{
	//		index++;
	//		if(p==q)
	//		{
	//			val=p->date_;
	//			return true;
	//		}
	//		p=p->next_;
	//		q=q->next_;
	//	}

	//}
	//return false;
}

int main()
{

	Clink link;
	for(int i=0;i<10;i++)
	{
		link.insertTail(i);
	}
	link.Show();
	cout<

你可能感兴趣的:(链表,数据结构)