第一章 排序
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符号)次比较。在最坏状况下则需要次比较,但这种状况并不常见。快速排序通常明显比其他{\displaystyle O(n\log n)}算法更快,因为它的内部循环(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
纸牌游戏:(小猫钓鱼)
规则:
- 将一副牌平均分为两份,每人一份。a先出牌,b再出牌放在上面,交替出牌。
- 出牌时,如果某人打出的牌与桌面上的牌大小相同,则将两张相同的牌及其中间的所有夹的牌全部取走,并依次放入自己手中的牌的末尾。
- 当任意一人的手中的牌全部出完时,游戏结束,对手胜利。
分析:
两个队列:代表两个玩家
一个栈: 代表桌面上的牌。
(一看表,竟然用了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章 枚举,遍历
炸弹人的游戏。
要点:
- 设定一个二维数组 a[[],[]] 储存炸弹人地图,模拟墙,平地,怪分别用字符表示。
- 遍历每个点,使用双嵌套循环, for i in 1..12 do
- 每个平地的点,可以放置💣,向各个方向(上下左右)移动判断,是怪,还是墙,是怪的话,则消灭它,增加灭怪数sum。是墙的话则停止,换方向。合计四个方向sum.
- 储存一个灭怪数最大的点的坐标,和灭怪数。在图遍历后返回。
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次。