掘金 后端 ( ) • 2024-04-20 10:48

本文介绍三种基础的抽象数据类型:背包、队列和栈。会用数组、变长数组和链表实现背包、队列和栈的API,它们是全书算法实现的起点和样板。照例全用Go语言实现。

背包、队列和栈

许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加、删除或是访问集合中的对象。在本节中,我们将学习三种这样的数据类型,分别是背包(Bag)、队列(Queue)和栈(Stack)。它们的不同之处在于删除或者访问对象的顺序不同。

背包、队列和栈数据类型都非常基础并且应用广泛。我们在本书的各种实现中也会不断用到它们。除了这些应用以外,本节中的实现和用例代码也展示了我们开发数据结构和算法的一般方式。

API

照例,我们对集合型的抽象数据类型的讨论从定义它们的API开始,每份API都含有一个无参数的构造函数、一个向集合中添加单个元素的方法、一个测试集合是否为空的方法和一个返回集合大小的方法。Stack和Queue都含有一个能够删除集合中的特定元素的方法。除了这些基本内容之外,我们将在以下几节中解释这几份API反映出的两种特性:泛型与迭代。

API如下:

  • Go里不存在无参构造函数的概念,改为初始化数据结构
  • 添加单个元素
  • 判断是否为空
  • 返回集合大小
  • Stack和Queue含有能够删除元素的方法
  • 支持泛型
  • 支持迭代

集合数据类型的实现

在讨论Bag、Stack和Queue的实现之前,我们会先给出一个简单而经典的实现,然后讨论它的改进并得到上述API的所有实现。

定容栈

作为热身,我们先来看一种表示容量固定的字符串栈的抽象数据类型,它的API和Stack的API有所不同:它只能处理String值,它要求用例指定一个容量且不支持迭代。 实现一份API的第一步就是选择数据的表示方式。对于FixedCapacityStackOfStrings,我们显然可以选择String数组。

具体程序:

package ch1

/* 定容栈
 * @Description
 * @Author: zyz
 * @Date: 2024/4/18 下午 11:07
 */

type FixedCapacityStackOfStrings struct {
	a []string
	N int
}

func NewFixedCapacityStackOfStrings(cap int) *FixedCapacityStackOfStrings {
	return &FixedCapacityStackOfStrings{
		a: make([]string, cap),
		N: 0,
	}
}

func (s *FixedCapacityStackOfStrings) IsEmpty() bool {
	return s.N == 0
}

func (s *FixedCapacityStackOfStrings) Size() int {
	return s.N
}

func (s *FixedCapacityStackOfStrings) Push(item string) {
	s.a[s.N] = item
	s.N++
}

func (s *FixedCapacityStackOfStrings) Pop() string {
	s.N--
	return s.a[s.N]
}

泛型

FixedCapacityStackOfStrings的一个缺点,就是只能处理string对象,这时候就需要引入泛型了。Go从1.18开始正式支持泛型,新增FixedCapacityStack。

具体实现:

package ch1

/*
 * @Description 支持泛型的定容栈
 * @Author: zyz
 * @Date: 2024/4/18 下午 11:24
 */

type FixedCapacityStack[T any] struct {
	a []T
	N int
}

func NewFixedCapacityStack[T any](cap int) *FixedCapacityStack[T] {
	return &FixedCapacityStack[T]{
		a: make([]T, cap),
		N: 0,
	}
}

func (s *FixedCapacityStack[T]) IsEmpty() bool {
	return s.N == 0
}

func (s *FixedCapacityStack[T]) Size() int {
	return s.N
}

func (s *FixedCapacityStack[T]) Push(item T) {
	s.a[s.N] = item
	s.N++
}

func (s *FixedCapacityStack[T]) Pop() T {
	s.N--
	return s.a[s.N]
}

迭代

在Java集合里,有Foreach语法糖可以通过迭代遍历并处理集合中的每个元素,但是Go里边没有这个语法糖,可以用for range来实现,来看以下接口。

迭代器接口:

package ch1

/**
 * @Description 迭代器接口
 * @Author zyz
 * @Date 2024/4/19 11:07
 **/

type Iterator[T any] interface {
	HasNext() bool
	Next() T
}

for-range遍历接口:

package ch1

/**
 * @Description for-range遍历接口,实现该接口的类型可以使用for-range遍历
 * @Author zyz
 * @Date 2024/4/19 11:22
 **/

type Rangeable[T any] interface {
	Elements() []T
}

集合只需实现这两个接口对应方法,即可完成迭代操作。

下压栈(基于数组)实现

下压栈这种数据结构比之前的数据结构要更完美,支持泛型、迭代并可以动态调整数组大小,可以作为所有集合类抽象数据结构类型实现的模板。

具体实现:

package ch1

import "reflect"

/**
 * @Description 下压(LIFO)栈(能够动态调整数组大小的实现)
 * @Author zyz
 * @Date 2024/4/19 09:53
 **/

type ResizingArrayStack[T any] struct {
	a []T
	N int
}

type ResizingArrayStackIterator[T any] struct {
	index int
	stack *ResizingArrayStack[T]
}

func (s *ResizingArrayStack[T]) Iterator() *ResizingArrayStackIterator[T] {
	return &ResizingArrayStackIterator[T]{
		index: 0,
		stack: s,
	}
}

func (s *ResizingArrayStackIterator[T]) HasNext() bool {
	return s.index < s.stack.N
}

func (s *ResizingArrayStackIterator[T]) Next() T {
	item := s.stack.a[s.index]
	s.index++
	return item
}

func NewResizingArrayStack[T any](cap int) *ResizingArrayStack[T] {
	return &ResizingArrayStack[T]{
		a: make([]T, cap),
		N: 0,
	}
}

func (s *ResizingArrayStack[T]) IsEmpty() bool {
	return s.N == 0
}

func (s *ResizingArrayStack[T]) Size() int {
	return s.N
}

func (s *ResizingArrayStack[T]) Resize(max int) {
	temp := make([]T, max)
	for i := 0; i < s.N; i++ {
		temp[i] = s.a[i]
	}
	s.a = temp
}

func (s *ResizingArrayStack[T]) Push(item T) {
	if s.N == len(s.a) {
		s.Resize(2 * len(s.a))
	}
	s.a[s.N] = item
	s.N++
}

func (s *ResizingArrayStack[T]) Pop() T {
	s.N--
	item := s.a[s.N]
	//为了垃圾回收器能够回收弹出的元素
	zeroValue := reflect.Zero(reflect.TypeOf(item)).Interface().(T)
	s.a[s.N] = zeroValue
	if s.N > 0 && s.N == len(s.a)/4 {
		s.Resize(len(s.a) / 2)
	}
	return item
}

func (s *ResizingArrayStack[T]) Elements() []T {
	return s.a[:s.N]
}

简单测试程序:

package ch1

import (
	"fmt"
	"testing"
)

/**
 * @Description
 * @Author zyz
 * @Date 2024/4/19 11:30
 **/

func TestResizingArrayStack_Elements(t *testing.T) {
	stack := NewResizingArrayStack[string](2)
	stack.Push("hello")
	stack.Push("world")

	for _, item := range stack.Elements() {
		fmt.Println(item)
	}
}

这里就是用for-range遍历的,ResizingArrayStack实现了Elements方法,该方法返回了一个切片,切片可以用for-range语法遍历。

链表

现在我们来学习一种基础数据结构的使用,它是在集合类的抽象数据类型实现中表示数据的合适选择。这是我们构造非Go直接支持的数据结构的第一个例子。

具体程序:

package ch1

/**
 * @Description 单向链表实现
 * @Author zyz
 * @Date 2024/4/19 13:54
 **/

type Node[T any] struct {
	item T
	next *Node[T]
}

type LinkList[T any] struct {
	first *Node[T]
	last  *Node[T]
	N     int
}

func NewLinkList[T any]() *LinkList[T] {
	return &LinkList[T]{
		first: nil,
		last:  nil,
		N:     0,
	}
}

// NewNode 初始化Node
func NewNode[T any](item T) *Node[T] {
	return &Node[T]{
		item: item,
		next: nil,
	}
}

// InsertFirst 表头插入节点
func (l *LinkList[T]) InsertFirst(item T) {
	oldFirst := l.first
	l.first = NewNode[T](item)
	l.first.next = oldFirst
	l.N++
	if l.N == 1 {
		l.last = l.first
	}
}

// DeleteFirst 表头删除节点
func (l *LinkList[T]) DeleteFirst() T {
	if l.IsEmpty() {
		return *new(T)
	}
	item := l.first.item
	l.first = l.first.next
	l.N--
	if l.IsEmpty() {
		l.last = nil
	}
	return item
}

func (l *LinkList[T]) IsEmpty() bool {
	return l.N == 0
}

// InsertLast 表尾插入节点
func (l *LinkList[T]) InsertLast(item T) {
	oldLast := l.last
	l.last = NewNode[T](item)
	if l.IsEmpty() {
		l.first = l.last
	} else {
		oldLast.next = l.last
	}
	l.N++
}

func (l *LinkList[T]) PrintList() {
	for x := l.first; x != nil; x = x.next {
		print(x.item)
		if x.next != nil {
			print(" -> ")
		}
	}
	println()
}

栈是一种后进先出的数据结构,由于已经有了上边基础,这里直接看程序即可。

package ch1

/**
 * @Description 下压堆栈(链表实现)
 * @Author zyz
 * @Date 2024/4/19 15:17
 **/

type Stack[T any] struct {
	first *Node[T]
	N     int
}

type StackIterator[T any] struct {
	current *Node[T]
}

func (s *Stack[T]) Iterator() *StackIterator[T] {
	return &StackIterator[T]{
		current: s.first,
	}
}

func (s *StackIterator[T]) HasNext() bool {
	return s.current != nil
}

func (s *StackIterator[T]) Next() T {
	item := s.current.item
	s.current = s.current.next
	return item
}

// Elements 返回一个包含所有栈元素的切片
func (s *Stack[T]) Elements() []T {
	var res []T
	for i := s.Iterator(); i.HasNext(); {
		res = append(res, i.Next())
	}
	return res
}

func NewStack[T any]() *Stack[T] {
	return &Stack[T]{
		first: nil,
		N:     0,
	}
}

// Push 压栈
func (s *Stack[T]) Push(item T) {
	oldFirst := s.first
	s.first = NewNode[T](item)
	s.first.next = oldFirst
	s.N++
}

// Pop 出栈
func (s *Stack[T]) Pop() T {
	if s.IsEmpty() {
		return *new(T)
	}
	item := s.first.item
	s.first = s.first.next
	s.N--
	return item
}

func (s *Stack[T]) IsEmpty() bool {
	return s.N == 0
}

func (s *Stack[T]) Size() int {
	return s.N
}

队列

先进先出的数据结构。

package ch1

/**
 * @Description 队列(链表实现)
 * @Author zyz
 * @Date 2024/4/19 15:31
 **/

type Queue[T any] struct {
	first *Node[T]
	last  *Node[T]
	N     int
}

type QueueIterator[T any] struct {
	current *Node[T]
}

func (q *Queue[T]) Iterator() *QueueIterator[T] {
	return &QueueIterator[T]{
		current: q.first,
	}
}

func (q *QueueIterator[T]) HasNext() bool {
	return q.current != nil
}

func (q *QueueIterator[T]) Next() T {
	item := q.current.item
	q.current = q.current.next
	return item
}

// Elements 返回一个包含所有队列元素的切片
func (q *Queue[T]) Elements() []T {
	var res []T
	for i := q.Iterator(); i.HasNext(); {
		res = append(res, i.Next())
	}
	return res
}

func NewQueue[T any]() *Queue[T] {
	return &Queue[T]{
		first: nil,
		last:  nil,
		N:     0,
	}
}

// Enqueue 入队
func (q *Queue[T]) Enqueue(item T) {
	oldLast := q.last
	q.last = NewNode[T](item)
	if q.IsEmpty() {
		q.first = q.last
	} else {
		oldLast.next = q.last
	}
	q.N++
}

// Dequeue 出队
func (q *Queue[T]) Dequeue() T {
	if q.IsEmpty() {
		return *new(T)
	}
	item := q.first.item
	q.first = q.first.next
	q.N--
	if q.IsEmpty() {
		q.last = nil
	}
	return item
}

func (q *Queue[T]) IsEmpty() bool {
	return q.N == 0
}

func (q *Queue[T]) Size() int {
	return q.N
}

背包(Bag)

背包是一种特殊数据的结构,不支持删除操作。

package ch1

/*
 * @Description 包数据结构
 * @Author: zyz
 * @Date: 2024/4/19 下午 07:47
 */

type Bag[T any] struct {
	first *Node[T]
}

type BagIterator[T any] struct {
	current *Node[T]
}

func (b *Bag[T]) Iterator() *BagIterator[T] {
	return &BagIterator[T]{
		current: b.first,
	}
}

func (b *BagIterator[T]) HasNext() bool {
	return b.current != nil
}

func (b *BagIterator[T]) Next() T {
	item := b.current.item
	b.current = b.current.next
	return item
}

func NewBag[T any]() *Bag[T] {
	return &Bag[T]{
		first: nil,
	}
}

func (b *Bag[T]) Add(item T) {
	oldFirst := b.first
	b.first = NewNode[T](item)
	b.first.next = oldFirst
}

func (b *Bag[T]) IsEmpty() bool {
	return b.first == nil
}

func (b *Bag[T]) Elements() []T {
	var res []T
	for i := b.Iterator(); i.HasNext(); {
		res = append(res, i.Next())
	}
	return res
}

小结

至此,用GO实现了三种数据结构,底层分别用到了数组和链表,请注意,这两种数据结构是以后其他数据结构的基础,要重点记下,还有就是用GO实现了类似Java集合的迭代方法,也请看一看。