数据结构与算法 GDScript描述 ⑴ 线性表

本文介绍了线性表和链表在GDScript中的实现,包括线性表的合并、删除重复值、约瑟夫环的模拟。讨论了线性表和单链表的优劣,详细讲解了单链表的初始化、操作和相关算法题,如反转单链表、求中间结点等。此外,还涉及了循环链表、双链表的概念和应用,如使用循环链表优化约瑟夫环问题。
摘要由CSDN通过智能技术生成

数据结构与算法 GDScript描述 ⑴ 线性表

****************************************************

简介:
线性表相关算法的GDScript描述。包括线性表,单链表,双链表,循环链表。以及相关的常见的面试题。

线性表

线性表

线性表是最常用且最简单的一种数据结构,它是n个数据元素的有限序列。

线性表之间的元素是有顺序的,存储是连续的。
好比第一元素住在一单元,第二个住在二单元,第三个住在三单元,以此类推。

在这里插入图片描述

线性表在GDScript中的语法实现:

GDScript中的数组本身就是一个线性表,GDScript已为其提供了非常详细的功能。可以查阅先关的文档。

线性表相关算法题:

线性表的合并

假设存在两个线性表
A [2, 5, 18, 6, 3, 25, 17]
B [7,1, 5, 23, 41, 16, 2]
求二者的并集并重新按大小排序

思路:
遍历B中的元素,不在A中的即加入A中。然后对线性表排序。

# 线性表的合并.gd

extends Node2D

var list_a = [2, 5, 18, 6, 3, 25, 17]
var list_b = [7,1, 5, 23, 41, 16, 2]
var list_c

func _ready():
	list_c = union_list(list_a,list_b)
	print(list_c)
	
func union_list(a,b):
	for i in b:
		if !(i in a): # 如果b的值不在a中则写入该值
			a.append(i)
	a.sort() # 从小到大重新排列数组 gdscrip自带函数
	#a.invert() 颠倒数组的顺序,可实现从大到小输出 gdscrip自带函数
	return a

[1, 2, 3, 5, 6, 7, 16, 17, 18, 23, 25, 41]

删除线性表中的重复值

存在一个线性表,删除其中重复的值。
思路,建一个新的线性表。
遍历原线性表,不在新线性表中的元素即写入。

# 线性表删除重复元素.gd

extends Node2D

var arr = [1,12,5,36,14,13,89,65,14,5,41]

# Called when the node enters the scene tree for the first time.
func _ready():
	var b = delete_same(arr)
	print(b) 
	
# 删除重复元素
func delete_same(a):
	var b = []
	for i in a:
		if !(i in b):
			b.append(i)
	return b		

[1, 12, 5, 36, 14, 13, 89, 65, 41]

使用线性表模拟约瑟夫环

约瑟夫环就是n个人围成一圈,从第一个人开始报数。报到第m个数的人出局。然后从下一人重新开始报数,直到最终剩余一人。
经典的约瑟夫环是41人,数到3出局,最终胜利者的位置是31。

思路:用位置取余的方式对线性表实现遍历,用数组自带的remove()函数来删除出局数组

# 线性表模拟约瑟夫环.gd

extends Object

var peoples = []
var lives = 100000 # 总人数
var interva = 4 # 报数间隔
var t1 = 0
var t2 = 0

func _ready():
	# 初始化数组
	for i in range(lives):
		peoples.append(i+1)
	t1 = OS.get_system_time_msecs()		
	print("幸存者为:",josephus_problem_2(peoples,lives,interva))
	t2 = OS.get_system_time_msecs()
	print("用时:",t2 - t1)
	
# 模拟约瑟夫环	
func josephus_problem_1(arr,lives_now,inter):
	var loca = -1 # 初始位置
	# 当剩下最后一个元素的时候则为最终胜利者
	while arr.size() > 1:
		# 循环报数
		loca = (loca + inter) % lives_now
		arr.remove(loca) 
		lives_now -= 1
		loca -= 1
	return arr[0]

算法改进:
上述算法在逻辑上没有任何问题,基本是对原问题的1:1再现。但在效率上却很低。
因为线性表在删除元素时的效率不高。而如果想提高删除元素的效率,可以使用循环链表。
下面的章节会有约瑟夫环的循环链表写法,效率会比用线性表高很多。这里用线性表来实现只是为了和链表进行对比。
更高效率的写法见递归算法。
因为线性表删除的低效,所以这里写一个不删除元素的做法。
思路:
对出局的元素不做删除处理,而是给其赋值为0。
当报数的时候,遇到元素为0则跳过继续报数。

func josephus_problem_2(arr,lives_now,inter):
	var loca = -1
	var disp = 0
	var tolal_lives = lives_now
	# 只剩一个元素时就是最终的胜利者
	while lives_now > 0:
		disp = 0
		# 报数
		while disp < inter: 
			loca = (loca + 1) % tolal_lives
			# 如果该位置的元素值不等于0 则报数+1
			if arr[loca] != 0:
				disp += 1
		arr[loca] = 0
		lives_now -= 1
	return loca + 1	

t1 = OS.get_system_time_msecs()
.
. 代码
.
t2 = OS.get_system_time_msecs()
print(“用时:”,t2 - t1)

使用上述代码可以对代码部分进行计时,从而观察各种算法的效率差别。当将人数设置的很大时,会看出后边的算法要优于前面的算法。

链表

单链表

链表是一种非连续存储的线性表,其中最基本的是单链表
所谓非连续存储,就好比第一个元素住在一单元,第二个元素可能住在五单元,三个住在七单元。
既不连续,也没规律。

单链表由一系列的节点构成, 每个节点包含数据Item和下一个节点的位置next
在这里插入图片描述

单链表只能沿next的方向向前,而不能向后
好比一单元的第一个元素知道下一个元素住在五单元。
五单元的元素知道下一个元素住在七单元。
但五单元的元素不知道自己上一个元素住在一单元。
七单元的也无法知道自己上一个元素住在哪里。

单链表的初始化和实例生成

初始化:

class_name SimpleLink

var item = null # 存储节点信息
var next = null # 存储下一节点位置

# 构造函数,指定item
func _init(data): 
	item = data

生成实例:

func new_link(item):
	return SimpleLink.new(item)
线性表和单链表的优劣比较

线性表的优势是查找元素非常方便,只需要输入元素的下标即可。
而链表的元素是没有位置信息的,要查找只能从第一个节点开始,通过next信息获得下一节点的位置,然后从下一节点再继续到下下个节点。

但链表的优势是元素的删除插入
线性表因为存在位置信息(下标),当你删除或插入一个元素后,其后所有元素的位置都要进行相应的调整。
而链表的删除和插入就简单多了。只需要调整对应位置的指针指向即可。

插入:
修改ai的next值为e,设定e的next值为ai+1,则完成插入操作
在这里插入图片描述
在这里插入图片描述
删除:
修改ai-1的next值为ai+1
在这里插入图片描述

头节点(head)、尾节点和尾指针(tail)

对于线性表,我告诉你 a[10] = 5,也就等于告诉了你这个线性表所有的值。
比如使用 print(a[0]) 就能打印出 第一个元素 的值。
使用 a.size(),就能知道线性表元素的个数。

而对于单链表,我告诉你其中一个节点,你无法知道其上一个节点是什么。
对于整个链表,你也只能知道之后的节点是什么。无从得知链表的全貌,除非这个节点是第一个节点

所以头节点就诞生了。头节点的目的就是标识链表的起点,并给后续的元素添加定位。
就好比一个举旗的导游,游客会慢慢聚集在他身后。

头节点并不是链表的第一个元素,头节点只是指向第一个元素。告诉你第一个元素在哪。
一个只有头节点的链表,其元素个数为 0,也就是一个空链表
头节点通常是一个空数据节点,只存储下一节点的位置信息。

尾节点的作用和头节点类似,指示最后一个元素的位置。在你需要对链表的尾部进行节点增、删时,尾节点的存在会显得很方便。

尤其是在创建新链表的时候,通过节点的尾部插入,可以很方便的形成链表。

尾指针
如果想指示最后一个元素的位置,并不是一定要用尾节点指向这个位置才可以。更简单是直接存储该元素的位置即可。而这个位置就是尾指针。

三者关系如下图:
在这里插入图片描述
头指针
既然可以用尾指针直接代替尾节点,为什么不用头指针代替头节点呢。
因为头节点可以早于链表建立。而且有了头节点,才好添加尾指针。

头节点和尾节点的初始化

头节点可以存储关于此链表的一些信息,但通常头节点为空数据节点。
尾指针始终指向最终节点,而初始化时,尾指针会指向头节点。

head = new_link(null) #建立一个空数据节点
tail = head  # 尾指针指向头节点

也可以将尾指针替换为尾节点

head = new_link(null) # 初始化头节点
tail = new_link(null) # 初始化尾节点
tail.next = head

从书写上看,很明显尾指针的方式更简单。
但因为GDScript函数参数为引用传递,所以有时候我更喜欢用尾节点的方式来创建。

# 从尾部插入新节点 尾指针写法
func tail_insert1(t:SimpleLink,item):
	t.next = new_link(item)
	#t = t.next 无效语句,无法作用于函数外
	return t.next
# 从尾部插入新节点 尾节点写法
func tail_insert2(t:SimpleLink,item):
	var new_node = new_link(item)
	t.next.next = new_node
	t.next = new_node

调用方式:

	head = new_link(null) # 初始化头节点
	tail = head
	tail = tail_insert1(tail,2)
	head = new_link(null) # 初始化头节点
	tail = new_link(null) # 初始化头节点
	tail.next = head
	tail_insert2(tail,4)

tail = tail_insert1(tail,2)

这样调用是因为,GDScript中,函数的参数是引用传递。
所以在 tail_insert1() 中,你无法用

t = t.next

来修改函数外尾指针tail的指向。因为t只是tail的引用。

单链表的语法实现
# 单链表.gd

extends Object
class_name SimpleLink

var item = null # 存储节点信息
var next = null # 存储下一节点位置

# 构造函数,指定item
func _init(data):
	item = data

# 打印item
func p():
	print(item)
单链表的基本操作
# 单链表基本操作.gd
extends Node2D

var head:SimpleLink # 设定头节点 
var tail:SimpleLink # 设定尾节点 
var nodes = []

func _ready():
	head = new_link(null) # 初始化头节点
	tail = new_link(null) # 初始化尾节点
	tail.next = head

# 构建一个新的节点	
func new_link(item):
	return SimpleLink.new(item)
	
# 从尾部插入新节点 尾节点写法
func tail_insert(t,item):
	var new_node = new_link(item)
	t.next.next = new_node
	t.next = new_node
	
# 从头部插入新节点		
func head_insert(h,t,item):
	var node = h.next
	var new_node = new_link(item)
	h.next = new_node
	new_node.next = node
	if t.next == h:
		t.next = new_node

# 在指定位置插入新节点 位置从0开始 0意味着插到头节点后面
func insert_node(h,t,item,index):
	var node = h
	var new_node = new_link(item)
	var next_node = null
	# 将指针定位到指定位置
	for i in range(index):
		node = node.next
		if node == null: # 判断指定位置是否超过了链表长度
			return false																							   
	next_node = node.next # 存储node的下一个节点
	node.next = new_node # 将node指向新节点
	new_node.next = next_node # 新节点指向刚才存储的node下一个节点
	if node == t.next: # 如果插入的位置是在最后,则相应要修改尾节点的位置
		t.next = new_node
	return true
		
#修改指定位置node的item		
func set_item(h,item,index):
	var node = h
	# 将指针定位到指定位置
	for i in range(index + 1):
		node = node
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值