目录
前言
最近在学习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不止!