C语言归并排序

归并排序

是建立在归并操作上的一种有效的排序算法,主要可以分为两步,分别是分割与合并,以下将以排序链表(交换节点)来说明。

百度百科:归并排序

图片来源:百度百科

1. 合并

合并是归并算法的重点,所以只要解决了合并的问题那归并的算法就比较简单了,所以这里先解决合并问题,从上图中可以看出整个合并的过程中,就是将两个有序链表合并为一个有序链表。

//链表节点的结构体
typedef struct node
{
    int val;
    
    struct node *next;
}Node;

//合并两个有序链表为一个有序链表
//参数1:有序链表1
//参数2:有序链表2
//返回值:合并后的有序链表
Node *merge_list(Node *list1,Node *list2)
{
	Node *head = NULL;  //合并后的有序链表的头指针
	Node *min_node = NULL;	//用于临时储存两条有序链表中

    //当链表1与链表2都为空时跳出循环
	while(list1 != NULL || list2 != NULL)
	{
		if(list1 != NULL)
		{
            //当list1不为空时执行这个部分
			if(list2 != NULL)
			{
                //当list1与list2都不为空时执行这个部分
				min_node = select_min(&list1,&list2);//通过select_min()函数找到两条链表中最小的头结点,并将其移除原来的链表,并将节点返回出来
				min_node -> next = NULL;//由于以下是用尾插法,所以需要将这个节点的指针域初始化为NULL
                
				if(head == NULL)
				{
                    //尾插法,需要判断新链表是否有节点,如果没有,直接将新链表的头指针指向节点
					head = min_node;
				}
				else
				{
                    //如果链表不为空则执行以下代码
					Node *tail = find_tail(head);//通过find_tail()找到新链表的尾结点
					tail -> next = min_node;//将取下的节点加入到链表尾
				}
			}
			else
			{
                //当list2为空时执行这个部分代码
            	//当list2为空时,不再需要对比两条链表,只需要把list1整条加入到新链表的尾部,并跳出循环
				Node *tail = find_tail(head);//通过find_tail()函数找到新链表的尾结点
				tail -> next = list1;//将list1完整加入到新链表中

				break;
			}
		}
		else
		{
            //当list1为空时执行这个部分代码
            //当list1为空时,不再需要对比两条链表,只需要把list2整条加入到新链表的尾部,并跳出循环
			Node *tail = find_tail(head);//通过find_tail()函数找到新链表的尾结点
			tail -> next = list2;//将list2完整加入到新链表中
			
			break;
		}
	}

	return head;//返回两条有序链表合并后的新链表
}

//找到两条链表中最小的节点并将其取下,并返回
//参数1:用于比对的链表1,由于有可能会改变其链表的头结点,所以需要传入其头指针的地址,所以为二级指针
//参数2:用于比对的链表2,由于有可能会改变其链表的头结点,所以需要传入其头指针的地址,所以为二级指针
//返回值:返回被取下的最小节点的地址
Node* select_min(Node **l1,Node **l2)
{
	Node *temp = NULL;

	if((*l1) -> val > (*l2) -> val)
	{
		temp = *l2;
		
		*l2 = (*l2) -> next;

		return temp;
	}
	else
	{
		temp = *l1;

		*l1 = (*l1) -> next;

		return temp;
	}
}

//用于找到链表的尾结点
//参数:需要找尾结点的链表的头指针
//返回值:传入链表的尾结点的地址
Node *find_tail(Node *head)
{
    //遍历链表,当节点的指针域为NULL时,说明当前节点为尾结点,结束循环
	while(head -> next != NULL)
	{
		head = head -> next;
	}

	return head;
}

3. 分割

归并的分割是二等分,也就是说分割点为长度的一半:
l e n / 2 len /2 len/2
在链表中因为需要将找到分割点的前一个节点,将其指针域赋值为NULL确保前半段为一条完整的链表,在用另一个头指针记录分割点来形成两条链表。在这个分割的过程中,需要将一开始的链表分割带不能在分割,也就是一个节点单独称为一条链表,所以需要在代码中用到递归。所以可以设计一下函数,先分割,再将其合并合并为一条有序链表,这个过程中也就完成了归并排序:

//归并排序
//参数1:需要分割并合并的链表的头指针
//返回值:合并后的链表的头节点的地址
Node *sort_list(Node *head)
{
	int len = list_len(head);//使用list_len()函数求链表的长度

    //如果传入链表的长度为1,则不能再分割,作为递归的出口
	if(len == 1)
	{
		return head;
	}
	
	Node *temp = head;//申请另一个头指针用于储存分割点的前一个节点

    //len为链表长度,所以分割点为len/2,所以通过循环找到分割点的前一个节点,则循环len/2-1次
    //循环结束后temp将指向分割点的前一个节点
	for(int i = 0;i < ((len / 2) - 1);i++)
	{
		temp = temp -> next; 
	}

	Node *head2 = temp -> next;//申请一个节点用于储存分割后的第二条链表,head2指向分割点
	temp -> next = NULL;//将前后链表完全分离,将分割点的前一个节点的指针域设为空,称为尾结点

	head = sort_list(head);//将链表1递归分割,并返回为一个有序链表
	head2 = sort_list(head2);//将链表2递归分割,并返回为一个有序链表

	return merge_list(head,head2);//将返回来的两条有序链表合并为一条有序链表并返回其头结点地址
}

//计算链表的长度
//参数1:需要求链表长度的链表的头结点地址
//返回值:所传入链表的长度
int list_len(Node *head)
{
	int len = 0;

    //遍历链表,没有一个节点len加一
	while(head != NULL)
	{
		len++;

		head = head -> next;
	}

	return len;
}

在完成两个步骤(执行sort_list()函数)之后,之前的无序链表将排序为一条有序链表。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值