排序专题 -----(2)归并排序

排序专题 -----(2)归并排序

一、以顺序表为存储结构的归并排序

  1. 自顶向下递归分治 (排序地基本逻辑略过,代码有详细注释)
// 伪代码
template <typename T>
void _mergeSort(T arr[], int l, int r)
{
	if(l >= r) return;
	int mid = l + (r-l)/2;				// 分治点
	_mergeSort(arr, l, mid);			// 左侧递归结果为有序序列
	_mergeSort(arr, mid+1, r);			// 右侧递归结果为有序序列
	
	if(arr[mid] > arr[mid+1])			// 若左侧的最大值 < 右侧的最小值, 不进行合并操作 
		_merge(arr, l, mid, r);			// 将arr[l...mid]和arr[mid+1...r]进行归并 
}

T aux[l-r+1];							// 申请辅助数组, 可以malloc动态申请,也可以用vector
void _merge(T arr[], int l, int mid, int r)
{
	int i, j, k;						// i、j分别为aux左、右侧数组游标, k为arr游标
	for(k = l; k <= r; ++k)
		aux[k] = arr[k];
	
	for(i = l, j = mid+1, k = i; i <= mid && j <= high; ++k)
	{
		if(aux[i] <= aux[j])			// 比较aux的左右两段中的元素
			arr[k] = aux[i++];			// 较小的复制到原数组arr, 下同
		else
			arr[k] = aux[j++];
	}
	/* 下列这两个while为同一操作, 故只会执行其中一个, 均为剩余数填充 */
	while(i <= mid)						// 左侧表未检测完
		arr[k++] = aux[i++];					
	while(j <= high)					// 右侧表未检测完
		arr[k++] = aux[j++];			
}
  1. 使用循环进行自底向上的归并排序
template <typename T> 
void mergeSortBu(T arr[], int n)
{
	for(int sz = 1; sz <= n; sz += sz)		// sz作为归并划分的步长
	{
		/* 四点说明:
		   1、对arr[i...i+sz-1]和arr[i+sz...i+sz+sz-1]进行归并
		   2、i+sz < n是为了防止前一段的右端点i+sz-1不会越界
		   3、每次归并之后的子表长为2*sz, 故for子循环自加2*sz 
		   4、min在merge中作用是, 防止后一段的右端点i+2*sz-1不会越界 */
		for(int i = 1;i+sz < n; i += 2*sz)
			if(a[i+sz-1] > arr[i+sz])		// 和自顶向下相同, 都优化限制合并条件
				__merge(arr, i, i+sz-1, min(i+2*sz-1, n-1));
	}
}

二、以链表为存储结构的归并排序

  1. 对链表自顶向下归并排序的过程如下。
    (1)、找到链表的中点,以中点为分界,将链表拆分成两个子链表。寻找链表的中点可以使用快慢指针的做法,快指针每次移动 2 步,慢指针每次移动 1 步,当快指针到达链表末尾时,慢指针指向的链表节点即为链表的中点。【Leetcode 876. 链表的中间结点】
    (2)、对两个子链表分别排序,递归到最后只有一个节点或者为空,比较两个不为空节点大小即可得到一个有序子链。
    (3)、递归合并链表。【Leetcode 21. 合并两个有序链表】
// 以下为Leetcode官方给出的题解,用重载函数转了接口
class Solution 
{
public:
	ListNode* sortList(ListNode* head)
	{
		return sortList(head, nullptr);
	}
	
	ListNode* sortList(ListNode* head, ListNode* tail)
	{
		if(nullptr == head)					  									// 空表直接返回	
			return head;												
											
		if(head->next == tail)				  									// 子表只有两个节点直接拆左右
		{									
			head->next = nullptr;									
			return head; 									
		}									
											
		ListNode* slow = head, *fast = head;  									// 快慢指针的方法确定中间位置
		while(fast != tail)									
		{									
			slow = slow -> next;									
			fast = fast -> next;									
			if(fast != tail)									
				fast = fast -> next;		  									// 快指针速度是慢指针两倍
		}									
		ListNode* mid = slow;
		return merge(sortList(head, mid), sortList(mid, tail));
	}
	
	ListNode* merge(ListNode* head1, ListNode* head2)
	{
		ListNode* dummyHead = new ListNode(0);									// 创建哑节点,同一链表操作
		ListNode* temp = dummyHead, *temp1 = head1, *temp2 = head2;
		while(temp1 != nullptr && temp2 != nullptr)
		{
			if(temp1->val <= temp2->val)	  									// 取值小的挂到temp上 
			{
				temp -> next = temp1;
				temp1 = temp1 -> next;
			}
			else
			{
				temp -> next = temp2;
				temp2 = temp2 -> next;
			}
			temp = temp -> next;			 									// 更新temp
		}									
		if(temp1 != nullptr)				 									// 值大的表一定有剩余, 挂到temp上
			temp -> next = temp1;	
		else if (temp2 != nullptr) 
			temp -> next = temp2;
		return dummyHead->next;
	}
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值