python有关基础题目之——合并列表

一、题目要求


给定一个区间的列表,将所有交叉范围的区间进行合并


详细要求

有个一随机生成的二维列表,二维列表中的每一个元素都是一个只有两个元素的列表,且下标为0的元素的值是要小于等于下标为1的元素值的
例如:
  [[12, 20], [4, 6], [3, 11], [15, 16]]

在这个列表当中,如果发现有子列表存在交叉范围,则进行合并,直到全部合并完全为止。在上述所出的例子当中我们发现:
  [4, 6] [3, 11]存在交叉范围,可以合并为[3, 11]
  [12, 20] [15, 16]存在交叉范围,可以合并为[12, 20]

那么最终的结果就是:

  [[3, 11], [12, 20]

最终的这两个元素之间是不存在交叉区间的


二、解题思路

首先,为了方便做交叉范围区间的合并,我们最好提前做一个排序,为什么呢,先来做一个情况分析,请看下图
在这里插入图片描述

当二维列表中的元素是第一种情况的时候,是不存在交叉范围的,此时不需要做合并
但如果是第2种或者是第3种的时候就要做合并了,而且第2种情况的合并是要比第3种情况合并起来方便实现的,除此之外呢还有第4种情况

那我们发现第一个元素的列表区间是要比第二个元素的区间是要大的

在这里插入图片描述
针对这些情况我们得出,在二维列表当中根据每一个子列表的下标为0的元素进行排序,也就是左侧区间,都排好序之后再进行遍历合并会更便捷


三、代码实现

1、二维列表制作

第一个要解决的问题就是如何产生一个具有一般性质的随机二维列表,而且还是满足题目要求的

1、先生成一个二维列表,其中加入随机数的控制,条件是每个子列表的下标为0的元素要小于等于下标为1的元素,代码如下,非常简单的实现

2、但是在实现过程中要注意,由于是随机数字的生成,所以如果我们要能够产生自定义规模的二维列表的话,就不能直接限制循环次数了,因为并不是每次循环left都能小于right,所以我在判断条件处改为长度的控制,只有满足条件的子列表生成的数量够了规定数量之后,才会停止。这样就能实现一般性的控制

import random

inner_list, outer_list = [], []

def create_twoDimension_array(l_l,l_r, r_l, r_r):

	global inner_list, outer_list

	left = random.randint(l_l, l_r)
	right = random.randint(r_l, r_r)

	if left < right:

		inner_list.append(left)
		inner_list.append(right)

		outer_list.append(inner_list)
		inner_list = []

num = int(input('请输入二维数组的规模:'))
left_l = int(input('请输入子列表下标为0的元素范围从'))
left_r = int(input('到'))
right_l = int(input('请输入子列表下标为1的元素范围从'))
right_r = int(input('到'))
while len(outer_list) != num:

	create_twoDimension_array(left_l, left_r, right_l, right_r)


print('生成的随机二维列表:{}'.format(outer_list))

二维列表生成之后进行排序,排序算法任选一个实现即可,然后可以在选择的基础上再进行优化,比如这里面我使用的是最简单的冒泡排序,这个排序有很大的优化空间,理解原理之后如果想要再次优化可以在我其他文章当中找到解决方案

# 按照每一个子列表的第一个元素进行排序
# 时间复杂度在O(n^2)
for i in range(len(outer_list)):
	for j in range(len(outer_list) - i - 1):
		if outer_list[j][0] > outer_list[j + 1][0]:
			outer_list[j], outer_list[j+1] = outer_list[j+1], outer_list[j]

print('使用BubbleSort排序后的二维列表:{}'.format(outer_list))

当然这里也给大家附上优化冒泡排序的代码,注释已经很详尽了。

具体意思就是冒泡排序的遍历如果不优化的话,即使是已经有序的元素,它也会再遍历一次,那这样的话实际上浪费时间了,可以设置一个交换的标记,如果发生交换了,那就改变标记的状态,并更新无序元素的区间范围。如果是有序的直接进行下一轮,这样能提高不少效率

def optimization_bubble_sort(res):
	'''
	每一轮排序,记录好最后一次交换的列表元素下标,作为无序列表的界限
	:param res: 制作的特殊列表
	:return:优化之后得到的已排序的列表
	'''
	lastExchangeIndex = 0  # 设置最后一次发生交换的元素下标
	sortIndex = len(res) - 1  # 设置无序列表的下标范围

	for i in range(len(res) - 1):
		# 确定是否排好序的标志
		isSorted = True
		for j in range(sortIndex):
			if res[j] > res[j + 1]:
				res[j], res[j + 1] = res[j + 1], res[j]
				isSorted = False
				lastExchangeIndex = j  # 更新最后一次发生交换的元素下标
		sortIndex = lastExchangeIndex  # 更新无序列表的下标范围
		if isSorted: break
	return res

2、合并操作

做合并操作的时候一定要注意,我的代码当中操作的是原列表,只要里面发生了合并,那么列表的规模就会降低,那我循环遍历的之后就不能使用for循环,for循环和列表的操作我在之前的文章里已经说过了,如果使用for循环和列表结合的话,如果发生删除操作,那么已经遍历过的元素是不会被再次遍历的,但是这个程序里面我要实现的时候只要发生合并,就要不断的从当前位置重新开始遍历,那for循环是无法实现这个操作的。

因此我使用while循环进行操作:

1、如果发生合并,那么下标不变,进行新一轮的循环
2、如果不发生合并,说明没有交叉范围的区间,下标加1,正常向后

每循环一次,就检测一次下标,当当前下标大于等于了当前列表的规模,那么说明列表当中合并操作已经全部完成,再遍历下去会发生下标越界报错,因此,要及时退出,得出最终列表

# 普通方法进行交叉范围列表的合并
t1 = len(outer_list) - 1  # 列表规模
i = 0  # 下标

while i < t1:
	# 第2种情况
	if outer_list[i][0] <= outer_list[i+1][0] <= outer_list[i][1] and \
		outer_list[i+1][0] <= outer_list[i][1] <= outer_list[i+1][1]:
		temp = outer_list[i] + outer_list[i+1]  # 子列表合并
		temp.remove(outer_list[i+1][0])
		temp.remove(outer_list[i][1])
		# 删除原来列表当中的子列表,将合并之后的新列表放进去
		outer_list.pop(i)
		outer_list.pop(i)
		# 处理好之后,还得放回去,不影响之后的操作
		outer_list.insert(i,temp)
		t1 -= 1  # 长度监控,列表元素删除之后,整个原列表的长度减1

	# 第4种情况
	elif outer_list[i][0] <= outer_list[i+1][0] <= outer_list[i][1] and \
		outer_list[i][0] <= outer_list[i+1][1] <= outer_list[i][1]:
		# 删除原来列表当中的子列表
		outer_list.pop(i + 1)
		t1 -= 1
	else:  # 不满足合并条件,正常向后遍历
		i += 1
	# 监控列表当中的元素下标是否越界,判断是否完成合并操作
	if i >= len(outer_list):
		break
# [[2, 18], [3, 16], [5, 18], [9, 12], [13, 14]]

print('合并操作之后的列表:%s'%outer_list)

3、运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完成之后,二位数组的规模我们可以自定义,产生随机二维列表的具体区间也可以自定义,这样一般性更强了,但是这个内容当中只是用列表的一般操作实现了交叉区间的合并,那大家可以看到是很麻烦的,代码量也不少。
那这个问题还能怎么优化呢?或者说还能不能换种思路去处理这个问题?
答案是有的,可以用面向对象去做


四、面向对象实现

1、先创建类

用对象的属性来表示子列表的下标0下标1的值,直接存入字符串列表中

# 创建二维列表的子列表元素对象
class Sub_list():

	def __init__(self, left, right):
		self.left = 0  # 初始值为0
		self.right = 0

	def __str__(self):
		return '[{},{}]'.format(self.left, self.right)

	def __repr__(self):
		return '[%s, %s]'%(self.left, self.right)

类创建好之后,开始书写方法

def list_merge(sub_list):
	# 先对子列表排序,用子列表的下标0为标志进行排序
	sub_list.sort(key = lambda x:x.left)

	# 创建一个空列表用于接收合并列表
	merge_res = []
	# 排好之后就可以直接根据相邻列表的下标值进行判断
	for sub_l in sub_list:
		# 第一种情况,相邻子列表中没有交叉范围
		if not merge_res or merge_res[-1].right < sub_l.left:
			merge_res.append(sub_l)
		# 当存在交叉范围的之后
		else:
			# 在前一个子列表的right 和 后一个子列表的 right中选出一个最大值,直接进行赋值覆盖
			merge_res[-1].right = max(merge_res[-1].right, sub_l.right)
	# 最后返回merge_res列表即可
	return merge_res

方法写好了,万事俱备只欠东风。开始创建对象,调用方法获取返回值

a1 = Sub_list(2, 10)
a2 = Sub_list(5, 13)
a3 = Sub_list(15, 20)
a4 = Sub_list(25, 27)
test = [a1, a2, a3, a4]
print(test)
res = list_merge(test)
print('合并后的列表:', res)

在这里插入图片描述
怎么样,不难吧,只要理解了原理,捋清楚思路,代码自然而然就出来了,有更好方法的朋友欢迎交流

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值