链表面试题【数据结构】

从尾到头打印单链表:

递归

void SListPrintBack(PNode pHead)	//时间复杂度O(N)
{
	if (pHead)
	{
		SListPrintBack(pHead->_pNext);
		printf("%d   ", pHead->_Data);
	}
}

删除一个无头单链表的非尾节点(不能遍历链表):

找到要删除节点的下一个节点,将它的值付给要删除的结点,删除下一个节点

void EraseNotTail(PNode pos)		//替换法删除	
{
	PNode pDel = NULL;
	if (NULL == pos)
	{
		printf("error! not found %d!\n", pos->_Data);
		return;
	}
	pDel = pos->_pNext;	//pos的后一个节点
	pos->_Data = pDel->_Data;	//赋值
	pos->_pNext = pDel->_pNext;	
	free(pDel);
}

在无头单链表的一个节点前插入一个节点(不能遍历链表):

在该节点后插入一个新节点,将值交换

void InsertFront(PNode pos, DataType data)
{
	PNode pNewNode = NULL;
	if (NULL == pos)
	{
		printf("error! not found %d!\n", pos->_Data);
		return;
	}
	pNewNode = BuySListNode(pos->_Data);//直接创建一个值和pos一样的新节点,省去swap
	pNewNode->_pNext = pos->_pNext;		//链接
	pos->_pNext = pNewNode;			
	pos->_Data = data;					//直接将data赋值给pos
}

单链表实现约瑟夫环(JosephCircle)

1.构建环

2.获取M

3.报数----删结点----循环

void JosephCircle(PNode *ppHead, int Step)
{
	PNode pDel = NULL;
	PNode pCur = NULL;
	assert(ppHead);
	pCur = *ppHead;
	while (pCur->_pNext != pCur)	//只剩一个元素
	{
		//报数
		int count = Step;
		while (--count)		//移动step-1次
		{
			pCur = pCur->_pNext;
		}
		//删除(替换法删除,删除后一个节点)
		pDel = pCur->_pNext;
		pCur->_Data = pDel->_Data;
		pCur->_pNext = pDel->_pNext;
		free(pDel);	//最后会被销毁,可以不置空
	}
	*ppHead = pCur;		//只剩最后一个节点
}

逆置/反转单链表     

三个指针法

头插法(新节点,旧的头结点next置空)

void ReverseSList(PNode *ppHead)		//三指针法
{
	PNode pPreCur = NULL;
	PNode pCur = NULL;
	PNode pCurTail = NULL;
	assert(ppHead);
	if (NULL == *ppHead && NULL == (*ppHead)->_pNext)
	{
		return;
	}
	pCur = *ppHead;

	while (pCur)						//  p1   p2   p3  
	{
		pCurTail = pCur->_pNext;			//p3 = p2->next
		pCur->_pNext = pPreCur;				//p2->next = p1、断开
		pPreCur = pCur;					//p1 = p2;
		pCur = pCurTail;				//p2 = p3
	}
	*ppHead = pPreCur;					//更新头结点
}

PNode ReverseSList2(PNode pHead)		//新节点头插法
{
	PNode pCur = pHead;
	PNode pCurNext = NULL;
	PNode pNewHead = NULL;

	while (pCur)
	{
		pCurNext = pCur->_pNext;	
		pCur->_pNext = pNewHead;
		pNewHead = pCur;
		pCur = pCurNext;
	}
	return pNewHead;
}

void ReverseSList3(PNode *ppHead)	//头插法
{
	//标记头结点的下一个节点
	//将其插入到头结点之前
	PNode pCur = NULL;
	PNode pCurNext = NULL;
	assert(ppHead);
	pCur = (*ppHead)->_pNext;	//标记头结点的后一个节点
	(*ppHead)->_pNext = NULL;	//将头结点的Next域置空
	while (pCur)	
	{
		pCurNext = pCur->_pNext;	//标记待头插的结点的下一个节点,防止找不到
		pCur->_pNext = *ppHead;		//头插
		*ppHead = pCur;			
		pCur = pCurNext;			
	}
}


单链表排序(冒泡排序)

   冒泡排序(三指针、pcur pnext  ptail

void BubbleSort(PNode pHead)
{
	PNode pCur = NULL;			
	PNode pNext = NULL;				
	PNode pTail = NULL;				//尾结点
	int flag = 0;
	if (NULL == pHead || NULL == pHead->_pNext)	//空链表或只有一个结点
	{
		return;
	}

	while (pHead != pTail)		//控制循环次数
	{
		flag = 0;
		pCur = pHead;
		pNext = pCur->_pNext;
		while (pNext != pTail)	//每一次循环
		{
			if (pCur->_Data > pNext->_Data)
			{
				DataType tmp = pCur->_Data;
				pCur->_Data = pNext->_Data;
				pNext->_Data = tmp;
				flag = 1;
			}
			pCur = pCur->_pNext;
			pNext = pCur->_pNext;
		}
		if (!flag)			//未发生交换
		{
			return;
		}
		pTail = pCur;					//将循环控制的尾指针向前移动
	}
}

合并两个有序链表,合并后依然有序

   定义新的头结点,依次从两个链表找到较小(大)的,链接到新的链表上即可

PNode MergeList(PNode pHead1, PNode pHead2)
{
	PNode pNewHead = NULL;
	PNode pTail = NULL;		//连接最后没连接完的链表
	PNode pNode1 = pHead1;		//第一个链表的指针
	PNode pNode2 = pHead2;		//第二个链表的指针
	if (NULL == pHead1 || NULL == pHead2)		//有一个空链表
	{
		return (pHead1) ? pHead1 : pHead2;
	}
	if (pNode1->_Data < pNode2->_Data)		//将头结点和值较小链表的头结点先连接起来
	{
		pNewHead = pNode1;
		pTail = pNode1;
		pNode1 = pNode1->_pNext;
	}
	else
	{
		pNewHead = pNode2;
		pTail = pNode2;
		pNode2 = pNode2->_pNext;
	}

	while (pNode1 && pNode2)			//两个链表都未插完
	{
		if (pNode1->_Data < pNode2->_Data)		//将值较小的插入
		{
			pTail->_pNext = pNode1;
			pNode1 = pNode1->_pNext;
		}
		else
		{
			pTail->_pNext = pNode2;
			pNode2 = pNode2->_pNext;
		}
		pTail = pTail->_pNext;			//尾指针向后移动
	}
	if (pNode1)							//第一个链表未插完
	{
		pTail->_pNext = pNode1;
	}
	if (pNode2)							//第二个链表未插完
	{
		pTail->_pNext = pNode2;
	}
	return pNewHead;
}


查找单链表的中间节点,要求只能遍历一次链表

快慢指针

PNode FindMidNode(PNode pHead)	//快慢指针
{
	PNode pFast = pHead;
	PNode pSlow = pHead;
	PNode pPreSlow = NULL;
	if (NULL == pHead)
	{
		return NULL;
	}
	while(pFast && pFast->_pNext)		//奇数个(fast->next 为空)、偶数个(fast为空)
	{
		pPreSlow = pSlow;
		pSlow = pSlow->_pNext;			//走一次	
		pFast = pFast->_pNext->_pNext;	//走两次
	}
	if (pFast)
	{
		return pSlow;
	}
	else
	{
		return pPreSlow;	//偶数个返回前一个节点
	}
//	return pSlow;					//偶数个节点时返回后一个节点
}


查找单链表的倒数第k个节点,要求只能遍历一次链表

快指针先走K步,然后快慢指针一起走

PNode FindLastKNode(PNode pHead, int K)		
{
	PNode pFast = pHead;
	PNode pSlow = pHead;
	if (NULL == pHead || 0 == K)
	{
		return NULL;
	}
	//让快指针先走K步
	while (K--)
	{
		if (NULL == pFast)
			return NULL;
		pFast = pFast->_pNext;
	}
	//快慢指针一起走,快指针为空时停止
	while (pFast)
	{
		pFast = pFast->_pNext;
		pSlow = pSlow->_pNext;
	}
	return pSlow;
}

删除链表的倒数第K个结点

找到第K个节点(头结点,非头结点),借助pre指针删除

int DeleteLastKNode(PNode *ppHead, int K)
{
	PNode pFast = NULL;
	PNode pPreSlow = NULL;
	PNode pSlow = NULL;
	assert(ppHead);
	if (NULL == *ppHead || 0 == K)
	{
		return 0;
	}
	pFast = *ppHead;
	pSlow = *ppHead;
	//快指针先走K步
	while (K--)			
	{
		if (NULL == pFast)	//快指针走的步数必须小于K
		{
			return 0;
		}
		pFast = pFast->_pNext;
	}
	//快慢指针一起走
	while (pFast)
	{
		pPreSlow = pSlow;
		pSlow = pSlow->_pNext;
		pFast = pFast->_pNext;
	}
	if (*ppHead == pSlow)	//删头结点
	{
		*ppHead = pSlow->_pNext;
	}
	else                   //非头结点
	{
		pPreSlow->_pNext = pSlow->_pNext;
	}
	free(pSlow);
	return 1;
}

判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算

每个算法的时间复杂度&空间复杂度。

快慢指针()

环的长度: 先判断带环

环的入口点:

结论:PL-----1-------PHead一次走一步

      PM----1------相遇点

      If(PL == PM)  入口点二者的相遇点即是入口点

1.两个指针一个从Head处走一个从Meet处走,相遇即为相遇点

2.在快慢指针相聚点截断,将环链表变为两个相交链表,因为相交链表尾部重合呈Y字型,  

  //     求两个链表长度之差K,再令一个指针从长链表开始先走K步,令另一个指针从短链表头开始,  

 //     两链表一起走,相遇点就为入口点 

 

PNode HasCircle(PNode pHead)
{
	PNode pFast = pHead;
	PNode pSlow = pHead;

	while (pFast && pFast->_pNext)
	{
		pFast = pFast->_pNext->_pNext;	//快指针一次走两步
		pSlow = pSlow->_pNext;

		if (pFast == pSlow)				//判断是否相遇
		{
			return pFast;
		}
	}
	return NULL;
}
int GetCircleLen(PNode pHead)		//环的大小
{
	PNode pCur = NULL;
	int size = 1;			//计算大小时 会少计算meet的前一个节点
	PNode MeetNode = HasCircle(pHead);
	if (NULL == MeetNode)
	{
		return 0;
	}
	pCur = MeetNode;
	while (pCur->_pNext != MeetNode)		//从meetNode开始计算
	{
		size++;
		pCur = pCur->_pNext;
	}
	return size;
}

PNode GetEnterNode(PNode pHead, PNode pMeetNode)		//入口点
{
	PNode pH = pHead;		//从头结点
	PNode pM = pMeetNode;

	if (pH == NULL || pM  == NULL)
	{
		return NULL;
	}
	while (pM != pH)
	{
		pM = pM->_pNext;
		pH = pH->_pNext;
	}
	return pM;
}

判断两个链表是否相交,若相交,求交点。(假设链表不带环):

直接看两个链表的最后一个结点(判断相交)

分别计算两条链表的长度,让长得一个先走差值步,然后一起走,最后走到相同的位置即为交点

int IsCrossWithoutCircle(PNode pHead1, PNode pHead2)
{
	PNode pTail1 = pHead1;
	PNode pTail2 = pHead2;
	if (NULL == pHead1 || NULL == pTail2)
	{
		return 0;
	}
	while (pTail1->_pNext)
	{
		pTail1 = pTail1->_pNext;
	}
	while (pTail2->_pNext)
	{
		pTail2 = pTail2->_pNext;
	}
	return pTail1 == pTail2;
}


PNode GetCrossNode(PNode pHead1, PNode pHead2)		//交点
{
	int size1 = 0;
	int size2 = 0;
	PNode pCur1 = NULL;
	PNode PCur2 = NULL;
	int k = 0;
	if (NULL == pHead1 || NULL == pHead2)
	{
		return NULL;
	}
	if (!IsCrossWithoutCircle(pHead1, pHead2))
	{
		return 0;
	}
	size1 = SListSize(pHead1);
	size2 = SListSize(pHead2);
	k = size1 - size2;
	if (k > 0)			//长的先走长度差值步
	{
		while (k--)
		{
			pCur1 = pCur1->_pNext;
		}
	}
	else
	{
		while (k++)		//从负值++
		{
			PCur2 = PCur2->_pNext;
		}
	}
	while (pCur1 != PCur2)		//两指针一起走	//肯定有交点最后一定相遇
	{
		pCur1 = pCur1->_pNext;
		PCur2 = PCur2->_pNext;
	}
	return pCur1;
}

判断两个链表是否相交,若相交,求交点。(假设链表可能带环)

1.两个链表都不带环

2.两个链表都带环

3.一个带一个不带

求交点

环内相交:

任一环内结点为交点

环外相交:

从任意相遇点的位置开始的计算两个链表的长度,(转换成无环相交)

从相遇点断开,链接链表的头结点,构成新的环,求环入口点,完成后恢复原来链表的状态


复杂链表的复制。一个链表的每个节点,有一个指向next指针指向

下一个节点,还有一个random指针指向这个链表中的一个随机节点

或者NULL,现在要求实现复制这个链表,返回复制后的新链表。

1.在原链表每个节点后插入值与当前结点相同的新节点

2.给新节点的随机指针域赋值----原链表当前结点随机指针域的Next

3.将新节点从原链表中拆下

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值