转自:https://www.cnblogs.com/hokky/p/8529042.html
性能比较:
直接插入排序
直接插入排序的核心思想就是:将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过。
因此,从上面的描述中我们可以发现,直接插入排序可以用两个循环完成:
- 第一层循环:遍历待比较的所有数组元素
- 第二层循环:将本轮选择的元素(selected)与已经排好序的元素(ordered)相比较。
如果:selected > ordered,那么将二者交换
#直接插入排序
def insert_sort(L):
#遍历数组中的所有元素,其中0号索引元素默认已排序,因此从1开始
for x in range(1,len(L)):
#将该元素与已排序好的前序数组依次比较,如果该元素小,则交换
#range(x-1,-1,-1):从x-1倒序循环到0
for i in range(x-1,-1,-1):
#判断:如果符合条件则交换
if L[i] > L[i+1]:
temp = L[i+1]
L[i+1] = L[i]
L[i] = temp
希尔排序
希尔排序的算法思想:将待排序数组按照步长gap进行分组,然后将每组的元素利用直接插入排序的方法进行排序;每次将gap折半减小,循环上述操作;当gap=1时,利用直接插入,完成排序。
同样的:从上面的描述中我们可以发现:希尔排序的总体实现应该由三个循环完成:
- 第一层循环:将gap依次折半,对序列进行分组,直到gap=1
- 第二、三层循环:也即直接插入排序所需要的两次循环。具体描述见上。
#希尔排序
def insert_shell(L):
#初始化gap值,此处利用序列长度的一般为其赋值
gap = (int)(len(L)/2)
#第一层循环:依次改变gap值对列表进行分组
while (gap >= 1):
#下面:利用直接插入排序的思想对分组数据进行排序
#range(gap,len(L)):从gap开始
for x in range(gap,len(L)):
#range(x-gap,-1,-gap):从x-gap开始与选定元素开始倒序比较,每个比较元素之间间隔gap
for i in range(x-gap,-1,-gap):
#如果该组当中两个元素满足交换条件,则进行交换
if L[i] > L[i+gap]:
temp = L[i+gap]
L[i+gap] = L[i]
L[i] =temp
#while循环条件折半
gap = (int)(gap/2)
简单选择排序
简单选择排序的基本思想:比较+交换。
- 从待排序序列中,找到关键字最小的元素;
- 如果最小元素不是待排序序列的第一个元素,将其和第一个元素互换;
- 从余下的 N - 1 个元素中,找出关键字最小的元素,重复(1)、(2)步,直到排序结束。
因此我们可以发现,简单选择排序也是通过两层循环实现。
第一层循环:依次遍历序列当中的每一个元素
第二层循环:将遍历得到的当前元素依次与余下的元素进行比较,符合最小元素的条件,则交换。
# 简单选择排序
def select_sort(L):
#依次遍历序列中的每一个元素
for x in range(0,len(L)):
#将当前位置的元素定义此轮循环当中的最小值
minimum = L[x]
#将该元素与剩下的元素依次比较寻找最小元素
for i in range(x+1,len(L)):
if L[i] < minimum:
temp = L[i];
L[i] = minimum;
minimum = temp
#将比较后得到的真正的最小值赋值给当前位置
L[x] = minimum
堆排序(最小k个数可见剑指offer题30)
堆的概念
堆:本质是一种数组对象。特别重要的一点性质:<b>任意的叶子节点小于(或大于)它所有的父节点</b>。对此,又分为大顶堆和小顶堆,大顶堆要求节点的元素都要大于其孩子,小顶堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求。
利用堆排序,就是基于大顶堆或者小顶堆的一种排序方法。下面,我们通过大顶堆来实现。
基本思想:
堆排序可以按照以下步骤来完成:
首先将序列构建称为大顶堆;
(这样满足了大顶堆那条性质:位于根节点的元素一定是当前序列的最大值)
(图中节点标注有错 应该是0开始)
取出当前大顶堆的根节点,将其与序列末尾元素进行交换;
(此时:序列末尾的元素为已排序的最大值;由于交换了元素,当前位于根节点的堆并不一定满足大顶堆的性质)
对交换后的n-1个序列元素进行调整,使其满足大顶堆的性质;
重复2.3步骤,直至堆中只有1个元素为止
# -*- coding:utf-8 -*-
def build_max_head(arr,heapsize,root_index):
#根据给定的根节点计算左右节点的index
left_index = 2*root_index+1
right_index = left_index+1
max_num_index = root_index
#如果左节点大于根节点,那么max_num_index=left_index
if left_index<heapsize and arr[left_index]>arr[max_num_index]:
max_num_index=left_index
#如果右节点大于根节点,那么max_num_index=right_index
if right_index<heapsize and arr[right_index]>arr[max_num_index]:
max_num_index=right_index
if max_num_index != root_index:
arr[root_index],arr[max_num_index] = arr[max_num_index],arr[root_index] #进行下一个节点的build_max
build_max_head(arr,heapsize,max_num_index)
def head_sort(arr):
#从最后一个节点开始,对整个堆(完全二叉树)进行build_max
for i in range((len(arr)+1)//2-1,-1,-1):#根索引最大范围(若是k,则将len换成k)#因为建立最大堆的最后一行的关系,所以要倒着来
build_max_head(arr,len(arr),i) #(若是k,则将len换成k)
#对从最后一个节点开始往前遍历
for i in range(len(arr)-1, -1, -1): #(若是k,则将len换成k)
#这个时候最大值就在根节点
#所以把这个最大值放到有序队列中
#简单来说就是把最大值放到最后
arr[i],arr[0] = arr[0],arr[i]
#互换之后,堆中就少了一个元素,所以当前堆的个数变了,变为i
#此时由于堆只变了根节点,因此只需要对根节点进行build_max
build_max_head(arr,i,0)
冒泡排序
思路:
- 将序列当中的左右元素,依次比较,保证右边的元素始终大于左边的元素;
( 第一轮结束后,序列最后一个元素一定是当前序列的最大值;) - 对序列当中剩下的n-1个元素再次执行步骤1。
- 对于长度为n的序列,一共需要执行n-1轮比较
(利用while循环可以减少执行次数)
def bubble_sort(L):
length = len(L)
for x in range(length):
for i in range(0,length-x):
if L[x] > L[i]:
L[x],L[i] = L[i],L[x]
快速排序(及优化)
快速排序的基本思想:挖坑填数+分治法
- 从序列当中选择一个基准数(pivot)
在这里我们选择序列当中第一个数最为基准数 - 将序列当中的所有数依次遍历,比基准数大的位于其右侧,比基准数小的位于其左侧
- 重复步骤1.2,直到所有子集当中只有一个元素为止。
用伪代码描述如下:
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中
def quick_sort(L,start,end):
if start < end:
i,j,mid = start,end,L[start]
while i < j:
while(i<j) and (L[j] >= mid):
j = j-1
if(i<j):
L[i] = L[j]
while(i<j) and (L[i] <= mid):
i = i+1
if(i<j):
L[j] = L[i]
L[i] = mid
quick_sort(L,start,i-1)
quick_sort(L,i+1,end)
return L
优化1:基准元素不再选择第一个元素,而是随机选择一个,这样避免了有序数组,时间复杂度最坏为log(n2) 的情况
优化2:三路快拍,把和基准元素相等的元素放在中间,比他小的在左边,大的在右边。这样避免了重复元素过多的情况
import random
def quicksort(arr, left, right):
# 只有left < right 排序
if left >=right:
return
#在列表里随机选一个数来作为基准元素
random_index = random.randint(left, right)
#把基准元素和第一个元素交换
arr[left], arr[random_index] = arr[random_index], arr[left]
pivot = arr[left]
#定义lt:小于v部分元素 的下标,初始是空的,因为arr[left]是基准元素
lt = left # arr[left+1...lt] < v
#gt 大于v 部分开始的下标,初始为空
gt = right + 1 # arr[gt...right] > v
i = left + 1 # arr[lt+1...i] == v
#终止条件:下标i 和gt 遇到一起,说明都排完了
while i < gt:
if arr[i] < pivot:
arr[i], arr[lt+1] = arr[lt+1], arr[i]
lt += 1
i += 1
elif arr[i] > pivot:
arr[i], arr[gt-1] = arr[gt-1], arr[i]
gt -= 1
else:
i += 1
#最后把第一个元素(基准元素)放到等于v的部分
arr[left], arr[lt] = arr[lt], arr[left]
#递归排序
quicksort(arr, left, lt-1)
quicksort(arr, gt, right)
归并排序
思路:
归并排序采用分而治之的原理:
一、将一个序列从中间位置分成两个序列;
二、在将这两个子序列按照第一步继续二分下去;
三、直到所有子序列的长度都为1,也就是不可以再二分截止。这时候再两两合并成一个有序序列即可。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr)//2 #mid = (int)(len(arr)/2)
left_arr = merge_sort(arr[:mid])
right_arr = merge_sort(arr[mid:])
return merge(left_arr,right_arr)
def merge(left_arr,right_arr):
empty = []
left_index = 0
right_index = 0
while left_index < len(left_arr) and right_index < len(right_arr):
if left_arr[left_index] < right_arr[right_index]:
empty.append(left_arr[left_index])
left_index += 1
else:
empty.append(right_arr[right_index])
right_index += 1
if left_index ==len(left_arr):
for num in right_arr[right_index:]:
empty.append(num)
else:
for num in left_arr[left_index:]:
empty.append(num)
return empty
if __name__ =='__main__':
arr = [1,3,5,7,9,2,8,0,-1,-2]
output = merge_sort(arr)
print(output)