合并多个有序数组

题目描述

我们现在有多个已经有序的数组,我们知道每个有序数组的元素个数和总共的有序数组的个数,现在请设计一个算法来实现这多个有序数组的合并(合并成一个数组);
例如:
输入:[1,2,3,4,5,],[6,7],[8,9,10],[11,12,13,14];
输出:[1,2,3,4,5,6,7,8,9,10,11,12,13,14];

函数接口:

int* MulArrSort(int** nums, int Row, int* Col, int* returnSize)
{
  //returnSize//返回合并的数组的元素个数
  //Row有多少个有序数组
  //Col数组,表示每个有序数组里面有多少个元素
}

思想

首先这道题的思路与合并两个有序数组 这道题的思路非常相似,都是每次从有序数组中选出较小的值出来,然后较小的值的下标往后挪动;
在这里插入图片描述

本题的思想也是如此,我们可以先将每个有序数组的最小值圈在一起,然后从这些最小值中选出较小的放入临时数组中,并且该较小值的下标也往后挪动一下:
比如:
在这里插入图片描述
在这里插入图片描述
现在问题的关键是,我们怎么从这些数据中选出较小值,解决了这个问题本题也就轻松一半!
有两个方法
1、暴力选举
就对这些数一个一个遍历,选出最小值,时间复杂度O(K),时间开销太大了;
2、建立小堆
我们可以对这些数据进行建立小堆处理,然后堆顶元素就是这些数据中的较小值!时间复杂度也很乐观:O(logK);
为此我们选择第二种办法:
但是我们是直接对这些单一数据进行建堆吗?
当然不是,首先我们得想到,我们选出了较小值然后呢?如何往后走?是在那个有序数组里面往后走呢?
这些都是我们需要思考的问题,因此为了解决如何往后走的问题,我们需要一个结构体,这个结构体:包含当前元素的下标当前数组的总元素个数当前有序数组的数组名
在这里插入图片描述

1、这就是我们建立的单个堆元素,我们以当前元素的大小来进行比较建立小堆;
2、然后每次选出堆顶元素所表示的值存储起来,同时更新堆顶元素,并重新利用向下调整算法调整小堆;
3、当某一个有序数组的元素被选完时,他就已经没有下个元素了,这时我们就需要将堆尾元素与堆顶元素进行替换,然后小堆的有效元素个数减1,而对于正常情况的话则对对顶元素进行正常替换即可;当小堆里面的有效元素没有了,就说明多个有序数组的合并也就完成了;

代码实现

struct Node
{
	int index;//记录当前元素的位置
	int* arr;//当前元素所在的数组
	int len;//当前元素所在的数组的总长度
};
void Swap(struct Node* a, struct Node* b)
{
	struct Node tmp = *a;
	*a = *b;
	*b = tmp;
}
void AdjustDown(struct Node* Heap, int top, int end)
{
	int parent = top;
	int child = 2*parent+1;
	while (child < end)
	{
		if (child + 1 < end && Heap[child + 1].arr[Heap[child + 1].index] < Heap[child].arr[Heap[child].index])
		{
			child++;
		}
		if (Heap[child].arr[Heap[child].index] >= Heap[parent].arr[Heap[parent].index])
			break;
		else
		{
			Swap(Heap+child,Heap+parent);
			parent = child;
			child = 2 * parent + 1;
		}
	}
}
int* MulArrSort(int** nums, int Row, int* Col, int* returnSize)
{
	*returnSize = 0;
	//计算合并数组的总元素个数
	for (int i = 0; i < Row; i++)
	{
		*returnSize += Col[i];
	}
	//合并的总数组,用于存储合并的元素
	int* ret = (int*)malloc(sizeof(int) * (*returnSize));
	if (!ret)
		exit(EXIT_FAILURE);
	//1、建立初始堆数组
	struct Node* Heap = (struct Node*)malloc(sizeof(struct Node) * (Row));
	if (!Heap)
		exit(EXIT_FAILURE);
	int size = 0;//记录有效数组个数
	for (int i = 0; i < Row; i++)
	{
		if (nums[i])//如果给的有序数组中有空数组,我们就不将其考虑进去建堆中
		{
			Heap[size].arr = nums[i];
			Heap[size].index = 0;
			Heap[size].len = Col[i];
			size++;
		}
	}
	int HeapSize = size;
	//2、建立小堆
	for (int top = (HeapSize - 1 - 1) / 2; top >= 0; top--)
	{
		AdjustDown(Heap,top,HeapSize);
	}
	int k = 0;//记录合并数组的下标
	struct Node NextNode ={ 0 };
	//3、开始选数
	while (HeapSize)
	{
		ret[k++] = Heap[0].arr[Heap[0].index];//存入堆顶元素
		NextNode = Heap[0];
		NextNode.index++;//下标往后挪
		//4、更新小堆
		if (NextNode.index >= NextNode.len)//说明某一组有序数组已经选完了,这时候堆顶元素的跟新不需要特俗处理,由于当前有序数组已经遍历完毕,没有下一个元素,因此NextNode是无效数据,因此堆顶数据不能用NextNode更新,需要用堆尾元素更新,然后有效小堆元素个数--;
		{
			Swap(Heap+HeapSize-1,Heap);
			HeapSize--;//有效元素--;
		}
		else//正常来到下一次的位置
		{
			Heap[0] = NextNode;//正常更新下一次位置
		}
		AdjustDown(Heap,0,HeapSize);
	}
	free(Heap);
	return ret;
}

测试:
在这里插入图片描述
MulArrSort 函数的时间复杂度取决于几个关键部分:

  1. 建立初始堆数组:这一步需要遍历每个给定的有序数组 nums(Row 次),并将其添加到堆中。由于每次添加一个元素到堆中的时间复杂度是 O(logN),其中 N 是堆的大小(在最坏情况下,等于 Row),因此这一步的时间复杂度是 O(Row * logRow),其中 Row 是给定的有序数组的数量。

  2. 建立小堆:这一步是在初始堆数组的基础上构建小堆,其时间复杂度是 O(Row),因为它是一个线性时间操作。

  3. 开始选数:在这一步中,您会反复从小堆中选择最小的元素并将其添加到结果数组 ret 中,直到小堆为空。这一步的时间复杂度是 O(n * logRow),其中 n 是所有元素的总数,logRow 是小堆的高度。

综合以上步骤,MulArrSort 函数的总时间复杂度是 O(n * logRow),其中 n 是所有元素的总数,logRow 是小堆的高度,Row 是给定的有序数组的数量。这个算法在处理大量元素时效率较高。但需要注意,这个算法的性能也取决于小堆的实现效率,通常情况下,堆排序的时间复杂度为 O(n * logn),n 是总元素数量。

实际上MulArrSort的时间复杂度主要落在while(HeapSize)这个循环里面,我们主要考虑这部分的时间复杂度就可以了,这部分的时间复杂度我们特俗的例子:
在这里插入图片描述

时间复杂度:O(n * logRow)
空间复杂度:O(Row)

变形题目

合并有序链表;
参考代码:

struct ListNode*BuyNode(int val)
{
    struct ListNode*Node=(struct ListNode*)malloc(sizeof(struct ListNode));
    Node->val=val;
    Node->next=NULL;
    return Node;
}
void Swap(struct ListNode**a,struct ListNode**b)
{
    struct ListNode*tmp=*a;
    *a=*b;
    *b=tmp;
}
void AdjustDown(struct ListNode** lists,int top,int end)
{
  int parent=top;
  int child=2*top+1;
  while(child<end)
  {
      if(child+1<end&&lists[child+1]->val<lists[child]->val)
      child++;
      if(lists[child]->val>=lists[parent]->val)
      break;
      else
      {
        Swap(lists+parent,lists+child);
        parent=child;
        child=2*parent+1;
      }
  }
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize){
         struct ListNode** Heap=(struct ListNode**)malloc(sizeof(struct ListNode*)*listsSize);
         int size=0;
         for(int i=0;i<listsSize;i++)
         {
             if(lists[i])
             {
                 Heap[size++]=lists[i];
             }
         }
         int HeapSize=size;
         for(int top=(HeapSize-2)/2;top>=0;top--)
         {
             AdjustDown(Heap,top,HeapSize);
         }
         struct ListNode*dummyhead=(struct ListNode*)malloc(sizeof(struct ListNode));
         dummyhead->next=NULL;
         struct ListNode*cur=dummyhead;
         while(HeapSize)
         {
             struct ListNode*next=Heap[0]->next;
             struct ListNode*NewNode=BuyNode(Heap[0]->val);
             cur->next=NewNode;
             cur=NewNode;
             if(next)
             {
                 Heap[0]=next;
             }
             else
             {
                 Swap(Heap,Heap+HeapSize-1);
                 HeapSize--;
             }
             AdjustDown(Heap,0,HeapSize);
         }
         struct ListNode*Head=dummyhead->next;
         free(dummyhead);
         free(Heap);
         return Head;
}
  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 19
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南猿北者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值