数据结构与算法基础-Go语言版-1.线性表

目录

前言

一、线性表是什么?

1.定义

2.存储结构

二、线性表的实现

1.顺序表

1.1.1 顺序表的结构定义

1.1.2 顺序表的初始化

1.1.3 判断空表

1.1.4 顺序表的插入

1.1.5 顺序表的删除

1.1.6 顺序表的修改

1.1.7 顺序表的查找

2.链表

2.1.1 链表的结构定义

2.1.2 链表的初始化

2.1.3 判断空表

2.1.4 链表的插入

2.1.5 链表的删除

2.1.6 链表的修改

2.1.7 链表的查找

三、练习:完成双向循环链表的插入与删除

3.1 双向循环链表的结构定义

3.2 双向循环链表的插入

3.3 双向循环链表的删除

总结



前言

最近在学习Go语言,同时在学习计算机基础四大件中的数据结构与算法,但在网上查找资源时,发现大部分数据结构与算法视频、博客等都很少使用Go语言来编写,正好小编在学习,于是决定写一个GO语言版的数据结构与算法系列博客,小编是初学者,很多地方学的可能不是很好,希望各位大佬能够多多指正。


提示:以下是本篇文章正文内容,下面案例可供参考






一、线性表是什么?

1.定义

       线性表,就是有限的数据按照一定的顺序存储的一个序列,其中,除了表头和表尾,表中任一元素都只有一个直接前驱和一个直接后继。

2.存储结构

        线性表分为顺序存储结构和链式存储结构,其中

        顺序存储结构:便于查找,但插入和删除比较麻烦

        链式存储结构:插入删除较为容易,但查找比较复杂




二、线性表的实现



1.顺序表

1.1.1 顺序表的结构定义

const MaxSize = 100
type sqList struct{
    data [MaxSize]int // 用去存储顺序表数据
    length int
}

1.1.2 顺序表的初始化

func initSqlist()*sqList{
    var data [MaxSize]int
    return &sqList{
        data:data,
        length:0,
    }
}

1.1.3 判断空表

func (s *sqList)isValid()bool{
    if s.length == 0{
        return true
    }
    return false
}

1.1.4 顺序表的插入

  • 思路:考虑到顺序表的存储结构在内存中是一块连续的内存,因此我们只要找到需要插入元素的位置,然后将该位置及其后面的元素依次往后移一位即可
  • 当然,我们在往顺序表插入元素时,首先要判断插入位置是否合法化,线性表中的元素是按一定顺序排列的序列,不能说想插在哪个位置就插在哪个位置。其次,如果顺序表满了,我们就不能继续插入了(可以选择扩容,但这里不展示)
  • 顺序表插入的思路如图所示

  • 代码实现如下:
// 将元素插入在第index个位置,时间复杂度为O(n)
func (s *sqList)insert(data int,index int)error{
    if index < 0 || index > s.length { // 判断索引值是否合法
        return errors.New("please input the right index\n")
        return
    }
    if s.length == MaxSize{ // 判断是否队满
        return errors.New("the list is full,can not insert\n")
        return
    }

    for i:= s.length-1;i>=index;i--{ // 将插入位置及以后的元素依次后移
        s.data[i+1] = s.data[i]
    }
    s.data[index] = data // 在插入位置插入元素
    s.length++
    return nil
}

1.1.5 顺序表的删除

  • 思路:与顺序表插入的操作相反,在进行删除操作时,我们首先得找到删除元素的位置,然后将其之后的元素依次向前移动一个位置,这样要删除的元素就会被覆盖掉,达到了删除该元素的目的。
  • 同样,删除时也要判断索引值是否合法,另外,如果顺序表为空,则没有元素可以删除。
  • 顺序表删除思路如图所示:

   

  • 代码实现如下:
// 删除顺序表中第index位置的元素,时间复杂度为O(n)
func (s *sqList)delete(index int)error{
    if index < 0 || index > s.length { // 判断索引值是否合法
        return errors.New("please input the right index\n")
    }
    if s.isValid(){ // 判断是否队空
        return errors.New("the list is vali,can not delete\n")
    }
    for i:=index;i<s.length;i++{
        s.data[i] = s.data[i+1]
    }
    s.length--
    return nil
}

1.1.6 顺序表的修改

// 修改顺序表中第index位置的值,时间复杂度为O(1)
func (s *sqList)modify(data int,index int)error{
    if index < 0 || index > s.length { // 判断索引值是否合法
        return errors.New("please input the right index\n")
    }
    if s.isValid(){ // 判断是否队空
        return errors.New("the list is vali,can not modify any value\n")
    }
    s.data[index] = data
    return nil
}

1.1.7 顺序表的查找

// 根据索引查找值,时间复杂度为O(1)
func (s *sqList)searchByIndex(index int)(int,error){
    if index < 0 || index > s.length { // 判断索引值是否合法
        return -1,errors.New("please input the right index\n")
    }
    if s.isValid(){ // 判断是否队空
        return -1,errors.New("the list is vali\n")
    }
    return s.data[index],nil
}

// 根据值查找索引,时间复杂度为O(n)
func (s *sqList)searchByValue(data int)(int,error){
    
    if s.isValid(){ // 判断是否队空
        return -1,errors.New("the list is vali\n")
    }
    
    for k,v := range s.data{
        if v == data{
            return k,nil
        }
    }
    return -1,errors.New("can not find the value\n")
}






2.链表

2.1.1 链表的结构定义

type lNode struct{
    data int
    next *lNode
}

func newLNode(data int)*lNode{
    return &lNode{
        data:data,
        next:nil,
    }
}

type  linklist struct{
    head *lNode //头节点,不存放数据
    length int // 记录链表长度
}

2.1.2 链表的初始化

// 初始化链表
func initLinklist()*linklist{
    return &linklist{
        head:&lNode{  // 头指针不存放数据
            data:-1,
            next:nil,
        },
        length:0,
    }
}

2.1.3 判断空表

func (l *linklist)isValid()bool{
    if l.length == 0{
        return true
    }
    return false
}

2.1.4 链表的插入

  • 思路:链表的插入,我们首先要找到插入位置,具体做法就是用一个指针(本例用p)从从头节点开始,一个节点一个节点地沿着链表移动,同时用一个变量记录索引(本例代码使用变量j),每移动一个节点,j就自动加1,直到j=index为止,此时,指针p指向了要插入节点的前一个节点的位置,接下来进行插入操作即可
  • 插入操作如图所示,图中展示了向链表中第0个位置插入一个元素的过程

 原链表

 插入后的链表

  • 如果找到链表结尾都无法找到插入位置,则返回错误
  • 代码实现如下:
// 链表的插入
func (l *linklist)insert(data int,index int)error{
    temp := newLNode(data)
    p ,j:= l.head,0
    for p != nil && j <index{ // 找到插入位置
        p = p.next
        j++
    } 
    if p == nil || j > index{
        return errors.New("insert failed\n")
    }
    temp.next = p.next // 将要插入节点的next指向原来在该位置的节点
    p.next = temp 
    l.length++
    return nil
    
}

2.1.5 链表的删除

  • 思路:寻找删除位置的思路与寻找插入位置是一样的,最终cur指向的是删除位置的节点,但与插入不同的是,我们还需要一个pre指针指向当前指针的前一个节点,这样才能使链表连起来。
  • 下图展示了如何删除一个链表第0个位置的节点

  • 代码实现如下:
// 链表的删除
func (l *linklist)delete(index int)error{
    if l.isValid(){
        return errors.New("the list is valid")
    }
    
    pre,cur,j:= l.head,l.head.next,0 // 链表删除要用到双指针
    for cur != nil && j < index{ // 找到删除位置
        pre = cur  // 记录当前指针
        cur = cur.next // 遍历下一个
        j++
    }
    if cur == nil || j > index{
        return errors.New("delete failed\n")
    }
    pre.next = cur.next
    cur.next = nil
    l.length--
    return nil
    
}

2.1.6 链表的修改

// 链表的修改
func (l *linklist)modify(data int,index int)error{
    if l.isValid(){
        return errors.New("the list is valid")
    }
    p ,j:= l.head,0
    for p != nil && j <index{ // 找到修改位置,p最终指向修改位置的前一个结点
        p = p.next
        j++
    } 
    if p == nil || j > index{
        return errors.New("modify failed\n")
    }
    p.next.data = data
    return nil
    
}

2.1.7 链表的查找

// 单链表的查找,根据索引值返回节点
func (l *linklist)searchByIndex(index int)(*lNode,error){
    
    if l.isValid(){
        return nil,errors.New("the list is valid")
    }
    p ,j:= l.head,0
    for p != nil && j <index{ // 找到索引位置,p最终指向索引位置的前一个结点
        p = p.next
        j++
    } 
    if p == nil || j > index{
        return nil,errors.New("search failed\n")
    }
    return p.next,nil;
}

三、练习:完成双向循环链表的插入与删除

3.1 双向循环链表的结构定义

type dLNode struct{
    data int
    pre *dLNode
    next *dLNode
}

func newDLNode(data int)*dLNode{
    return &dLNode{
        data:data,
        pre:nil,
        next:nil,
    }
}

type dLinkList struct{
    head *dLNode
    length int
}

func initDLList()*dLinkList{
    head := newDLNode(-1)
    head.pre = head
    head.next = head
    return &dLinkList{
        head:head,
        length:0,
    }
}

3.2 双向循环链表的插入

  • 思路:双向链表的插入其实和单链表的插入区别不大,只是在插入的时候需要考虑两个指针的指向问题
  • 下图展示了如何在一个双向链表的第0号位置插入元素

 

  •  代码实现如下:
func (dl *dLinkList)insert(data int,index int)error{
    temp := newDLNode(data)
    p ,j:= dl.head,0
    for p != nil && j <index{ // 找到插入位置
        p = p.next
        j++
    } 
    if p == nil || j > index{
        return errors.New("insert failed\n")
    }
    temp.next = p.next
    temp.pre = p
    p.next = temp
    temp.next.pre = temp
    
    dl.length++
    return nil
}

3.3 双向循环链表的删除

  • 同理,双向循环链表的删除操作与单链表相似,由于双向链表的每个节点都有指向其前驱和后继节点的指针,因此不需要额外添加一个指针来记录当前节点的前驱。
  • 下图展示了从双向循环链表中删除第0号元素节点的思路

func (dl *dLinkList)delete(index int)error{
    p ,j:= dl.head,0
    for p != nil && j <index{ // 找到插入位置,最终p指向的是索引位置的前一个节点
        p = p.next
        j++
    } 
    if p == nil || j > index{
        return errors.New("delete failed\n")
    }
    p = p.next   // 将p移动到待删除的节点位置
    p.pre.next = p.next
    p.next.pre = p.pre
    p.next = nil
    p.pre = nil
    dl.length--
    return nil
}






总结

以上就是关于数据结构与算法基础的线性表的基本实现,如果有更好的实现方法欢迎在评论区与我分享,我会虚心学习各位大佬的经验。

本文参考书:《大话数据结构》

生命不息,coding不止!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值