列表查找
顺序查找(线性查找)
从列表第一个元素开始,依次顺序搜索,直到搜索到最后一个元素,时间复杂度为O(n).
def linear_search(arr, target):
for i in range(len(arr)): #for i, v in enumerate(li):
if arr[i] == target:
return i # 如果找到目标元素,返回其索引
return -1 #如果未找到目标元素,返回-1
#例子
arr = [3,5,2,8,10,4]
target = 8
result = linear_search(arr, target)
if result != -1:
print(f"目标元素 {target} 在数组中的索引为 {result}")
else:
print(f"目标元素 {target} 未在数组中找到。")
二分查找(折半查找)
1.二分查找也是一种在数组中查找数据的算法
2.二分查找有两个限制条件:
(1)查找的数量只能是一个,不能是多个;
(2)查找的对象在逻辑上必须是有序的;
3.二分查找思想:二分查找通过比较数组中间的数据与目标数据的大小,可以得知目标数据是在数组的左边还是右边。因此,比较一次就可以把查找范围缩小一半。重复执行该操作就可以找到目标数据,或得出目标数据不存在的结论。
4.二分查找利用已排好序的数组,每一次查找都可以将查找范围减半。查找范围内只剩一个数据时查找结束。
5.二分查找的时间复杂度为O(logn)
6.线性查找和二分查找的对比:
(1)二分查找必须建立在数据已经排好序的基础上才能使用,因此添加数据时必须加到合适的位置,这就需要额外耗费维护数组的时间。
(2)线性查找时,数组中的数据可以是无序的,因此添加数据时也无须顾虑位置,直接把它加在末尾即可,不需要耗费时间。
二分查找示意图
def binary_search(arr, target):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid # 如果找到目标元素,返回其索引
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1 # 如果未找到目标元素,返回-1
# 测试
arr = [1, 3, 5, 7, 9, 11, 13]
target = 7
result = binary_search(arr, target)
if result != -1:
print(f"目标元素 {target} 在数组中的索引为 {result}")
else:
print(f"目标元素 {target} 未在数组中找到")
冒泡排序
从前到后(即从下标较小的元素开始)依次比较相邻元素的值,若发现逆序则交换位置,使值较大的元素逐渐从前移向后部。
1.比较相邻的元素。如果第一个比第二个大,就交换它们两个。
2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4. 重复步骤1~3,直到排序完成。
def bubble_sirt(li):
for i in range(len(li)-1):
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j],li[j+1] = li[j+1],li[j]
冒泡排序——链表实现
num=int(input('节点数:'))
n=[]
head=-1#填空2,尾
data=randint(10,99)
n.append([data,head])
head=0#头
for i in range(1,num):
data=randint(10,99)
n.append([data,n[i-1][1]])
n[i-1][1]=i#填空3,len(n)-1也对,n[i][1]+=1,会做成倒置链表
print(n)
print('原始链表为:')
plist(head)#填空4,输出链表
n.append([-1,head])
print(n)
head=len(n)-1
tail=-1
f=True#f冒泡优化,某趟比较没有交换,排序结束
s=0
while n[head][1]!=tail and f:#填空5
p=head
q=n[p][1]
f=False
while n[q][1]!=tail: #原始链表d->a->b->c
if n[q][0]>n[n[q][1]][0]: #a>b
n[p][1]=n[q][1] # d->b a->b->c
n[q][1]=n[n[q][1]][1] # d->b a->c b->c
n[n[p][1]][1]=q # d->b a->c b->a 即d->b->a->c实现了b->a升序
q=n[p][1]# q=b
f=True#填空6
#q=n[q][1],p=n[p][1]实现下轮比较的两数下移,使得冒泡正常进行
q=n[q][1] #q=a
p=n[p][1] #p=b
s=s+1
tail=q #填空7,每次排完一遍后,将tail指针向前挪动一个单位,因为下沉,最大(或最小)的结点已经到最后了,已经不需要再进行排序了
plist(head)
p=n[head][1]
plist(p)
print(s)
快速排序
通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。 算法描述 快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
从数列中挑出一个元素,称为 “基准”(pivot);
重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
def quick_sort(arr):
if len(arr) <= 1:
return arr
else:
pivot = arr[0]
less_than_pivot = [x for x in arr[1:] if x <= pivot]
greater_than_pivot = [x for x in arr[1:] if x > pivot]
return quick_sort(less_than_pivot) + [pivot] + quick_sort(greater_than_pivot)
归并排序
建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述 - 把长度为n的输入序列分成两个长度为n/2的子序列; - 对这两个子序列分别采用归并排序; - 将两个排序好的子序列合并成一个最终的排序序列。
def merge_sort(arr):
if len(arr) <= 1:
return arr
# 分割数组
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 递归地对左右两部分进行归并排序
left_sorted = merge_sort(left_half)
right_sorted = merge_sort(right_half)
# 合并两个已排序的部分
return merge(left_sorted, right_sorted)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] < right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result += left[i:]
result += right[j:]
return result
基数排序
按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。
算法描述 - 取得数组中的最大数,并取得位数; - arr为原始数组,从最低位开始取每个位组成radix数组; - 对radix进行计数排序。
def counting_sort(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for i in range(n):
index = arr[i] // exp
count[index % 10] += 1
for i in range(1, 10):
count[i] += count[i - 1]
i = n - 1
while i >= 0:
index = arr[i] // exp
output[count[index % 10] - 1] = arr[i]
count[index % 10] -= 1
i -= 1
for i in range(n):
arr[i] = output[i]
def radix_sort(arr):
max_num = max(arr)
exp = 1
while max_num // exp > 0:
counting_sort(arr, exp)
exp *= 10
计数排序
不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
算法描述 - 找出待排序的数组中最大和最小的元素; - 统计数组中每个值为i的元素出现的次数,存入数组C的第i项; - 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加); - 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
def counting_sort(arr):
max_num = max(arr)
min_num = min(arr)
count_arr = [0] * (max_num - min_num + 1)
output = [0] * len(arr)
# 统计每个元素出现的次数
for num in arr:
count_arr[num - min_num] += 1
# 计算累加次数
for i in range(1, len(count_arr)):
count_arr[i] += count_arr[i - 1]
# 填充排序后的数组
for num in reversed(arr):
output[count_arr[num - min_num] - 1] = num
count_arr[num - min_num] -= 1
# 将排序后的数组复制回原始数组
for i in range(len(arr)):
arr[i] = output[i]
桶排序
计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
算法描述 - 设置一个定量的数组当作空桶; - 遍历输入数据,并且把数据一个一个放到对应的桶里去; - 对每个不是空的桶进行排序; - 从不是空的桶里把排好序的数据拼接起来.
def bucket_sort(arr):
# 创建桶并初始化为空列表
num_buckets = 10
buckets = [[] for _ in range(num_buckets)]
# 将数据分配到对应的桶中
for num in arr:
index = num // 10 # 这里简单地将数字除以10作为桶的索引
buckets[index].append(num)
# 对每个非空桶进行排序,这里使用了插入排序
for bucket in buckets:
if bucket:
insertion_sort(bucket)
# 将排序后的桶中数据合并为最终排序结果
k = 0
for i in range(num_buckets):
for j in range(len(buckets[i])):
arr[k] = buckets[i][j]
k += 1
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
希尔排序
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
def shell_sort(arr):
n = len(arr)
gap = n // 2
while gap > 0:
for i in range(gap, n):
temp = arr[i]
j = i
while j >= gap and arr[j - gap] > temp:
arr[j] = arr[j - gap]
j -= gap
arr[j] = temp
gap //= 2
选择排序
初始状态:无序区为R[1..n],有序区为空;
第i趟排序(i=1,2,3…n-1)开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中-选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R[i+1..n)分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区;
n-1趟结束,数组有序化了。
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
插入排序
1. 从第一个元素开始,该元素可以认为已经被排序。
2. 取出下一个元素,在已经排序的元素序列中从后向前扫描。
3. 如果该元素(已排序)大于新元素,将该元素移到下一位置。
4. 重复步骤3,直到找到已排序的元素小于或等于新元素的位置。
5. 将新元素插入到该位置后。
6. 重复步骤2~5,直到所有元素均被排序。
插入排序的时间复杂度为O(n^2),适用于小规模数据或基本有序的数据集。
视频原图为排序算法:插入排序【图解+代码】_哔哩哔哩_bilibili
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
堆排序
1. 构建最大堆:将待排序的序列构建成一个最大堆。最大堆是一种满足父节点大于等于子节点的堆结构。
2. 将堆顶元素(最大值)与末尾元素交换:将堆顶元素(当前最大值)与堆的末尾元素进行交换,这样最大值就被放置到了数组的末尾。
3. 调整堆结构:交换元素后,需要对剩下的元素重新调整成最大堆。
4. 重复步骤2和3:持续重复这个过程,直到所有元素都被排序完成。
5. 最终得到的排序结果为从小到大的有序序列。
堆排序的时间复杂度为O(nlogn),具有不稳定性,但适用于大数据量的排序。
def heapify(arr, n, i):
largest = i # 将最大值设为根节点
l = 2 * i + 1 # 左子节点
r = 2 * i + 2 # 右子节点
# 如果左子节点比根节点大,更新最大值
if l < n and arr[l] > arr[largest]:
largest = l
# 如果右子节点比根节点大,更新最大值
if r < n and arr[r] > arr[largest]:
largest = r
# 如果最大值不是根节点,交换根节点与最大值
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
# 递归调整子树
heapify(arr, n, largest)
def heapSort(arr):
n = len(arr)
# 构建最大堆
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 一个个从堆顶取出元素
for i in range(n - 1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # 交换
heapify(arr, i, 0)