1月8日 啊哈!算法。--排序3种,桶,冒泡,二分快速排序法;队列,栈;纸牌游戏,炸弹人游戏, 数字的全排列,使用深度优先搜索(全排列),广度优先搜索

第一章 排序 

 


 

1. 桶算法(简单):

缺点:太占空间,只能对数本身排序,无用。 

C: 

int a[11], i,j,t;

for (i= 0;i <= 0; i++){

... 

 

ruby:

For ... In

本质上for...in是语法糖,ruby translates it into sht like: each do..end

You can use for to iterate over any Object that reponds to the method each, such as Array or a Range. 

a = []
for i in 0..10 do
  a[i] = 0
end
print a
puts "\n"   #屏幕上换行
a[2] = 1
a[3] = 1
a[5] = 2
a[8] = 1
# 正序排列
for i in 0..10 do
  j = 1
  while j <= a[i]
    print i
    j += 1
  end
end
puts "\n"
# 反序排列
i = 10
while i >=0
  j = 1
  while j <= a[i]
    print i
    j += 1
  end
  i -= 1
end

 

puts "\n"

 

# 结果是:
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# 23558
# 85532

 

 


 

2.冒泡排序 

 

比较相邻的两个元素,如果他们的顺序错误就把他们交换过来。

n个数,n-1轮。 每轮归位1个最大/最小数,已经归位的数下一轮无需再比较。

双重嵌套循环,时间复杂度n的平方,费时!

 

# 实践冒泡排序

 

def selection_sort(arr)
  i = 0                    
  temp = 0              # 用于冒泡的数据交换
  while i < arr.size - 1  # 只比较n-1次,因为有n个数
    j = 1                  
    while j < arr.size - i
      # 已经定位的数字无需在下次循环中比较。

      # 所以每次内循环都会减少,用减 i 来实现。

      if arr[j-1] <= arr[j]  
      # 另外,大于等于 是 从小到大排序
        temp = arr[j -1]
        arr[j-1] = arr[j]
        arr[j] = temp
      end
      j = j + 1
    end
    i = i + 1
  end
  return arr
end
arr =  [7, 68, 42, 46, 9, 91, 77, 46, 86, 1]
answer = selection_sort(arr)
puts answer.to_s

 


 

 

3 二分排序,快速排序。

步骤:

1. 在序列中随便找一个数作为基准数。最左边设i,最右边设j

2. 先从右往左找小于基准数的数,再从左往右找大于基准数的数,找到后两数交换位置。(升序)

3. 然后重复2和3步骤。直到i和j最终相遇,结束本次循环。 

4. 把基准数和最终相遇的数交换,结束。此步骤定位了一个数。

5. 原基准数左边和右边的序列再分别重复1-4的步骤。继续定位新的基准数。直到全部数字定位结束。

原理:

每一轮将这轮的基准数归位。 

跳跃式交换,总的交换就比较少,用时较少。

在平均状况下,排序个项目要大O符号)次比较。在最坏状况下则需要次比较,但这种状况并不常见。快速排序通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。

 

 

def quicksort(a,left,right)
  if left > right                # 设定何时停止该方法的调用。
    return
  end

 

  temp = a[left]              #设定最左边的为基准数
  i = left
  j= right
  while i != j                   # i和j从两边向中间靠拢,直到i和j重合,结束本轮循环。

    #  根据是升序还是降序来选择 比较运算符号。

    while a[j] <= temp && i < j  #顺序很重要,要先从右往左找。找大于基准数的数,这里是降序排序。
      j -= 1
    end
    while a[i] >= temp && i < j  #再从左向右找,这里找小于基准数的数。
      i += 1
    end
    if i < j                    #当i 和 j 没有相遇的时候,交换数据
      t = a[i]
      a[i] = a[j]
      a[j] = t
    end
  end
  a[left] = a[i]              #将基准数归位。
  a[i] = temp

  print a.to_s + "\n"      #打印每次交换的结果

 

  quicksort(a,left,i-1)       # 在quciksort的方法中,再次调用本方法,形成递归的过程。这个是处理i左边。                                       # 直到穷尽,即当left>right时停止调用该方法。
  quicksort(a,i+1,right)    # 这个处理i右边
end

 

a =  [7, 68, 42, 46, 9, 91, 77, 46, 86, 1]
quicksort(a,0,9)
# 0和9 代表数组最左边和最右边的key 
print a

 

结果:

[86, 68, 42, 46, 9, 91, 77, 46, 7, 1]
[91, 86, 42, 46, 9, 68, 77, 46, 7, 1]
[91, 86, 42, 46, 9, 68, 77, 46, 7, 1]
[91, 86, 77, 46, 46, 68, 42, 9, 7, 1]
[91, 86, 77, 46, 46, 68, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1]
[91, 86, 77, 68, 46, 46, 42, 9, 7, 1] #最终结果

 

 


 


 

 第二章队列,栈,链表

队列:first in first out

栈:last in first out

这是2个数据算法,c给封装成结构体类型。方便直接调用。

Ruby语法实现:

 

队列:把631758924,按照去掉第一个,把第二个放到队列最后。。。的方式运行程序。

 

q = %w(0 6 3 1 7 5 8 9 2 4)    #q[0] = 0是想要head 指向第一个数据。

head = 1
tail = 10      # tail = 10是指 队列最后一个数据的后一个空位置
dump = []      # 去掉的数据放到这里。
while head < tail
  printf("%d ",q[head])
  dump << q[head]

 

  head += 1     #移动头部指针
  q[tail] = q[head]     #把指针指向的数据放到队列后的第一个空位置。
  tail += 1     #移动tail指针
  head += 1     #头部指针继续移动
end
p dump #=> ["6", "1", "5", "9", "4", "7", "2", "8", "3"]

  



栈: 例子:验证是否是回文, 下面代码只适用于奇数个数据

string = "【21a1a1a12【"
s = string.split("")
s1= []
len = string.size      #计算长度
mid = len/2 + 1      #找到正的中间数
i = 1
while i < mid           #把mid之前的数据入栈
  s1 << s[i - 1]
  i += 1
end
k = 1                   # 把栈内的数据依次拿出和后面的数据比较
while k < mid
  if s1[mid - k - 1] != s[mid + k - 1]
    print "no"          #不同则不是回文,跳出循环
    break
  end
  k += 1
end
if k == mid       #循环顺利结束则k==mid是回文
  print "yes"
end

 

 

 

 


 

 纸牌游戏:(小猫钓鱼)

 

规则:

  1. 将一副牌平均分为两份,每人一份。a先出牌,b再出牌放在上面,交替出牌。 
  2. 出牌时,如果某人打出的牌与桌面上的牌大小相同,则将两张相同的牌及其中间的所有夹的牌全部取走,并依次放入自己手中的牌的末尾。
  3. 当任意一人的手中的牌全部出完时,游戏结束,对手胜利。

分析:

两个队列:代表两个玩家

一个栈: 代表桌面上的牌。

 (一看表,竟然用了2个小时,深陷其中了,呜呜。)

class Player
  attr_accessor :data, :head, :tail
  def initialize(data, head, tail)
    @data = data
    @head = 1
    @tail = 1
  end
end
player1 = Player.new([],1,1)
player2 = Player.new([],1,1)
table = []                          # 栈,设定一个数组,用于存储轮流打出的牌,规矩是last in first out
top = 0                             # 设定桌面上最上面的牌,是桌面上的第几张牌
player1.data = [2,4,1,2,5,6]        # 队列1, 规矩是first in first out
player1.head = 0                    # 设定头和尾后1个位置的索引
player1.tail = 6
player2.data = [3,1,3,5,6,4]        # 队列2
player2.head = 0
player2.tail = 6
# game begin
while player1.head < player1.tail && player2.head < player2.tail  #当队列不为空的时候执行循环
  temp = player1.data[player1.head]  #选手1 扔牌到桌面,存一个临时变量temporary
  flag = 0                           #用来判断此轮选手1能否赢,0没有赢,1赢(取走桌面上的牌)
  i = 1                              #定义一个临时变量,用于和top比较
  while i <= top                     #枚举遍历桌面上每张牌和选手扔的牌比较。
    if temp == table[i-1]            #如果有相同大小的牌,则本轮赢了,flag设为1.循环结束
      flag = 1
      break
    end
    i += 1
  end
  if flag == 0                       #没有赢牌
    player1.head += 1                #player1手里的第一张牌,通过head索引,变化。
    top += 1                         #牌桌最上面的牌是第top张
    table << temp                    #牌桌上增加了一张牌
  else                               #赢牌了
    player1.head += 1                #player1手里的第一张牌,通过head索引,变化。
    player1.data[player1.tail] = temp#把扔出的牌拿回,放在自己的牌的最下面。
    player1.tail += 1                #tail索引向后移动1位置
    while table[top - 1 ] != temp    #按照规则,把牌桌上的牌从上到下依次拿走,放到自己的牌的下面。直到遇见相同的那张牌,停止循环。
      player1.data[player1.tail] = table[top - 1]
      player1.tail += 1
      table.delete(table.last)       #使用了delete和last方法。记录下删除桌上刚拿走的牌
      top -= 1                       #桌面上的牌,相应减少一张。
    end
    #别忘记:把桌上相同的那张牌也拿走,这是由于while循环条件的问题,导致的代码。
    player1.data[player1.tail] = table[top - 1]
    player1.tail += 1
    table.delete(table.last)
    top -= 1
  end
  if player1.head == player1.tail    #player1手里没牌了,输掉了游戏。统计游戏结果。
    print "player2 win\n"
    print "player2 当前的牌:\n"
    while  player2.head < player2.tail
      puts player2.data[player2.head]
      player2.head += 1
    end
    print "牌桌上剩下的牌:\n"
    table.each{|i| p i }
    break
  end
# 轮到player2出牌啦,代码都一样。
  temp = player2.data[player2.head]
  flag = 0
  i = 1
  while i <= top
    if temp == table[i-1]
      flag = 1
      break
    end
    i += 1
  end
  if flag == 0
    player2.head += 1
    top += 1
    table << temp
  else
    player2.head += 1
    player2.data[player2.tail] = temp
    player2.tail += 1
    while table[top -1 ] != temp
      player2.data[player2.tail] = table[top - 1]
      player2.tail += 1
      table.delete(table.last)
      top -= 1
    end
    player2.data[player2.tail] = table[top - 1]
    player2.tail += 1
    table.delete(table.last)
    top -= 1
  end
  if player2.head == player2.tail
    print "player1 win\n"
    print "player1 当前的牌:\n"
    while  player1.head < player1.tail
      puts player1.data[player1.head]
      player1.head += 1
    end
    print "牌桌上剩下的牌:\n"
    table.each{|i| p i }
    break
  end
end

 




第3章 枚举,遍历 

炸弹人的游戏。 

 

要点:

  1. 设定一个二维数组 a[[],[]] 储存炸弹人地图,模拟墙,平地,怪分别用字符表示。
  2. 遍历每个点,使用双嵌套循环, for i in 1..12 do
  3. 每个平地的点,可以放置💣,向各个方向(上下左右)移动判断,是怪,还是墙,是怪的话,则消灭它,增加灭怪数sum。是墙的话则停止,换方向。合计四个方向sum.
  4. 储存一个灭怪数最大的点的坐标,和灭怪数。在图遍历后返回。
 a = [  # o 是墙, g是怪,x是平地
  ["o","o","o","o","o","o","o","o","o","o","o","o","o"],
  ["o","g","g","x","g","g","g","o","g","g","g","x","o"],
  ["o","o","o","x","o","g","o","g","o","g","o","g","o"],
  ["o","x","x","x","x","x","x","x","o","x","x","g","o"],
  ["o","g","o","x","o","o","o","x","o","g","o","g","o"],
  ["o","g","g","x","g","g","g","x","o","x","g","g","o"],
  ["o","g","o","x","o","g","o","x","o","x","o","o","o"],
  ["o","o","g","x","x","x","g","x","x","x","x","x","o"],
  ["o","g","o","x","o","g","o","o","o","x","o","g","o"],
  ["o","x","x","x","g","o","g","g","g","x","g","g","o"],
  ["o","g","o","x","o","g","o","g","o","x","o","g","o"],
  ["o","g","g","x","g","g","g","o","g","x","g","g","o"],
  ["o","o","o","o","o","o","o","o","o","o","o","o","o"]
]
i = j = 0    #用于双重循环,遍历每个点
x = y = 0    #作为临时变量,测算上下左右的杀怪数
map = 0      #用于记录较大的杀怪数
p1 = q1 = 0  #用于记录较大的杀怪数的放置炸弹的位置
for i in 1..12 do
  for j in 1..12 do
    if a[i][j] == "x"
      sum = 0         #计算当前点消灭怪的数,用于和map比较
      #向下统计消灭怪的数,之后还有上,左,右
      x = i
      y = j
      while a[x][y] != "o"
        if a[x][y] == "g"
          sum += 1
        end
        x += 1
      end
      #向上统计
      x = i
      y = j
      while a[x][y] != "o"
        if a[x][y] == "g"
          sum += 1
        end
          x -= 1
      end
      #向左统计
      x = i
      y = j
      while a[x][y] != "o"
        if a[x][y] == "g"
          sum += 1
        end
         y -= 1
      end
      #向右统计
      x = i
      y = j
      while a[x][y] != "o"
        if a[x][y] == "g"
          sum += 1
        end
        y += 1
      end
      if sum > map
        map = sum
        p1 = i
        q1 = j
      end
    end
  end
end
printf("将炸弹放置在(%d, %d),最多可以消灭%d个怪", p1, q1, map)

 

 


 

 数的全排列

 什么是全排列?

 输入一个指定点的数n,输出1-n的全排列。

下面的代码 (未完成品,只支持3,4)

 

 

a = b = c = d = e = f = g = h = j =0

 

print "请输入一个数:"
num = gets.to_i
for a in 1..num do
  for b in 1..num do
    for c in 1..num do
      if num == 4
        for d in 1..num do
          if num == 5
            return
          else
            if a!= b && a != c && a != d && b != c && b != d && c != d
              print a,b,c,d, "\n"
            end
          end
        end
      else
        if a != b && a != c && b != c
          printf("%d%d%d\n", a, b, c)
        end
      end
    end
  end
end

 


 

第四章  万能的搜索 

 上题如果遍历的话,写代码太麻烦了。使用Depth First Search,DFS可以优化代码。

 关键:当下如何做 。至于下一步如何做和当下如何做是一样的。

def dfs(step)

  判断边界

  尝试每种可能,循环 do

    继续下一步 dfs(step + 1)

  end 

end 

 

上题优化: 

def depth_first_search(step)
  # 判断边界
  if (step == $n + 1) #表示站在第n+1个盒子面前,前面的盒子已经放好扑克牌
    j = 1
    print "本轮排列结果:"
    for j in 1..$n do
      printf("%d", $box[j])
    end
    print "\n"
    return  p "return:第#{step}个盒子调用method,通过边界判断后返回到上一次调用方法的下一行代码"
  end
  # 用循环尝试每1种可能
  i = 1
  while i <= $n
    if $hand[i] == "have"   # 判断牌i是否还在手上
      $box[step] = i
      p "这步是box#{step}获得牌#{i}"
      $hand[i] = "none"
      # 继续下一步
      depth_first_search(step + 1) #函数递归处理,自己调用自己
      $hand[i] = "have"            #这是非常重要的一步。
    end
    i += 1
  end
# 返回。因为对函数做了递归处理,结束本次调用函数后,返回到上一次调用的下一行代码。"
  return p "return:本次是第#{step}个盒子调用,返回第#{step - 1}个盒子"
end

 

$box = []
$hand = [nil,"have","have","have","have","have","have","have","have","have"]
p "请输入一个整数,代表手里有从1到n张牌"
$n = gets.to_i
depth_first_search(1)

 


第二节 解救同伴,另一道深度优先搜索

 

输出maze地图:
[nil, 0, 0, 1, 0]
[nil, 0, 0, 0, 0]
[nil, 0, 0, 1, 0]
[nil, 0, 1, 0, 0]
[nil, 0, 0, 0, 1]

 

# 深度优先搜索
$maze  = [[nil],[nil,0,0,1,0],[nil,0,0,0,0],[nil,0,0,1,0,],[nil,0,1,0,0],[nil,0,0,0,1]]  #迷宫
$book  = [[nil],[nil,"?","?","?","?"],[nil,"?","?","?","?"],
[nil,"?","?","?","?"],[nil,"?","?","?","?"],[nil,"?","?","?","?"]]  #路线图
print "输出maze地图:\n"
i = 0
for i in 1..5 do
  p $maze[i]
end
startx = starty = 1  # 起点坐标
# 终点坐标
$endx = 4
$endy = 3
$book[startx][starty] = "p" #定位起点坐标,并标记已经pass
$min = 100 #假设的最短路径
def dfs(x,y,step)
# 定位界限
  if x == $endx && y == $endy
    if step < $min
      $min = step
    end
    print "当前步数:#{step}步\n当前路线图:\n"
    i = 0
    for i in 1 ..5 do
      p $book[i]
    end
    return # "回到上一步,继续尝试其他走法"
  end
# 定位方向数组,按照右,下,左,上的顺时针顺序尝试
  next_direction = [[0,1],[1,0],[0,-1],[-1,0]]
# 枚举4种走法
  k = 0
  for k in 0..3 do
    tx = ty = 0  #设为下一点的坐标
    # 计算下一点坐标
    tx = x + next_direction[k][0]
    ty = y + next_direction[k][1]
    # 判断是否越界
    if tx < 1 || tx > 5 || ty < 1 || ty > 4
      next
    end
    # 判断是否遇到障碍物,或者已经走过。
    if $maze[tx][ty] == 0 && $book[tx][ty] == "?"
      $book[tx][ty] = "p"
      dfs(tx,ty,step+1)
      # print "障碍测试22\n"
      $book[tx][ty] = "?"
    end
  end
  return # 回到上一步
end
dfs(startx,starty,0)
print "最短路径:#{$min}"

 答案:

当前步数:7步
当前路线图:
[nil, "p", "p", "?", "?"]
[nil, "?", "p", "p", "p"]
[nil, "?", "?", "?", "p"]
[nil, "?", "?", "p", "p"]
[nil, "?", "?", "?", "?"]
当前步数:9步
当前路线图:
[nil, "p", "p", "?", "?"]
[nil, "?", "p", "?", "?"]
[nil, "p", "p", "?", "?"]
[nil, "p", "?", "p", "?"]
[nil, "p", "p", "p", "?"]
当前步数:9步
当前路线图:
[nil, "p", "p", "?", "?"]
[nil, "p", "p", "?", "?"]
[nil, "p", "?", "?", "?"]
[nil, "p", "?", "p", "?"]
[nil, "p", "p", "p", "?"]
当前步数:7步
当前路线图:
[nil, "p", "?", "?", "?"]
[nil, "p", "p", "p", "p"]
[nil, "?", "?", "?", "p"]
[nil, "?", "?", "p", "p"]
[nil, "?", "?", "?", "?"]
当前步数:9步
当前路线图:
[nil, "p", "?", "?", "?"]
[nil, "p", "p", "?", "?"]
[nil, "p", "p", "?", "?"]
[nil, "p", "?", "p", "?"]
[nil, "p", "p", "p", "?"]
当前步数:9步
当前路线图:
[nil, "p", "?", "?", "?"]
[nil, "p", "p", "p", "p"]
[nil, "p", "p", "?", "p"]
[nil, "?", "?", "p", "p"]
[nil, "?", "?", "?", "?"]
当前步数:7步
当前路线图:
[nil, "p", "?", "?", "?"]
[nil, "p", "?", "?", "?"]
[nil, "p", "?", "?", "?"]
[nil, "p", "?", "p", "?"]
[nil, "p", "p", "p", "?"]
最短路径:7%

 


 


 

 

 广度优先搜索--层层递进

 核心思想:层层递进。

 

#地图

 

$maze  = [[nil],[nil,0,0,1,0],[nil,0,0,0,0],[nil,0,0,1,0],[nil,0,1,0,0],[nil,0,0,0,1]]

 

#路线图

$book  = [[nil],[nil,0,0,0,0],[nil,0,0,0,0],[nil,0,0,0,0],[nil,0,0,0,0],[nil,0,0,0,0]]  

# 队列x,y,step.  x,y用于记录maze坐标,step记录步数
x = []
y = []
step = [0]

#指针

head = 0
tail = 1
# 往队列插入maze入口坐标,起点
x[head] = 1
y[head] = 1

# 第一步设为0

step[tail] = 0 

#标记第一步已走 

$book[1][1] = 1 

 

# 用于遍历每个点的4个方向的坐标变化,右,下,左,上的顺时针顺序

next_direction = [[0,1],[1,0],[0,-1],[-1,0]] 

 

#终点坐标

endx = 4
endy = 3

 

# 临时坐标,起始为1
temp_x = 1
temp_y = 1

 

# 旗帜,意味终点 

flag = "false" 

k = 0

while head < tail   #用于结束循环:当head移动到tail时意味着没有坐标了。tail🈯️ 空。

 #遍历每个点的四个方向的新点,判断各种条件,确认是否成立。

  for k in 0..3 do
    temp_x = x[head] + next_direction[k][0]
    temp_y = y[head] + next_direction[k][1]
    # 边界判断
    if temp_x < 1 || temp_x > 5 || temp_y < 1 || temp_y > 4
      next #下个循环
    end
    # 如果没有撞到障碍物,并且是第一次走这个点,则为true.
    if $maze[temp_x][temp_y] == 0 &&  $book[temp_x][temp_y] == 0
      # 宽搜只标记,不解除标记。
      $book[temp_x][temp_y] = 1
      # 插入新的点的坐标到记录队列x,y. 也是预设值为1 的tail的位置。
      x << temp_x
      y << temp_y
      # tail代表当前点的坐标x,y的索引(子点),head代表父点的坐标的索引(来源点)
      # step.size计算走的步数,step的值来代表第几”层“。(层层递进)
      step[tail] = step[head] + 1 # 子点的轮数等于父点轮数+1
      tail += 1 #再次指向队尾的后面一个位置,空。
      # 打印标记图和 地图
      # system "clear" #配合sleep动态显示
      for l in 1..5 do
        print $book[l], " | ",$maze[l],"\n"
      end
      p step[tail - 1]
      p "迷宫横坐标#{x}"
      p "迷宫纵坐标#{y}"
      p "走了#{step.size - 1}步,#{step}"
      print "\n\n"
      # sleep 4
    end
    # 终点判断
    if temp_x == endx && temp_y == endy
      flag = "到达终点"
      break
    end
  end

 

  if flag == "到达终点"
    break
  else
    head += 1 #重要,代表一个点扩展结束,head指向队列的该下一点。
  end
end

 



 


 

炸弹人。结果得到的是错误的。 

不知道是什么原因,while head < tail只循环了4次。 

posted @ 2018-01-08 20:22  Mr-chen  阅读(324)  评论(0编辑  收藏  举报