快速排序与归并排序的链式实现(golang)

前言

众所周知 快速排序 是一种平均时间性能在 O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(nlog(n))的算法。归并排序 最坏情况性能和平均时间性能都在 O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(nlog(n))。因为思想简单,性能优秀,十分常见。

即使思想简单,但实现依然十分不易。主要原因大概是因为大多采用数组进行实现,为了降低内存开销,会复用已有的数组,从而使空间复杂度降至 O ( 1 ) O(1) O(1)。为了达到这一目的,人们采用一些奇技淫巧,让代码理解、实现都变容易出错。

但是链表结点天然具有可复用性,如果能用链表来实现这两种算法,不仅能达到省内存的目的,而且去掉 奇技淫巧后的代码结构更接近于算法的本质思想,易于理解和在实际项目中手动实现。

本文不再赘述这两种算法的思想和基于数组的实现方式。仅提供基于链表的实现方式的介绍。

链表结构

链表结点结构(以整数型为例)如下。

type ListNode struct {
	data int       // 链表结点的数据
	next *ListNode // 下一个结点的指针
}

type List *ListNode

快速排序

算法首先把链表拆分成sublist和pivot两个子链表,初始时这两个链表都为空。
其中pivot结点中的值就是原链表首元结点的值。
另外有一个指针node指向原链表第二个结点

在这里插入图片描述
然后从node开即往后遍历,如果某个结点的值比pivot中的值小,则把该节点挂在sublist链表中。否则挂在pivot链表中。如下图,3因为比7小,所以挂在sublist,8因为比7大所有挂在pivot链表。
在这里插入图片描述
遍历完成后,分别对这个两子链表进行排序。直接递归调用即可。排序后如下图所示。
在这里插入图片描述
最后把这两个子链表合并即可

在这里插入图片描述

平均时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(nlog(n))

空间复杂度: O ( 1 ) O(1) O(1)

代码如下:

func QuickSort(list List) {
	// 如果链表没有元素或只有一个元素,那么不用排序
	if list.next == nil || list.next.next == nil {
		return
	}

	// 指定第一个元素为pivot, 第二个元素为node
	node := list.next.next
	pivot := list.next
	pivot.next = nil
	sublist := list
	sublist.next = nil

	// 把其它元素根据大小分别放在pivot的左边和右边。 
	// 原链表将拆分成以sublist和pivot为头结点的两个链表
	for node != nil {
		nodeNext := node.next
		if node.data < pivot.data {
			node.next = sublist.next
			sublist.next = node
		} else {
			node.next = pivot.next
			pivot.next = node
		}
		node = nodeNext
	}

	// 递归对两个子链表进行排序
	QuickSort(sublist)
	QuickSort(pivot)

	// 合并两个链表
	i := list
	for i.next != nil {
		i = i.next
	}
	i.next = pivot
}

归并排序

在链表上实现归并排序关键在于如何快速地找到分割点将链表分割成两个子链表。本文提供的方法只需要扫描链表一遍即可确定分割点(如代码所示)。

算法首先找到分割点midNode, 该结点及其之前的结点被分在list1中,之后的结点被分在list2中。
之后递归地对这两个子链表进行排序。如下图。
在这里插入图片描述

最后,把排序后的list1和list2归并即可。归并过程如下,详细如图:

  • 归并过程首先会把list1和list2指向两个子链表的第一个节点。把list链表置空。
  • 然后,依次摘取两个子链表中具有较小数值的结点放入list链中。
  • 最后得到的list就是归并完成的结果。
    在这里插入图片描述
    平均时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(nlog(n))
    最好和最坏性况时间复杂度: O ( n ⋅ l o g ( n ) ) O(n\cdot log(n)) O(nlog(n))
    空间复杂度: O ( 1 ) O(1) O(1)
func MergeSort(list List) {
	// 如果链表没有元素或只有一个元素,那么不用排序
	if list.next == nil || list.next.next == nil {
		return
	}

	// 计算链表长度, 并找到分割点
	midNode := list
	shouldNext := true
	for i := list.next; i != nil; i = i.next {
		if shouldNext {
			midNode = midNode.next
		}
		shouldNext = !shouldNext
	}

	// 把链表分成两个子链表
	list1 := list
	list2 := &ListNode{next: midNode.next}
	midNode.next = nil

	// 分别对两个子链表递归处理
	MergeSort(list1)
	MergeSort(list2)

	// 归并且合并两个子链表
	list1 = list1.next
	list2 = list2.next
	tail := list
	tail.next = nil
	for list1 != nil && list2 != nil {
		if list1.data < list2.data {
			tail.next = list1
			list1 = list1.next
			tail = tail.next
		} else {
			tail.next = list2
			list2 = list2.next
			tail = tail.next
		}
	}

	for list1 != nil {
		tail.next = list1
		list1 = list1.next
		tail = tail.next
	}

	for list2 != nil {
		tail.next = list2
		list2 = list2.next
		tail = tail.next
	}
}

测试

本文把两个算法排序的结果与go语言自带的排序功能进行比较,验证了算法实现的正确性。

func main() {
	list1 := new(ListNode)
	list2 := new(ListNode)
	array := make([]int, 100)
	// 随机生成100个数
	rand.Seed(time.Now().UnixMicro())
	for i := 0; i < 100; i++ {
		number := rand.Intn(100)
		list1.next = &ListNode{data: number, next: list1.next}
		list2.next = &ListNode{data: number, next: list2.next}
		array[i] = number
	}

	QuickSort(list1)
	MergeSort(list2)
	sort.Ints(array)

	// 输出, 并验证排序是否正确
	i1 := list1.next
	i2 := list2.next
	j := 0
	for i1 != nil {
		if i1.data != array[j] {
			panic("list1 not equal")
		}
		if i2.data != array[j] {
			panic("list2 not equal")
		}
		fmt.Print(i1.data, ",")
		i1 = i1.next
		i2 = i2.next
		j++
	}
	fmt.Println()
}

测试结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值