动态规划——高效彩票开奖算法

问题描述

用户可以从每月的 1~最大月份天数(这里统一为 31 天),选出不重复的 7 个数字,进行投注

开奖时会产生7个不同的数字,

根据彩票的数字和开奖彩票的相近程度,划分为1~7等奖,

                                

问题分析

假定玩家在开奖前共计投注 N 张彩票,需要计算出这其中每张彩票的奖项。由于彩票至少为N,所以可以预期算法不会好于O(N),如果在判断一张彩票时,步数过多,那么随着彩票的增多,开奖速度毫无疑问会大幅下降。所以如何判断彩票是中几等奖,直接影响开奖效率。

算法1

以上面的开奖数字1,2,3,4,5,6,7来说,一张彩票的形式是1,2,3,4,5,6,7那么就是1等奖,1等级只有1种可能;而2等奖有(1,2,3,4,5,6,*、*,2,3,4,5,6,7)2种可能;同理3等级有(1,2,3,4,5,*,*、*,2,3,4,5,6,*、*,*,3,4,5,6,7)3种情况,依此类推4等奖有4种可能,5等奖有5种可能,6等奖有6种可能,7等奖有7种可能,共计1+2+3+4+5+6+7=28种情况

1.可以在开奖时,生成1,2,3,4,5,6,7的所有中奖形式,时间花费为O(7)*O(7) = O(49),这项可以忽略

2.尝试对彩票从1等奖到7等奖进行7个奖项的判断;而7个奖项中判断最少次数的1等级只有1种可能,而7等级最多有 7种可能;时间花费为O(7)

3.需要根据开奖等级从开奖数字中取出至多7种可能;时间花费为O(7)

4.另外这里还没有被判断彩票的所有可能,在进行比较之前需要计算出被判定彩票的1~7等奖的所有形式;花费为O(7)*O(7),对每张彩票都要进行这不操作,这部分不能忽略

5.需要根据开奖等级从一张彩票中取出至多7种可能;时间花费为O(7)

这种情况下,1等奖判断需要比较1次……7等奖至多需要7*7,也就是49次,时间复杂度为O(N)*O(7)*O(7)*O(7)*O(7)*O(7) = O(16801N),看起来常数项不小。

实际表现

算法1,实际表现不如人意,处理100000彩票,需要花费12s左右,而且由于总是从1等级判断直到7等奖,它的花费总是 O(N)*O(7)*O(7)*O(7)*O(7)*O(7) = O(16801N);要知道实际情况下,中奖的彩票是极少数的,中大奖的彩票就更加少得可怜,另外一张奖品连7等奖都没有中的话,那是不可能中更高奖项的,因此算法1进行了很多不必要的比较,生成了不必要的中奖可能。

算法2

对算法1不足的地方进行改进

其中,由于中奖的彩票是极少数。现在优先从7等奖开始判断,如果中了7等奖,再去判断是否中了6等奖,一旦不中奖直接丢掉剩下奖项的判断。

另外,被判断的奖票不再每次生成7等奖的所有情况,而是判断每1级别的奖项就生成每1级别的奖项可能。这里时间花费O(49)降低至O(7),总的时间花费降低为 O(2401N)

总的来说,这番改良,可以将预期时间花费为之前的 1/7 

实际表现

算法2相比算法1有些许改进,同样处理100000彩票,花费了 1.76s。总的来说,算法1是一个直观的算法,而算法2仅仅是算法1的改良版本。他们的效率我仍然不能接受,所以有了算法3

算法3

想要获得更快的算法,需要对问题进行抽象,同时需要存储更多彩票的信息。

这个问题的关键就是如果快速判断一张彩票的中奖情况,

中奖的判断又可以分为1.是否中奖,2.中了几等奖。这在算法1中,是直接将彩票和中奖彩票进行比较的,没有先关注是否中奖,这势必会进行很多不必要的计算。

所谓中奖与否,可以抽象为,一组有序数字,相对于 “目标” 组有序数字的相同情况,毕竟有1个数字相同一定中7等奖,2个数字有可能中6等奖……

这里中奖与否问题准确来讲变成了,是否可能中奖,毕竟有2个数字相同只是有可能中 6 等奖,要中 6 等奖,除非 这两个数字的排列顺序,能在 “目标” 组数组中找到对应

中奖与否跟数字的顺序有关,原先的数据结构不能获得每个数字的 “坐标” 信息

到这里,算法就较为明了了。

先找出,一张彩票和开奖彩票有几个共同相同数字,

如果相同数字 1 个,那么直接中 7 等奖,处理结束

如果相同数字 0 个,那么不中奖,同样处理结束

如果数字 >= 2 个,进入判断中几等奖的流程

中几等奖的问题,就是一组数字相对于目标组数字的相同连续序列情况,

我们要求最大中几等奖,可以抽象为,求一组数字相对于目标组数字的,最大相同连续序列中数字的个数

 算法总的来说分为两步

预先使用 map 记录开奖彩票每个数字的下标信息,散列表插入时间平均O(1),插入7次,这部分时间花费可以忽略

1.对每张彩票,确定相同的数字个数。由于数字范围1~31,可以选择同时遍历一张彩票以及开奖彩票数组,并将他们放入到大小而 32 的数组中,放入时如果该数字个数 == 2 就认为找到了一个相同的数字,并且额外记录这个数字在数组中的下标。这部分时间操作O(7) 在于 N 张彩票,共计花费 O(7N)

2.找到两组数字中,相同又相序的最大序列。还是同时遍历一张彩票和开奖彩票,处理共有的数字,对于两组数字:如果当前数字坐标比上一个数字坐标大1,我们就认为数字连续,否则中断。只有两组数字都处于连续状态时,我们可以累加这张彩票相对于开奖奖票的相同相序数字个数,否则相同相序数字个数归1,重新开始。直至最后一个数字。这步的时间花费最多为O(7),再加上有N张票,共计至多O(7N)

3.两步共计时间花费 O(14N),可以看到常数项很小,基本可以算是一个良好的线性算法啦。

实际表现

经过前面的分析,不那么直观的算法3,效率还行。处理100000彩票,花费仅有 0.02s 。当彩票量是1000000时,花费也是线性增长到 0.19s

算法实现

这里为了减少篇幅,只展示了算法3 也就是函数 Open_lottery3(),其他是一些辅助函数

package gameproblem

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"time"

	"github.com/vertgenlab/gonomics/fileio"
)

//single lottery num
type lot_item struct {
	seven_num [7]int
	luck_val  int
}



const LOTTERY_COUNT int = 100000 // // 100000

//1,2,3,4,5,6,7
// 6 等奖 有连续两个数字 想通
// 5 等奖 ……3
// 4……				4
//3				5
//2				6
//1 			7

//生成 10万张奖票
func Init_lottery_file() {
	var err error
	// the WriteFile method returns an error if unsuccessful
	if _, err := os.Stat("lotteries.txt"); err == nil {
		fmt.Printf("File exists\n")
		return
	}
	f, err := os.Create("lotteries.txt")
	if err != nil {
		fmt.Printf("File create failed\n")
		return
	}

	all_lotteris := make([]lot_item, LOTTERY_COUNT)
	for i := 0; i < LOTTERY_COUNT; i++ {
		lottery := Get_a_lottery()

		var lot_item1 lot_item
		for j := 0; j < 7; j++ {
			lot_item1.seven_num[j] = lottery[j]
		}

		all_lotteris[i] = lot_item1

	}
	for i := 0; i < len(all_lotteris); i++ {
		lottery_str := fileio.IntSliceToString(all_lotteris[i].seven_num[:])

		_, err := f.WriteString(lottery_str + "\n")

		// handle this error
		if err != nil {
			// print it out
			fmt.Println(err)
			break
		}
	}
	f.Close()

}

func get_all_lotties() ([]lot_item, int) {
	all_lotteris := make([]lot_item, LOTTERY_COUNT)
	f, err := os.Open("lotteries.txt")
	if err != nil {
		fmt.Println(err)
		return all_lotteris, -1
	}

	scanner := bufio.NewScanner(f)
	// optionally, resize scanner's capacity for lines over 64K, see next example
	j := 0
	for scanner.Scan() {
		txt := scanner.Text()
		lottery := fileio.StringToIntSlice(txt)
		// fmt.Println(lottery)
		var lot_item1 lot_item
		for i := 0; i < 7; i++ {
			lot_item1.seven_num[i] = lottery[i]
		}
		if j < LOTTERY_COUNT {
			all_lotteris[j] = lot_item1
			all_lotteris[j].luck_val = -1
		}

		j++

	}
	return all_lotteris, 0
}
func deleteElement(slice []int, index int) []int {
	return append(slice[:index], slice[index+1:]...)
}

func Get_a_lottery() [7]int {
	numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31}
	lottery := [7]int{0, 0, 0, 0, 0, 0}
	for i := 0; i < 7; i++ {
		randindex := rand.Intn(len(numbers))
		lottery[i] = numbers[randindex]
		numbers = deleteElement(numbers, randindex)
	}

	return lottery
}

func get_all_num_from_a_lottery_n(seven []int, n int) []string {
	i := n
	dis := 7 - i
	iCount := 0
	var num_strs []string
	// var num reward_num2
	for start_pos := 0; start_pos+dis <= 7; start_pos++ {
		tmp_num := seven[start_pos : start_pos+dis]
		tmp_num_str := fileio.IntSliceToString(tmp_num)
		num_strs = append(num_strs, tmp_num_str)

		// all_possible_reward.reward_str.num_str[n] = append(all_possible_reward.reward_str.num_str[n], tmp_num_str)
		// for k := 0; k < len(tmp_num); k++ {
		// 	num.num[iCount][k] = tmp_num[k]

		// }

		iCount++
	}
	// all_possible_reward.reward_str.num_str[n] = append(all_possible_reward.reward_str.num_str[n], num_strs)

	return num_strs
}

func get_all_num_from_a_lottery(seven []int) s_all_rewad {
	var all_possible_reward s_all_rewad
	for i := 0; i < 7; i++ { //0~6 依次处理 1到 7等级

		dis := 7 - i
		iCount := 0
		var num_strs []string
		for start_pos := 0; start_pos+dis <= 7; start_pos++ {
			tmp_num := seven[start_pos : start_pos+dis]
			tmp_num_str := fileio.IntSliceToString(tmp_num)
			num_strs = append(num_strs, tmp_num_str)
			for k := 0; k < len(tmp_num); k++ {
				all_possible_reward.reward.num[i][iCount][k] = tmp_num[k]

			}

			iCount++
		}
		all_possible_reward.reward_str.num_str = append(all_possible_reward.reward_str.num_str, num_strs)

	}

	return all_possible_reward
}



func Open_lottery3() {
	all_lotteris, rtcode := get_all_lotties()
	if 0 != rtcode {
		fmt.Printf("get lotteries failed,open lottery suspend \n")
		return
	}

	fmt.Printf("read lottery %d 张\n", len(all_lotteris))
	var seven []int
	seven = []int{28, 29, 1, 9, 6, 10, 2} //开奖的号码 1,2,3,4,5,6,7
	var m_seven map[int]int
	m_seven = make(map[int]int)
	for y := 0; y < 7; y++ {
		m_seven[seven[y]] = y //记录文字信息
	}

	var compare_times int = 0
	start := time.Now().UnixMilli()
	for x := 0; x < LOTTERY_COUNT; x++ { //O(N)

		tmpseven := all_lotteris[x].seven_num

		numbers := [32]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
		sameCount := 0
		var m_tmpseven map[int]int
		m_tmpseven = make(map[int]int)
		for j := 0; j < 7; j++ { //7
			//将开奖数字以及一张彩票融进 numbers
			numbers[tmpseven[j]]++

			if 2 == numbers[tmpseven[j]] {
				//不关心具体相同数字是多少,只关心相同的个数

				//额外记录 下标
				m_tmpseven[tmpseven[j]] = j
				sameCount++
			}

			numbers[seven[j]]++

			if 2 == numbers[seven[j]] {
				//不关心具体相同数字是多少,只关心相同的个数
				sameCount++
				m_tmpseven[seven[j]] = j
			}
		}

		if 0 == sameCount {
			continue
		}

		//如果相同的只有一个那么直接就是 6 等奖
		if 1 == sameCount {
			all_lotteris[x].luck_val = 7
			continue
		}

		//统计相同的张数 就是最大能中的奖项
		//此时从5等级开始,只不过上限确定了
		//此时关注 是否有序性
		//得出最大的相同连续序列
		//两边相邻,才算,单方面相邻不算

		//由于此时相同的数字至少有两个,所以每次循环最多 7+7-2 = 12 次
		var last_pos int = -2
		var last_tmp_pos int = -2
		var tmp_cont_count int = 1 //连续挨着的最大数字个数,为最终的奖项
		var max_cont_count int = 1
		for k := 0; k < 7; k++ { //7
			if 2 != numbers[tmpseven[k]] {
				continue
			}
			cur_pos := m_seven[tmpseven[k]]
			cur_tmp_pos := m_tmpseven[tmpseven[k]]
			var is_seven_cont bool = false
			var is_tmp_seven_cont bool = false

			if cur_pos == (last_pos + 1) {
				is_seven_cont = true
			}

			//当前pos 比 前一个 大 1 就算相邻
			if cur_tmp_pos == (last_tmp_pos + 1) {
				is_tmp_seven_cont = true
			}

			if true == is_tmp_seven_cont && true == is_seven_cont {
				tmp_cont_count++ //累加 连续数
			} else {

				if tmp_cont_count > max_cont_count {
					max_cont_count = tmp_cont_count //可能找到更大的数
				}

				tmp_cont_count = 1 //清零 重置

			}

			last_pos = cur_pos
			last_tmp_pos = cur_tmp_pos

		}

		if tmp_cont_count > max_cont_count {
			max_cont_count = tmp_cont_count //可能找到更大的数
		}

		if 0 < max_cont_count {
			all_lotteris[x].luck_val = 7 - max_cont_count + 1
			continue
		}

		// if -1 != all_lotteris[x].luck_val {
		// 	fmt.Printf("lucky lottery: %d reward: lottery: ", all_lotteris[x].luck_val+1)
		// 	fmt.Println(all_lotteris[x].seven_num)

		// 	if 0 == all_lotteris[x].luck_val {
		// 		fmt.Printf("has a specal reward\n")
		// 	}
		// }

	}

	end := time.Now().UnixMilli()
	fmt.Printf("find all lucky lotteries costs %.2f s, costs : %d times\n", float64(end-start)/1000.0, compare_times)

	//对每张彩票,从 6等级开始判断
	// for x := 0; x < LOTTERY_COUNT; x++ {
	// 	if -1 != all_lotteris[x].luck_val {
	// 		fmt.Printf("lucky lottery: %d reward: lottery: ", all_lotteris[x].luck_val)
	// 		fmt.Println(all_lotteris[x].seven_num)
	// 	}
	// }

}

算法分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值