理论介绍
本节介绍链表的操作,在本节中我们将学到下面的内容:
- 生成一个链表
- 链表添加节点
- 链表删除节点
- 链表插入节点
- 获取链表长度
- 查找链表节点
- 获取所有节点
- 链表反转
之前我们在go语言中,我们用interface来表示范性,今天我们说说interface在go语言当中的另一层含义——接口
在go语言中,接口类型是对其他类型行为的抽象和概括,接口不会跟特定的实现细节绑定在一起,接口可以让我们的函数更加灵活以及更具有适应能力。接口类型具体描述了一系列方式的集合,一个实现了这些方法的具体类型是这个接口类型的实例。在我们下面的代码中head就是LinkNoder的一个实例.
代码实现
package linklist
import (
"fmt"
"log"
)
// Item 可以理解为范性,也就是任意的数据类型
type Item interface {
}
// 一个节点,除了自身的数据之外,还必须指向下一个节点,尾部节点指向为nil
type LinkNode struct {
Payload Item // Payload 为任意数据类型
Next *LinkNode
}
type LinkNoder interface {
// go语言接口,在这个接口里面,我们可以定义一系列的方法。
Add(payload Item)
Delete(index int) Item
Insert(index int, payload Item)
GetLength() int
Search(payload Item) int
GetAll(index int) Item
Reverse() *LinkNode
}
// 创建一个新的链表。 函数,对比与上面的方法,函数是没有绑定任何对象的。
// go语言的函数需要指明参数跟返回值,在此函数中,我们的参数是length,返回值是一个LinkNode对象
// 除了绑定之外,函数跟方法并没有什么不同
func NewLinkNode(length int) *LinkNode {
if length <= 0{
fmt.Printf("链表长度必须大于0")
log.Panic("链表长度必须大于0")
}
var head *LinkNode
head = &LinkNode{}
for i := 0; i<length; i++{
var newNode *LinkNode
newNode = &LinkNode{Payload: i}
newNode.Next = head
head = newNode
}
return head
}
从上面的接口定义中,我们看到在接口里面我们定义了七个方法。 接下来,我们就一一实现这七个方法
添加元素 Add方法
// go语言方法,对比与下面的NewLinkNode,方法可以理解为面向对象里面对象的方法,虽然实现的功能
// 跟函数类似,但是方式是绑定在对象上的,也就是说我们此处的Add是绑定与head这个LinkNode对象的。
// 这个是go语言区别与其他语言的设计方式,也是go语言很重要的一部分。
func (head *LinkNode) Add(payload Item) {
// 这里采用尾部插入的方式,给链表添加元素
point := head
for point.Next != nil{
point = point.Next
}
newNode := LinkNode{payload, nil}
point.Next = &newNode
// 头部插入
//newNode := LinkNode{payload, nil}
//newNode.Next = head
}
添加元素这里我们提供了头插法跟尾部插入两种实现。头部插入就是每次都将新加入的节点的next设置为原来节点的头。
尾部加入首先需要将节点移动到尾部,然后将尾部节点的next指向新节点。
获取链表长度 GetLength方法
func (head *LinkNode) GetLength() int {
iterator := head
var length int
for iterator.Next != nil{
length ++
iterator = iterator.Next
}
return length
}
func (head *LinkNode) GetAll() []Item {
dataList := make([]Item, 0, head.GetLength())
point := head
for point.Next != nil{
dataList = append(dataList, point.Payload)
point = point.Next
}
dataList = append(dataList, point.Payload)
return dataList
}
获取链表长度其实很好理解,就是遍历整个链表,然后设置一个自增值,每遍历一个节点,自增值+1,直到链表全部遍历完成。 获取到的自增值的值也就是链表的长度。
删除元素 Delete
““
// 删除元素,并且返回删除的节点的值
func (head *LinkNode) Delete(index int) Item {
// 边界条件
linkLength := head.GetLength()
if index < 0 || index > linkLength{
fmt.Printf("index out of range %d, please check.", linkLength)
return "index out of range, please check."
}
point := head
for i := 0; i<index; i++{
point = point.Next // 移动point到index位置,然后将其next指向next.next
}
point.Next = point.Next.Next
data := point.Next.Payload
return data
}
““
在此方法中,我们根据链表的索引对链表元素进行删除。 逻辑解读: 首先我们需要移动到索引节点的上一个节点也就是index-1的节点, 然后跳过index节点,将index-1的节点直接指向index+1 ,也就是index-1 的next为index+1,这样我们就在链表中删除了index这一个节点。
插入元素 Insert方法
// 插入元素
func (head *LinkNode) Insert(index int, payload Item){
linkLength := head.GetLength()
if index < 0 || index > linkLength{
fmt.Printf("index out of range %d, please", linkLength)
return
}
point := head
for i:=0; i<index; i++{
point = point.Next
}
newNode := LinkNode{Payload:payload}
newNode.Next = point.Next
point.Next = &newNode
}
插入跟删除类似,理解了删除,插入也就很好立即了。 我们首先移动到index-1 的节点,然后将index-1的next指向新加入的节点newNode,然后newNode的next指向原来的index, 这样我们就实现了链表的插入。
查找 Search方法
func (head *LinkNode) Search(payload Item) int {
point := head
index := 0
for point.Next != nil{
if point.Payload == payload{
return index
}else {
index ++
point = point.Next
// 边界条件
if index > head.GetLength() -1 {
break
}
continue
}
}
// 判断最后一个元素是否匹配
if point.Payload == payload {
return index
}
return -1 // 不存在时的返回值
}
链表查找元素,其实就是遍历整个链表,然后对比,是否是需要的值,如果是返回索引index,如果不是往下继续遍历,直到遍历完所有节点。这里需要注意边界条件,让程序正常退出,防止引起死循环的bug。
获取所有节点 GetAll方法
func (head *LinkNode) GetAll() []Item {
dataList := make([]Item, 0, head.GetLength())
point := head
for point.Next != nil{
dataList = append(dataList, point.Payload)
point = point.Next
}
dataList = append(dataList, point.Payload)
return dataList
}
这个操作比较简单,就是遍历整个链表,然后将链表中的每个节点都加入到一个列表中,最后返回
链表反转
// 循环的方式反转一个链表
func (head *LinkNode) Reverse() *LinkNode {
if head == nil || head.Next == nil{
return head
}
var reverseHead *LinkNode
var p *LinkNode
reverseHead = head
head = head.Next
reverseHead.Next = nil
p = head.Next
for head != nil{
head.Next = reverseHead
reverseHead = head
head = p
if p != nil{
p = p.Next
}
}
return reverseHead
}
// 递归的方式实现链表的反转
func (head *LinkNode) RecursiveReverse() *LinkNode {
if head == nil || head.Next == nil{
return head
}
second := head.Next
newHead := second.RecursiveReverse()
second.Next = head
head.Next = nil
return newHead
}
在上面提供了两种链表反转的方式, 一种是采用循环的方式,一种是采用递归的方式。 理解起来都比较简单,其实就是设置一些临时变量,然后操作节点的指向。 具体看看代码, 文字描述有点啰嗦。
测试
package linklist
import "testing"
func TestLinkNode_Add(t *testing.T) {
head := NewLink(10)
for head.Next != nil{
t.Errorf("%s", head.Payload)
head = head.Next
}
}
func TestLinkNode_Reverse(t *testing.T) {
head := NewLink(10)
//newHead := head.RecursiveReverse()
newHead := head.Reverse()
for newHead != nil{
t.Errorf("%s", newHead.Payload)
newHead = newHead.Next
}
}
这里我的测试写的比较简单,大家可以尝试自己先写写。如果在实现过程中有问题的,可以访问我的github查看源码。