K条单向链表合并

一、概述

  • K条有序链表合并,按顺序合并链表如:
  • 链表A:1-3-5
  • 链表B:2-4
  • 链表C:3-6-7
  • 链表D:7-8-9
  • 链表E:10-11
  • 合并结果:1-2-3-4-5-6-7-8-9

二、代码

构建A、B、C、D、E 5条链表。

        public static void main(String[] args) {
	        // 构造链表A
		LinkNode linkNodeA1 = new LinkNode(1);
		LinkNode linkNodeA2 = new LinkNode(3);
		LinkNode linkNodeA3 = new LinkNode(5);
		linkNodeA1.next = linkNodeA2;
		linkNodeA2.next = linkNodeA3;
		
		// 构造链表B
		LinkNode linkNodeB1 = new LinkNode(2); 
		LinkNode linkNodeB2 = new LinkNode(4);
		linkNodeB1.next = linkNodeB2;
		
		// 构造链表C
		LinkNode linkNodeC1 = new LinkNode(3); 
		LinkNode linkNodeC2 = new LinkNode(6);
		LinkNode linkNodeC3 = new LinkNode(7);
		linkNodeC1.next = linkNodeC2;
		linkNodeC2.next = linkNodeC3;
		
		// 构造链表D
		LinkNode linkNodeD1 = new LinkNode(7); 
		LinkNode linkNodeD2 = new LinkNode(8);
		LinkNode linkNodeD3 = new LinkNode(9);
		linkNodeD1.next = linkNodeD2;
		linkNodeD2.next = linkNodeD3;
		
		// 构造链表E
		LinkNode linkNodeE1 = new LinkNode(10); 
		LinkNode linkNodeE2 = new LinkNode(11);
		linkNodeE1.next = linkNodeE2;
				
		// 合并链表
		LinkNode linkNode = merge5(new LinkNode[]{linkNodeA1, linkNodeB1, linkNodeC1, linkNodeD1, linkNodeE1});
		while (null != linkNode) {
			System.out.println(linkNode.obj);
			linkNode = linkNode.next;
		}
	}
        static class LinkNode {
		// 数据
		public Integer obj;
		// 下一个结点
		public LinkNode next;
		
		public LinkNode(Integer obj) {
			super();
			this.obj = obj;
		}
	}

        public static LinkNode linkMerge(LinkNode ln1, LinkNode ln2) {
		if(null == ln1) return ln2;
		if(null == ln2) return ln1;
		
		// 设置虚拟头结点
		LinkNode head = new LinkNode(-1);  
		// temp 指向每一次合并之后的最后一个结点:用于指定下一个结点
		LinkNode temp = head;
		while (null != ln1 && null != ln2) {
			if(ln1.obj <= ln2.obj) {
				temp.next = ln1;
				temp = ln1;
				ln1 = ln1.next;
			} else {
				temp.next = ln2;
				temp = ln2;
				ln2 = ln2.next;
			}
		}
		if(null == ln1) {
			temp.next = ln2;
		} else {
			temp.next = ln1;
		}
		return head.next;
	}

思路1:最笨的办法:时间复杂度是O(nlogn),空间复杂度是O(n)

  • 先添加到数组 -> 排序 -> 生成链表

 

    public static LinkNode merge(List<LinkNode> linkNodes) {
		if(null == linkNodes || linkNodes.size() == 0) return null;
		
		// 1、添加到数据 时间负责度O(n)
		List<LinkNode> nodes = new ArrayList<Links.LinkNode>();
		for(LinkNode linkNode : linkNodes) {
			while (null != linkNode) {
				nodes.add(linkNode);
				linkNode = linkNode.next;
			}
		}
		
		// 2、排序 时间负责度O(nlogn)
		nodes.sort((LinkNode node1, LinkNode node2) -> {
			return node1.obj - node2.obj;
		});
		// 3、生成新的链表 时间负责度O(n)
		LinkNode linkNode = new LinkNode(-1);
		LinkNode cur = linkNode;
		for (LinkNode node : nodes) {
			if(cur.obj == node.obj) {
				continue;
			}
			cur.next = node;
			cur = node;
		}
		return linkNode.next;
	}

思路2、逐一比较法,跟Link类中的方法类似(取每一条链表中的第一个元素比较,往复循环),时间复杂度:O(nk)

  • while中循环次数k = 所有节点的个数
  • for循环的次数为链表的条数n
  • 所以时间复杂度为O(nk)
  • 第一种方法的时间度为O(nlogn),所以第一种和第二种方法那种好就看nk和nlogn值的大小了,也就是k和logn值的大小了,计算出来的值哪个小,就更适合用哪种方法
        public static LinkNode merge2(LinkNode[] linkNodes) {
		LinkNode firstLinkNode = new LinkNode(-1);
		LinkNode cur = firstLinkNode;
		while (true) {
			int minIndex = -1;
			for(int i = 0; i < linkNodes.length; i++) {
				LinkNode linkNode = linkNodes[i];
				if(null == linkNode) {
					continue;
				}
				if(-1 == minIndex || linkNode.obj < linkNodes[minIndex].obj) {
					minIndex = i;
				}
			}
			if(-1 == minIndex) {
				break;
			}
			System.out.println("第条"+minIndex+"链表最小,值为:" + linkNodes[minIndex].obj);
			// 将下一个结点指向最小的一条链表的头部
			if(cur.obj != linkNodes[minIndex].obj) {
				cur.next = linkNodes[minIndex];
				cur = linkNodes[minIndex];
			}
			linkNodes[minIndex] = linkNodes[minIndex].next;
		}
		return firstLinkNode.next;
	}

思路3、两两比较,跟Link中一样,然后用生成的新链表和下一条链表进行合并,时间复杂度:O(nk)

  

    public static LinkNode merge3(LinkNode[] linkNodes) {
		if(null == linkNodes || linkNodes.length == 0) {
			return null;
		}
		LinkNode linkNode = linkNodes[0];
		for(int i = 1; i < linkNodes.length; i++) {
			linkNode = linkMerge(linkNode, linkNodes[i]);
		}
		return linkNode;
	}
  • 第一个for循环 时间复杂度是O(n)
  • linkMerge方法的时间复杂度是n次ln1的长度+ln2的长度,并且每次ln1的长度或ln2的长度是变化的。
  • 所以假设有n个结点,k条链表,每一条链表的长度就是n/k,因此k-1第的总和为:
(n/k+n/k) + (2*n/k+n/k) + (3*n/k+n/k) + ... + ((k-1)*n/k+n/k)

2*n/k + 3*n/k + 4*n/k + k*n/k

[(2+k)*(k-1)/2]*n/k

O(k^2 * n/k)

O(kn)

所以时间复杂度也为O(nk)

思路4、优先级队列(小顶堆) ,时间复杂度:O(nlogk),空间复杂度O(k)

 

    public static LinkNode merge4(LinkNode[] linkNodes) {
		if(null == linkNodes || linkNodes.length == 0) {
			return null;
		}
		LinkNode head = new LinkNode(-1);
		LinkNode cur = head;
		
		// 将所有节点的头结点添加到优先队列(小顶堆)中
		PriorityQueue<LinkNode> queue = new PriorityQueue<>((LinkNode node1, LinkNode node2) -> {
			return node1.obj - node2.obj;
		});
		// 因为队列里有k个元素,所以添加元素到优先队列的时间复杂度是logk,因为循环k次,所以这个for循环的时间复杂度为O(klogk)
		for(LinkNode node : linkNodes) {
			queue.offer(node);
		}
		// 不断删除推顶元素,并且把堆顶元素的next添加到集合中
		// 因为队列里有k个元素,所以添加元素到优先队列的时间复杂度是logk,,因为要将n个节点串联删除和串联起来,所以这个for循环的时间复杂度为O(n2logk),去除常数为O(nlogk)
		while (!queue.isEmpty()) {
			LinkNode linkNode = queue.poll();	// O(logk)
			cur.next = linkNode;
			cur = linkNode;
			if(null != linkNode.next) {
				queue.offer(linkNode.next);		// O(logk)
			}
		}
		return head.next;
	}
  •  空间复杂度O(k),在创建队列的时候将K条链表加到优先级队列,也就是说将k个元素添加到优先级队列
  • 时间复杂度: 第一个for循环klogk, while循环时间复杂度nlogk,所以时间复杂度为O(nlogk)

思路5、分治策略,时间复杂度:O(nlogk)

 

    public static LinkNode merge5(LinkNode[] linkNodes) {
		if(null == linkNodes || linkNodes.length == 0) {
			return null;
		}
		// 这种方法时间复杂度O(nlogk)
		int step = 1;
		while (step < linkNodes.length) {	// 循环logk次 O(logk)
			for(int i = 0; i < linkNodes.length; i = (i+2*step)) {	// 将所有节点串联起来:O(n)
				if(i+step >= linkNodes.length) {
					break;
				}
				linkNodes[i] = linkMerge(linkNodes[i],linkNodes[i+step]); 
			}
			step = step*2;
		}
		return linkNodes[0];
		
		// 便于理解 时间复杂度O(nlogk)
//		LinkedList<LinkNode> list = new LinkedList<>();
//		ArrayList<LinkNode> templist = new ArrayList<>();	// 每次合并链表之后存放的合并之后的链表集合
//		for(LinkNode linkNode : linkNodes) {	// O(k)
//			list.add(linkNode);
//		}
//		// 当为基数时,取出最后一位,让链表为偶数条,执行分之策略 -> 然后在和最后一条合并
//		LinkNode lastLinkNode = null;
//		if(list.size() % 2 != 0) {
//			lastLinkNode = list.removeLast();
//		}
//		while (list.size() > 1) {	// 循环logk次 O(logk)
//			// 清空集合元素
//			remove(templist);
//			for(int i = 0; i < list.size(); i=i+2) { // 将所有节点串联起来:O(n)
//				// 合并链表
//				linkNodes[i] = linkMerge(list.get(i), list.get(i+1));
//				templist.add(linkNodes[i]);
//			}
//			// 清空集合元素
//			remove(list);
//		    list.addAll(templist);
//		}
//		if(null != lastLinkNode) {
//			return linkMerge(list.get(0), lastLinkNode);
//		} else {
//			return list.get(0);
//		}
	}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值