Python数据结构与算法分析课后习题(第二版) 第五章 课后练习

目录:

1.进行随机实验,测试顺序搜索算法与二分搜索算法在处理整数列表时的差异。
2.随机生成一个有序的整数列表。通过基准测试分析文中给出的二分搜索函数(递归版本与循环版本)。请解释你得到的结果。
3.不用切片运算符,实现递归版本的二分搜索算法。别忘了传入头元素和尾元素的下标。随机生成一个有序的整数列表,并进行基准测试。
4.为散列表实现len方法__len__。
5.为散列表实现in方法__contains__。
6.采用链接法处理冲突时,如何从散列表中删除元素?如果是采用开放定址法,又如何做呢?有什么必须处理的特殊情况?请为HashTable类实现del方法。
7.在本章中,散列表的大小为11。如果表满了,就需要增大。请重新实现put方法,使得散列表可以在载荷因子达到一个预设值时自动调整大小(可以根据载荷对性能的影响,自己决定预设值)。
8.实现平方探测这一再散列技巧。
9.使用随机数生成器创建一个含500个整数的列表。通过基准测试分析本章中的排序算法。它们在执行速度上有什么差别?
10.利用同时赋值特性实现冒泡排序。
11.可以将冒泡排序算法修改为向两个方向“冒泡”。第一轮沿着列表“向上”遍历,第二轮沿着列表“向下”遍历。继续这一模式,直到无须遍历为止。实现这种排序算法,并描述它的适用情形。
12.利用同时赋值特性实现选择排序。
13.针对同一个列表使用不同的增量集,为希尔排序进行基准测试。
14.不用切片运算符,实现mergesort函数。
15.有一种改进快速排序的办法,那就是在列表长度小于某个值时采用插入排序(这个值被称为“分区限制”)。这是什么道理?重新实现快速排序算法,并给一个随机整数列表拍戏。采用不同的分区限制进行性能分析。
16.修改quickSort函数,在选取基准值时采用三数取中法。通过实验对比两种技巧的性能差异。



1.进行随机实验,测试顺序搜索算法与二分搜索算法在处理整数列表时的差异

import timeit  # 导入时间库(段代码的运行时间)
import random  # 导入 随机库
import matplotlib.pyplot as plt  # 导入图表库(生成图表)

# 顺序查找
def orderedSequentialSearch(alist, item):  # 顺序搜索函数(列表,数据项)
    pos = 0  # 计算器
    found = False  # 结果先设为假

    while pos < len(alist) and not found:  # 计数小于列表长度和结果不为真就循环
        if alist[pos] == item:  # 如果列表对应元素==数据项
            found = True  # 结果为真
        else:  # 反之
            pos = pos+1  # 计算器+1

    return found  # 返回查找结果


# 二分查找
def binarySearch(alist, item):  # 二分搜索函数递归版(列表,数据项)
    if len(alist) == 0:  # 如果列表长度==0
        return False  # 返回假
    else:  # 反之
        midpoint = len(alist)//2  # 取列表长度的一般作为下标并赋值个变量
        if alist[midpoint] == item:  # 如果列表里的元素==你要查的数据项
            return True  # 返回真
        else:  # 反之
            if item < alist[midpoint]:  # 如果数据项小于列表里的元素
                return binarySearch(alist[:midpoint], item)  # 递归调用往前搜
            else:  
                return binarySearch(alist[midpoint+1:], item)  # 递归调用往后搜


"""数据可视化没有学,无法解释"""
lenx = []
osy = []
bsy = []
color = ['c', 'b', 'g', 'r', 'm', 'y', 'k', 'w']

for i in range(1000000, 100000000, 1000000):
    """列表有序时"""
    # t1 = timeit.Timer("orderedSequentialSearch(x,%d)" %
    #                   i, "from __main__ import  x, orderedSequentialSearch")
    # t2 = timeit.Timer("binarySearch(x,%d)" %
    #                   i, "from __main__ import  x, binarySearch")

    """列表有序时"""
    t1 = timeit.Timer("orderedSequentialSearch(x,random.randrange(%d))" %
     i, "from __main__ import random, x, orderedSequentialSearch")
    t2 = timeit.Timer("binarySearch(x,random.randrange(%d))" %
     i, "from __main__ import random, x, binarySearch")
    
    x = list(range(i))
    # np.random.shuffle(x)
    findtime1 = t1.timeit(number=1)
    # x = list(range(i))
    findtime2 = t2.timeit(number=1)
    print("%d, %15.6f,%15.6f" % (i, findtime1, findtime2))
    lenx.append(i)
    osy.append(findtime1)
    bsy.append(findtime2)
    ax = plt.gca()
    # 去掉边框
    ax.spines['top'].set_color('none')
    ax.spines['right'].set_color('none')
    # 移位置 设为原点相交
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data', 0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data', 0))
    # plt.ylim(0, 0.0001)
    plt.scatter(lenx, osy, c=color[3],
                edgecolors='r', label='orderSequentialSearch')
    plt.scatter(lenx, bsy, c=color[1], edgecolors='b',
                marker='^', label='binarySearch')
    plt.xlabel('lenth(list)')
    plt.ylabel('time(/s)')
    plt.title('Search(Order)_analysis')
    plt.legend()
    plt.show()

# 结果分析:对于顺序搜索算法,当要搜索的列表有序时,其性能远远低于二分搜索算法
# 结果分析:对于顺序搜索算法,当要搜索的列表无序时,其性能有时候优于二分搜索
# 总的结果二分搜索快

2.随机生成一个有序的整数列表。通过基准测试分析文中给出的二分搜索函数(递归版本与循环版本)。请解释你得到的结果。

import timeit  # 导入时间库(段代码的运行时间)
import random  # 导入 随机库
import matplotlib.pyplot as plt  # 导入图表库(生成图表)

# 二分查找循环版
def binarySearch(alist, item):   # 二分搜索函数循环版(列表,数据项
    first = 0  # 计算器
    last = len(alist) - 1  # 列表长度
    found = False  # 结果先设为假
    while first <= last and not found:  # 计数小于等于列表长度和结果没找到时循环
        midpoint = (first + last) // 2  # 算出列表中间
        if alist[midpoint] == item:  # 如果列表里的元素==数据项
            found = True  # 结果为真
        else:  # 反之
            if item < alist[midpoint]:  # 如果数据项小于列表下标对应的元素
                last = midpoint - 1  # 列表长度大于中间再减一半
            else:  # 反之
                first = midpoint + 1  # 计算器等于中间长度+1
    return found


# 二分查找递归版
def binarySearch2(alist, item):  # 二分搜索函数递归版(列表,数据项)
    if len(alist) == 0:  # 如果列表长度==0
        return False  # 返回假
    else:  # 反之
        midpoint = len(alist)//2  # 取列表长度的一半作为下标并赋值个变量
        if alist[midpoint] == item:  # 如果列表里的元素==你要查的数据项
            return True  # 返回真
        else:  # 反之
            if item < alist[midpoint]:  # 如果数据项小于列表里的元素
                return binarySearch2(alist[:midpoint], item)  # 递归调用往前搜
            else:
                return binarySearch2(alist[midpoint+1:], item)  # 递归调用往后搜


"""数据可视化没有学,无法解释"""
lenx = []
b1y = []
b2y = []
color = ['c', 'b', 'g', 'r', 'm', 'y', 'k', 'w']

for i in range(1000000, 100000000, 1000000):
    t1 = timeit.Timer("binarySearch(x,random.randrange(%d))" %
                      i, "from __main__ import random, x, binarySearch")
    t2 = timeit.Timer("binarySearch2(x,random.randrange(%d))" %
                      i, "from __main__ import random, x, binarySearch2")
    x = list(range(i))
    findtime1 = t1.timeit(number=1)
    findtime2 = t2.timeit(number=1)
    print("%d, %15.6f,%15.6f" % (i, findtime1, findtime2))
    lenx.append(i)
    b1y.append(findtime1)
    b2y.append(findtime2)
    ax = plt.gca()
    # 去掉边框
    ax.spines['top'].set_color('none')
    ax.spines['right'].set_color('none')
    # 移位置 设为原点相交
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data', 0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data', 0))
    plt.scatter(lenx, b1y, c=color[3], edgecolors='r', label='binarySearch1')
    plt.scatter(lenx, b2y, c=color[1], edgecolors='b',
                marker='^', label='binarySearch2')
    plt.xlabel('lenth(list)')
    plt.ylabel('time(/s)')
    plt.title('binarySearch_analysis')
    plt.legend()
    plt.show()

# 结果分析 :对于二分搜索,随着列表长度的增长,循环版本的性能要高于递归版本

3.不用切片运算符,实现递归版本的二分搜索算法。别忘了传入头元素和尾元素的下标。随机生成一个有序的整数列表,并进行基准测试。

import timeit  # 导入时间库(段代码的运行时间)
import random  # 导入 随机库
import matplotlib.pyplot as plt  # 导入图表库(生成图表)

# 二分查找递归版
def binarySearch2(alist, item):  # 二分搜索函数递归版(列表,数据项)
    if len(alist) == 0:  # 如果列表长度==0
        return False  # 返回假
    else:  # 反之
        midpoint = len(alist)//2  # 取列表长度的一半作为下标并赋值个变量
        if alist[midpoint] == item:  # 如果列表里的元素==你要查的数据项
            return True  # 返回真
        else:  # 反之
            if item < alist[midpoint]:  # 如果数据项小于列表里的元素
                return binarySearch2(alist[:midpoint], item)  # 递归调用往前搜
            else:
                return binarySearch2(alist[midpoint+1:], item)  # 递归调用往后搜

# (不用切片运算符)实现递归版本
def binarySearch3(alist, left, right, item):  # 二分搜索函数递归版(列表,左,右,数据项)
    if len(alist) == 0:  # 如果列表长度==0
        return False  # 返回假
    else:  # 反之
        # midpoint = len(alist) // 2
        midpoint = (left + right) // 2  # # 算出列表中间(最小数+最大数)除2取整
        if alist[midpoint] == item:  # 如果列表里的元素==你要查的数据项
            return True  # 返回真
        else:  # 反之
            if item < alist[midpoint]:  # 如果数据项小于列表里的元素
                # 递归调用往前搜
                return binarySearch3(alist, left, midpoint - 1, item)
            else:  # 反之
                # 递归调用往后搜
                return binarySearch3(alist, midpoint + 1, right, item)


lenx = []
b1y = []
b2y = []
color = ['c', 'b', 'g', 'r', 'm', 'y', 'k', 'w']

for i in range(1000000, 100000000, 1000000):
    t1 = timeit.Timer("binarySearch3(x, 0, len(x) - 1,random.randrange(%d))" %
                      i, "from __main__ import random, x, binarySearch3")
    t2 = timeit.Timer("binarySearch2(x,random.randrange(%d))" %
                      i, "from __main__ import random, x, binarySearch2")
    x = list(range(i))
    findtime1 = t1.timeit(number=1)
    findtime2 = t2.timeit(number=1)
    print("%d, %15.6f,%15.6f" % (i, findtime1, findtime2))
    lenx.append(i)
    b1y.append(findtime1)
    b2y.append(findtime2)
    ax = plt.gca()
    # 去掉边框
    ax.spines['top'].set_color('none')
    ax.spines['right'].set_color('none')
    # 移位置 设为原点相交
    ax.xaxis.set_ticks_position('bottom')
    ax.spines['bottom'].set_position(('data', 0))
    ax.yaxis.set_ticks_position('left')
    ax.spines['left'].set_position(('data', 0))
    plt.scatter(lenx, b1y, c=color[3], edgecolors='r', label='binarySearch3')
    plt.scatter(lenx, b2y, c=color[1], edgecolors='b',
                marker='^', label='binarySearch2')
    plt.xlabel('lenth(list)')
    plt.ylabel('time(/s)')
    plt.title('binarySearch_analysis')
    plt.legend()
    plt.show()

# 结果分析:对于递归版本的二分搜索算法,切片操作的性能要远远低于传入头尾下标操作。

4.为散列表实现len方法__len__。

5.为散列表实现in方法__contains__。

6.采用链接法处理冲突时,如何从散列表中删除元素?如果是采用开放定址法,又如何做呢?有什么必须处理的特殊情况?请为HashTable类实现del方法。

class HashTable:
    def __init__(self):  # 属性
        self.size = 11  # 散列表初始大小
        self.slots = [None] * self.size  # 定义键相当于卡槽
        self.data = [None] * self.size  # 定义值相当于你存入的元素

    def __len__(self):  # 4.为散列表实现len方法
        return len(self.slots)

    def __contains__(self, item):  # 5.为散列表实现in方法
        return item in self.slots

    def __delitem__(self, key):  # 6.删除方法
        startslot = self.hashfunction(key, len(self.slots))  # 获取键的卡槽编号
        stop = False  # 停止先设为假
        found = False  # 搜寻结果先为假
        position = startslot  # 卡槽编号赋值给变量
        while not found and not stop:  # 没找到和没停止就循环
            if self.slots[position] == None:  # 如果键列表卡槽里的值是空
                print('Error: None')  # 没找到
                break  # 停止
            elif self.slots[position] == key:  # 键列表卡槽里的值==你要找的
                found = True  # 搜寻结果先为真
                self.slots[position] = None  # 键列表对应的值设为空
                self.data[position] = None  # 值列表对应的值设为空
                data = self.data[position]  # 
            else:  # 反之不空也不是你要找的
                position = self.rehash(position, len(self.slots))  # 重新获取键的卡槽编号
                if position == startslot:  # 如果重新获取键的编号和以前一样
                    stop = True  # 停止

    def put(self, key, data):  # 添加方法(键,值)
        hashvalue = self.hashfunction(key, len(self.slots))  # 获取键的卡槽编号
        if self.slots[hashvalue] == None:  # 如果键列表卡槽编号为空
            self.slots[hashvalue] = key  # 把相应的位置赋值(键)
            self.data[hashvalue] = data  # 值列表相应的位置赋值(值)
        else:  # 反之散列里有了
            if self.slots[hashvalue] == key:  # 如果卡槽被占了
                self.data[hashvalue] = data  # 就把卡槽里的东西换了
            else:  # 反之卡槽号被了占了
                nextslot = self.rehash(hashvalue, len(self.slots))  # 就调用线性探测法重新找
                while self.slots[nextslot] != None and self.slots[nextslot] != key:  # 下一个卡槽不是空和卡槽里的键不等于当前键就循环
                    nextslot = self.rehash(nextslot, len(self.slots))  # 找到卡槽了就赋值给变量

                if self.slots[nextslot] == None:  # 如果这个卡槽为空
                    self.slots[nextslot] = key  # 键
                    self.data[nextslot] = data  # 值
                else:  # 反之不为空
                    self.data[nextslot] = data  # 就把值替换了



    def hashfunction(self, key, size):  # 获取键的卡槽编号
        return key % size  # 取余法

    def rehash(self, oldhash, size):  # 如果卡槽号被占了就重新获取卡槽编号
        return (oldhash + 1) % size  # 线性探测法


    def get(self, key):  # 返回键对应的值
            startslot = self.hashfunction(key, len(self.slots))  # 取余后的卡槽编号
            data = None  # 值为空
            stop = False  # 停止先设为假
            found = False  # 搜寻结果先为假

            position = startslot  
            while self.slots[position] != None and not found and not stop:  # 键列表卡槽号不是空,还没找到,还没停
                if self.slots[position] == key:  # 键列表卡槽里的值==你要找的
                    found = True  # 结果为真
                    data = self.data[position]  
                else:  # 反之没找到
                    position = self.rehash(position, len(self.slots))  # 用线性探测法再找
                    if position == startslot:  # 如果找一圈
                        stop = True  # 停止
                    
            return data  # 返回值

    def __getitem__(self, key):  # 实现像字典一样输出键值
        return self.get(key)

    def __setitem__(self, key, data):  # 实现像字典一样输入键值
        self.put(key, data)


if __name__ == '__main__':
    h = HashTable()  # 创建对象
    h[54] = 'cat'  # 输入
    h[26] = 'dog'
    h[93] = 'lion'
    h[17] = 'bird'
    h[77] = 'tiger'
    h[31] = 'cow'
    h[44] = 'goat'
    h[55] = 'pig'
    h[20] = 'chicken'

    print(h.slots)  # 打印键列表
    print(h.data)  # 打印值列表
    print(h[999])  # 查找
    print(len(h))  # 返回卡槽长度
    print(20 in h)  # 键在不在字典里面
    del h[20]  # 删除20号
    print(20 in h)  # 检查删除没有
    print(h.slots)  # 打印键列表
    print(h.data)  # 打印值列表

7.在本章中,散列表的大小为11。如果表满了,就需要增大。请重新实现put方法,使得散列表可以在载荷因子达到一个预设值时自动调整大小(可以根据载荷对性能的影响,自己决定预设值)。

from HashTable import*  # 导入散列类所有函数

class HashTable2(HashTable):  # 继承散列类
    def __init__(self):  # 继承散列类的属性
        super().__init__()

    def overload(self):
        slotsB = self.slots  # 键
        dataB = self.data  # 值
        self.slots = [None] * self.size  # 定义键(列表)
        self.data = [None] * self.size  # 定义值(列表)
        for i in range(len(self.slots)):  # 在散列长度里循环
            if slotsB[i] != None:  # 键列表的当前为空
                hashvalue = self.hashfunction(
                    slotsB[i], len(self.slots))  # 获取键的卡槽编号
                if self.slots[hashvalue] == None:  # 如果键列表卡槽编号为空
                    self.slots[hashvalue] = slotsB[i]  # 把追加的键赋值到键列表
                    self.data[hashvalue] = dataB[i]  # 把追加的值赋值到键列表
                else:  # 反之键列表卡槽编号不为空
                    nextslots = self.rehash(hashvalue, len(self.slots))  # 就调用线性探测法重新找
                    # 下一个卡槽不是空和卡槽里的键不等于当前键就循环
                    while self.slots[nextslots] != None and self.slots[nextslots] != slotsB[i]:
                        nextslots = self.rehash(
                            nextslots, len(self.slots))  # 找到卡槽了就赋值给变量
                    if self.slots[nextslots] == None:  # 如果键列表卡槽编号为空
                        self.slots[nextslots] = slotsB[i]  # 把追加的键赋值到键列表
                        self.data[nextslots] = dataB[i]  # 把追加的值赋值到键列表
                    else:  # 反之不为空
                        self.data[nextslots] = dataB[i]  # 就把值替换了

    def put(self, key, data):  # 重写添加方法(键,值)
        k = 0.9
        sum = 0  # 计数器(存了多少个)
        for i in range(self.size):  # 遍历散列表
            if self.slots[i] != None:  # 如果键列表不等于空
                sum += 1  # 计数器+1
        if sum / self.size >= k:  # 如果存的个数大于了k
            self.size += 1
            for j in range(1):
                self.slots.append(None)  # 增加键列表长度
                self.data.append(None)  # 增加值列表长度
            self.overload()
        hashvalue = self.hashfunction(key, len(self.slots))  # 获取键的卡槽编号
        if self.slots[hashvalue] == None:  # 如果键列表卡槽编号为空
            self.slots[hashvalue] = key  # 把相应的位置赋值(键)
            self.data[hashvalue] = data  # 值列表相应的位置赋值(值)
        else:  # 反之键列表卡槽编号不为空
            if self.slots[hashvalue] == key:  # 如果卡槽被占了
                self.data[hashvalue] = data  # 就把卡槽里的东西换了
            else:  # 反之键列表卡槽号被了占了
                nextslots = self.rehash(
                    hashvalue, len(self.slots))  # 就调用线性探测法重新找
                # 下一个卡槽不是空和卡槽里的键不等于当前键就循环
                while self.slots[nextslots] != None and self.slots[nextslots] != key:
                    nextslots = self.rehash(
                        nextslots, len(self.slots))  # 找到卡槽了就赋值给变量
                if self.slots[nextslots] == None:  # 如果这个卡槽为空
                    self.slots[nextslots] = key  # 键
                    self.data[nextslots] = data  # 值
                else:  # 反之不为空
                    self.data[nextslots] = data  # 就把值替换了

if __name__ == '__main__':
    h = HashTable2()  # 创建对象
    h[54] = 'cat'  # 输入
    h[26] = 'dog'
    h[93] = 'lion'
    h[17] = 'bird'
    h[77] = 'tiger'
    h[31] = 'cow'
    h[44] = 'goat'
    h[55] = 'pig'
    h[20] = 'chicken'

    h[707] = 'tioger'
    h[301] = 'coow'

    h[404] = 'gooat'  # 超出范围了
    # h[505] = 'poig'
    # h[200] = 'choicken'
    # h[717] = 'tbioger'
    # h[311] = 'cboow'
    # h[414] = 'gbooat'
    # h[515] = 'pboig'
    # h[210] = 'cbhoicken'
    print(h.slots)  # 打印键列表
    print(h.data)  # 打印值列表
    print(h[999])  # 查找
    print(len(h))  # 返回卡槽长度
    print(20 in h)  # 键在不在字典里面
    del h[20]  # 删除20号
    print(20 in h)  # 检查删除没有
    print(h.slots)  # 打印键列表
    print(h.data)  # 打印值列表

 8.实现平方探测这一再散列技巧。

    def rehash_square(self, oldhash, size, m):
        return (oldhash + m**2) % size

注意:overload(), put(), get()里的rehash函数都改成rehash_square


9.使用随机数生成器创建一个含500个整数的列表。通过基准测试分析本章中的排序算法。它们在执行速度上有什么差别? 

import random
import timeit

def bubbleSort(alist):  # 冒泡排序
    for passnum in range(len(alist) - 1, 0, -1):  # 第一层循环确定有几个元素就循环几次
        for i in range(passnum):  # 第二层循环把从第一层取的数比较一遍
            if alist[i] > alist[i+1]:  # 如果当前数大于它右边的数
                temp = alist[i]  # 就赋值给变量
                alist[i] = alist[i+1]  # 再把小数右边的数赋值给右边
                alist[i+1] = temp  # 再把变量记录的数右移一位


def shortBubbleSort(alist):  # 短冒泡排序
    exchange = True  # 结果先为真
    passnum = len(alist) - 1  # 返回列表长度下标次0开始所以-1
    while passnum > 0 and exchange:  # 第一层循环确定有几个元素就循环几次
        exchange = False  # 结果设为假
        for i in range(passnum):  # 列表长度里循环
            if alist[i] > alist[i + 1]:  # 如果当前数大于它右边的数
                exchange = True  # 结果设为真
                temp = alist[i]  # 进行换位操作
                alist[i] = alist[i + 1]
                alist[i + 1] = temp
        passnum -= 1  # 长度-1


def selectionSort(alist):  # 选择排序(把最大的放后面)
    for fillslot in range(len(alist)-1, 0, -1):  # 从列表长度每次-1的循环(从大往小走)
        positionOfMax = 0  # 计数器
        for location in range(1, fillslot + 1):  # 从第一个元素开始遍历(从小往大走)
            if alist[location] > alist[positionOfMax]:  # 如果右边的元素大于左边
                positionOfMax = location  # 记住本轮最大数的下标
        # 本轮结束后把最大元素的位置和列表最后元素的位置互换(列表长度会递减)
        alist[fillslot], alist[positionOfMax] = alist[positionOfMax], alist[fillslot]


def insetionSort(alist):  # 插入排序(把最小的放前面)
    for index in range(1, len(alist)):  # 遍历列表
        currentvalue = alist[index]  # 列表第二个元素(从1开始)赋值给变量
        position = index  # 把下标赋值给变量
        while position > 0 and alist[position-1] > currentvalue:  # 左边的元素大于右边就循环(从第0个元素开始)
            alist[position] = alist[position-1]  # 然后左元素的值赋值给右边(此时两边一样)
            position -= 1  # 下标往左移
        alist[position] = currentvalue  # 循环结束后就完成了左右互换


def shellSort(alist):  # 希尔排序(主函数)
    # 间隔设定
    sublistcount = len(alist) // 2  # 切割子列表的步长
    while sublistcount > 0:
        # 子列表排序
        for startposition in range(sublistcount):
            gapInsertSort(alist, startposition, sublistcount)
        # print("步长为", sublistcount, "的结果", alist)
        sublistcount //= 2  # 改变步长缩小 


def gapInsertSort(alist, start, gap):  # 希尔排序副函数(就是插入排序(列表,子列表起始位置,步长))
    for i in range(start+gap, len(alist), gap):  # (拆子列表)
        currentvalue = alist[i]  # 记录当前循环列表里的值
        position = i  # 把下标赋值给变量
        while position >= gap and alist[position-gap] > currentvalue:  # 循环次数小于步长和左边的元素大于右边就循环
            alist[position] = alist[position-gap]  # 然后左元素的值赋值给右边(此时两边一样)
            position -= gap  # 下标往左移
        alist[position] = currentvalue  # 循环结束后就完成了左右互换



# 一.拆
def mergeSort(alist):  # 归并排序
    if len(alist) > 1:  # 递归出口(列表长度小于1就不拆了)
        mid = len(alist) // 2  # 从列表中间开此拆
        lefthalf = alist[:mid]  # 获取列表开始到中间的元素
        righthalf = alist[mid:]  # 获取列表中间到结束的元素

        mergeSort(lefthalf)  # 递归调用自己再拆左边
        mergeSort(righthalf)  # 递归调用自己再拆右边
# 二.合
        i = 0  # 左列表下标
        j = 0  # 右列表下标
        k = 0  # 合并后的列表下标
        # 1.等长的情况
        while i < len(lefthalf) and j < len(righthalf):  #
            if lefthalf[i] < righthalf[j]:  # 左边对应下标的值小于右边的
                alist[k] = lefthalf[i]  # 它上一层列表的第一个值是左边的
                i = i + 1  # 左边下标+1
            else:  # 反之左边的大
                alist[k] = righthalf[j]  # 它上一层列表的第一个值是右边的
                j = j + 1  # 右边下标+1
            k = k + 1  # 上一层列表下标+1
        # 2.不等长的情况
        while i < len(lefthalf):  # i的长度变化小于左列表的实际长度
            alist[k] = lefthalf[i]  # 把左边列最后一个添加到上一层的最后
            i = i + 1  # 左边下标+1
            k = k + 1  # 上一层列表下标+1

        while j < len(righthalf):  # j的长度变化小于右列表的实际长度
            alist[k] = righthalf[j]  # 把右边列最后一个添加到上一层的最后
            j = j + 1  # 右边下标+1
            k = k + 1  # 上一层列表下标+1


def quickSort(alist):  # 1.快排主函数
    quickSortHelper(alist, 0, len(alist)-1)  # 调用函数


def quickSortHelper(alist, first, last):  # 2.定义递归函数(列表,左下标,右下标)
    if first < last:  # 左边下标小于左边下标
        splitpoint = partition(alist, first, last)  # 调用找分割点函数把结果赋值给变量
        quickSortHelper(alist, first, splitpoint-1)  # 对分割点左边排序
        quickSortHelper(alist, splitpoint+1, last)  # 对分割点右边排序


def partition(alist, first, last):  # 3.找分割点函数函数(列表,左下标,右下标)
    pivotvalue = alist[first]  # 把列表的第一个值作为分割点
    leftmark = first+1  # 左标初值
    rightmark = last  # 右标初值
    done = False  # 结果先设为假
    while not done:  #
        # 向右移动左标 (左标<=右标和左分割点的值<=分割点)
        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
            leftmark = leftmark + 1  # 左标右移

        # 向左移动右标(右标>=右左标和右分割点的值>=分割点)
        while rightmark >= leftmark and alist[rightmark] >= pivotvalue:
            rightmark = rightmark - 1  # 右标左移

        # 两标相错就结束移动
        if rightmark < leftmark:  # 如果右标小于左标
            done = True  # 结果为真
        else:  # 反之
            # 左右标的值交换
            alist[leftmark], alist[rightmark] = alist[rightmark], alist[leftmark]

	# 放分割点
    temp = alist[first]
    alist[first] = alist[rightmark]
    alist[rightmark] = temp

    return rightmark


x = []
for i in range(500):
    x.append(random.randint(1, 1000))

t1 = timeit.Timer("bubbleSort(x)", "from __main__ import x, bubbleSort")
print('bubbleSort', t1.timeit(number=1000), 'milliseconds')

t2 = timeit.Timer("shortBubbleSort(x)",
                  "from __main__ import x, shortBubbleSort")
print('shortBubbleSort', t2.timeit(number=1000), 'milliseconds')

t3 = timeit.Timer("selectionSort(x)", "from __main__ import x, selectionSort")
print('selectionSort', t3.timeit(number=1000), 'milliseconds')

t4 = timeit.Timer("insetionSort(x)", "from __main__ import x, insetionSort")
print('insetionSort', t4.timeit(number=1000), 'milliseconds')

t5 = timeit.Timer("shellSort(x)", "from __main__ import x, shellSort")
print('shellSort', t5.timeit(number=1000), 'milliseconds')

t6 = timeit.Timer("mergeSort(x)", "from __main__ import x, mergeSort")
print('mergeSort', t6.timeit(number=1000), 'milliseconds')

t7 = timeit.Timer("quickSort(x)", "from __main__ import x, quickSort")
print('quickSort', t7.timeit(number=1000), 'milliseconds')

 结果如下:对于长度为500的列表,排序速度由慢至快排序为:冒泡 < 快速(递归) < 选择 < 归并 < 希尔排序 < 插入 < 短冒泡


 10.利用同时赋值特性实现冒泡排序

def bubbleSort(alist):
    for passnum in range(len(alist) - 1, 0, -1):  # 第一层循环确定有几个元素就循环几次
        for i in range(passnum):  # 第二层循环把从第一层取的数比较一遍
            if alist[i] > alist[i + 1]:  # 如果列表左边的数>右边的数
                alist[i], alist[i + 1] = alist[i + 1], alist[i]  # 左右互换(同时赋值)


alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
bubbleSort(alist)
print(alist)

11.可以将冒泡排序算法修改为向两个方向“冒泡”。第一轮沿着列表“向上”遍历,第二轮沿着列表“向下”遍历。继续这一模式,直到无须遍历为止。实现这种排序算法,并描述它的适用情形 

def bubbleSort(alist, size):  # 定义函数(列表,长度)
    left = 0  # 左边初始
    right = size - 1  # 右边
    flag = True  # 初始为真
    while flag:  # 无限循环
        flag = False  # 停止循环
        # 向上遍历 
        for i in range(left, right, 1):  # 在列表长度内循环(从前开始)
            if alist[i] > alist[i+1]:  # 如果列表左边的数>右边的数
                alist[i], alist[i+1] = alist[i+1], alist[i]  # 左右互换
                flag = True  # 改为无限循环
        right -= 1  # 列表长度-1
        # 向下遍历
        for j in range(right, left, -1): # 在列表长度内循环(从后开始)
            if alist[j] < alist[j-1]:# 如果列表右边的数<左边的数
                alist[j], alist[j-1] = alist[j-1], alist[j]  # 右左互换
                #flag = True  # 改为无限循环
        left += 1  # 列表长度-1


if __name__ == '__main__':
    b = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    bubbleSort(b, len(b))
    print(b)

 12.利用同时赋值特性实现选择排序。

def selectionSort(alist):  # 选择排序(把最大的放后面)
    for fillslot in range(len(alist)-1, 0, -1):  # 从列表长度每次-1的循环(从大往小走)
        positionOfMax = 0  # 计数器
        for location in range(1, fillslot + 1):  # 从第一个元素开始遍历(从小往大走)
            if alist[location] > alist[positionOfMax]:  # 如果右边的元素大于左边
                positionOfMax = location  # 记住本轮最大数的下标
        # 本轮结束后把最大元素的位置和列表最后元素的位置互换(列表长度会递减)
        alist[fillslot], alist[positionOfMax] = alist[positionOfMax], alist[fillslot]
if __name__ == '__main__':
    b = [54, 26, 93, 17, 77, 31, 44, 55, 20]
    selectionSort(b)
    print(b)

 13.针对同一个列表使用不同的增量集,为希尔排序进行基准测试。

import timeit
import random
def shellSort(alist):  # 希尔排序(主函数)
    # 间隔设定
    sublistcount = len(alist) // 2  # 切割子列表的步长
    while sublistcount > 0:
        # 子列表排序
        for startposition in range(sublistcount):
            gapInsertSort(alist, startposition, sublistcount)
        # print("步长为", sublistcount, "的结果", alist)
        sublistcount //=  2  # 改变步长缩小

def shellSort2(alist):
    # 间隔设定
    sublistcount = len(alist) // 2  # 切割子列表的步长
    while sublistcount > 0:
        # 子列表排序
        for startposition in range(sublistcount):
            gapInsertSort(alist, startposition, sublistcount)
        # print("步长为", sublistcount, "的结果", alist)
        sublistcount //= 4  # 改变步长缩小
        
def gapInsertSort(alist, start, gap):  # 希尔排序副函数(就是插入排序(列表,子列表起始位置,步长))
    for i in range(start+gap, len(alist), gap):  # (拆子列表)
        currentvalue = alist[i]  # 记录当前循环列表里的值
        position = i  # 把下标赋值给变量
        # 循环次数小于步长和左边的元素大于右边就循环
        while position >= gap and alist[position-gap] > currentvalue:
            alist[position] = alist[position-gap]  # 然后左元素的值赋值给右边(此时两边一样)
            position -= gap  # 下标往左移
        alist[position] = currentvalue  # 循环结束后就完成了左右互换

if __name__ == '__main__':
    x = []
    for i in range(500):
        x.append(random.randint(1, 1000))

    t1 = timeit.Timer("shellSort(x)", "from __main__ import x, shellSort")
    print('n/2^k:', t1.timeit(number=1000), 'milliseconds')

    t2 = timeit.Timer("shellSort2(x)", "from __main__ import x, shellSort2")
    print('2^k - 1:', t2.timeit(number=1000), 'milliseconds')



 14.不用切片运算符,实现mergesort函数(归并排序)

def mergesortwithpointer(ml, tmp, left, right):  # 定义函数(列表,空列表起始点,结束点)
    if left < right:  # 递归出口
        mid = (left + right) // 2  # 从列表中间开此拆
        mergesortwithpointer(ml, tmp, left, mid)  # 递归调用自己再拆左边
        mergesortwithpointer(ml, tmp, mid + 1, right)  # 递归调用自己再拆左边
        i, j, k = left, mid + 1, left  # 左列表下标,右列表下标,合并后的列表下标
        # 1.等长的情况
        while i <= mid and j <= right:
            if ml[i] < ml[j]:  # 左边对应下标的值小于右边的
                tmp[k] = ml[i]  # 就把左边值赋值给空列表对应下标位置上
                i += 1  # 左边下标+1
            else:  # 反之左边的大
                tmp[k] = ml[j]  # 就把右边值赋值给空列表对应下标位置上
                j += 1  # 右边下标+1
            k += 1  # 空列表下标+1
        # 1.不等长的情况
        while i <= mid:  # i的长度变化<=中间长度
            tmp[k] = ml[i]  # 把左边列最后一个添加到空列表对应下标位置上
            i += 1  # 左边下标+1
            k += 1  # 空列表下标+1

        while j <= right:  # j的长度变化<=列表实际长度
            tmp[k] = ml[j]  # 把右边列最后一个添加到上一层的最后
            j += 1  # 右边下标+1
            k += 1  # 空列表下标+1

        for a in range(left, right + 1):  # 把空列表的值赋值给原列表
            ml[a] = tmp[a]


sml = [54, 26, 93, 17, 77, 31, 44, 55, 20]

smllen = len(sml)  # 获取列表长度
tmp = [0]*smllen  # 定义全部为0的空列表
mergesortwithpointer(sml, tmp, 0, smllen - 1)  # 调用排序函数
print(sml)

15.有一种改进快速排序的办法,那就是在列表长度小于某个值时采用插入排序(这个值被称为“分区限制”)。这是什么道理?重新实现快速排序算法,并给一个随机整数列表拍戏。采用不同的分区限制进行性能分析。
  快速排序对于小规模的数据集性能不是很好,但是快速排序算法使用了分治技术,最终来说大的数据集都要分为小的数据集来进行处理。由此可以得到的改进就是,当数据集较小时,不必继续递归调用快速排序算法,而改为调用其他的对于小规模数据集处理能力较强的排序算法来完成。

"""15.有一种改进快速排序的办法,那就是在列表长度小于某个值时采用插入排序(这个值被称为“分区限制”)
。这是什么道理?重新实现快速排序算法,并给一个随机整数列表拍戏。采用不同的分区限制进行性能分析。
快速排序对于小规模的数据集性能不是很好,但是快速排序算法使用了分治技术,
最终来说大的数据集都要分为小的数据集来进行处理。由此可以得到的改进就是,
当数据集较小时,不必继续递归调用快速排序算法,而改为调用其他的对于小规模数据集处理能力较强的排序算法来完成。"""
import random
import timeit


def quickSort2(alist, k):  # 1.快排主函数(列表,列表长度)
    quickSortHelper2(alist, 0, len(alist) - 1, k)  # 调用递归函数


def quickSortHelper2(alist, first, last, k):  # 2.定义递归函数(列表,左下标,右下标,列表长度)
    if first < last:  # 左边下标小于左边下标
        # 递归出口
        if last - first <= k:  # 右下标-左下标<= k
            temp = insetionSort(alist[first:last+1])  # 调用插入排序函数
            alist[first:last+1] = temp
        else:
            splitpoint = partition(alist, first, last)  # 调用找分割点函数把结果赋值给变量
            quickSortHelper2(alist, first, splitpoint - 1, k)  # 对分割点左边排序
            quickSortHelper2(alist, splitpoint + 1, last, k)  # 对分割点右边排序


def insetionSort(alist):  # 插入排序(把最小的放前面)
    for index in range(1, len(alist)):  # 遍历列表
        currentvalue = alist[index]  # 列表第二个元素(从1开始)赋值给变量
        position = index  # 把下标赋值给变量
        # 左边的元素大于右边就循环(从第0个元素开始)
        while position > 0 and alist[position-1] > currentvalue:
            alist[position] = alist[position-1]  # 然后左元素的值赋值给右边(此时两边一样)
            position -= 1  # 下标往左移
        alist[position] = currentvalue  # 循环结束后就完成了左右互换
    return alist  # 返回列表


def partition(alist, first, last):  # 3.找分割点函数函数(列表,左下标,右下标)
    pivotvalue = alist[first]  # 把列表的第一个值作为分割点
    leftmark = first + 1  # 左标初值
    rightmark = last  # 右标初值
    done = False  # 结果先设为假
    while not done:
        # 向右移动左标 (左标<=右标和左分割点的值<=分割点)
        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
            leftmark += 1
            
        # 向左移动右标(右标>=右左标和右分割点的值>=分割点)
        while leftmark <= rightmark and alist[rightmark] >= pivotvalue:
            rightmark -= 1

        # 两标相错就结束移动
        if rightmark < leftmark:  # 如果右标小于左标
            done = True  # 结果为真
        else:  # 反之
            # 左右标的值交换
            alist[leftmark], alist[rightmark] = alist[rightmark], alist[leftmark]
            
    # 放分割点
    temp = alist[first]
    alist[first] = alist[rightmark]
    alist[rightmark] = temp

    return rightmark


if __name__ == '__main__':
    x = []
    for i in range(500):
        x.append(random.randint(1, 1000))

    t1 = timeit.Timer("quickSort2(x, 5)", "from __main__ import x, quickSort2")
    print('k = 5:', t1.timeit(number=1000), 'milliseconds')

    t2 = timeit.Timer("quickSort2(x, 50)",
                      "from __main__ import x, quickSort2")
    print('k = 50:', t2.timeit(number=1000), 'milliseconds')

    t3 = timeit.Timer("quickSort2(x, 100)",
                      "from __main__ import x, quickSort2")
    print('k = 100:', t3.timeit(number=1000), 'milliseconds')

    t4 = timeit.Timer("quickSort2(x, 200)",
                      "from __main__ import x, quickSort2")
    print('k = 200:', t4.timeit(number=1000), 'milliseconds')

结果:对于长度为500的数列,随着k值增大,排序速度越快。 


16.修改quickSort函数,在选取基准值时采用三数取中法。通过实验对比两种技巧的性能差异。

# 16.修改quickSort函数,在选取基准值时采用三数取中法。通过实验对比两种技巧的性能差异
import timeit
import random
from quickSort import *

# 快排三数取中法
def quickSort2(alist):  # 1.快排主函数
    quickSortHelper2(alist, 0, len(alist) - 1)  # 调用函数


def quickSortHelper2(alist, first, last):  # 2.定义递归函数(列表,左下标,右下标)
    if first < last:
        splitpoint = partition2(alist, first, last)  # 调用找分割点函数把结果赋值给变量
        quickSortHelper2(alist, first, splitpoint - 1)  # 对分割点左边排序
        quickSortHelper2(alist, splitpoint + 1, last)  # 对分割点右边排序


def partition2(alist, first, last):  # 3.找分割点函数函数(列表,左下标,右下标)
    mid = (first + last) // 2  # 定位列表中间
    firstvalue = alist[first]  # 列表起始值
    midvalue = alist[mid]  # 列表中间值
    lastvalue = alist[last]  # 列表结尾值
    if midvalue > firstvalue and midvalue < lastvalue:  # 如果中间值>结尾值和中间值<起始值
        alist[first], alist[mid] = alist[mid], alist[first]  # 列表起始值跟列表中间值互换
    elif lastvalue > firstvalue and lastvalue < midvalue:  # 如果结尾值>起始值和结尾值<中间值
        alist[first], alist[last] = alist[last], alist[first]  # 列表起始值跟列表结尾值互换
    pivotvalue = alist[first]  # 把列表的起始值作为分割点
    leftmark = first + 1  # 左标初值
    rightmark = last  # 右标初值
    done = False  # 结果先设为假
    while not done:
        # 向右移动左标 (左标<=右标和左分割点的值<=分割点)
        while leftmark <= rightmark and alist[leftmark] <= pivotvalue:
            leftmark += 1  # 左标右移
            
        # 向左移动右标(右标>=右左标和右分割点的值>=分割点)
        while leftmark <= rightmark and alist[rightmark] >= pivotvalue:
            rightmark -= 1  # 右标左移
            
        # 两标相错就结束移动
        if rightmark < leftmark:  # 如果右标小于左标
            done = True  # 结果为真
        else:  # 反之
            # 左右标的值交换
            alist[leftmark], alist[rightmark] = alist[rightmark], alist[leftmark]
    # 放分割点
    temp = alist[first]
    alist[first] = alist[rightmark]
    alist[rightmark] = temp
    return rightmark


if __name__ == '__main__':
    x = []
    for i in range(1000):
        x.append(random.randint(1, 1000))

    t1 = timeit.Timer("quickSort(x)", "from __main__ import x, quickSort")
    print('quickSort:', t1.timeit(number=1000), 'milliseconds')

    t2 = timeit.Timer("quickSort2(x)", "from __main__ import x, quickSort2")
    print('quickSort2:', t2.timeit(number=1000), 'milliseconds')

 如图:三数取中法要比未改进的的快速排序速度快的多得多

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值