数据结构与算法初步
1 查找算法
目标:在指定列表中判断目标元素是否存在。
1.1 顺序查找
列表等容器中每个数据元素都有属于自己的索引,索引值是有序的,因此可以利用索引按顺序访问每个数据,通过使用索引遍历数据来查找目标元素称为顺序查找。
顺序查找原理
目标是判断待查找的目标元素在列表中是否存在。
从列表中的第一个元素开始,通过索引依次检查每个元素,直到找到目标元素,或者遍历完整个列表。如果遍历完整个列表,则说明待查找的目标元素在列表中不存在。
def order_search(alist, item):
found = False
pos = 0
while True:
if alist[pos] == item:
found = True
break
else:
pos += 1
if pos == len(alist):
break
return found
l = [21, 12, 3, 66, 15]
print(order_search(l, 3))
1.2 二分查找
二分查找是一种在排序后的列表中查找某一特定元素的算法。
从列表的中间元素开始,
如果中间元素正好是待查找的目标元素,则搜索过程结束;
如果目标元素大于或者小于中间元素,则将查找范围缩小到列表大于或小于中间元素的那一半,从中间元素开始比较。
如果缩小范围后列表为空,则说明待查找的目标元素在列表中不存在。
这种搜索算法每一次比较都使搜索范围缩小一半。
def binary_search(alist, item):
left = 0 # 序列第一个元素下标
right = len(alist) - 1 # 序列中最后一个元素下标
found = False
while left <= right:
mid = (left + right) // 2 # 中间元素下标
if item == alist[mid]: # 找到了待查找查找的目标元素
found = True
break
else:
if item < alist[mid]: # 目标元素可能存在于中间元素左侧
right = mid - 1
else: # 目标元素可能存在于中间元素右侧
left = mid + 1
return found
alist = [1, 2, 3, 4, 5]
print(binary_search(alist, 5))
2 排序算法
2.1 冒泡排序
冒泡排序,Bubble Sort
冒泡排序需要重复地访问待排序的列表,每次比较两个元素,将数值较大者放置在右侧。重复上面的过程直到不存在需要交换的两个元素,此时列表已经完成排序。
def bubble_sort(alist):
for j in range(len(alist) - 1):
for i in range(len(alist) - 1 - j):
if alist[i] > alist[i + 1]:
alist[i], alist[i + 1] = alist[i + 1], alist[i]
return alist
alist = [3, 8, 5, 7, 6, 2, 1]
print(bubble_sort(alist))
2.2 选择排序
选择排序,Selection sort
首先在未排序的列表中找到最小(大)元素,存放到排序列表的起始位置,然后再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序列表的末尾。以此类推,直到所有元素均排序完毕。
def selection_sort(alist):
for j in range(len(alist) - 1):
max_index = 0 # 保存最大值下标,一开始假设第0个元素为最大值。
for i in range(1, len(alist) - j):
if alist[i] > alist[max_index]:
max_index = i
alist[max_index], alist[len(alist) - 1 - j] = alist[len(alist) - 1 - j], alist[max_index]
return alist
alist = [3, 8, 5, 7, 6, 2, 1]
print(selection_sort(alist))
2.3 插入排序
插入排序,Insertion Sort,本质是构造有序列表。
将乱序的列表分为两部分,
有序部分:初始时只包含第一个元素;
无序部分:其余元素。
操作:每次从无序部分中将一个元素插入到有序部分的对应位置中。
def insertion_sort(alist):
n = len(alist)
for i in range(1, n):
unsorted_ele = alist[i]
j = i - 1
while j >= 0:
if unsorted_ele < alist[j]:
alist[j + 1] = alist[j]
j -= 1
else:
break
alist[j + 1] = unsorted_ele
return alist
alist = [3, 8, 5, 7, 6, 2, 1]
print(insertion_sort(alist))
2.4 希尔排序
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。
希尔排序属于非稳定排序算法,因为不同子组中的相同元素可能会交换位置。
希尔排序针对插入排序的特点进行改进:
- 插入排序一般是低效的,因为插入排序每次只能将数据移动一位。
- 插入排序处理几乎已经排好序的列表时效率非常高,几乎可以达到线性排序的效率。
基本思想:
- 对于长度为n的列表,取一个小于n的整数gap(gap称为增量),将待排序元素分成若干组子序列,所有距离为gap的倍数的记录放在同一个组中;
- 对各组内的元素进行直接插入排序,使得每组中的元素都是有序的;
- 减小gap的值,重复执行上述的分组和排序操作。
- 当gap=1时的插入排序完成后,整个列表是有序的。
# 希尔排序
def shell_sort(arr):
n = len(arr)
gap = int(n / 2)
while gap > 0:
for i in range(gap, n):
unsorted_ele = arr[i]
j = i - gap
while j >= 0 and unsorted_ele < arr[j]:
arr[j + gap] = arr[j]
j -= gap
arr[j + gap] = unsorted_ele
gap = int(gap / 2)
return arr
# 插入排序是gap=1的希尔排序
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
unsorted_ele = arr[i]
j = i - 1
while j >= 0 and unsorted_ele < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = unsorted_ele
return arr
alist = [3, 8, 5, 7, 6, 2, 1]
print(shell_sort(alist))
2.5 快速排序
快速排序使用分治法(Divide and conquer)策略将一个列表分为较小和较大的两个子列表,然后递归地对两个子列表进行排序。
- 挑选基准:从数列中挑出一个元素,称为"基准"(pivot),初始时将乱序列表中的第一个元素或最后一个元素作为基准。
- 分割:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素放在基准后面,与基准值相等的元素可以到任何一边,这会影响算法的稳定性。分割结束后,对基准值的排序就完成了。
- 递归排序子序列:通过递归,将小于基准值元素的子序列和大于基准值元素的子序列以上面的方式进行分割操作。
# 分割
def partition(arr, low, high):
i = (low - 1) # 最小元素索引
pivot = arr[high] # 用组中最后一个元素作为基准
for j in range(low, high):
# 当前元素小于或等于pivot时
if arr[j] <= pivot:
i += 1
# 每次将小于或等于pivot的元素放到下标为i+1的元素的左边
arr[i], arr[j] = arr[j], arr[i]
# 组内所有小于或等于pivot的元素都处于下标为i+1的元素的左边,将pivot元素与下标为i+1的元素交换,完成此次分割。
arr[i + 1], arr[high] = arr[high], arr[i + 1]
# 将组内pivot元素的下标返回,用于递归中进一步分组的起始下标或终止下标。
return (i + 1)
# arr[] --> 排序数组
# low --> 起始索引
# high --> 结束索引
# 快速排序函数
def quick_sort(arr, low, high):
if low < high:
pi = partition(arr, low, high)
quick_sort(arr, low, pi - 1)
quick_sort(arr, pi + 1, high)
return arr
alist = [6, 1, 2, 7, 9, 3, 4, 5, 10, 8]
n = len(alist)
print(quick_sort(alist, 0, n - 1))