线性表--单链表(二)

线性表–单链表(二)

1、链表数据结构

typedef struct node {
	data_t data;
	struct listnode *next;
}linknode, *linklist;

2、如何找出链表的第K个元素?

我们在日常的思维中,输入一个链表并输出其第K个元素符合我们的习惯,那么倒数第K个结点该怎么找到呢?

linklist getKthFromEnd(linklist head, int k) {
	linklist former = head;
	linklist latter = head;
	int i = 0;
	for (i = 0; i < k; i++) {
		former = former->next;
	}
	
	while (former) {
		latter = latter->next;
        former = former->next;
	}
	
	return latter;
}

算法思路:

快慢指针,先让快指针走K步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第K个结点。

①实际的思路,其实就是两个指针相互相隔K个元素,此时二者以相同的间隔移动到链表的最后,此时慢指针也就成为了链表的倒数第K个结点。

②当然如果链表不长的话,也可以通过先遍历链表所有元素,并记录元素个数N。然后从开头移动到N-K+1位置的数据域即为所找元素。

3、如何找出链表的中间结点

给定一个带有头结点head的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

linklist middleNode(linklist head) {
	linklist sloweer = head;
	linklist faster = head;
	
	while ((faster != NULL) && (faster->next != NULL)) {
		faster = faster->next->next;
		slower = slower->next;
	}
	
	return slower;
}

算法思路:这是一个典型的双指针问题,定义一个快指针,一个慢指针。快指针一次走两步,慢指针一次走一步。当快指针走到结尾时,慢指针指向的就是中间结点。

注:这里注意一个细节:题目中“若有两个中间结点,则返回第二个中间结点”。此时可以在草稿纸上写写画画,就拿自己74的左右手的两根指头同步移动,可以得出:快指针可以前进的条件是:当快指针和当前快指针的下一个结点都非空。

如果题目要求“在两个中间结点的时候,返回第一个中间结点”,此时,快指针可以前进的条件是:当前快指针的下一个结点和当前快指针的下下一个结点都非空。

4、反转链表

在上一篇中,我们已经写了反转链表的相关算法,但那个是一般的“新链表头部插值法”,这里我们再提出一种递归的方法。

linklist reverselist (linklist head) {
	if (head == NULL || head->next == NULL) {
		return head;
	}else {
		linklist mid = head;
		linklist latter = mid->next;
		
		head = reverselist(latter);
		
		latter->next = mid;
		mid->next = NULL;
		return head;
	}
}

①反转链表实际就是重新指向结构体中的next指针。我们需要修改下一个结点的next指针指向前一个结点。所以在遍历链表时候我们需要逐个修改链表的指针指向。这个题目也可以用递归来做,一直递归到链表的最后一个结点,该节点就是反转后的头结点,记作head。

②此后,每次函数在返回的过程中,让当前结点的下一个结点的next指针指向当前结点。同时,让当前结点的next指针指向null,从而事项从链表尾部开始的局部反转。当递归函数全部出栈后,链表反转完成。

③递归程序怎么设计?

​ 递归程序我们可以看做是嵌套的程序,但嵌套过程繁杂,我们怎么简洁地区考虑呢?这里提出一点我自己的看法,以上述程序为例,总的可以分为三部分,第一部分为linklist mid = head; linklist latter = mid->next;,中间部分为head = reverselist(latter); 结尾部分为latter->next = mid;mid->next = NULL;return head;因而我们可以这么认为,中间部分递归函数的之前部分,即第一部分为“递归进入”执行程序,而递归函数后,即最后一部分为 “递归返回”执行程序,也就是函数通过递归函数,执行N遍第一部分,然后再执行N遍最后一部分。最后达到算法效果。当然需要考虑递归的特点,即其中的变量随着函数一层层进入和返回,是不断调整和变化的。

5、环形链表

给定一个链表,返回链表开始入环的第一个节点。如果链表无环,则返回NULL。

linklist detect_cycle(linklist head) {
	linklist slow = head;
	linklist fast = head;
	
	while (fast != NULL && fast->next != NULL) {
		slow = slow->next;
		fast = fast->next->next;
		
		if (slow == fast) {
			fast = head;
			while (slow !=fast) {
				slow = slow->next;
				fast = fast->next;
			}
			return slow;
		}
	return NULL;
}

解题思路
在这里插入图片描述

假设有两个指针,分别为快慢指针fast和slow,快指针每次走两步,慢指针每次前进一步,如果有环则两个指针必定相遇;
A:链表起点
B:环起点
C:相遇点
X:环起点到相遇点距离
Y:链表起点到环起点距离
R: 环的长度
s:第一次相遇时走过的路程
慢指针slow第一次相遇走过的路程 S1 = Y + X: ( 1)

快指fast第一次相遇走过的路程 S2=2S1 = Y + X + NR;(2)

说明:快指针的速度是慢指针的两倍,相同时间内路程应该是慢指针的两倍,Y +X+ NR是因为快指针可能经过N圈后两者才相遇:
把(1)式代入(2)式得 : Y = NR -X:表明头节点和C点的慢指针会在B点相遇,此处就是环节点

5、单链表相交,如何求交点?

给定两个( 单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第i个节点是同一节点(引用完全相,则这两个链表相交。

linklist get_intersection_node(linklist headA, linklist headB) {
	linklist p = headA;
	linklist q = headB;
	
	while (p != q) {
		if (p == NULL) {
			p = headB;
		}else {
			p = p->next;
		}
		
		if (q == NULL) {
			q = headA;
		}else {
			q = q->next;
		}
	}
	
	return q;
}

算法思路:

根据题意,两个链表相交的点是指: 两个指针指向的内容相同,则说明该结点记在A链表上又在B链表上,进而说明A和B是相交的而对于相交的情况,两条链表一定是这种结构:
在这里插入图片描述

为什么呢?
因为如果链表A和链表B相交于D的话,那么说明D结点即在A上又在B上,而D之后的元素自然也就均在A和B上了,因为他们是通过next指针相连的.
如果有相交的结点D的话,每条链的头结点先走完自己的链表长度,然后回头走另外的一条链表,那么两结点定为相交于D点,因为这时每个头结点走的距离是一样的,都是 AD + BD + DC,而他们每次又都是前进1,所以距离相同,速度又相同,固然一定会在相同的时间走到相同的结点上,即D点

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值