第六章堆排序之“用最小堆将k个已排序链表合并为一个排序链表”(练习6.5-8)

问题:请给出一个时间为O(nlgk),用来将k个已排序链表合并为一个排序链表的算法。此处的n为所有输入链表中元素的总数。(提示:用一个最小堆来做k路合并)

编程思路:

假设k个链表都是非降序排列的。

(1)取k个元素建立最小堆,这k个元素分别是k个链表的第一个元素。建堆的时间复杂度O(k)。

(2)堆顶元素就是k个链表中最小的那个元素,取出它。时间复杂度O(1)。

(3)若堆顶元素所在链表不为空,则取下一个元素放到堆顶位置,这可能破坏了最小堆性质,所以进行堆调整。堆调整时间复杂度O(lgk)。若为空,则此子链表已经被合并完毕,则删除最小堆的堆顶元素,此时最小堆的heapSize减小了1 。删除指定元素时间复杂度O(lgk)。

(4)重复步骤(2)~(3)n-k次。总的时间复杂度是O(k)+O(nlgk)即O(nlgk)。


要先定义一个数据结构Node,主要是为了找链表所在索引方便,这个程序我先用随机函数生成4个子链表(一个二维数组,为了省事就让每个子链表的元素个数相同)然后用冒泡排序分别对每个链表排序,然后合并它们。


#include <stdio.h>
#include <string.h>
#include <time.h>

#define BUFFER_SIZE 10

typedef struct
{
	int data;
	int index;
}Node;

void Output(Node (*a)[BUFFER_SIZE],int k,int len)
{
	int i=0;
	int j=0;
	for(i=0;i<k;i++)
	{
		printf("第%d个链表:",i); 
		for(j=0;j<len;j++)
		{
			printf("%d ",a[i][j].data);
		}
		printf("\n");
	}
} 


void BubbleSort(Node (*a)[BUFFER_SIZE],int k,int len)
{
	int i=0;
	int j=0;
	int h=0;
	int tmp=0;
	
	for(h=0;h<k;h++)
	{
		for(i=1;i<len;i++)
		{
			for(j=0;j<len-i;j++)
			{
				if(a[h][j].data>a[h][j+1].data)
				{//因为每一次都是在每个子链表内部排序,所以就没有必要交换index了,因为同一个子链表中的index都是一样的 
					tmp=a[h][j].data;
					a[h][j].data=a[h][j+1].data;
					a[h][j+1].data=tmp;
				}	
			}
		}
	}
}
//堆调整,保持堆的性质 
void MinHeapIfy(Node *a,int i,int heapSize)
{
	int smallest=0;
	int left=0;
	int right=0;
	Node tmp;
	
	while(i<heapSize)
	{
		left=i<<1;
		right=(i<<1)+1;
		
		if(left<=heapSize&&a[i].data>a[left].data)
		{
			smallest=left;
		}
		else
		{
			smallest=i;
		}
		if(right<=heapSize&&a[smallest].data>a[right].data)
		{
			smallest=right;
		}
		
		if(i!=smallest)
		{//在C++中针对Node可以定义一个赋值构造函数就不用像下面这样子交换了 
			tmp.data=a[i].data;
			tmp.index=a[i].index;
			a[i].data=a[smallest].data;
			a[i].index=a[smallest].index;
			a[smallest].data=tmp.data;
			a[smallest].index=tmp.index;
			
			i=smallest;
		}
		else
		{
			break;
		}
	}
	
} 
//建堆 
void BuildMinHeap(Node *a,int heapSize)
{
	int i=0;
	for(i=heapSize/2;i>1;i--)
	{
		MinHeapIfy(a,i,heapSize);
	}
} 

//删除堆中指定元素
void MinHeapDelete(Node *a,int i,int *heapSize) 
{
	Node tmp;
	tmp.data=a[i].data;
	tmp.index=a[i].index;
	
	a[i].data=a[*heapSize].data;
	a[i].index=a[*heapSize].index;
	
	if(a[i].data==tmp.data)
	{
		(*heapSize)--;
	}
	else if(a[i].data>tmp.data)
	{
		(*heapSize)--;
		MinHeapIfy(a,i,*heapSize);
	}
	else if(a[i].data<tmp.data)
	{
		(*heapSize)--;
		while(i>1&&a[i>>1].data>a[i].data)
		{
			tmp.data=a[i].data;
			tmp.index=a[i].index;
			a[i].data=a[i>>1].data;
			a[i].index=a[i>>1].index;
			a[i>>1].data=tmp.data;
			a[i>>1].index=tmp.index;
		}
	}
}
//合并k个子链表 
void Merge(Node (*a)[BUFFER_SIZE],int k,Node *result)
{
	int heapSize=0;
	int i=0;
	Node tmp[BUFFER_SIZE+1];
	int j=0;
	int index=0;
	int n=k*BUFFER_SIZE;//k个链表中的元素总数 
	int b[k];//用来记录每个子链表该哪个元素添加进堆中 
	memset(b,0,sizeof(b)); 
	//用每个链表的第一个元素,共k个,建立最小堆 
	for(i=0;i<k;i++)
	{
		tmp[i+1].data=a[i][0].data;
		tmp[i+1].index=a[i][0].index;
		b[i]++; 
	}
	heapSize=k;
	BuildMinHeap(tmp,heapSize);
	while(n>0)
	{
		//将堆顶元素放入输出链表 
		result[j].data=tmp[1].data;
		result[j].index=tmp[1].index;
		index=result[j].index;
		j++;
		n--;//子链表少了一个元素
		
		if(b[index]<BUFFER_SIZE)
		{//该子链表还未空,则继续从该子链表选取元素入堆 
			//将index子链表的下一个元素放到堆顶,然后进行堆调整 
			tmp[1].data=a[index][b[index]].data;
			tmp[1].index=a[index][b[index]].index;
			b[index]++;//index子链表待加入堆中的下一个元素 
			MinHeapIfy(tmp,1,heapSize);
		}
		else
		{
			MinHeapDelete(tmp,1,&heapSize);
		}
	}
	BuildMinHeap(tmp,k);
	
}
int main()
{
	int i=0;
	int j=0;
	Node a[4][BUFFER_SIZE];
	Node result[4*BUFFER_SIZE];
	//随机生成k个链表,k=4 
	srand((unsigned)time(NULL));
	for(i=0;i<4;i++)
	{
		for(j=0;j<BUFFER_SIZE;j++)
		{
			a[i][j].data=rand()%1000;
			a[i][j].index=i;
		}
	} 
	printf("随机生成的k个链表:\n");
	Output(a,4,BUFFER_SIZE); 
	
	BubbleSort(a,4,BUFFER_SIZE);
	printf("对k个链表进行冒泡排序:\n"); 
	Output(a,4,BUFFER_SIZE);
	
	Merge(a,4,result);
	printf("合并K个链表:\n"); 
	for(i=0;i<4*BUFFER_SIZE;i++)
	{
		printf("%d ",result[i].data);
	}
	system("pause");
	return 0;
} 



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值