大话数据结构 - 排序算法总结

本章讲到的排序算法,从算法的复杂性上分为两大类:简单算法(冒泡排序,简单选择排序,直接插入排序),改进算法(希尔排序,堆排序,归并排序,快速排序)。从整体上来讲,简单算法的时间复杂度大多为o(n2),改进算法的时间复杂度为o(nlogn),原因在于改进算法大都利用了二叉树的优点。下面对每种排序算法从以下几个方面进行详细的解释:1) 原始算法,2) 优化算法(如果存在),3) 时间复杂度,4) 空间复杂度,5) 优点,6) 缺点, 7) 改进点,8) 适用场景。

 


> 冒泡算法(Bubble Sort)

1. 原始算法:

冒泡算法的出现是为了解决最简单的交换排序中,得到每个位置排序结果的过程中,不会对其他位置的排序产生帮助。从而引申出一种类似冒泡过程的排序算法,从序列的最末端开始比较,遇到比它重的记录,它就会漂上来,遇到比它轻的记录,它就停在那里,让比它轻的记录继续上浮,但它也在这个过程中向上漂了一段距离。代码的实现如下:

 1 def swap(x, y):
 2     temp = L[x]
 3     L[x] = L[y]
 4     L[y] = temp
 5  
 6 def BubbleSort(L):
 7     for i in range(len(L)):
 8         for j in range(len(L) - 2, i - 1, -1):
 9             if L[j] > L[j + 1]:
10                 swap(j, j + 1)

2. 优化算法:

由于原始算法在j的循环过程中,会两两比较位于序列后端的记录大小情况,如果已经满足排序的结果,则不会发生记录交换。这样就可以通过设置一个flag,判断是否应该进入内层循环,默认值为True,在内层循环的开始设为False,在发生交换时设为True,用此方式来判断是否已经完成了排序。代码如下:

1 def BubbleSort2(L):
2     flag = True
3     for i in range(len(L)):
4         if flag is True:
5             flag = False
6             for j in range(len(L) - 2, i - 1, -1):
7                 if L[j] > L[j + 1]:
8                     swap(j, j + 1)
9                     flag = True

3. 时间复杂度:

对于优化算法来讲,最好情况是第一次比较就完成了排序,比较的次数为n-1,时间复杂度为o(n);最差情况是原序列是逆序,此时需要比较的次数为1+2+...+(n-1)=n(n-1)/2,时间复杂度为o(n2)。

4. 空间复杂度:

冒泡算法由于是两两交换,只需要一个额外的temp空间进行保存即可,因此空间复杂度可以认为是o(1)。

5. 优点:

6. 缺点:

7. 改进点:

8. 适用场景:

 


> 简单选择排序(Simple Selection Sort)

1. 原始算法:

简单选择,也就是按照序列的次序,依此找出当前剩余最小的记录,并和当前次序的记录进行交换。由于每次交换都已知是当前最合适的,因此简单选择排序的记录交换次数最少。代码实现如下:

1 def SelectSort(L):
2     for i in range(len(L)):
3         minus = i
4         for j in range(i, len(L)):
5             if L[minus] > L[j]:
6                 minus = j
7         swap(i, minus)

2. 时间复杂度:

简单选择排序由于每个循环过程都需要比较,因此比较的次数为1+2+...+(n-1)=n(n-1)/2。对于交换次数来说,最好的情况是原序列就是正序,交换0次,最差情况是原序列是逆序,交换n-1次,时间复杂度依然为o(n2)。

3. 空间复杂度:

这里只需要一个额外的temp空间,空间复杂度为o(1)。

4. 优点:交换次数最少

5. 缺点:比较次数最多

6. 改进点:

7. 适用场景:

 


> 直接插入排序(Straight Insertion Sort)

1. 原始算法:

直接插入排序的思路是类似扑克牌的排序。假设我们有一手扑克牌是53462,首先将3插入到5的前面,得到35462;下面看4,4小于5而大于3,因此应该插入到35的中间,得到34562;6是正确排序,无需移动;2要比所有左侧的记录都小,因此其他记录依此向后移动,2插入到序列的最前端。总结起来就是,按顺序(这里是从左到右)搜索序列,每当遇到不符合排序规则的记录时,和前面的各记录比较,直到找到合适的位置插入。实现算法如下:

 1 def InsertSort(L):
 2     for i in range(1, len(L)):
 3         print(L)
 4         if L[i-1] > L[i]:
 5             flag = L[i]
 6             for j in range(i-1, -1, -1):
 7                 if L[j] > flag:
 8                     L[j+1] = L[j]
 9                 else:
10                     break
11                 if j == 0:
12                     j -= 1
13             L[j+1] = flag

2. 时间复杂度:

最好的情况,序列本来就是有序的,那么比较的次数为o(n);最坏的情况下,序列是逆序的,此时需要比较2+3+...+n = (n+2)(n-1)/2,移动次数为3+4+..+(n+2) = (n+4)(n-1)/2。这样平均的比较和移动次数为n2/4,得到时间复杂度为o(n2),和冒泡排序和简单选择排序同样的时间复杂度,直接插入排序的效率也更高一些。

3. 空间复杂度:

同样是o(1)。

4. 优点:

5. 缺点:

6. 改进点:

7. 适用场景:

 


> 希尔排序(Shell Sort)

1. 原始算法:

希尔排序是针对直接插入排序的改进算法。基本思路是将序列进行分割,将相隔某个增量的记录组成一个子序列,进行直接插入排序;然后逐步减少增量,从粗到细的完成排序过程。代码实现如下:

 1 def ShellSort(L):
 2     increment = len(L)
 3     while increment > 0:
 4         increment = int(increment / 3)
 5         for i in range(increment, len(L)):
 6             if L[i] < L[i-increment]:
 7                 save = L[i]
 8                 j = i - increment
 9                 try:
10                     while L[j] > save:
11                         L[j + increment] = L[j]
12                         j -= increment
13                 except:
14                     pass
15                 L[j + increment] = save

2. 时间复杂度:

3. 空间复杂度:

4. 优点:

5. 缺点:

6. 改进点:

7. 适用场景:

 


> 堆排序(Heap Sort)

1. 原始算法:

堆排序的实现分成两个阶段。第一阶段是将序列重新排列成堆的方式。所谓堆,就是将序列看成完全二叉树,树上每个结点的值都大于它的子孙的值。通过根节点和左右孩子的比较,将序列里较大的值不断交换上移到二叉树的上层,在对每个结点都做类似的处理后,得到的序列就是大顶堆;如果把较小的值放在二叉树的上层,那么就是小顶堆。第二阶段就是将大顶堆的根节点和最后一个枝叶交换,那么最大值就放置在序列的最末尾,再将新的新结点对剩余的序列进行堆的处理,把第二大的值放置在二叉树的根节点,并和序列的倒数第二个结点交换,以此类推最终得到排序的结果。代码实现如下:

 1 def swap(x, y):
 2     temp = L[x]
 3     L[x] = L[y]
 4     L[y] = temp
 5 
 6 def HeapArray(index, max_index):
 7     if max_index == 0:
 8         return
 9     while True:
10         if index * 2 + 1 == max_index:
11             child = index * 2 + 1
12         else:
13             if L[index * 2 + 1] < L[index * 2 + 2]:
14                 child = index * 2 + 2
15             else:
16                 child = index * 2 + 1
17         if L[child] > L[index]:
18             swap(index, child)
19         index = child
20         if index * 2 + 1 > max_index:
21             break       
22     
23 def HeapSort(L):
24     for i in range(int(len(L)/2 - 1), -1, -1):
25         HeapArray(i, len(L) - 1)
26     for i in range(len(L) - 1, 0, -1):
27         swap(0, i)
28         HeapArray(0, i - 1)

2. 时间复杂度:

3. 空间复杂度:

4. 优点:

5. 缺点:

6. 改进点:

7. 适用场景:

 


> 归并排序(Merging Sort)

1. 原始代码(递归):

归并排序的思路是,通过将原序列按照中间的位置进行两两拆分,分别对这两部分进行分别排序后,归并到最终的结果中。通过递归不断拆分到最小记录,将比较后的结果参与上一层的排序,直到得到最终的排序结果。在归并的过程中,由于每个子序列已经是正确排序的,比较两个子序列的第一个元素,将最小的取出来并把index后移,参与和另一个序列当前记录的比较,直到两个子序列的所有记录都参与比较,这时就得到了正确的排序结果。代码实现如下:

 1 def MergeSort(L):
 2     MSort(L, 0, len(L) - 1)
 3     
 4 def MSort(L, b, e):
 5     if b == e:
 6         return
 7     else:
 8         m = int((b + e) / 2)
 9         MSort(L, b, m)
10         MSort(L, m + 1, e)
11         Merge(L, b, m, e)
12         
13 def Merge(L, b, m, e):
14     index1 = b
15     index2 = m + 1
16     temp = []
17     while True:
18         if index1 == m + 1 and index2 == e + 1:
19             break
20         elif index1 == m + 1:
21             temp.append(L[index2])
22             index2 += 1
23         elif index2 == e + 1:
24             temp.append(L[index1])
25             index1 += 1
26         elif L[index1] < L[index2]:
27             temp.append(L[index1])
28             index1 += 1
29         elif L[index2] < L[index1]:
30             temp.append(L[index2])
31             index2 += 1
32     for i in range(b, e + 1):
33         L[i] = temp[i - b]

2. 优化代码(非递归):

归并排序的非递归算法会减少因为递归带来的时间和空间损耗,并且思路更加简明。先将序列中两两记录进行排序,这样再把得到的结果按照4个一组进行排序,直到序列的长度。如果有散落的结尾序列存在,还要在排序结束前将之和前面的序列再做一次排序,代码如下:

 1 def MergingSort2(L):
 2     step = 2
 3     while True:
 4         start = 0
 5         while True:
 6             if start > len(L) - 1:
 7                 break
 8             elif start + step <= len(L):
 9                 print(start, start + int((step - 1) / 2), start + step - 1)
10                 Merge(L, start, start + int((step - 1) / 2), start + step - 1)
11             start = start + step
12         if step * 2 > len(L):
13             break
14         else:
15             step *= 2
16     if step is not len(L):
17         Merge(L, 0, step - 1, len(L) - 1)

3. 时间复杂度:

4. 空间复杂度:

5. 优点:

6. 缺点:

7. 改进点:

8. 适用场景:

 


> 快速排序(Quick Sort)

1. 原始代码:

快速排序的代码思路就是不断的找到序列首部记录的正确位置,并以它来分割其他部分为两个子序列,再对子序列进行同样的排序,代码如下:

 1 def QuickSort(L):
 2     QSort(L, 0, len(L) - 1)
 3     
 4 def QSort(L, low, high):
 5     if low < high:
 6         pivot = Partition(L, low, high)
 7         QSort(L, low, pivot - 1)
 8         QSort(L, pivot + 1, high)
 9         
10 def Partition(L, low, high):
11     pivotkey = L[low]
12     while low < high:
13         while low < high and L[high] >= pivotkey:
14             high -= 1
15         swap(low, high)
16         while low < high and L[low] <= pivotkey:
17             low += 1
18         swap(low, high)
19     return low

2. 时间复杂度:

3. 空间复杂度:

4. 优点:

5. 缺点:

6. 改进点:

7. 适用场景:

posted @ 2014-10-30 23:20  周葳  阅读(492)  评论(0编辑  收藏  举报