数据结构学习:有关带头结点的单向链表的常见考题(c语言)

1.将链表中pp结点之后的结点原地逆置(反转)。

解题思路:
通过设置两个指针ss\ssnext指针,其中一个ss指针指向pp结点的下一个节点,将pp指针后面的结点置空,留下链表中在pp结点前的元素,再利用设置的另一指针ssnext指针保存ss指针下一结点的信息,接着将ss指向的结点插入pp结点的后面,ss指针接着再指向指向下一结点,直到ss指针为空。

//1.将链表中pp结点之后的结点原地逆置(反转)。
void ReverseList(LNode *pp)
{
	LNode *ss,*ssnext;//设置两个指针ss\ssnext。 
	
	ss=pp->next;//ss指针指向pp结点的下一个节点。 
	pp->next=NULL;//将pp指针后面的结点置空,留下链表中在pp结点前的元素。 
	
	while(ss!=NULL)
	{
		ssnext=ss->next;//ssnext指针保存ss指针下一结点的信息。 
		
		//将ss指向的结点插入pp结点的后面。 
		ss->next=pp->next;
		pp->next=ss;
        
        ss=ssnext;//ss指针接着再指向下一结点。 
	} 
}

2.反向打印链表中全部的元素。

解题思路:
利用递归实现,当指针一直没有指向表尾时,便一直调用自身函数,直到指针为空返回,便从链表结尾从后往前执行输出语句。 时间复杂度为O(n)。

//2.反向打印链表中全部的元素。
void PrintList1(LNode *pp)
{
	if(pp == NULL) return;//判断指针是否为空。 
	
	PrintList1(pp->next);//调用自身函数。 
	
	printf("%3d",pp->data);//输出语句。 
}

3.找出带头结点的单链表倒数第k(k>0)个结点。

解题思路
可以设置两个指针,一个先出发设为fast指针,一个后出发设为slow指针,先让fast指针从表头移动到正数第k个结点,再设置另slow指针保持与先出发的指针一样的速度,向后移动,直到fast指针移动到表尾,这时slow移动的位置就是表长减去k的位置,也就是倒数第k个位置。

//3.找出带头结点的单链表倒数第k(k>0)个结点。
LNode *FindKNode(LinkList LL,unsigned int kk)
{
	LNode *fast=LL;//设置一个先出发的fast指针。 
	int ii=0;
	while((fast!=NULL)&&(ii<kk))
	{
		fast=fast->next;//fast指针移动到第k个结点。 
		ii++;
	}
	if(fast == NULL) return NULL;//当fast指针等于空时,说明表长小于k,返回空。 
	
	LNode *slow=LL->next;//设置一个后出发的slow指针。 
	while(fast!=NULL)//当fast指针移动到表尾的时候slow指针刚好移动到第k个结点位置 
	{
		slow=slow->next;
		fast=fast->next;
	}
	
	return slow; 
}

4.判断单链表是否有环,并找到环的入口。

解题思路:
思路一
慢指针走一步,快指针走两步。快指针是慢指针速度的两倍,当快指针的路程是慢指针的两倍时,它们会相遇。
注意找环的入口的时候,需要通过计算:设从表头到环入口的距离为len,环的周长为peri,在慢指针进入环中的长度为x后与快指针相遇,快指针经过n圈环与慢指针相遇。
已知快指针的路程为两倍的慢指针的路程, 则可以得到:
len+n* peri+x=2*(len+x)整理可得:len=n*peri-x,因此在快慢指针相遇后再设置一个指针从表头出发,保持与慢指针一样的速度 ,两个指针将会在环入口相遇。
图解:
带环链表

//4.判断单链表是否有环,并找到环的入口。
LNode *FindLoop(LinkList LL)
{
	LNode *fast,*slow;//设置快慢指针。 
	fast=LL;
	slow=LL;
	while(fast!=NULL)
	{
		fast=fast->next->next;//快指针fast走两步。 
		slow=slow->next;//慢指针slow走一步。 
		if(slow == fast) break;//快指针fast的路程是慢指针slow的两倍时,它们会相遇。
	} 
	if(fast == NULL) { printf("该链表没有环!\n"); return NULL; }//当快指针fast走到表尾时证明该链表没有环。 
	
	
	//寻找环的入口。 
	LNode *enter=LL;//设置一个指针指向头结点。 
	
	while(slow!=enter)
	{
		slow=slow->next;
		enter=enter->next;
	}
	
	return enter;
}

思路二
还可以通过指针每走一步就遍历它所经过的结点,如果找到了相同的结点,那么就证明该链表具有环,且环的入口就是所找到的相同的结点。时间复杂度为O(n^2)

5.找出两个汇聚单链表的公共结点,如果没有汇聚,返回空,如果有,返回汇聚的第一个结点。

解题思路
对于解决这道题,可以先求出两个链表长度之差n,再将较长链表的指针从头移动 n个结点,然后让较短链表的指针与较长链表的指针同时移动,直到找到汇聚点或者其中一个链表的表尾。时间复杂度为O(n)。

//5.找出两个汇聚单链表的公共结点,如果没有汇聚,返回空,如果有,返回汇聚的第一个结点。
//先写一个求链表长度的函数。
// 求链表的长度,返回值:>=0-表LL结点的个数。
int  LengthList(LinkList LL)
{
  if (LL == NULL) { printf("链表LL不存在。\n"); return 0; } // 判断链表是否存在。

  LNode *pp=LL->next;  // 从头结点的下一个节点开始。

  int length=0;

  while (pp != NULL) { pp=pp->next; length++; }

  return length;
}

LNode *FindJoin(LinkList La,LinkList Lb)
{
	int lena,lenb,len;//定义三个整型变量,分别为La,Lb链表的长度,len为两个链表长度之差。 
	lena=LengthList(La);
	lenb=LengthList(Lb);
	
	len=lena-lenb;//求出两个链表长度的差值。 
	
	while(lena>lenb&&len>0)
	{// 链表长度La>Lb的情况 
		La=La->next;
		len--;
	}
	while(lenb>lena&&len<0)
	{// 链表长度La<Lb的情况 
		Lb=Lb->next;
		len++;
	}
	
	while(La!=Lb&&La!=NULL&&Lb!=NULL)
	{
		La=La->next;
		Lb=Lb->next;
	}
	if(La == NULL||Lb == NULL) return NULL; 
	
	if(La == Lb) return La;	
}

6.将线性表L=(a1,a2,a3,…,an-2,an-1,an)变成L=(a1,an,a2,an-1,a3,an-2,…)。数据结点的个数为偶数。

题目详情
假设线性表L=(a1,a2,a3,…,an-2,an-1,an)采用带头结点的单链表保存,数据结点的个数为偶数。
请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点。
最后得到线性表L=(a1,an,a2,an-1,a3,an-2,…)。
解题思路
对于解决这道题,分为三步解答,详情见代码。

//6.将线性表L=(a1,a2,a3,...,an-2,an-1,an)变成L=(a1,an,a2,an-1,a3,an-2,...)。数据结点的个数为偶数。
void ReplaceList(LinkList LL)
{
	if(LL == NULL) return;//链表为空,退出函数。 
	
	//第一步:设置两个速度不一样的指针,其中快指针fast走两步,慢指针slow走一步。 
	LNode *fast,*slow;
	fast=LL;
	slow=LL;
	
	while(fast->next!=NULL)
	{
		fast=fast->next->next;
		slow=slow->next;
	}
	//循环过后fast停留在链表最后一个结点,slow在第n/2个结点。 
	
	//第二步:将slow指针之后的结点原地逆置。
	//如由a1,a2,a3,a4,a5,a6,a7,a8逆置成a1,a2,a3,a4,a8,a7,a6,a5 
	ReverseList(slow);//将slow后面的结点原地逆置的自定义函数。

    //第三步:将slow指针之后的结点一次插入到前面的结点中间 
    LNode *front,*back,*tmp;
    front=LL->next;//front指针指向链表第一个数据结点。 
    back=slow->next;//bcak指针指向slow指针的后一个结点 
    
    slow->next=NULL;//将链表只留下前半段,这一步很重要。 
	
	//插入操作 
	while(back!=NULL)
	{
		tmp=back->next;//用一个临时指针变量暂存back后一个结点的信息。
		
		//将back结点插入到前面的结点中。 
		back->next=front->next;
		front->next=back;
		
		back=tmp;//back指针重新指回后面的结点。 
		front=front->next->next;//将front指向下一个需要插入结点的前一个结点的位置。 如由a1,a8,a2,a3...中a1到a2。 
	}
	 
	/*
	//注意:如果使用下面这段代码进行循环将会出错,因为slow是在链表中的指针。
	LNode *pp,*tmp;
	pp=LL->next;
	slow=slow->next;
	 
	while(slow!=NULL);
	{
		tmp=slow->next;//这里相当于用tmp指向slow结点的下一结点;
		
		//下面这一步使slow结点的下一个结点插入到pp结点之后 
		slow->next=pp->next;pp->next=slow;
		
		slow=tmp;//slow指向slow的下一结点
		//也就是指向了插入到pp之后的那个结点,因此slow指针的轨迹将会不断在链表中形成环,永远不会为空。 
		pp=pp->next->next;
	}
	*/
}

参考学习视频:B站数据结构学习视频

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
计算二叉树的节点个数可以使用递归的方式来实现。以下是一个用C语言实现的示例代码: ```c #include <stdio.h> #include <stdlib.h> // 定义二叉树节点结构 struct TreeNode { int data; struct TreeNode* left; struct TreeNode* right; }; // 创建新节点 struct TreeNode* createNode(int data) { struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode)); if (newNode == NULL) { printf("内存分配失败!\n"); exit(1); } newNode->data = data; newNode->left = NULL; newNode->right = NULL; return newNode; } // 计算二叉树节点个数的函数 int countNodes(struct TreeNode* root) { if (root == NULL) { return 0; } return 1 + countNodes(root->left) + countNodes(root->right); } int main() { // 创建二叉树 struct TreeNode* root = createNode(1); root->left = createNode(2); root->right = createNode(3); root->left->left = createNode(4); root->left->right = createNode(5); // 计算节点个数 int count = countNodes(root); printf("二叉树的节点个数为:%d\n", count); return 0; } ``` 这段代码中,我们首先定义了一个二叉树节点的结构体`TreeNode`,包含数据`data`以及左右子节点的指针`left`和`right`。然后,我们创建了一个`createNode`函数用于创建新的节点,并初始化节点的数据和子节点指针。接下来,我们定义了一个`countNodes`函数,用于递归计算二叉树的节点个数。最后,在`main`函数中创建了一个二叉树,并调用`countNodes`函数计算节点个数并输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幺九久99

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值