3-4 8精彩算法集合。struct(C,ruby) Ruyb类对象和结构体, 3-5

在本章我遇到了c语言的struct数据,即自定义的数据结构。比如:

struct edge

{

   int u;

   int v;

   int w; 

};

题目给了一组数据,用edge储存。需要按照w大小排序。我开始不知道如何用ruby实现,后来想到之前的题目也遇到过(小猫钓鱼)。我定义了一个类,在类中储存了数据。

谷歌了一下,发现详细讲解ruby的struct的文章。下面是摘录和自己的理解:👇 

 

https://ruby-china.org/topics/21617  Ruby 中的 OpenStruct 详解(Struct效率高)

https://www.zybuluo.com/elibinary/note/760820   Ruyb类对象和结构体

https://ruby-doc.org/core-2.5.0/Struct.html  Ruby-struct文档

      Struct is a convenient way to bundle a number of attributes together, using accessor methods, without having to write an explicit class.

      快速声明,提高效率的方法。 

在 Ruby 中, 也有 Struct, 纯 C 实现, 用于快速声明一个 Class, 例如:

 Person = Struct.new(:age, :name, :sex)

me=Person.new(24,'Spirit','male')

me.age # => 24

me.name='Test'

me.name # => 'Test'

me.height # => NoMethodError

 


 案例:镖局运镖--图的最小生成数

如果一个连通无向图不包含回路,就是一棵数。这里求一个图的最小生成数

 

算法: (用到并查集森林,用一维数组实现。开始每个🌲都是孤立的,通过一些条件将这些树分类合并,合并的过程就是认爹的过程。遵守:靠左原则,找根节点原则)

 首先,按照边的权重从小到大排序,每次从剩余的边中选择权值较小的且边的两个点不在同一集合内的边(不产生回路),加入到生成数中,直到加 入了n-1条边为止。

def getf(city)
  if $f[city] == city
    return city
  else
    # 路径压缩
    return $f[city] = getf($f[city])
  end
end

 

def merge(from, to)
  # t1,t2分别是from,to的根节点,老大。
  t1 = getf(from)
  t2 = getf(to)
  if t1 != t2
    return $f[t2] = t1
  end
  return false
end

 

def quicksort(left,right,e)
  i = left
  j = right
  if left > right
    return
  end
  # 设定基准点
  temp = left
  while i != j
    while i < j && e[j].route >= e[temp].route
      # 当e[j]大于等用基准点时,j向左移动。直到找到小于基准点,结束循环,等待交换
      j -= 1
    end
    while i < j && e[i].route <= e[temp].route
      i += 1
    end
    if i < j
      x = e[i]
      e[i] = e[j]
      e[j] = x
    end
  end

 

# class Edge
#   attr_accessor :from, :to, :route
#
#   def initialize(from, to, route)
#     @from = from
#     @to = 1
#     @route = 1
#   end
# end
# 结构体:不用直接定义class了
Edge = Struct.new(:from, :to, :route)
# 输入边的信息:
e = []
n = 6
m = 9
i = 1
for i in 0..m-1 do
  print "请输入起点,终点和路费#{i}:\n"
  e[i] = Edge.new(gets.to_i, gets.to_i, gets.to_i)
end
# e[0] = Edge.new(0, 0,0)
# e[1] = Edge.new(2, 4,11)
# e[2] = Edge.new(3, 5, 13)
# e[3] = Edge.new(4, 6, 3)
# e[4] = Edge.new(5, 6, 4)
# e[5] = Edge.new(2, 3, 6)
# e[6] = Edge.new(4, 5, 7)
# e[7] = Edge.new(1, 2, 1)
# e[8] = Edge.new(3, 4, 9)
# e[9] = Edge.new(1, 3, 2)
# 按照权值从小到大对边进行快速排序
# qicksort(1,m, e)
e.sort!{|a,b|a.route<=>b.route}
puts e
# 初始化并查集,6个城市(节点)f = []
$f = [nil]
for i in 1..6 do
  $f[i] = i
end
p $f

 

# 核心部分
count = 0 #目的是让6个点相连成最小生成数,边是n-1:5条边
sum = 0   #route之和
for i in 1..m do
  if merge(e[i].from, e[i].to)
    count += 1
    sum += e[i].route
    p "#{i}:#{$f}"
  end
  if count == n-1
    p "break啦"
    break
  end
end
p sum
p $f

 

 


另一种最短生成树算法。 

prim算法:

 

总结:

      图中所有点分为(树顶点:已被入选生成树的点)和 非树顶点 。首先选择任意一个顶点加入到生成树(成为树的根)。然后找出一条边添加到生成树,这需要枚举每个树顶点到每个非树顶点的所有边,然后找到最短边加入到生成树。(写代码不需要枚举所有顶点到非树顶点的边,这样太费时。用disz最短路径法,只需要更新dis中的新加入的树顶点到其他非树顶点的最短距离)照此方法重复n-1次,直到所有点都加入生成树。 

 

     类似dijkstra最短路径法。用dis数组记录各个非树顶点到生成树的最短距离,用dis[k] 和e[j][k]比较,然后更新最短路径。因为只要离生成树最近即可,不用dis[j]+e[j][k]和dis[k]比较(这是源点到k的距离比较算法)。

 

 代码:O(n^2)

 

count = 0
sum = 0
#
n = 6
m = 9
e = [
  nil,
  [nil, 0, 1, 2, 999, 999, 999],
  [nil, 1, 0, 6, 11, 999, 999],
  [nil, 2, 6, 0, 9, 13, 999],
  [nil, 999, 11, 9, 0, 7, 3],
  [nil, 999, 999, 13, 7, 0, 4],
  [nil, 999, 999, 999, 3, 4, 0]

 

# 记录加入生成数的点,0表示未加入生成数的非树顶点。

 

book = [nil,0,0,0,0,0,0]
i = 1
j = 1

# dis[]

 

dis = [nil]
for i in 1..n do
  dis[i] = e[1][i]
end
# 核心
book[1] = 1
count += 1
while count < n
  min = 9999
  for i in 1..n do
    # 找到离根点1最近的点,加入树。
    if book[i] == 0 && dis[i] < min
      min = dis[i]
      j = i
    end
  end
  book[j] = 1
  count += 1
  sum += dis[j]
  # 扫描所有当前顶点j的所有边,再以j为中间点,更新树到每一个非树顶点的距离
  k = 1
  for k in 1..n do
    if book[k] == 0 && dis[k] > e[j][k]
      dis[k] = e[j][k]
    end
  end
end
p sum

 

使用堆,每次选边的时间复杂度下降到logM,

使用邻接表来储存图。整个算法o(MlogM).

dis 记录生成树到各非树顶点的距离

h   代表最小堆,值储存顶点编号,索引是堆的位置,从上到下,从左到右。

position记录每个顶点在最小堆的位置。索引就是顶点,值是最小堆的位置。 

 

def shiftdown(i,h)
  flag = 0
  temp = 0
  n = h.size - 1
  while i*2 <= n && flag == 0
    if $dis[h[i]] > $dis[h[i*2]]
      temp = i*2
    else
      temp = i
    end
    if i*2 + 1 <= n
      if $dis[h[temp]] > $dis[h[i*2 + 1]]
        temp = i*2 + 1
      end
    end
    if temp != i
      t = h[i]
      h[i] = h[temp]
      h[temp] = t
      # 同步更新$position
      t = $position[h[i]]
      $position[h[i]] = $position[h[temp]]
      $position[h[temp]] = t
      #
      i = temp
    else
      flag = 1
    end
  end
  return h
end
def shift(h)
  # 删除掉堆顶的城市后,把记录它在堆位置的$position值变更为0。
  t = h[1]
  $position[t] = 0
  h[1] = h.last
  $position[h[1]] = 1
  h.pop #删除最后一个
  return shiftdown(1,h)
end
def shiftup(i,h)
  flag = 0
  # 如果是堆顶,返回,不需要调整
  if i == 1
    return h
  end
  while i != 1 && flag == 0
    # 子节点小于父节点则交换.否则不需要交换,退出循环。
    if $dis[h[i]] < $dis[h[i/2]]
      t = h[i]
      h[i] = h[i/2]
      h[i/2] = t
      # 同步更新$position
      t = $position[h[i]]
      $position[h[i]] = $position[h[i/2]]
      $position[h[i/2]] = t
    else
      flag = 1
    end
    # 因为交换了位置,继续向上判断大小,更新i
    i = i/2
  end
  return h
end
#
n = 6
m = 9
#
# a = [nil, 2, 3, 4, 5, 2, 4, 1, 3, 1]
# b = [nil, 4, 5, 6, 6, 3, 5, 2, 4, 3]
# c = [nil, 11, 13, 3, 4, 6, 7, 1, 9, 2]
# 无向图,需要反向再存储一遍边的信息。a->b,b->a的r一样但都要储存到数组中,2*m
a = [nil, 2, 3, 4, 5, 2, 4, 1, 3, 1, 4, 5, 6, 6, 3, 5, 2, 4, 3]
b = [nil, 4, 5, 6, 6, 3, 5, 2, 4, 3, 2, 3, 4, 5, 2, 4, 1, 3, 1]
r = [nil, 11, 13, 3, 4, 6, 7, 1, 9, 2, 11, 13, 3, 4, 6, 7, 1, 9, 2]
# 邻接表储存边
# first 数组记录了每个点最后存入的边的编号。如果一个点没有出边则记录为-1.
# before 用于记录某个点的边的前一条编号
# before 储存了所有的初始编号-1和first中没有储存的边的编号
# before[i]储存了“编号为i的边”的出点a的“上一条邻接边”的编号。
# i是出点a最后读入的邻接边。
first = [nil]
before = [nil]
i = 1
for i in 1..n do
  first[i] = -1
end
for i in 1..2*m do
  before[i] = first[a[i]]
  first[a[i]] = i
end
#count记录数的点的个数,sum记录路径和
count = 0
sum = 0
# 用于记录顶点是否加入了生成树,0表示顶点未加入
book = [nil ,0,0,0,0,0,0]
book[1] = 1
count += 1
# 初始化$dis:1点到其他点的距离为无穷
$dis = [nil,0,999,999,999,999,999]
# k开始储存顶点1最后的邻接边的编号
k = first[1]
# 通过循环,$dis数组储存了1点的所有邻接点的距离,和非邻接点的距离999
while k != -1
  $dis[b[k]] = r[k]
  k = before[k]
end
p "dis:#{$dis}" #=>[nil, 0, 1, 2, 999, 999, 999]
# 初始化堆h:索引是堆的位置,比如1就是堆顶。值是城市编号
# 初始化$position:索引是每个城市号,值是在最小堆的位置。
size = 6
h = [nil]
$position = [nil]
for i in 1..size do
  h[i] = i
  $position[i] = i
end
# 非叶节点3,2,1 创建最小堆
i = size/2
while i >= 1
  # p "创建最小堆循环:#{i}"
  h = shiftdown(i, h)
  i -= 1
end
p "创建最小堆后:h#{h}"
p $position
# 删除堆首的城市1,把城市6移到堆首,然后向下调整
h = shift(h)
puts "第一次删除最小堆第一个顶点后\n#{h}"
p $position
# 每次拿出最小堆的根节点j,扫描当前j的所有的边,以j为中间节点,进行松弛。
while count < n
  # 找到离生成树最近的城市号(就是堆顶),加入生成树,再更新堆,sum,count
  # 第一轮j是顶点2,当前最小堆的根节点,离顶点1最近的点。
  j = h[1]
  book[j] = 1
  h = shift(h)
  count += 1
  sum += $dis[j]
  # 对新加入的树节点j到非树节点k的距离进行比较,更新生成树到每个非树节点的距离
  k = first[j]
  while k != -1
    if book[b[k]] == 0 && $dis[b[k]] > r[k]
      $dis[b[k]] = r[k]
      p "b[k]#{b[k]}"
      p "$position[b[k]]:#{$position[b[k]]}"
      # 更新距离,由于距离变小了,因此,堆不再是最小堆,b[k]顶点在堆中的位置需要向上调整!!
      h = shiftup($position[b[k]],h)
      p "#{count}次循环:#{h}"
      p "$position:#{$position}"
    end
    k = before[k]
  end
end
p sum 


 

posted @ 2018-03-04 16:39  Mr-chen  阅读(187)  评论(0编辑  收藏  举报