算法精解(三):C语言描述(链表常见问题)

1.如何判断两个单链表是否相交,如果相交,找出交点(两个链表都不存在环)

如果两个单链表相交,那应该呈“Y”字形,则交点之后的节点是相同的。

所以判断是否相交,只需看两个链表的最后一个节点是否为同一个即可。

假设两个单链表的长度分别为L1、L2(L1 > L2),则(L1-L2)的值就是交汇之前两个链表的长度差;

因此,只有让更长的链表先走L1-L2步,然后两个链表开始一起走,如果某次走到一个相同的节点,该节点即为交点。

typedef struct ListNode{
	int data;
	struct ListNode *next;
}ListNode;               //定义节点

static int GetListLength(ListNode *head)
{
	int n=0;
	while(head != NULL)     
	{
		n++;
	}
	return n;
}

static ListNode * FindCommonNode(ListNode *a,ListNode *b)
{
	int i;
	int n1 = GetListLength(a);
	int n2 = GetListLength(b);
	
	//如果a<b,则互换a,b位置重新取长度
	if(a<b)
	{
		return FindCommonNode(b,a);
	}

	for(i=0;i<n1-n2;i++)  //较长的先走多出长度
	{
		a = a->next;
	}
	//相同长度时,同时向下走,指向相同时,则为交点
	while(a != NULL && a != b)
	{
		a = a->next;
		b = b->next;
	}
	return a;
}

2.判断一个链表是否有环,并找到环的入口点 

如果一个单链表有环,那应该呈“6”字形或“O”形。

设置两个指针(fast, slow),初始值都指向头节点,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定 相遇:如果链表是呈"O"字形,则slow刚好遍历完一次的时候,与fast相遇;如果呈“6”字形,则更早相遇。

当fast若与slow相遇时,slow还没有遍历完链表,而fast已经在环内循环了n圈(1<=n)。假设slow走了s步,则 fast走了2s步(fast步数还等于s 加上在环上多转的n圈),设环长为r,则:

2s = s + nr,简化为 s= nr

s = x + y,x为链表起点到环入口点的距离,y是slow在环内走过的距离;

可以得到 x = y - s = y - nr,从链表头、相遇点分别设一个指针(p1, p2),每次各走一步,当p1走过距离x时到达入口点,而p2走过的距离为y-nr,y是相遇点与入口点的距离,因此y也走到了入口点,也就是说p1、p2在环入口点相遇了。

//链表是否有环
static ListNode* FindLoopPort(ListNode* head)
{
	ListNode* slow = head;
	ListNode* fast = head;
	
	//找相遇节点
	while(fast != NULL && fast->next != NULL)
	{
		slow = slow->next;
		fast = fast->next->next;
		if(slow == fast)
			break;
	}
	if(fast == NULL || fast->next ==NULL)  //走完链表,无环
		return NULL;
		
	//有环时,此时fast在相遇点,slow设置为起点,再相遇时,为环入口点
	slow = head;
	while(slow != fast)
	{
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}

 3.求一个单链表(无环)的中间节点

设置两个指针(fast, slow),初始值都指向头节点,slow每次前进一步,fast每次前进二步,当fast走到末尾时,slow刚好指向中间节点。

4.假如链表长度为N,如何返回链表的倒数第K个结点

假设用两个指针,指针P1先走K-1步,然后指针P2才开始走,当指针P1遍历完链表时,P2还剩K-1个结点没有遍历,此时,P2为第K个节点,返回P2即可。

//求倒数第n个节点
ListNode *FindLastKNode(ListNode *Head, int n)
{
    ListNode* p1=head;
    ListNode* p2=head;
    while(--n == 0 && p1 != NULL)
    {
        p1 = p1->next;
    }
    if(p1 == NULL) return NULL;
    while(p1->next != NULL)
    {
        p2 = p2->next;
        p1 = p1->next;
    }
    return p2;
}

5.如何反转一个单链表(或逆序输出)

static Node* ReverseList(Node* Head)
{
	Node* pNode = Head;
	Node* pNext = NULL;
	Node* pPrev = NULL;

	while (pNode)
	{
		pNext = pNode->next;
		if (NULL == pNext)
		{
			Head = pNode;
		}
		pNode->next = pPrev; //断开原本的链,反转指向
		pPrev = pNode;       //将逆置后的头,付给pPrev,pPrev始终为逆置后链表的第一位
		pNode = pNext;       //将节点后推一位,循环反转

	}
	return Head;
}

//递归实现逆置,此时默认为有头结点的链表
 Node* reverselist1(Node* head)
{
	static Node *h = head; //记录头结点 
	Node *c = NULL;
    //当遍历到链表结尾部分是,a为倒数第二位,b为倒数第一位
	if (head == NULL || head->next == NULL)    
		return head; 
	c = reverselist1(head->next); //开始递归
	if (head != h)      //没有返回到头结点时,从最后一位依次逆转指向,并把逆转后的节点返回,继续进行逆转
	{
		head->next->next = head;
		head->next = NULL;
		return c;
	}
	else        //返回到头结点时,将已经全部逆置的链表附在头结点后面,完成逆置。返回头结点。
	{
		head->next = c;
		return head;
	}
	
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章