数据结构:希尔排序、归并排序、快速排序完整代码、时间复杂度、可视化

以下是tjdx,经管学院,数据结构课程的一次作业报告(本人自己的)
写的不好不对的请见谅指正
未贴出运行结果图片,希望大家自己动手尝试

一.三种排序方法介绍

(一)希尔排序(Shell Sort)

1. 基本思想:

把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量主键递减,每组包含的关键词也来越多,当增量减至 1 时,整个文件恰被分成一组,算法终止。

2. 代码实现:

"""希尔排序"""
def shell_sort(lst):
n = len(lst)
gap = n // 2
while gap > 0:
for i in range(gap, n):
for j in range(i, gap - 1, -gap):
if lst[j] < lst[j - gap]:
lst[j], lst[j - gap] = lst[j - gap], lst[j]
else:
break
#打印每一步排序结果,便于后续分析
print("一趟排序后的列表:",lst)
gap //= 2
return lst

3. 时间复杂度分析:

1 )过程分解(以具体线性表为例打印出每一步执行后的排序结果):
#以一个长为 6 的列表为实例,打印出排序的逐步过程
alist=[5,9,4,6,3,7]
print("希尔排序过程如下:")
print("原列表:",alist)
a_shell_result=shell_sort(alist)
print("最终结果为:",a_shell_result)
运行结果如下:
2 )理论推导:
初始化间隔:初始间隔是 n/2 ,然后每次减半,直到间隔为 1
插入排序:在每一个间隔下,希尔排序实际上是进行了多个插入排序。每个插入排序的时间复杂度是 O(k) ,其中 k 是插入排序的长度。
迭代次数:希尔排序需要进行 log(n/d) 次迭代,其中 d 是初始间隔。
总时间复杂度:总时间复杂度是 O(n log(n/d)) 。当 d = 1 时,总时间复杂度是 O(nlog n)。

(二)归并排序(Merge Sort)

1. 基本思想:

使用递归,将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序。

2. 代码实现:

"""归并排序"""
def merge_sort(lst):
if lst is None:
return []
n = len(lst)
#基线条件(结束条件):当分割后的列表长度为 1 时结束,返回分割后的
列表
if n <= 1:
return lst
#整除 2 找到中间元素的位置
mid = n // 2
left=lst[:mid]
right=lst[mid:]
print("归并排序合并前:",left,right)
#递归思想,对左右每一个子列表进行分割,直到其长度为 1 停止,实现"分"
left_sorted= merge_sort(left)
right_sorted= merge_sort(right)
merge_result=merge(left_sorted,right_sorted)
print("归并排序合并后:",merge_result)
return merge_result
#实现"治"的函数,即对左(left)右(right)两个数组进行排序
def merge(left,right):
i = 0
j = 0
#一个空列表用来存储结果
result = []
#两表均非空
if left!=None and right!=None:
#使用两个指针,分别在左右两列表中移动,将较小的放在结果中
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=result+left[i:]+right[j:]
#右表为空
if right==None and left!=None:
#当右列表为空时,将左列表元素放在结果中
while i < len(left):
result.append(left[i])
i += 1
#左表为空
if left==None and right!=None:
#或当左列表为空时,将右列表元素放在结果中
while j < len(right):
result.append(right[j])
j += 1
return result

3. 时间复杂度分析

1 )过程分解(以具体线性表为例打印出每一步执行后的排序结果):
alist=[5,9,4,6,3,7]
print("归并排序过程如下:")
print("原列表:",alist)
a_merge_result=merge_sort(alist)
print("最终结果为:",a_merge_result)
运行结果如下:
2 )理论推导:
分解:将数组分成两个子数组,时间复杂度是 O(log n)
合并:将两个子数组合并成一个有序数组,时间复杂度是 O(n)
迭代次数:归并排序需要进行 log n 次迭代。
总时间复杂度:总时间复杂度是 O(n log n)

(三)快速排序(Quick Sort)

1. 基本思想:

通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均
比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序
列有序。

2. 代码实现:

"""快速排序"""
def quick_sort(arr, low, high):
if low < high:
pivot_index = partition(arr, low, high)
# 打印当前数组的状况
print("一趟排序后的列表:", arr)
# 对左侧子数组进行快速排序
quick_sort(arr, low, pivot_index - 1)
# 对右侧子数组进行快速排序
quick_sort(arr, pivot_index + 1, high)
return arr
#进行一次排序,返回枢轴位置的函数
def partition(arr, low, high):
#将第一个元素设为枢轴
pivot = arr[low]
#左指针指向列表第二个元素
left = low + 1
#右指针指向列表最后一个元素
right = high
#左指针指向元素小于枢轴,则指针后移
while left <= right and arr[left] <= pivot:
left = left + 1
#右指针指向元素大于枢轴,则指针前移
while arr[right] >= pivot and right >= left:
right = right - 1
#当不满足以上两个条件时,若左右指针不相等,则交换两个值的位置
if left<right:
arr[left], arr[right] = arr[right], arr[left]
#不满足 if 条件,即当两指针重合时该位置为要找的枢轴位置,将枢轴值
与此位置值交换
arr[low], arr[right] = arr[right], arr[low]
#返回枢轴位置
return right
3. 时间复杂度分析:
(1)过程分解(以具体线性表为例打印出每一步执行后的排序结果):
print("快速排序过程如下:")
print("原列表:",alist)
a_quick_result=quick_sort(alist,0,len(alist)-1)
print("最终结果为:",a_quick_result)
运行结果如下:
2 )理论推导:
选择枢轴:选择枢轴的时间复杂度是 O(1)
分区:分区的时间复杂度是 O(n)
递归:快速排序是递归算法,递归深度是 log n
总时间复杂度:总时间复杂度是 O(n log n) 。在最坏情况下,分区的时间复杂度是 O(n^2) ,导致总时间复杂度是 O(n^2)

二.时间曲线图

1. 实验全部代码:

import random
import time
import matplotlib.pyplot as plt
import numpy as np
import sys
# 增加递归深度限制
sys.setrecursionlimit(10000)
"""希尔排序"""
def shell_sort(lst):
n = len(lst)
gap = n // 2
while gap > 0:
for i in range(gap, n):
for j in range(i, gap - 1, -gap):
if lst[j] < lst[j - gap]:
lst[j], lst[j - gap] = lst[j - gap], lst[j]
else:
break
#打印每一步排序结果,便于后续分析
# print("一趟排序后的列表:",lst)
gap //= 2
return lst
"""归并排序"""
def merge_sort(lst):
if lst is None:
return []
n = len(lst)#基线条件(结束条件):当分割后的列表长度为 1 时结束,返回分割后的
列表
if n <= 1:
return lst
#整除 2 找到中间元素的位置
mid = n // 2
left=lst[:mid]
right=lst[mid:]
# print("归并排序合并前:",left,right)
#递归思想,对左右每一个子列表进行分割,直到其长度为 1 停止,实现"分"
left_sorted= merge_sort(left)
right_sorted= merge_sort(right)
merge_result=merge(left_sorted,right_sorted)
# print("归并排序合并后:",merge_result)
return merge_result
#实现"治"的函数,即对左(left)右(right)两个数组进行排序
def merge(left,right):
i = 0
j = 0
#一个空列表用来存储结果
result = []
#两表均非空
if left!=None and right!=None:
#使用两个指针,分别在左右两列表中移动,将较小的放在结果中
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=result+left[i:]+right[j:]
#右表为空
if right==None and left!=None:
#当右列表为空时,将左列表元素放在结果中
while i < len(left):
result.append(left[i])
i += 1
#左表为空
if left==None and right!=None:
#或当左列表为空时,将右列表元素放在结果中
while j < len(right):result.append(right[j])
j += 1
return result
# """快速排序"""
## 双指针的快速排序
#3 进行一次排序,返回枢轴位置的函数
# def partition(arr, low, high):
#
#将第一个元素设为枢轴
#
pivot = arr[low]
#
#左指针指向列表第二个元素
#
left = low + 1
#
#右指针指向列表最后一个元素
#
right = high
#
#左指针指向元素小于枢轴,则指针后移
#
while left <= right and arr[left] <= pivot:
#
left = left + 1
#
#右指针指向元素大于枢轴,则指针前移
#
while arr[right] >= pivot and right >= left:
#
right = right - 1
#
#当不满足以上两个条件时,若左右指针不相等,则交换两个值的位置
#
if left<right:
#
arr[left], arr[right] = arr[right], arr[left]
#
#不满足 if 条件,即当两指针重合时该位置为要找的枢轴位置,将枢轴
值与此位置值交换
#
arr[low], arr[right] = arr[right], arr[low]
#
#返回枢轴位置
#
return right
# def quick_sort(arr, low, high):
#
if low < high:
#
pivot_index = partition(arr, low, high)
#
# 打印当前数组的状况
#
# print("一趟排序后的列表:", arr)
#
# 对左侧子数组进行快速排序
#
quick_sort(arr, low, pivot_index - 1)
#
# 对右侧子数组进行快速排序
#
quick_sort(arr, pivot_index + 1, high)
#
return arr
#另一种快速排序代码(课上)
def quicksort(array):
if len(array) < 2:
return array # 基线条件
else:
pivot = array[0] # 递归条件less = [i for i in array[1:] if i <= pivot] # 小于基准值的数
组
greater = [i for i in array[1:] if i > pivot] # 大于基准值的
数组
array=quicksort(less) + [pivot] + quicksort(greater)
# print("一趟排序后的列表:", array)
return array
# """一个实例"""
# #以一个长为 6 的列表为实例,打印出排序的逐步过程
# alist=[5,9,4,6,3,7]
# print("希尔排序过程如下:")
# print("原列表:",alist)
# a_shell_result=shell_sort(alist)
# print("最终结果为:",a_shell_result)
# alist=[5,9,4,6,3,7]
# print("归并排序过程如下:")
# print("原列表:",alist)
# a_merge_result=merge_sort(alist)
# print("最终结果为:",a_merge_result)
# alist=[5,9,4,6,3,7]
# print("快速排序过程如下:")
# print("原列表:",alist)
# a_quick_result=quick_sort(alist,0,len(alist)-1)
# print("最终结果为:",a_quick_result)
"""绘制时间图像"""
#每一长度下的重复次数,多次随机生成列表取平均值,避免偶然性
repeat=50
#储存不同列表长度下三种排序算法用时的列表
shell_times=[]
merge_times=[]
quick_times=[]
#储存列表长度的列表
lengths = [i for i in range(10,2000,100)]
#列表中元素(随机数)范围
min_value=1
max_value=100
#希尔排序性能检测
for length in lengths:
shell_total_time=0
for i in range(repeat):
random_list = np.random.randint(min_value, max_value,
size=length)shell_start_time=time.time()
shell_sort(random_list)
shell_end_time=time.time()
shell_total_time+=shell_end_time-shell_start_time
shell_time=shell_total_time/repeat
shell_times.append(shell_time)
#归并排序性能检测
for length in lengths:
merge_total_time=0
for i in range(repeat):
random_list = np.random.randint(min_value,
max_value,size=length)
merge_start_time=time.time()
merge_sort(random_list)
merge_end_time=time.time()
merge_total_time+=merge_end_time-merge_start_time
merge_time=merge_total_time/repeat
merge_times.append(merge_time)
#快速排序性能检测
for length in lengths:
quick_total_time=0
for i in range(repeat):
random_list = np.random.randint(min_value, max_value,
size=length)
quick_start_time=time.time()
quicksort(random_list)
quick_end_time=time.time()
quick_total_time+=quick_end_time-quick_start_time
quick_time=quick_total_time/repeat
quick_times.append(quick_time)
#开始绘图
plt.figure(figsize=(10, 6))
plt.plot(lengths, shell_times, label='Shell Sort')
plt.plot(lengths, merge_times, label='Merge Sort')
plt.plot(lengths, quick_times, label='Quick Sort')
plt.title('Comparasion of Three Sort Methods')
plt.xlabel('List Length')
plt.ylabel('Average Execution Time')
plt.grid(True)
plt.legend()
plt.show()

2.运行结果(在不同大小线性表(N)时,三种排序算法的时间曲线图):

三.优化策略

1. 希尔排序

(1) 选择合适的增量序列:希尔排序的性能很大程度上取决于增量序列的选择。选择一个好的增量序列可以提高算法的效率。
(2) 使用插入排序:在子数组内使用插入排序,可以提高希尔排序的性能。

2. 归并排序

(1) 使用原地归并:归并排序的合并步骤可以使用原地归并,减少额外的存储空间需求。
(2) 二分查找分割:在选择分割点时,使用二分查找可以减少比较次数,提高算法的效率。

3. 快速排序

(1) 选择合适的枢轴:选择一个好的枢轴可以减少最坏情况下的比较次数,提高算法的效率。
(2) 使用三数取中法:使用三数取中法选择枢轴,可以减少最坏情况下的比较次数。
(3) 使用快速选择:快速选择算法可以在 O(n) 的时间内找到第 k 小的元素,可以用来优化快速排序。

四.效率对比

1.平均和最坏情况:

归并排序和快速排序在平均和最坏情况下的时间复杂度都是 O(n log n),而希尔排序在平均情况下是 O(n log n) ,但在最坏情况下是 O(n^2)

2.空间复杂度:

希尔排序的空间复杂度为 O(1) ,归并排序的空间复杂度为 O(n) ,快速排序的空间复杂度为 O(log n) O(1)

3.稳定性:

希尔排序和快速排序是不稳定的,而归并排序是稳定的。

4.实现难度:

归并排序的实现相对简单,而快速排序和希尔排序的实现相对复杂 一些。
综合来看,对于任意长度、顺序未知的列表而言,快速排序算法是所有排序算法中相对效率最高的(但实际中存在快速排序时间较长的情况,可能与列表的选择有关)。经统计,不同列表情况下,其在最坏情况下的时间相对最短,而平均、最好情况下与其他两种算法用时相似。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值