排序

排序是以某种顺序从集合中放置元素的过程。例如,单词列表可以按字母顺序或按长度排序。

1、冒泡排序

冒泡排序需要多次遍历列表。

它比较相邻的项并交换那些无序的项。

每次遍历列表将下一个最大的值放在其正确的位置。实质上,每个项“冒泡”到它所属的位置。

下图展示了冒泡排序的第一次遍历。阴影项正在比较它们是否乱序。如果在列表中有 n 个项目,则第一遍有 n-1 个项需要比较。

在第二次遍历的开始,现在最大的值已经在正确的位置。有 n-1 个项留下排序,意味着将有 n-2 对。

由于每次通过将下一个最大值放置在适当位置,所需的遍历的总数将是 n-1。

在完成 n-1 遍之后,最小的项肯定在正确的位置,不需要进一步处理。 

冒泡排序通常被认为是最低效的排序方法,因为它必须在最终位置被知道之前交换项。

复杂度是O(N^2)。

 1 def bubblesort(ls):
 2     # 升序排序
 3     for i in range(len(ls)-1,0,-1):
 4         for i in range(i):
 5             if ls[i] > ls[i+1]:
 6                 ls[i],ls[i+1] = ls[i+1],ls[i]
 7     # 降序排序
 8     # for i in range(len(ls)-1,0,-1):
 9     #     for i in range(i):
10     #         if ls[i] < ls[i+1]:
11     #             ls[i],ls[i+1] = ls[i+1],ls[i]
12 
13 alist = [54, 26, 93, 17, 77, 31, 44, 55, 20]
14 bubblesort(alist)
15 print(alist)

短冒泡排序

特别地,如果在遍历期间没有交换,则我们知道该列表已排序。 如果发现列表已排序,可以修改冒泡排序提前停止。

这意味着对于只需要遍历几次列表,冒泡排序具有识别排序列表和停止的优点。

 1 def shortBubbleSort(alist):
 2     exchanges = True
 3     passnum = len(alist)-1
 4     while passnum > 0 and exchanges:
 5        exchanges = False
 6        for i in range(passnum):
 7            if alist[i]>alist[i+1]:
 8                exchanges = True
 9                temp = alist[i]
10                alist[i] = alist[i+1]
11                alist[i+1] = temp
12        passnum = passnum-1
13 
14 alist=[20,30,40,90,50,60,70,80,100,110]
15 shortBubbleSort(alist)
16 print(alist)

2、选择排序

选择排序改进了冒泡排序,每次遍历列表只做一次交换

选择排序在他遍历时寻找最大的值,并在完成遍历后,将其放置在正确的位置。

与冒泡排序一样,在第一次遍历后,最大的项在正确的地方。 第二遍后,下一个最大的就位。遍历 n-1 次排序 n 个项,因为最终项必须在第(n-1)次遍历之后。

def selectionSort(ls):
    # 开始遍历列表所有索引0-8,列表长度为9
    # 列表索引从8,7,6,5,4,3,2,1
    for i in range(len(ls)-1,0,-1):
        # 其实索引从0开始
        position_of_max = 0
        # i=8: j=1,2,3,4,5,6,7
        # i=7: j=1,2,3,4,5,6
        for j in range(1,i+1):
            # 如果j索引位置的值大于当前索引位置的值,
            # 那么就将最大值索引改为这个索引
            if ls[j] > ls[position_of_max]:
                position_of_max = j
        # 此时,找到了最大值对应的索引,找到之后,交换一下
        ls[j],ls[position_of_max] = ls[position_of_max],ls[j]
alist = [54,26,93,17,77,31,44,55,20]
selectionSort(alist)
print(alist)

 选择排序与冒泡排序有相同数量的比较,因此也是 O(n^2)。 然而,由于交换数量的减少,选择排序通常在基准研究中执行得更快。 事实上,对于我们的列表,冒泡排序有 20 次交换,而选择排序只有 8 次。

3、插入排序

仍然是 O(n^2)。

它始终在列表的较低位置维护一个排序的子列表。然后将每个新项 “插入” 回先前的子列表,使得排序的子列表称为较大的一个项。

下图展示了插入排序过程。阴影项表示算法进行每次遍历时的有序子列表。

我们开始假设有一个项(位置 0 )的列表已经被排序。在每次遍历时,对于每个项 1至 n-1,将针对已经排序的子列表中的项检查当前项。当我们回顾已经排序的子列表时,我们将那些更大的项移动到右边。 当我们到达较小的项或子列表的末尾时,可以插入当前项。

下图是第五次遍历的图:

存在 n-1 个遍历以对 n 个排序。从位置1开始迭代并移动位置到n-1,因为这些是需要插回到排序子列表中的项。

移位操作中,将值向上移动到列表中的一个位置,在其后面插入。注意,这不是以前算法中的完全交换。

从位置 1 开始迭代并移动位置到 n-1,因为这些是需要插回到排序子列表中的项。第 8 行执行移位操作,将值向上移动到列表中的一个位置,在其后插入。请记住,这不是像以前的算法中的完全交换。

插入排序的最大比较次数是 n-1 个整数的总和。同样,是 O(n^2)。然而,在最好的情况下,每次通过只需要进行一次比较。这是已经排序的列表的情况。

关于移位和交换的一个注意事项也很重要。通常,移位操作只需要交换大约三分之一的处理工作,因为仅执行一次分配。

在基准研究中,插入排序有非常好的性能。

 1 def insertsort(ls):
 2     # i从1开始是因为i也是要进行比较的值得索引position,
 3     # 要与前面的有序小列表进行比较
 4     for i in range(1,len(ls)):
 5         # 索引1位置的值给currentvalue变量
 6         currentvalue = ls[i]
 7         # 当前索引,
 8         position = i
 9         # positon索引的值和前面的小列表中的每个元素进行比较,
10         # 将position位置的值 与 前面的小列表的所有值从后往前,迭代进行比较,知道找到合适的位置
11         while position > 0 and ls[position-1] > currentvalue:
12             ls[position] = ls[position-1]
13             position = position-1
14         ls[position] = currentvalue
15 alist = [54,26,93,17,77,31,44,55,20]
16 insertsort(alist)
17 print(alist)

4、希尔排序

希尔排序(有时称为“递减递增排序”)通过将原始列表分解为多个较小的子列表来改进插入排序

选择这些子列表的方式是希尔排序的关键。不是将列表拆分为连续项的子列表,希尔排序使用增量i(有时称为 gap,通过选择 i 个项的所有项来创建子列表。

下面列表有九个项。如果我们使用三的增量,有三个子列表,每个子列表可以通过插入排序进行排序。

完成这些排序后,我们得到下图列表。虽然这个列表没有完全排序,但发生了很有趣的事情。 通过排序子列表,我们已将项目移动到更接近他们实际所属的位置

使用增量为 1 的插入排序; 换句话说也就是标准插入排序。

我们之前说过,增量的选择方式是希尔排序的独特特征。 ActiveCode 1中展示的函数使用不同的增量集。在这种情况下,我们从 n/2 子列表开始。下一次,n/4 子列表排序。 最后,单个列表按照基本插入排序进行排序。 Figure 9 展示了我们使用此增量的示例的第一个子列表。

 1 def shell_sort(list):
 2     n = len(list)
 3 
 4     #初始步长
 5     gap = n//2
 6     while gap > 0:
 7         #按照初始步长进行直接插入排序
 8         for j in range(gap,n):
 9             i = j
10             #插入排序
11             while i >= gap and list[i-gap] > list[i]:
12                 list[i-gap],list[i] = list[i],list[i-gap]
13                 i -= gap
14 
15         #得到新的步长
16         gap = gap//2

乍一看,你可能认为希尔排序不会比插入排序更好,因为它最后一步执行了完整的插入排序。 然而,结果是,该最终插入排序不需要进行非常多的比较(或移位),因为如上所述,该列表已经被较早的增量插入排序预排序。 换句话说,每个遍历产生比前一个“更有序”的列表。 这使得最终遍历非常有效。

虽然对希尔排序的一般分析远远超出了本文的范围,我们可以说,它倾向于落在 $$O(n)$$ 和 $$O(n^2)$$ 之间的某处,基于以上所描述的行为。对于 Listing 5中显示的增量,性能为 $$O(n^2)$$ 。 通过改变增量,例如使用2^k -1(1,3,7,15,31等等),希尔排序可以在 $$O(n^{\frac{3}{2}})$$处执行。

5、归并排序

使用分而治之策略作为提高排序算法性能的一种方法。 

 1 """
 2 归并排序
 3 最优时间复杂度:O(nlogn)
 4 最坏          :O(nlogn)
 5 稳定性:稳定
 6 
 7 与其他排序的区别:利用一个新列表将算法排序后的元素存储当中,空间换时间
 8 """
 9 
10 import time
11 import random
12 
13 def merge_sort(list):
14     """归并排序"""
15     n = len(list)
16     if n <= 1:
17         return list
18 
19     #最大整数
20     mid = n//2
21     #left 利用递归 截取的列表形成的有序列表
22     left_list = merge_sort(list[:mid])
23     #right利用递归截取的列表形成的有序列表
24     right_list = merge_sort(list[mid:])
25     #创建左右标记记录列表值的索引
26     left_pointer, right_pointer = 0,0
27     #创建新空列表
28     result = []
29     #循环 比较数值大小
30     #退出循环条件:当左右游标其中一个等于所在列表的长度时
31     while left_pointer < len(left_list) and right_pointer < len(right_list):
32         #判断左值和右值大小
33         if left_list[left_pointer] <= right_list[right_pointer]:
34             result.append(left_list[left_pointer])
35             #每判断一次 游标加一
36             left_pointer +=1
37         else:
38             result.append(right_list[right_pointer])
39             right_pointer +=1
40 
41     #将最后一个数值加入到新列表中
42     result += left_list[left_pointer:]
43     result += right_list[right_pointer:]
44     #返回值
45     return result
46 
47 def new_num(lis):
48     for i in range(50):
49         j = random.randint(1,100)
50         lis.append(j)
51 
52 if __name__=='__main__':
53     first_time = time.time()
54     #空列表
55     lis = []
56 
57     #生成列表
58     new_num(lis)
59     print(lis)
60 
61     #进行列表排序
62     print(merge_sort(lis))
63 
64     #结束时间
65     last_time = time.time()
66 
67     print('time = : {}'.format(last_time- first_time))

为了分析 mergeSort 函数,我们需要考虑组成其实现的两个不同的过程。

首先,列表被分成两半。我们已经计算过(在二分查找中)将列表划分为一半需要 log^n 次,其中 n 是列表的长度。

第二个过程是合并。列表中的每个项将最终被处理并放置在排序的列表上。因此,大小为 n 的列表的合并操作需要 n 个操作。此分析的结果是 logn的拆分,其中每个操作花费 n,总共 nlogn 。

归并排序是一种 O(nlogn) 算法。

重要的是注意,mergeSort 函数需要额外的空间来保存两个半部分,因为它们是使用切片操作提取的。如果列表很大,这个额外的空间可能是一个关键因素,并且在处理大型数据集时可能会导致此类问题。

6、快速排序

快速排序使用分而治之来获得与归并排序相同的优点,而不使用额外的存储。然而,作为权衡,有可能列表不能被分成两半。当这种情况发生时,我们将看到性能降低。

快速排序首先选择一个值,该值称为 枢轴值。分区从通过在列表中剩余项目的开始和结束处定位两个位置标记(我们称为左标记右标记)开始。分区的目标是移动相对于枢轴值位于错误侧的项,同时也收敛于分裂点。 下图展示了我们定位54的位置的过程。

过程:

选第一个值54作为枢轴值,左游标从26开始,找到大于54的值停下,右游标从列表尾开始,找到第一个小于54的停下,然后交换这两个值。一直进行下去,知道左右游标相邻,然后将枢轴值放到这两个游标索引的中间。

 1 """
 2 快速排序
 3 最优时间复杂度:O(nlogn)
 4 最坏时间复杂度:O(n2)
 5 稳定性:不稳定
 6 
 7 """
 8 import time
 9 import random
10 
11 
12 def quick_sort(list, star, end):
13     # 递归的退出条件
14     if star >= end:
15         return
16 
17     # low为序列左边的由左向右移动的游标
18     low = star
19 
20     # high 为序列右边的由右向左移动的游标
21     high = end
22 
23     # 设定其实元素为要寻找位置的基准元素
24     mid_value = list[star]
25 
26     while low < high:
27         # 如果low 与high 未重合,high 指向的元素比基准元素大,则high向左移
28         # 直到找到右游标小于minvalue的值,进行下一步
29         while low < high and list[high] >= mid_value:
30             high -= 1
31 
32         # 将high 指向的元素放到low 的位置上
33         # 不用担心list[low]原来的值的会消失,因为之前已经将mid_value = list[star],这里的star=low
34         list[low] = list[high]
35 
36         # 如果low与high未重合,low指向的元素比基准元素小,low的向右移
37         # 直到左游标找到比midvalue大的值
38         while high > low and list[low] < mid_value:
39             low += 1
40 
41         # 将low指向的元素放到high的位置上
42         list[high] = list[low]
43 
44     # 退出循环后,low 与high重合,此时所指位置为基准元素的正确位置
45     # 循环退出时 将基准元素放到该位置
46     list[low] = mid_value
47 
48     # 对基准元素的左边的子序列进行快速排序
49     quick_sort(list, star, low-1)
50 
51     # 对基准元素右边的子序列进行快速排序
52     quick_sort(list, low+1, end)
53 
54 
55 def new_num(lis):
56     """随机生成50个数加入列表中"""
57     for i in range(50):
58         j = random.randint(0,10000)
59         lis.append(j)
60 
61 
62 if __name__ == '__main__':
63 
64     first_time = time.time()
65     # 空列表
66     lis = []
67 
68     # 随机函数添加到列表中
69     new_num(lis)
70 
71     # 列表排序
72     quick_sort(lis, 0, len(lis)-1)
73 
74     print(lis)
75 
76     # 结束时间
77     last_time = time.time()
78 
79     print("共用时%s" % (last_time - first_time))

 

posted @ 2019-04-22 20:53  Austin_anheqiao  阅读(270)  评论(0编辑  收藏  举报