【数据结构】--oj_合并两个有序链表(详解)

【数据结构】--oj_合并两个有序链表(详解)_第1张图片

目录

方法一:无头结点的方法 

方法二:有头结点的方法


题述:

已给函数头:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)

已给出链表的结构体定义:

struct ListNode
{
    struct ListNode* next;
    int val;
};

已知l1、l2分别指向两个链表

要求:

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。(完善mergeTwoLists函数

示例:【数据结构】--oj_合并两个有序链表(详解)_第2张图片

方法一:无头结点的方法 

思路:1、依次比较链表中的节点,每次取小的节点,尾插到新的链表后面。

问题一:找到较小的节点后,那是不是就要找到尾节点进行尾插,难道每次尾插完都找尾节点吗?这样效率肯定很低,所以我们可以再用一个指针tail来存尾节点。再创建一个指针head用来表示另一个没有头结点的新链表。 后续的尾插我们就可以尾插到head链表中

问题二:这个单链表是否有头结点,有头结点是一种情况,没有头结点就是另一种情况,相比而言有头节点的方式更简单,oj题一般都是默认链表无头结点的,但是我们可以创建头结点,所以这就可以推出两种方法

【数据结构】--oj_合并两个有序链表(详解)_第3张图片

【数据结构】--oj_合并两个有序链表(详解)_第4张图片

 代码如下:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	//如果其中一个为空,就返回另一个
	if (l1 == NULL)
	{
	//如果刚开始就有一个为空
	//那就直接返回另一个
	//如果两个都是NULL,那正好返回NULL就好了
		return l2;
	}
	if (l2 == NULL)
	{
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	while (l1 && l2)
	{//循环走下去的条件就是l1和l2均不为NULL
	//如果其中一个为空了,就说明走完了
		if (l1 ->val < l2->val)
		{
			if (head == NULL)
			{
			//第一次尾插的话,我们是在新链表无头结点的情况下
			// 后续的插入才会在有节点的情况,
			// 而这两种情况代码不同,所以要分类讨论
			//所以直接使头和尾被赋值为l1
			//tail主要是用来找尾节点的
				head = tail = l1;
			}
			else
			{
			//进入else,说明新创的链表中已经有节点,所以
			//直接插入在后面的节点
				tail->next = l1;//尾插节点
				tail = l1;//tail用来保存尾节点
			}
			l1 = l1->next;//每次尾插完一个数据,l1会往后走
		}
		else
		{
			if (head == NULL)
			{
				//第一次尾插
				head = tail = l2;
			}
			else
			{
				tail->next = l2;
				tail = l2;
			}
			l2 = l2->next;
		}
	}
	if (l1)
	{//如果l1不为空,那么就是l2走完了
	//此时直接把l1剩余部分尾插到新链表中即可
		tail->next = l1;
	}
	if (12)
	{
		tail->next = l2;
	}
	return head;
}

 上段代码的缺点就是while循环中对于l1和l2有几处重复部分,所以可以在上段代码的基础上再修改一下,思路一样,head原来为NULL,那我就先找一个最小的让head指向这个节点就行,就不用在while循环中l1和l2先判断if才行,下面是更简洁的代码:

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	//如果其中一个为空,就返回另一个
	if (l1 == NULL)
	{
		//如果刚开始就有一个为空
		//那就直接返回另一个
		//如果两个都是NULL,那正好返回NULL就好了
		return l2;
	}
	if (l2 == NULL)
	{
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	//先取两个链表中一个小的去做第一个
	//能做第一个,因为两个链表都是升序的
	if (l1->val < l2->val)
	{
		head = tail = l1;
		l1 = l1->next;
	}
	if (l1->val > l2->val)
	{
		head = tail = l2;
		l2 = l2->next;
	}
	while (l1 && l2)
	{//循环走下去的条件就是l1和l2均不为NULL
	//如果其中一个为空了,就说明走完了
		if (l1->val < l2->val)
		{
			tail->next = l1;//尾插节点
			tail = l1;//tail用来保存尾节点
			l1 = l1->next;//每次尾插完一个数据,l1会往后走
		}
		else
		{
			tail->next = l2;
			tail = l2;
			l2 = l2->next;
		}
	}
	if (l1)
	{//如果l1不为空,那么就是l2走完了
	//此时直接把l1剩余部分尾插到新链表中即可
		tail->next = l1;
	}
	if (l2)
	{
		tail->next = l2;
	}
	return head;
}

方法二:有头结点的方法

原来我们题目给的是没有头结点的,但是我们可以自己创建一个头结点,他不存储数据,他是为了使后面找元素方便,值得注意的是这个head和tail都要动态内存开辟一个,最后在用完之后还需要释放,并且最后不能直接return head,要return head->next,因为题目本来就没给你头结点,你就不能返回你自己创建的头,本质上应该从head->next开始

#include
#include
struct ListNode
{
	struct ListNode* next;
	int val;
};
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
	//如果其中一个为空,就返回另一个
	if (l1 == NULL)
	{
		//如果刚开始就有一个为空
		//那就直接返回另一个
		//如果两个都是NULL,那正好返回NULL就好了
		return l2;
	}
	if (l2 == NULL)
	{
		return l1;
	}
	struct ListNode* head = NULL, * tail = NULL;
	//哨兵位的头结点,head和tail刚开始均指向头结点
	head = tail = (struct ListNode*)malloc(sizeof(struct ListNode));
	while (l1 && l2)
	{//循环走下去的条件就是l1和l2均不为NULL
	//如果其中一个为空了,就说明走完了
		if (l1->val < l2->val)
		{
			tail->next = l1;//尾插节点
			tail = l1;//tail用来保存尾节点
			l1 = l1->next;//每次尾插完一个数据,l1会往后走
		}
		else
		{
			tail->next = l2;
			tail = l2;
			l2 = l2->next;
		}
	}
	if (l1)
	{//如果l1不为空,那么就是l2走完了
	//此时直接把l1剩余部分尾插到新链表中即可
		tail->next = l1;
	}
	if (l2)
	{
		tail->next = l2;
	}
	struct ListNode* list = head->next;
	free(head);
	return list;
}

你可能感兴趣的:(【数据结构】知识篇+代码讲解,链表,数据结构,算法)