链表排序算法

1)题目

对乱序的链表进行排序,要求空间复杂度为常数。(LeetCode 148 中等)

 

输入: 4->2->1->3

输出: 1->2->3->4

 

输入: -1->5->3->4->0

输出: -1->0->3->4->5

 

2)思路

       对于这题我有两种解法,一种是时间复杂度O(n2)的暴力解法,一种是时间复杂度O(nlogn)的基于归并思想的解法。

         对于暴力解法,我们会将整个链表分为“有序段”和“无序段”两段。设立一个虚拟的头指针q,其next域指向“有序段”的第一个节点;再设定一个指针p直接指向无序链表的第一个节点。其排序思路是将无序链表中的第一个节点p与有序链表中的所有节点挨个比较(通过不停移动q指针实现),找到合适的位置插入p即可。在刚开始的时候有序链表中只有原链表中的第一个节点,无序链表则有剩下的全部节点。由此可以看出总的时间复杂度为O(n2)

         对于归并解法,归并的思想是不停的寻找链表的中间位置,并且通过递归将整个链表拆分成多个长度为1或者2的小链表,在基础上按大小顺序合并成新的链表。这个思想在数组的排序中很好理解,但是在链表的排序中需要注意a.将每段最后一个元素的next域指向空;b.构造一个next域指向合并后有序链表的第一个节点的虚拟指针;由此可见总的时间复杂度为O(nlogn)

2)代码

// 1.复杂度O(n^2)的暴力解法
	public static ListNode sortList1(ListNode head) {
		if (head == null || head.next == null)
			return head;
		ListNode p = head;
		ListNode r = p.next;
		p.next = null;
		// 头结点(非元素节点)其next阈永远指向已排序链表中的第一个节点
		ListNode record = new ListNode(-1);	//该节点的next域一直记录有序链表的头结点
		record.next = head;	//刚开始的时候,有序链表中只有原链表中的第一个元素。
		p = r;	//p指向无序链表中的第一个节点
		while (p != null) {
			r = p.next;
			ListNode q = record; // 拿到有序链表中的第头结点(非元素节点)
			//寻找p节点的插入位置
			while (q != null && q.next != null && q.next.val < p.val)
				q = q.next;
			p.next = q.next;
			q.next = p;	
			p = r;
		}
		return record.next;
	}

	// 2.复杂度O(nlogn)的归并解法
	public static ListNode sortList2(ListNode head) {
		if(head == null || head.next == null)
			return head;
		//将链表一分为二
		ListNode mid = getMidNode(head);
		ListNode begin = mid.next;
		mid.next = null;	
		ListNode headA = sortList2(head); 
		ListNode headB = sortList2(begin); 
		
		return merge(headA, headB);
	}

	// 首先找到链表中的中间节点
	static ListNode getMidNode(ListNode head) {
		// 基于快慢指针的思想找中间节点
		ListNode fast = head;
		ListNode slow = head;
		// 当“快”指针为空时,“慢”指针就指向中间节点
		while (fast.next != null && fast.next.next != null) {
			fast = fast.next.next;
			slow = slow.next;
		}
		return slow;
	}
	// 基于归并排序的思想,这里传入的链表A与链表B都是有序的
	static ListNode merge(ListNode headA, ListNode headB) {
		ListNode head = new ListNode(-1);// 这里是新new出来的头结点,指向有序链表的第一个元素节点
		ListNode record = head; 
		while (headA != null && headB != null) {
			if(headA.val < headB.val){
				head.next = headA;
				headA = headA.next;
			}else{
				head.next = headB;
				headB = headB.next;
			}
			head = head.next;
		}
		
		head.next = headA == null ? headB : headA;
		return record.next;

	}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值