三、线性表——链表

1. 快慢指针

1)如何判断单链表是否存在环 ?

问题描述:给定一个单链表L,L为头指针,判断该链表内是否局部存在环?

方法: 快慢指针,快指针的速度为慢指针的2倍,若快慢指针相遇则有环。

#include<iostream>
using namespace std;
typedef struct LNode   //定义单链表
{
	int data;
	struct LNode *next;
}LNode, *LinkList;

void List_TailInsert(LinkList L)  //尾插法建立单链表(这里建立了一个带循环的链表)
{
	LNode *s, *r=L;
	int x = 0;
	cout<<"请分别输入数据建立链表:";
	cin>>x;
	while(x != 9999)  //输入结束条件
	{
		s = new LNode[sizeof(LNode)];
		s->data = x;
		r->next = s;
		r=s;
		r->next = NULL;
		cin>>x;
	}
	if(L!=NULL && L->next!=NULL && L->next->next!=NULL && L->next->next->next!=NULL)
		r->next = L->next->next->next;

}

bool circle(LinkList L)
{
	LinkList slow, fast;
	slow = L->next;
	fast = L->next;
	while(fast!=NULL && fast->next!=NULL && fast->next->next!=NULL)
	{
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)
			return true;
	}
	return false;
}

void main()
{
	LinkList L;
	bool result;
	L = new LNode[sizeof(LNode)];
	L->next = NULL;
	List_TailInsert(L);
	result = circle(L);
	if(result == true)
		cout<<"该链表局部存在环"<<endl;
	else
		cout<<"该链表局部不存在环"<<endl;

}

//输入:3 5 2 6 8 4 9999(输入结束标志) ,其中4再指向2 
//输出:该链表局部存在环
//输入:3 6 9999(输入结束标志) 
//输出:该链表局部不存在环

2)找到单链表中倒数第k个结点。

找出解决方法
要求:尽可能高效
例如:一个链表有6个结点,(1,2,3,4,5,6)
这个链表的倒数第3个结点是:值为4的结点。

方法: 快慢指针,快指针先走k步。

#include<iostream>
using namespace std;
typedef struct LNode   //定义单链表
{
	int data;
	struct LNode *next;
}LNode, *LinkList;

void List_TailInsert(LinkList L)  //尾插法建立单链表
{
	LNode *s, *r=L;
	int x = 0;
	cout<<"请分别输入数据建立链表:";
	cin>>x;
	while(x != 9999)  //输入结束条件
	{
		s = new LNode[sizeof(LNode)];
		s->data = x;
		r->next = s;
		r=s;
		cin>>x;
	}
	r->next = NULL;
}

LNode findNode(LinkList L, int k)
{
	LinkList slow, fast;
	int i;
	slow = L->next;	
	fast = L->next;
	for(i = 0; i < k; i++)
		fast = fast->next;
	while(fast!=NULL)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return *slow;
}
void main()
{
	LinkList L;
	LNode result;
	int k = 3;
	L = new LNode[sizeof(LNode)];
	L->next = NULL;
	List_TailInsert(L);
	result = findNode(L, k);
	cout<<result.data<<endl;
}

3)找出str1和str2的共同后缀的起始位置

假定用带头结点的单链表保存单词,当两个单词有相同的后缀时,
则可共享相同的后缀存储空间,例如:loading和being,
设计一个高效的算法,找出str1和str2的共同后缀的起始位置。(可能有也可能没有。)
分析算法的时空效率。

步骤:
1.两次遍历分别求出链表长度
2.长链表先走两链表相差的步数
3.两链表同时走
若两指针地址相同,则为共同后缀的起始位置。

注: 为满足有共同后缀,需手动建立链表。

#include<iostream>
using namespace std;
typedef struct LNode   //定义单链表
{
	char data;
	struct LNode *next;
}LNode, *LinkList;

void List_TailInsert(LinkList L)  //尾插法建立单链表
{
	LNode *s, *r=L;
	char x;
	cout<<"请分别输入数据建立链表(以#作为结束标志):";
	cin>>x;
	while(x != '#')  //输入结束条件
	{
		s = new LNode[sizeof(LNode)];
		s->data = x;
		r->next = s;
		r=s;
		cin>>x;
	}
	r->next = NULL;
}

LinkList findPostfix(LinkList L1, LinkList L2)
{
	int len1=0, len2=0, distance=0;
	LinkList p1=L1, p2=L2;
	int i;

	while(p1->next != NULL)
	{
		p1 = p1->next;
		len1++;
	}
	while(p2->next != NULL)
	{
		p2 = p2->next;
		len2++;
	}

	p1 = L1;
	p2 = L2;
	if(len1 > len2)
	{
		distance = len1 - len2;
		for(i=0; i<distance && p1!=NULL; i++)
			p1 = p1->next;
	}
	else
	{
		distance = len2 - len1;
		for(i=0; i<distance && p2!=NULL; i++)
			p2 = p2->next;
	}
	
	while(p1!=p2 && p1->next!=NULL && p2->next!=NULL)
	{
		p1 = p1->next;
		p2 = p2->next;
	}
	return p1;
}

void main()
{
	LinkList L1, L2, p;
//	手动建立链表L1:loading
	L1 = new LNode[sizeof(LNode)];
	L1->next = NULL;
    LNode *s, *r=L1, *tag;	
	s = new LNode[sizeof(LNode)];
	s->data = 'l';
	r->next = s;
	r=s;
	s = new LNode[sizeof(LNode)];
	s->data = 'o';
	r->next = s;
	r=s;
	s = new LNode[sizeof(LNode)];
	s->data = 'a';
	r->next = s;
	r=s;
	s = new LNode[sizeof(LNode)];
	s->data = 'd';
	r->next = s;
	r=s;
	s = new LNode[sizeof(LNode)];
	s->data = 'i';
	r->next = s;
	r=s;
	tag=r;    //标记后缀位置
	s = new LNode[sizeof(LNode)];
	s->data = 'n';
	r->next = s;
	r=s;
	s = new LNode[sizeof(LNode)];
	s->data = 'g';
	r->next = s;
	r=s;
	r->next = NULL;

//	手动建立链表L2:being
	L2 = new LNode[sizeof(LNode)];
	L2->next = NULL;
	LNode *s1, *r1=L2;	
	s1 = new LNode[sizeof(LNode)];
	s1->data = 'b';
	r1->next = s1;
	r1=s1;
	s1 = new LNode[sizeof(LNode)];
	s1->data = 'e';
	r1->next = s1;
	r1=s1;
	
	r1->next = tag;   //使L2的后缀指向L1的后缀,两个后缀共享存储空间。

	p = findPostfix(L1, L2);
	
	cout<<"str1:loading, str2:being"<<endl;
	cout<<"str1和str2的共同后缀起始位置为:"<<p<<endl;
	cout<<"后缀起始位置的内容为:"<<p->data<<endl;
}

//L1:loading#, L2:being#

4)判断两个链表是否相交?

给定两个单链表,判断两个单链表是否相交?
假设两个单链表均没有环。

法一:
1.两次遍历分别求出链表长度
2.长链表先走两链表相差的步数
3.两链表同时走
若两指针地址相同,则为共同后缀的起始位置。
注:为满足两链表相交,需手动建立链表。

法二:
先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表头部第一个节点(尾部指针的next本来指向的是null)。
这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?
这样进行转换后就可以从链表头部进行判断了,其实并不用。通过简单的了解我们就很容易知道,
如果新链表是有环的,那么原来第二个链表的头部一定在环上。因此我们就可以从第二个链表的头部进行遍历的,
从而减少了时间复杂度(减少的时间复杂度是第一个链表的长度),找到第一个和第一个链表重复的节点即是两个链表的交点;
如果找到最后为null,则说明两个单向链表不相交.

判断是否存在环:
步骤:设置两个指针同时指向head,其中一个一次前进一个节点(P1),另外一个一次前进两个节点(P2)。
p1和p2同时走,如果其中一个遇到null,则说明没有环,如果走了N步之后,二者指向地址相同,那么说明链表存在环。

法三:
Hash计数法:如果两个链表相交则两个链表就会有共同的结点;而结点地址又是结点唯一标识。
因而判断两个链表中是否存在地址一致的节点,就可以知道是否相交了。
可以对第一个链表的节点地址进行hash排序建立hash表,然后针对第二个链表的每个节点的地址查询hash表,
如果它在hash表中出现则说明两个链表有共同的结点。
这个方法的时间复杂度为:O(max(len1+len2))但同时还得增加O(len1)的存储空间存储哈希表。

2. 在O(1)时间删除单链表结点

给定单链表L及其中一个结点地址p,定义一个函数实现在O(1)时间删除该结点。

方法: 把p的值赋给p->next,删除p->next.

#include<iostream>
using namespace std;
typedef struct LNode   //定义单链表
{
	int data;
	struct LNode *next;
}LNode, *LinkList;

void List_TailInsert(LinkList L)  //尾插法建立单链表
{
	LNode *s, *r=L;
	int x = 0;
	cout<<"请分别输入数据建立链表:";
	cin>>x;
	while(x != 9999)  //输入结束条件
	{
		s = new LNode[sizeof(LNode)];
		s->data = x;
		r->next = s;
		r=s;
		cin>>x;
	}
	r->next = NULL;
}

void deleteNode(LinkList L, LinkList p)
{
	LinkList q = p->next;
	p->data = q->data;
	p->next = q->next;
}

void printLink(LinkList L)//输出链表
{
	LinkList p;
	p = L->next;
	cout<<"删除后的链表为:";
	while(p != NULL)
	{
		cout<<p->data<<" ";
		p = p->next;
	}
	cout<<endl;
}

void main()
{
	LinkList L, p;
	L = new LNode[sizeof(LNode)];
	L->next = NULL;
	List_TailInsert(L);
	p = L->next->next;
	deleteNode(L, p);
	printLink(L);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值