数据结构-go

链表 #

链表定义 #

type ListNode struct{//单链表
  Val int
  Next *ListNode
}
type DoubleNode struct{//双链表
	Val int
	Prev *DoubleNode
	Next *DoubleNode
}

创建链表 #

func CreatListNode(list []int) (tai *ListNode ){
  head := &ListNode{Val: list[0]} //无头节点情况  head:=&ListNode{}
  tail := head
	for i := 1; i < len(list); i++ {      //有头节点,这里i=0
		head.Next = &ListNode{Val: list[i]}
		head = head.Next
	}
  return tail
}
func CreatDoubleNode(list []int) (head *DoubleNode) { //创建双链表
	p := &DoubleNode{}
	q := p
	for i := 0; i < len(list); i++ {
		p.Next = &DoubleNode{Val: list[i]}
		p.Next.Prev = p
		p = p.Next
	}
	return q
}
func main() {
	list := []int{1, 2, 3, 4, 5}
  tail:=Creat(list)
	print(tail.Next.Val)
  head := CreatDoubleNode(list)
	println(head.Next.Next.Val)
	print(head.Next.Next.Prev.Val)
}

相关算法 #

实现单链表逆序 #

//从链表第二个节点开始,把遍历到的结点插入到头结点的后面,直到结束。
func InsertReverse(head *ListNode) {
	if head == nil || head.Next == nil {
		return
	}
	var cur *ListNode    //当前结点
	var next *ListNode   //后继结点
	cur = head.Next.Next //指向第二个结点
	head.Next.Next = nil //第一结点后面断开
	for cur != nil {
		next = cur.Next
		cur.Next = head.Next
		head.Next = cur
		cur = next
	}
}
func reverseLinkedList(head *ListNode) {
    var pre *ListNode
    cur := head
    for cur != nil {
        next := cur.Next
        cur.Next = pre
        pre = cur
        cur = next
    }
}
从头到位输出链表 #

递归输出

func ReversPrint(head *ListNode){
	if head==nil{
		return
	}
	ReversPrint(head.Next)
	Println(head.Val)
}

从无序链表中移除重复项 #

输入:head=[1,3,1,5,5,7]
输出:[1,3,5,7]
//双重循环直接在链表上操作,外层循环用一个指针从第一个节点开始遍历整个链表,然后内层循环用另外一个指针遍历其余节点,将相等结点删除。
func RemovDup(head *ListNode) *ListNode {
	if head == nil {
		return head
	}
	var pre *ListNode
	pre = head
	var next *ListNode
	var cur *ListNode  //帮助删除的前驱指针
	for pre != nil {//外层循环
		next = pre.Next
		x := pre.Val
		cur = pre
		for next != nil { //内层循环
			if x == next.Val { //相等就删除
				cur.Next = next.Next
				next = next.Next
			} else {  //不相等
				cur = cur.Next
				next = next.Next
			}
		}
		pre = pre.Next
	}
	return head
}

从有序链表中移除重复项 #

输入:head = [1,1,2]
输出:[1,2]
func deleteDuplicates(head *ListNode) *ListNode {
	var pre *ListNode
	pre = head
	var next *ListNode
	if pre == nil {  //排除为空情况
		return head
	}
	for pre.Next != nil {
		next = pre.Next
		if pre.Val == next.Val {
			pre.Next = next.Next
		} else {
			pre = pre.Next
		}
	}
	return head
}

从有序链表中移除重复项2 #

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
//对链表中的结点直接进行相加操作,把相加的和存储到新的链表中对应的结点中,同时还要记录结点相加后的进位。
func deleteDuplicates(head *ListNode) *ListNode {
	if head == nil { //排除为空
		return head
	}
	var pre *ListNode
	cur := &ListNode{-1, head} //亮点在于创建头节点 防止第一第二结点重复
	pre = cur
	for pre.Next != nil && pre.Next.Next != nil {
		if pre.Next.Val == pre.Next.Next.Val { //如果相等了  找一个值 一个一个剔除
			x := pre.Next.Val
			for pre.Next != nil && pre.Next.Val == x {
				pre.Next = pre.Next.Next
			}
		} else {
			pre = pre.Next
		}
	}
	return cur.Next
}

计算两个单链表所代表的数之和 #

输入:head1=[3,4,5,6,7,8]
		 head2=[9,8,7,6,5]
输出:head=[2,3,3,3,3,9]
func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
    dummy := &ListNode{}//定义结构体指针赋值为空
    for dy,rst :=dummy,0;l1 != nil || l2 != nil || rst !=0;dy = dy.Next{
        if l1 != nil {
             rst += l1.Val
             l1 = l1.Next
        }
        if l2 != nil {
            rst += l2.Val
            l2 = l2.Next
        }
        dy.Next = &ListNode{Val: rst % 10}
        rst /=10
    }
    return dummy.Next
}

对链表进行重新排序 #

排序前:1,2,3,4,5,6,7
排序后:1,7,2,6,3,5,4
//1、先找出链表的中间节点;
//2、对链表的后半部分子链表进行逆序;
//3、把链表的前半部分子链表与逆序后的后半部分子链表进行合并
func findMiddleNode(head *ListNode) *ListNode { //找出中间节点
	if head == nil && head.Next == nil {
		return head
	}
	fast := head    //快指针,每次走两步
	slow := = head  //慢指针,每次走一步
	slowPre := head //slow的前一个指针,方便断开
	for fast != nil && fast.Next != nil {
		slowPre = slow
		slow = slow.Next
		fast = fast.Next.Next
	}
	slowPre.Next = nil
	retrun slow
}
func reverse(head *ListNode) *ListNode { //对链表进行逆序
	if head == nil && head.Next == nil {
		return head
	}
	var pre *ListNode  //前驱结点
	var next *ListNode //当前结点
	for head != nil {
		next = head.Next
		head.Next = pre
		pre = head
		head = next
	}
	return pre
}
func Reorder(head *ListNode) {
	if head == nil && head.Next == nil {
		return head
	}
	cur1 := head
	mid := findMiddleNode(head)
	cur2 := reverse(mid)
	var tmp *ListNode
	for cur.Next != nil { //合并链表
		tmp = cur1.Next
		cur1.Next = cur2
		cur1 = tmp
		tmp = cur2.Next
		cur2.Next = cur1
		cur2 = tmp
		cur1.Next = cur2
	}
}

找出单链表中倒数第K个元素 #

输入:head = [1,2,3,4,5], n = 2
输出:4
//快慢指针法
//设置两个指针,让其中一个指针比另一个指针先前移k步,然后两个指针同时向前移动,直到先行的指针为nil时,另一个指针所指位置就是要找的位置。
func FindLastK(head *ListNode, k int) *ListNode {
	fast := head.Next
	slow := head.Next
	i := 0
	for i = 0; i < k && fast != nil; i++ {
		fast = fast.Next
	}
	if i < k { //如果i小于k 就结束 说明链遍历完了  
		return nil
	}
	for fast != nil {
		slow = slow.Next
		fast = fast.Next
	}
	return slow
}

检测一个较大单链表是否有环 * #

//快慢指针法
//慢指针前进一步,快指针前进两步,如果快指针等于慢指针,则证明这个链表有环
func IsLoop(head *ListNode) *ListNode {
	fast := head
	low := head
	for fast != nil && fast.Next != nil {
		fast = fast.Next.Next
		low = low.Next
		if fast == low {
			return fast
		}
	}
	return nil
}

检测一个较大单链表是否有环2 #

力扣142

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
//当发现slow与fast相遇时,我们再额外使用一个指针ptr。起始,它指向链表头部;随后,它和slow每次向后移动一个位置。最终,它们会在入环点相遇。
func detectCycle(head *ListNode) *ListNode {
    if head==nil||head.Next==nil{
        return nil
}
    fast:=head
    low:=head
    for fast!=nil&&fast.Next!=nil{
        fast=fast.Next.Next
        low=low.Next
        if fast==low{
            pre:=head
            for pre!=low{
                pre=pre.Next
                low=low.Next
            }
            return low
        }
    }
    return nil
}

把链表相邻元素翻转 #

输入:[1,2,3,4,5,6,7]
输出:[2,1,4,3,6,5,7]
//力扣24题
//就地逆序,通过调整结点指针域的指向来直接调换相邻的两个结点。
func swapPairs(head *ListNode) *ListNode { //假设这里存在头结点
  if head!=nil&&head.Next!=nil{
    return head
  }
  pre:=head
  cur:=head.Next
  next:=head
  for cur!=nil&&cur.Next!=nil{
    next=cur.Next.Next   //next指向第三个节点
		pre.Next=cur.Next    //头节点之乡第二个结点
    cur.Next.Next=cur    //第二个节点指向第一个结点
    cur.Next=next        //第一个结点指向第三个结点
    pre=cur              //pre 指向第一个结点 就是新链表的第二个结点
    cur=next             //cur 指向第三个结点 开始新的循环
  }
  return head
}

把链表以K个结点为一组进行翻转 #

力扣25题
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
首先把前K个结点看成一个子链表,采用前面介绍的方法进行翻转,把翻转后的子链表接到头结点后面,然后把接下来的K个结点看成另外一个单独的链表进行翻转,把翻转后的子链表接到上一个已经完成翻转子链表的后面。
func InsertReverse(head *ListNode) {//翻转子链表
	if head == nil || head.Next == nil {
		return head
	}
  var pre,next *ListNode
  for head!=nil{   
		next=head.Next
    head.Next=pre
    pre=head
    head=next
  }
  return pre
}
func reverseKGroup(head *ListNode, k int) *ListNode { //假设没有空头结点
  if head ==nil||head.Next==nil{
    return head
  }
  var begin,pre,end,pNext *ListNode
  pre=&ListNode{-1,head}
  begin=head 
  for begin!=nil{
    end=begin
    for i:=1;i<k;i++{
      if end.Next!=nil{
        end=end.Next
      }else{
        break
      }
    }
    pNext=end.Next   //下一个要翻转的开头
    end.Next=nil     //结尾断开
    pre.Next=InsertReverse(begin)//翻转链表 放到pre后面
    begin.Next=pNext     //begin就变成了翻转后的结尾,接到pNext
    pre=begin          //pre 放到结尾
    begin=pNext        //从下一个开头开始循环
  }
  return head
}

翻转链表2 #

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
func reverseBetween(head *ListNode, left, right int) *ListNode {
  pre:=&ListNode{-1,head}//设置头结点,防止left为1干扰
  head=pre
  for i:=0;i<left-1;i++{
    pre=pre.Next
  }
  cur:=pre.Next
  for i:=left;i<right;i++{  //翻转链表 直到 right
    next:=cur.Next
    cur.Next=next.Next
    next.Next=pre.Next
    pre.Next=next 
  }
  return head.Next
}

合并两个有序链表 #

力扣21题
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
func mergeTwoLists(L1 *ListNode, L2 *ListNode) *ListNode { //叫什么归并 假设无空头结点
	var head *ListNode
  head=&ListNode(-1,L1)   //设置头结点
  pre:=head
  for L1!=nil&&L2!=nil{
    if L1.Val>=L2.Val{   //指向小的
      pre.Next=L2
      L2=L2.Next         //都往后移位
      pre=pre.Next
    }else{
      pre.Next=L1
      L1=L1.Next
      pre=pre.Next
    }
  }
  //结束之后看看谁还有剩余
  if L1!=nil{
    pre.Next=L1
  }
  if L2!=nil{
    pre.Next=L2
  }
  return head.Next
}

在只给定单链表中某个结点指针的情况下删除该结点 #

删除结点5前链表:1,2,3,4,5,6,7
删除结点5之后链表:1,2,3,4,6,7
通过把这个结点后面的数据复制到前面解决
func RemoveNode(node *ListNode) boo1 {
  if node==nil||node.Next==nil{  //node为最后一个结点也完成不了
    return false
  }
  for node.Next.Next!=nil{
     node.Val=node.Next.Val
     node=node.Next
  }
  node.Val=node.Next.Val
  node.Next=nil
  return true 
}

判断两个单链表(无环)是否交叉 #

力扣160题
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。

输入:listA = [4,1,8,4,5], listB = [5,6,1,8,4,5]
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
//首先判断链表headA和headB是否为空,如果至少一个人为空,则两个链表不相交,返回nil,当两个链表都不为空时,创建两个指针pa和pb,同时往后移,如果pa为空,pa指向headB,如果Pb为空,pb指向headA,当pa和pb指向同一个结点或者都为空时,返回他们指的结点或为nil
func getIntersectionNode(headA, headB *ListNode) *ListNode {
  if headA==nil||headB==nil{
    return nil
  }
  pa:=headA
  pb:=headB
  for pa!=pb {  //遍历如果pa=pb=nil 退出
    if pa==nil{
      pa=headB
    }else{
      pa=pa.Next
    }
    if pb==nil {
      pb=headA
    }else{
      pb=pb.Next
    }
  }
  return pa
}
//如果两个链表相交,那么两个链表从相交点到链表结束都是相同的结点,必然是Y字型,所以,判断两个链表的最后一个结点是不是相同即可。即先遍历一个链表,直到尾部,再遍历一个链表,如果同样走到同样的尾结点,则相交,记录下链表长度n1,n2,再遍历一次,长链表先出发n1-n2步,之后同时前进,相遇的第一个结点为相交结点。
func getIntersectionNode(headA, headB *ListNode) *ListNode {
  if headA==nil||headB==nil{ //假设无头结点
    return nil
  }
  pa:=headA
  pb:=headB
  a,b:=1,1    //计数
  for pa.Next!=nil{ 
    pa=pa.Next
    a++
  }
  for pb.Next!=nil{
    pb=pb.Next
    b++
  }
  if pa==pb{  //证明相交
    if a>b{
      n:=a-b
      for i:=0;i<n;i++{
        headA=headA.Next
      }
      for headA!=headB{
        headA=headA.Next
        headB=headB.Next
      }
      return headA
    }else{
      n:=b-a
      for i:=0;i<n;i++{
        headB=headB.Next
      }
      for headA!=headB{
        headA=headA.Next
        headB=headB.Next
      }
      return headA
    }
    
  }else{      //证明不相交
    return nil
  }
}

旋转链表 #

力扣61题

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
//先计数n,看链表有多少个元素,让后将链表变成环, 然后将头结点后移n-k%n个
func rotateRight(head *ListNode, k int) *ListNode {
	
}

分割链表 #

力扣86题

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
//维护两个链表,large,small,遇到比x大的 放到large后面,遇到比x小的,放到small后面,然后收尾相接
func partition(head *ListNode, x int) *ListNode {
    large:=&ListNode{}
    small:=&ListNode{}
    cur:=small
    pre:=large
    for head!=nil{
        if head.Val<x{
            small.Next=head
            small=small.Next
        }else{
            large.Next=head  
            large=large.Next
        }
         head=head.Next
    }
    large.Next=nil
    small.Next=pre.Next
    return cur.Next
}

#

栈的定义 #

/**
栈:限制插入和删除只能在一个位置上进行的表,该位置是表的末端,叫做栈的顶(top)对栈的基本操作有push(进栈)和pop(出栈)。
基本算法:
进栈(push):
1.若top>=n时,作出错误处理(进栈前先检查栈是否已满,满则溢出,不满则进入2)
2.置top = top + 1(栈指针加1,指向进栈地址)
3.s(top) = x ,结束(x为新进栈的元素)
出栈(pop):
1.若top <=0,则给出下溢信息,作出错处理(出栈前先检查栈是否为空,空则下溢,不空走2)
2.x = s(top),出栈后的元素赋值给x
3.top = top -1 ,栈指针减1,指向栈顶
 */

切片 #

// 定义常量栈的初始大小
const initSize int = 20
type Stack struct {
// 容量
 	size int
 	// 栈顶
 	top int
 	// 用slice作容器,定义为interface{}接收任意类型
 	data []interface{}
 }
// 判断栈是否为空
func (s *Stack) IsEmpty() bool  {
	return s.top == -1
}
 // 判断栈是否已满
func (s *Stack) IsFull() bool {
	return s.top == s.size - 1
}
 // 入栈
func (s *Stack) Push(data interface{}) bool  {
	// 首先判断栈是否已满
	if s.IsFull() {
		fmt.Println("stack is full, push failed")
		return false
	}
	// 栈顶指针+1
	s.top++
	// 把当前的元素放在栈顶的位置
	s.data[s.top] = data
	return true
}
 // pop,返回栈顶元素
func (s *Stack) Pop() interface{}  {
	// 判断是否是空栈
	if s.IsEmpty() {
		fmt.Println("stack is empty , pop error")
	return nil
}
	// 把栈顶的元素赋值给临时变量tmp
	tmp := s.data[s.top]
	// 栈顶指针-1
	s.top--
	return tmp
}
 // 栈的元素的长度
func (s *Stack)GetLength() int {
	length := s.top + 1
	return length
}
 // 清空栈
func (s *Stack) Clear()  {
	s.top = -1
}
 // 遍历栈
func (s *Stack) Traverse()  {
	// 是否为空栈
	if s.IsEmpty() {
		fmt.Println("stack is empty")
	}
	for i := 0 ; i <= s.top; i++ {
		fmt.Println(s.data[i], " ")
	}
}

链表 #

type Node struct {
  data interface{}
  Next *Node
}
type Stack struct {
  length int
  top *Stact
}
//入栈
func (s *Stack) Push (value interface{}){
  n:=&Node{value,s.top}
  s.head=n
  s.length++
}
//出栈
func (s *Stack) Pop interface{}{
  if s.length==0{
    return nil
  }
  n:=s.top
  s.top = n.Next
  s.length--
  return n.data
}

实现栈 #

数组实现栈 #

 // 创建并初始化栈,返回strck
func createStack() Stack  {
	s := Stack{}
	s.size = initSize
	s.top = -1
	s.data = make([]interface{}, initSize)
	return s
}
	s1 := Stack{            //初始化栈
		size: len(s),
		top:  -1,
		data: make([]int, len(s)+1),
	}

链表实现栈 #

采用头插法

相关算法 #

根据入栈序列判断可能的出栈序列 #

输入push=[12345] pop=[3,2,5,4,1]
输出ture
思路:使用一个栈模拟入栈顺序
1.把push序列依次入栈,直到栈顶元素等于Pop序列的第一个元素,然后栈顶元素出栈,POP序列移动到第二个元素。
2.如果栈顶继续等于pop序列现在的元素,则继续出栈并pop后移,否则对push序列继续入栈。
3.如果push序列已经全部入栈,但是pop序列未全部遍历,而且栈顶元素不等于当前pop元素,那么这个序列不是一个可能的出栈序列。如果栈为空,而且pop序列也全部被遍历过,则说明这是一个可能的pop序列。
时间复杂度O(n),空间复杂度O(n)
const initSize int = 20
type Stack struct {
	size int
	top  int
	data []int
}
func (s *Stack) IsEmpty() bool { //判断栈是否为空
	return s.top == -1
}
func (s *Stack) IsFull() bool { //判断栈是否已满
	return s.top == s.size-1
}
func (s *Stack) Push(data int) bool {
	if s.IsFull() {
		return false
	}
	s.top++ //栈顶指针加1
	s.data[s.top] = data
	return true
}
func (s *Stack) Pop() int {
	if s.IsEmpty() { //判断是否栈空
		return -1
	}
	tmp := s.data[s.top]
	s.top--
	return tmp
}
func (s *Stack) Top() int {
	if s.IsEmpty() {
		return -1
	}
	return s.data[s.top]
}
func IsPopSerial(push []int, pop []int) bool {
	pushlen := len(push)
	poplen := len(pop)
	if pushlen == 0 || poplen == 0 || pushlen != poplen { //判断两个长度
		return false
	}
	s := Stack{ //初始化一个栈
		size: initSize,
		top:  -1,
		data: make([]int, initSize),
	}
	pushIndex := 0
	popIndex := 0
	for pushIndex < pushlen {
		s.Push(push[pushIndex]) //push元素依次入栈  直到栈顶等于pop序列顶第一个元素
		pushIndex++
		for !s.IsEmpty() && s.Top() == pop[popIndex] { //栈顶元素出栈,pop序列移动到下一个元素
			s.Pop()
			popIndex++
		}
	}
	if s.IsEmpty() && popIndex == poplen { //栈为空,且pop序列中元素全被遍历过
		return true
	}
	return false
}
func main() {
	push:=[]int{1,2,3,4,5}
	pop:=[]int{3,4,1,2,5}
	print(IsPopSerial(push,pop))
}

用O(1)的时间复杂度求栈中的最小元素 #

思路:在实现的时候采用空间换时间,使用两个栈结构,一个栈用来存储数据,另外一个栈用来存储栈的最小元素。
如果当前入栈的元素比原来栈中的最小值还小,则把这个值压入保存最小元素的栈中;在出栈的时候,如果当前出栈的元素恰好为当前栈中的最小值,则保存最小值的栈顶元素也出栈,使得当前最小值变为当前最小值入栈之前的那个最小值。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.
type MinStack struct { //定义两个栈
	stack *Stack
	minStack *Stack
}
func (s *MinStack)Push(data int){ //入栈
	s.stack.Push(data)            //普通栈首先先入
	if s.minStack.IsEmpty(){      //保存最小值的栈 如果空则入
		s.minStack.Push(data)
	}else{
		if data<=s.minStack.Top(){   //不为空比较一下 再入
			s.minStack.Push(data)
		}
	}
}
func (p *MinStack)Pop()int  {   //出栈
	topData:=p.stack.Pop()         //普通栈直接出
	if topData==p.Min(){          //如果出的是最小值,则保存最小值的栈 出栈
		p.minStack.Pop()
	}
	return topData
}
func (p * MinStack)Min()int  {
	if p.minStack.IsEmpty(){    //如果为空,返回一个特定值
		return math.MaxInt32
	}else{
		return p.minStack.Top()  //不为空返回栈顶元素
	}
}

用两个栈模拟队列 #

思路:A为插入栈,B为弹出栈。
如果栈B不为空,则直接弹出栈B的数据。
如果栈B为空,则一次弹出栈A的数据,放入B中,再弹出栈B的数据。
type StackQueue struct{  //定义两个栈
  aStack *Stack
  bStack *Stack
}
func (s *StackQueue) Push (data int) { //只有a入栈
  s.aStack.Push(data)
}
func (s *StackQueue) Pop () int{
  if s.bStack.IsEmpty() {    //b为空,弹出a的数据放入b,再弹出栈b数据
    for !s.aStack.IsEmpty(){
      s.bStack.Push(s.aStack.Pop())
    }
  }
  return s.bStack.Pop()   //b不为空直接弹出
}

队列 #

定义 #

切片 #

type queue struct {
	data  []int
	front int //队头
	rear  int //队尾
}

//判断队列是否为空
func (s *queue) IsEmpty() bool {
	return s.front == s.rear
}

//返回队列大小
func (s *queue) Size() int {
	return s.rear - s.front
}

//返回队列首元素
func (s *queue) GetFront() int {
	if s.IsEmpty() {
		panic(errors.New("队列为空"))
	}
	return s.data[s.front]
}

//返回队尾元素
func (s *queue) GetBack() int {
	if s.IsEmpty() {
		panic(errors.New("队列为空"))
	}
	return s.data[s.rear-1]
}

//删除队列头元素
func (s *queue) DeQueue() {
	if s.rear > s.front {
		s.rear--
		s.data = s.data[1:]
	} else {
		panic(errors.New("队列为空"))
	}
}

//把新元素加入队列尾
func (s *queue) EnQueue(item int) {
	s.data = append(s.data, item)
	s.rear++
}

链表 #

type ListNode struct { //单链表
	Val  int
	Next *ListNode
}
type LinkQueue struct {
	head *ListNode
	end  *ListNode
}

//判断队列是否为空
func (s *LinkQueue) IsEmpty() bool {
	return s.head == nil
}

//获取队列中元素个数
func (s *LinkQueue) Size() int {
	size := 0
	node := s.head
	for node != nil {
		node = node.Next
		size++
	}
	return size
}

//入队列,队尾入
func (s *LinkQueue) EnQueue(temp int) {
	node := &ListNode{Val: temp}
	if s.head == nil {
		s.head = node
		s.end = node
	} else {
		s.end.Next = node
		s.end = node
	}
}

//出队列,对头出
func (s *LinkQueue) DeQueue() {
	if s.head == nil {
		panic(errors.New("队列为空"))
	}
	s.head = s.head.Next
	if s.head == nil {
		s.end = nil
	}
}

//取得队列首元素
func (s *LinkQueue) GetFront() int {
	if s.head == nil {
		panic(errors.New("队列为空"))
	}
	return s.head.Val
}

//取得队列尾元素
func (s *LinkQueue) GetBack() int {
	if s.end == nil {
		panic(errors.New("队列为空"))
	}
	return s.end.Val
}

实现队列 #

切片 #

func CreatQueue() queue {  //初始化一个队列
	s := queue{}
	s.rear = 0
	s.front = 0
	s.data = make([]int, 0)
	return s
}

s := queue{
		rear:  0,
		front: 0,
		data:  make([]int, 0),
	}

链表 #

s:=&LinkQueue{}

相关算法 #

用队列实现栈 #

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
思路:一个队列实现栈,队列为空,直接入队,队列不为空,入队,前面的全部出队再入队。
type MyStack struct {
	stack *queue
}

func Constructor() (s MyStack) { //定义栈
	return
}

func (this *MyStack) Push(x int)  {
  n:=this.stack.Size
  this.stack.EnQueue(x)
  for ; n>0;n--{
    c:=this.stack.GetFront //得到队头元素
    this.stack.EnQueue(c)  //插入队尾
    this.stack.DeQueue     //删除队头
  }
}

func (this *MyStack) Pop() int {
  a:=this.stack.GetBack
  this.stack.DeQueue
  return a
}

func (this *MyStack) Top() int {
	return this.stack.GetFront
}

func (this *MyStack) Empty() bool {
	return this.stack.IsEmpty
}

实现LRU缓存方案 #

请你设计并实现一个满足  LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
* 使用双向链表实现的队列队列的最大容量为缓存的大小在使用的过程中把最近使用的页面移动到队列首最近没有使用使用的页面将被放在队列尾的位置
* 使用一个哈希表把页号作为键把缓存在队列中的结点的地址作为值
  当引用一个页面时所需的页面在内存中我们需要把这个页对应的结点移动到队列的前面如果所需的页面不在内存中我们将它存储在内存中简单地说就是将一个新结点添加到队列的前面并在哈希表中更新相应的结点地址如果队列是满的那么就从队列尾部移除一个结点并将新结点添加到队列的前面
//很烦直接复制粘贴了
type LRUCache struct {
    size int
    capacity int
    cache map[int]*DLinkedNode
    head, tail *DLinkedNode
}

type DLinkedNode struct {
    key, value int
    prev, next *DLinkedNode
}

func initDLinkedNode(key, value int) *DLinkedNode {
    return &DLinkedNode{
        key: key,
        value: value,
    }
}

func Constructor(capacity int) LRUCache {
    l := LRUCache{
        cache: map[int]*DLinkedNode{},
        head: initDLinkedNode(0, 0),
        tail: initDLinkedNode(0, 0),
        capacity: capacity,
    }
    l.head.next = l.tail
    l.tail.prev = l.head
    return l
}

func (this *LRUCache) Get(key int) int {
    if _, ok := this.cache[key]; !ok {
        return -1
    }
    node := this.cache[key]
    this.moveToHead(node)
    return node.value
}


func (this *LRUCache) Put(key int, value int)  {
    if _, ok := this.cache[key]; !ok {
        node := initDLinkedNode(key, value)
        this.cache[key] = node
        this.addToHead(node)
        this.size++
        if this.size > this.capacity {
            removed := this.removeTail()
            delete(this.cache, removed.key)
            this.size--
        }
    } else {
        node := this.cache[key]
        node.value = value
        this.moveToHead(node)
    }
}

func (this *LRUCache) addToHead(node *DLinkedNode) {
    node.prev = this.head
    node.next = this.head.next
    this.head.next.prev = node
    this.head.next = node
}

func (this *LRUCache) removeNode(node *DLinkedNode) {
    node.prev.next = node.next
    node.next.prev = node.prev
}

func (this *LRUCache) moveToHead(node *DLinkedNode) {
    this.removeNode(node)
    this.addToHead(node)
}

func (this *LRUCache) removeTail() *DLinkedNode {
    node := this.tail.prev
    this.removeNode(node)
    return node
}

二叉树 #

定义 #

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

实现 #

//设置结点的值
func (node *TreeNode) SetValue(value int) {
	node.Val = value
}

//创建结点
func CreatNode(value int) *TreeNode {
	return &TreeNode{value, nil, nil}
}

//递归查找结点
func (node *TreeNode) FindNode(n *TreeNode, x int) *TreeNode {
	if n == nil {
		return nil
	} else if n.Val == x {
		return n
	} else {
		p := node.FindNode(n.Left, x)
		if p != nil {
			return p
		}
		return node.FindNode(n.Right, x)
	}
}

//递归求树的高度
//对于任意一个子树的根节点来说,它的深度=左右子树深度的最大值+1
func (node *TreeNode) GetTreeHeigh(n *TreeNode) int {
	if n == nil {
		return 0
	} else {
		lHeigh := node.GetTreeHeigh(n.Left)
		rHeigh := node.GetTreeHeigh(n.Right)
		if lHeigh > rHeigh {
			return lHeigh + 1
		} else {
			return rHeigh + 1
		}
	}
}

//非递归求树的高度
//借助队列,在进行层次遍历时,记录遍历的层数
func (node *TreeNode) GetTreeHeigh2() int {
	if node == nil {
		return 0
	}
	layers := 0
	nodes := []*TreeNode{node}
	for len(nodes) > 0 {
		layers++
		size := len(nodes) //每层的结点树
		count := 0
		for count < size {
			count++
			curNode := nodes[0]
			nodes = nodes[1:]
			if curNode.Left != nil {
				nodes = append(nodes, curNode.Left)
			}
			if curNode.Right != nil {
				nodes = append(nodes, curNode.Right)
			}
		}
	}
	return layers
}

//递归前序遍历二叉树
func (node *TreeNode) PreOrder(n *TreeNode) {
	if n != nil {
		print("%d", n.Val)
		node.PreOrder(n.Left)
		node.PreOrder(n.Right)
	}
}

//递归中序遍历二叉树
func (node *TreeNode) InOrder(n *TreeNode) {
	if n != nil {
		node.InOrder(n.Left)
		print("%d", n.Val)
		node.InOrder(n.Right)
	}
}

//递归后序遍历二叉树
func (node *TreeNode) PostOrder(n *TreeNode) {
	if n != nil {
		node.PostOrder(n.Left)
		node.PostOrder(n.Right)
		print("%d", n.Val)
	}
}

//层次遍历(广度优先遍历)
func (node *TreeNode) BreadthFirstSearch() {
	if node == nil {
		return
	}
	result := []int{} //创建队列
	nodes := []*TreeNode{node}
	for len(nodes) > 0 {
		curNode := nodes[0] //访问结点
		nodes = nodes[1:]
		result = append(result, curNode.Val) //入队
		if curNode.Left != nil {
			nodes = append(nodes, curNode.Left)
		}
		if curNode.Right != nil {
			nodes = append(nodes, curNode.Right)
		}
	}
	for _, v := range result {
		print(v)
	}
}
//创建一颗树
func main() {
	root := CreateNode(5)
	root.left = CreateNode(2)
	root.right = CreateNode(4)
	root.left.right = CreateNode(7)
	root.left.right.left = CreateNode(6)
	root.right.left = CreateNode(8)
	root.right.right = CreateNode(9)
}

相关算法 #

二叉树相关算法

B树和B+树 #

#

查找 #

相关算法 #

二分查找1 #

请实现无重复数字的升序数组的二分查找

给定一个 元素升序的、无重复数字的整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标(下标从 0 开始),否则返回 -1

输入:[-1,0,3,4,6,10,13,14],13
返回值:6
func search( nums []int ,  target int ) int {
    left,right:=0,len(nums)-1
    mid:=0
    for left<=right{
        mid=left+(right-left)/2    //二分查找精髓
        if nums[mid]==target{
            return mid
        }
        if nums[mid]>target{
            right=mid-1
        }else{
            left=mid+1
        }
    }
    return -1
}

二维数组中的查找 #

在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

[

[1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15]

]

给定 target = 7,返回 true。

给定 target = 3,返回 false。

数据范围:矩阵的长宽满足 0≤n,m≤5000≤n,m≤500 , 矩阵中的值满足 0≤val≤1090≤val≤109 进阶:空间复杂度 O(1)O(1) ,时间复杂度 O(n+m)O(n+m)

示例1

输入:
7,[[1,2,8,9],[2,4,9,12],[4,7,10,13],[6,8,11,15]]
返回值:true
func Find( target int ,  array [][]int ) bool { //关键在于左边的比你小,你下面的比你大,故从右上开始
    m,n:=len(array),len(array[0])
    for i,j:=0,n-1;i<m&&j>=0;{
        if array[i][j]==target{
            return true
        }
        if array[i][j]>target{   //往左下查找
            j--
        }else{
            i++
        }
    }
    return false
}

排序 #

算法比较 #

排序方法 最好时间 平均时间 最坏时间 辅助存储 稳定性 备注
简单选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定 n小时较好
直接插入排序 O(n) O(n^2) O(n^2) O(1) 稳定 大部分已有序时较好
冒泡排序 O(n) O(n^2) O(n^2) O(1) 稳定 n小时较好
希尔排序 O(N) O(nlogn) O(ns)1<s<2 O(1) 不稳定 s是所选分组
快速排序 O(nlogn) O(nlogn) O(n^2) O(logn) 不稳定 n大时较好
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定 n大时较好
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定 n大时较好

虽然直接插入排序和冒泡排序的速度比较慢,但是当初始序列整体或局部有序时,这两种排序算法会有比较好的效率。当初始序列整体或局部有序时,快速排序算法的效率会下降。当排序序列较小且不要求稳定时,直接选择排序效率好;要求稳定时,冒泡排序效率较好。


堆排序、快速排序的时间复杂度以及分别适用什么场景 #

当数据规模较大时,应用速度最快的排序算法,可以考虑使用快速排序

快速排序平均时间复杂度:O(nlogn)

快速排序最好时间复杂度:O(nlogn)

快速排序平均空间复杂度:O(logn)

堆排序不会出现快排那样最坏情况,且堆排序所需的辅助空间比快排要少,但是这两种算法都不是稳定的,要求排序时是稳定的,可以考虑用归并排序

堆排序平均时间复杂度:**O(n*logn)**空间复杂度几乎为0(只用到几个临时变量)

对记录较少的文件效果一般,对于记录较多的文件很有效,其运行时间主要耗费在创建堆和反复调整堆上。

(1)当数据规模较小时候,可以使用简单的直接插入排序或者直接选择排序。

(2)当文件的初态已经基本有序,可以用直接插入排序和冒泡排序。

(3)当数据规模较大时,应用速度最快的排序算法,可以考虑使用快速排序。当记录随机分布的时候,快速排序平均时间最短,但是出现最坏的情况,这个时候的时间复杂度是O(n^2),且递归深度为n,所需的占空间为O(n)。

(4)堆排序不会出现快排那样最坏情况,且堆排序所需的辅助空间比快排要少,但是这两种算法都不是稳定的,要求排序时是稳定的,可以考虑用归并排序。

(5)归并排序可以用于内部排序,也可以使用于外部排序。在外部排序时,通常采用多路归并,并且通过解决长顺串的合并,缠上长的初始串,提高主机与外设并行能力等,以减少访问外存额外次数,提高外排的效率。

选择排序 #

[3 4 2 1 7 6 8 9 5 0]*
[0 4 2 1 7 6 8 9 5 3]
[0 1 2 4 7 6 8 9 5 3]
[0 1 2 3 7 6 8 9 5 4]
...
[0 1 2 3 4 5 6 7 8 9]

平均时间复杂度:O(n^2)

空间复杂度:O(1)

代码 #
func SelectSort(data []int) {
	l := len(data) //得到数组长度
	for i := 0; i < l; i++ {
		tmp := i    //定位
		for j := i + 1; j < l; j++ {
			if data[tmp] >= data[j] {
				tmp = j  //定位到最小值下标
			}
		}
		data[i], data[tmp] = data[tmp], data[i] //交换
	}
}
func main() {
	data := []int{0, 0, 0, 0, 0, 0}
	SelectSort(data)
	fmt.Println(data)
}

插入排序 #

[3 4 2 1 7 6 8 9 5 0]*
[3 4 2 1 7 6 8 9 5 0]
[3 4 2 1 7 6 8 9 5 0]
[2 3 4 1 7 6 8 9 5 0]
[1 2 3 4 7 6 8 9 5 0]
...
[0 1 2 3 4 5 6 7 8 9]

平均时间复杂度:O(n^2)

空间复杂度:O(1)

代码 #
func InsertSort(data []int) {
	if data == nil { //如果为空返回
		return
	}
	for i := 1; i < len(data); i++ { //默认从第二个开始
		tmp := 0
		for j := tmp; j < i; j++ {
			if data[i] < data[j] {   //发现后面的小,开始交换  否则j++到i
				data[j], data[i] = data[i], data[j] //交换
			}
		}
	}
}
func main() {
	data := []int{5, 4, 1, 1, 0, 5, 0}
	InsertSort(data)
	fmt.Println(data)
}

冒泡排序 #

[3 4 2 1 7 6 8 9 5 0]*
[3 2 1 4 6 7 8 5 0 9]
[2 1 3 4 6 7 5 0 8 9]
[1 2 3 4 6 5 0 7 8 9]
...
[0 1 2 3 4 5 6 7 8 9]

平均时间复杂度:O(n^2)

空间复杂度:O(1)

代码 #
func BubbleSort(data []int) {
	l := len(data)
	for i := 0; i < l-1; i++ {
		for j := 0; j < l-1-i; j++ {  //每次循环都会有一个数值到达指定位置,故j<l-1-i 无需后面比较
			if data[j] > data[j+1] {
				data[j], data[j+1] = data[j+1], data[j]
			}
		}
	}
}
func main() {
	data := []int{5, 4, 3, 1, 0}
	BubbleSort(data)
	fmt.Println(data)
}

归并排序 #

[3 4 2 1 7 6 8 9 5 0]*
[3 4][1 2][6 7][8 9][0 5]
[1 2 3 4][6 7 8 9][0 5]
[1 2 3 4 6 7 8 9][0 5]
[0 1 2 3 4 5 6 7 8 9]

二路归并过程需要进行logn趟。每一趟归并操作,就是将两个有序子序列进行归并,而每一对有序子序列归并时,记录的比较次数均小于等于记录的移动次数,记录移动的次数均等于文件中记录的个数n,即每一趟归并的时间复杂度为O(n)

平均时间复杂度:O(nlogn)

空间复杂度:O(n)

代码 #
// 自顶向下归并排序,排序范围在 [begin,end) 的数组
func MergeSort(array []int, begin int, end int) {
    // 元素数量大于1时才进入递归
    if end - begin > 1 {
        // 将数组一分为二,分为 array[begin,mid) 和 array[mid,high)
        mid := begin + (end-begin+1)/2
        // 先将左边排序好
        MergeSort(array, begin, mid)
        // 再将右边排序好
        MergeSort(array, mid, end)
        // 两个有序数组进行合并
        merge(array, begin, mid, end)
    }
}
// 归并操作
func merge(array []int, begin int, mid int, end int) {
    // 申请额外的空间来合并两个有序数组,这两个数组是 array[begin,mid),array[mid,end)
    leftSize := mid - begin         // 左边数组的长度
    rightSize := end - mid          // 右边数组的长度
    newSize := leftSize + rightSize // 辅助数组的长度
    result := make([]int, 0, newSize)
    l, r := 0, 0
    for l < leftSize && r < rightSize {
        lValue := array[begin+l] // 左边数组的元素
        rValue := array[mid+r]   // 右边数组的元素
        // 小的元素先放进辅助数组里
        if lValue < rValue {
            result = append(result, lValue)
            l++
        } else {
            result = append(result, rValue)
            r++
        }
    }
    // 将剩下的元素追加到辅助数组后面
    result = append(result, array[begin+l:mid]...)
    result = append(result, array[mid+r:end]...)
    // 将辅助数组的元素复制回原数组,这样该辅助空间就可以被释放掉
    for i := 0; i < newSize; i++ {
        array[begin+i] = result[i]
    }
    return
}

希尔排序 #

通常间隔为总长度的一半

[3 4 2 1 7 6 8 9 5 0]*
[3 4 2 1 0 6 8 9 5 7]5
[0 1 2 4 3 6 5 7 8 9]2
[0 1 2 3 4 5 6 7 8 9]1

希尔排序的关键并不是随便地分组后各自排序,而是将相隔某个“增量”的记录组成一个子序列,实现跳跃式地移动,使得排序的效率提高。

平均时间复杂度:O(n*logn)

空间复杂度:O(1)

代码 #
func ShellSort(data []int) {
	for gap := len(data) / 2; gap > 0; gap = gap / 2 {// 进行分组
		for i := gap; i < len(data); i++ {// i 待排序的元素   // 插入排序	
			for j := i; j >= gap; j = j - gap {// j 在比较过程中, 待排序元素的位置
				if data[j-gap] <= data[j] {// 同组左边的元素 <= 待排序元素
					break
				}
				data[j-gap], data[j] = data[j], data[j-gap]// 交换
			}
		}
	}
}
func main() {
	data := []int{5, 4, 3, 1, 0}
	ShellSort(data)
	fmt.Println(data)
}

堆排序 #

平均时间复杂度:O(n*logn)

空间复杂度几乎为0(只用到几个临时变量)

对记录较少的文件效果一般,对于记录较多的文件很有效,其运行时间主要耗费在创建堆和反复调整堆上。

即使在最坏情况下,其时间复杂度也为O(n*logn)。

堆排序主要包括两个过程:一是构建堆;二是交换堆顶元素与最后一个元素的位置。

具有n个结点的完全二叉树深度为(log2n)+1,其中(log2n)+1是向下取整。

完全二叉树性质:
下标为i的结点的父结点下标:(i-1)/2
下标为i的结点的左孩子结点下标:i*2+1
下标为i的结点的右孩子结点下标:i*2+2
代码 #
func HeapSort(arr []int) []int {
	length := len(arr)
	for i := 0; i < length; i++ {
		lastmesslen := length - i   //长度减1缩短堆大小,最后端元素位置定型
		HeapScortMax(arr, lastmesslen) //调整堆
		//fmt.Println(arr)
		if i < length { //将最前面的跟最后面的换一下
			arr[0], arr[lastmesslen-1] = arr[lastmesslen-1], arr[0]
		}
		//fmt.Println("ex", arr)
	}
	return arr
}

func HeapScortMax(arr []int, length int) []int {
	//length := len(arr)
	if length <= 1 {
		return arr
	} else {
		depth := length/2 - 1 //节点下标,n,2*n+1,2*n+2 最大父节点下标
		for i := depth; i >= 0; i-- {
			topmax := i  //指向父结点
			left := 2*i + 1  //左孩子下标
			right := 2*i + 2 //右孩子下标
			if left <= length-1 && arr[left] < arr[topmax] { //防止越界   这里< 输出由大到小 
				topmax = left   //定位
			}
			if right <= length-1 && arr[right] < arr[topmax] { //注意topmax  >输出由小到大
				topmax = right
			}

			if topmax != i {   //如果topmax发生变化交换位置
				arr[i], arr[topmax] = arr[topmax], arr[i]
			}
		}
		return arr
	}
}
func main() {
	arr := []int{15, 21, 0, 23, 8, -1}
	fmt.Print(HeapSort(arr))
}

快速排序 #

快速排序采用分而治之的思想

每次排序均有一个数字到达其最终位置,左边均比其小,右边均比其大。

当数据规模较大时,应用速度最快的排序算法,可以考虑使用快速排序

最坏时间复杂度:O(n^2)

平均时间复杂度:O(nlogn)

最好时间复杂度:O(nlogn)

平均空间复杂度:O(logn)

对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前一部分的所有记录均比后一部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。

代码 #
func sort(arry []int, left, right int) { //数组,左右下标
	if left >= right {
		return
	}
	i := left
	j := right
	temp := arry[i] //用于交换值
	for i < j {
		for i < j && arry[j] > temp { //从后往前 如过后面的大于前面的 则j--
			j--
		}
		if i < j { //到这里证明arry[i]>=arry[j]
			arry[i] = arry[j] //让arry[i]=arry[j]
			i++               //向后移
		}
		for i < j && arry[i] < temp {
			i++
		}
		if i < j { //到这里证明arry[i]>=temp
			arry[j] = arry[i]
			j--
		}
	}
	arry[i] = temp
	sort(arry, left, i-1) //左右放入递归
	sort(arry, i+1, right)
}
func QuickSort(arry []int) {
	sort(arry, 0, len(arry)-1)
}
func main() {
	data := []int{5, 4, 9, 8, 7, 6, 0, 1, 3, 2}
	QuickSort(data)
	fmt.Println(data)
}