排序——归并排序

目录

思路

演示

代码

补充

非递归实现

例题

题目

思路

代码


思路

     归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。

     将已有序的子序列合并,得到完全有序的序列。即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并

演示

     实现它走的是一个递归,先让一个序列不断对半拆分(递推),拆到只有一个数据的序列时(递推结束条件),开始回归排序(回归)。具体如下图。

     

代码

     在排序时不能在原数组上进行排序,解决这个问题其实只需要开辟一个与原数组大小一样的空间,在排完一个后把该数据存入开辟的数组中,当这次序排完后把数据拷贝到原数组中即可。

void _MergeSort(int* arr, int* temp, int left, int right)
{
	if (left == right)
		return;

	int mid = (left + right) / 2;
	_MergeSort(arr, temp, left, mid);
	_MergeSort(arr, temp, mid + 1, right);

	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int i = left;//这里的i = left,我是让原数组的起始位置与开辟的数组的该位置对齐
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			temp[i++] = arr[begin1++];
		}
		else
		{
			temp[i++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		temp[i++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		temp[i++] = arr[begin2++];
	}

	memcpy(arr + left, temp + left, (right - left + 1) * sizeof(int));//拷贝到原数组
}

//归并排序
void MergeSort1(int* arr, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc");
		return;
	}
	_MergeSort(arr, temp, 0, n - 1);//这是一个左闭右闭
	free(temp);
	temp = NULL;
}

补充

非递归实现

     我们用下面的这组数据来讲解怎么实现。

     回顾上面递归实现,可以发现是先对下标为0~1排序在对下标为2~3排序...这次排完后对下标0~3排序以此类推。所以我们是不是可以创建一个变量gap用来控制上面下标变化。

     下面的代码主框架其实没变,就是递归改成循环了。

//归并排序(非递归)
void MergeSort2(int* arr, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc");
		return;
	}
	
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + 2 * gap - 1;
			int i = j;

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
				{
					temp[i++] = arr[begin1++];
				}
				else
				{
					temp[i++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				temp[i++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				temp[i++] = arr[begin2++];
			}

			memcpy(arr + j, temp + j, (end2 - j + 1) * sizeof(int));
		}

		gap *= 2;
	}

	free(temp);
	temp = NULL;
}

     上面的代码就只能对2的倍数进行排序如果不是就无法排序,下面的图片是对一个只有10个数据的序列下标访问情况。

      划红线的都是越界访问的,可以发现第二行和第三行有个共同特征都是begin2大于等于n遇到这种情况直接跳出循环即可。如果end2大于等于n让他等于n-1即可。

//归并排序(非递归)
void MergeSort2(int* arr, int n)
{
	int* temp = (int*)malloc(sizeof(int) * n);
	if (temp == NULL)
	{
		perror("malloc");
		return;
	}
	
	int gap = 1;
	while (gap < n)
	{
		for (int j = 0; j < n; j += 2 * gap)
		{
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + 2 * gap - 1;
			int i = j;

			if (begin2 >= n)
				break;
			if (end2 >= n)
				end2 = n - 1;


			while (begin1 <= end1 && begin2 <= end2)
			{
				if (arr[begin1] < arr[begin2])
				{
					temp[i++] = arr[begin1++];
				}
				else
				{
					temp[i++] = arr[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				temp[i++] = arr[begin1++];
			}
			while (begin2 <= end2)
			{
				temp[i++] = arr[begin2++];
			}

			memcpy(arr + j, temp + j, (end2 - j + 1) * sizeof(int));
		}

		gap *= 2;
	}

	free(temp);
	temp = NULL;
}

例题

题目

该题链接:https://leetcode.cn/problems/sort-list/description/ 

思路

     我第一次看到这道题时,想的是用插入排序来解决但是没有实现,因为有一个测试用例很长很长。所以有解决这道题就要用比较快的排序,考虑到该题的特殊性用归并排序解决是最合理的。

     这道题的解决思路和上面基本一样,也是不断对半拆分。递推结束条件会有所变化。当头指针指向的下一个是尾指针时,即开始返回并把头指针的指向置为空。然后调用合并两个有序链表的函数即可实现。

代码

//合并两个有序链表的函数
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 
{
    if(list1 == NULL)
        return list2;
    if(list2 == NULL)
        return list1;

    struct ListNode* n1 = list1, *n2 = list2;
    struct ListNode* newhead = NULL, *newend = NULL;
    newhead = newend = (struct ListNode*)malloc(sizeof(struct ListNode));
    
    while(n1 && n2)
    {
        if(n1->val < n2->val)
        {
            newend->next = n1;
            newend = newend->next;
            n1 = n1->next;
        }
        else
        {
            newend->next = n2;
            newend = newend->next;
            n2 = n2->next;
        }
    }
    if(n1 == NULL)
    {
        newend->next = n2;
    }
    if(n2 == NULL)
    {
        newend->next = n1;
    }

    struct ListNode* ret = newhead->next;
    free(newhead);
    newhead = NULL;
    return ret;
}


struct ListNode* MergeSort(struct ListNode* phead, struct ListNode* ptail)
{
    if(phead->next == ptail)
    {
        phead->next = NULL;
        return phead;
    }

    struct ListNode* slow = phead, *fast = phead;
    while(fast != ptail)
    {
        slow = slow->next;
        fast = fast->next;
        if(fast != ptail)
            fast = fast->next;
    }
    struct ListNode* mid = slow;

    struct ListNode* l1 = MergeSort(phead, mid);
    struct ListNode* l2 = MergeSort(mid, ptail);

    return mergeTwoLists(l1, l2);
}


struct ListNode* sortList(struct ListNode* head) {
    return head == NULL ? NULL : MergeSort(head, NULL);
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值