快速排序(quick sort)
首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
将数组分割成两个数组之后再分别对剩下的两个数组执行排序,这样循环,直到剩一个元素。
import time,random
import copy
def cal_time(func): #该装饰器用来测量函数运行的时间
def wrapper(*args,**kwargs):
t1 = time.time()
res = func(*args,**kwargs)
t2 = time.time()
print("%s running time: %s secs." % (func.__name__, t2 - t1))
return res
return wrapper
def query_sort(data,left,right):
if left < right:
mid = partition(data,left,right)
query_sort(data,left,mid-1)
query_sort(data,mid+1,right)
def partition(data,left,right):
temp = data[left]
while left < right:
while left<right and data[right] >= temp:
right -=1
data[left] = data[right]
while left<right and data[left] <= temp:
left +=1
data[right] = data[left]
data[left] = temp
return left
@cal_time
def query_sort_h(data):
query_sort(data,0,len(data)-1)
@cal_time
def sort_fun(data):
data.sort()
data = list(range(10000))
random.shuffle(data)
data1 = copy.deepcopy(data)
print(data)
query_sort_h(data)
sort_fun(data1)
print(data)
二叉树
树的特征和定义
树是一种重要的非线性 数据结构,直观地看,它是 数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。 树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示。树在计算机领域中也得到广泛应用,如在编译源程序时,可用树表示源程序的语法结构。又如在 数据库系统中,树型结构也是信息的重要组织形式之一。一切具有层次关系的问题都可用树来描述。
树(Tree)是元素的集合。我们先以比较直观的方式介绍树。下面的数据结构是一个树:
树有多个节点(node),用以储存元素。某些节点之间存在一定的关系,用连线表示,连线称为边(edge)。边的上端节点称为父节点,下端称为子节点。树像是一个不断分叉的树根。
每个节点可以有多个子节点(children),而该节点是相应子节点的父节点(parent)。比如说,3,5是6的子节点,6是3,5的父节点;1,8,7是3的子节点, 3是1,8,7的父节点。树有一个没有父节点的节点,称为根节点(root),如图中的6。没有子节点的节点称为叶节点(leaf),比如图中的1,8,9,5节点。从图中还可以看到,上面的树总共有4个层次,6位于第一层,9位于第四层。树中节点的最大层次被称为深度。也就是说,该树的深度(depth)为4。
如果我们从节点3开始向下看,而忽略其它部分。那么我们看到的是一个以节点3为根节点的树:
三角形代表一棵树
再进一步,如果我们定义孤立的一个节点也是一棵树的话,原来的树就可以表示为根节点和子树(subtree)的关系:
上述观察实际上给了我们一种严格的定义树的方法:
1. 树是元素的集合。
2. 该集合可以为空。这时树中没有元素,我们称树为空树 (empty tree)。
3. 如果该集合不为空,那么该集合有一个根节点,以及0个或者多个子树。根节点与它的子树的根节点用一个边(edge)相连。
上面的第三点是以递归的方式来定义树,也就是在定义树的过程中使用了树自身(子树)。由于树的递归特征,许多树相关的操作也可以方便的使用递归实现。我们将在后面看到。
树的实现
树的示意图已经给出了树的一种内存实现方式: 每个节点储存元素和多个指向子节点的指针。然而,子节点数目是不确定的。一个父节点可能有大量的子节点,而另一个父节点可能只有一个子节点,而树的增删节点操作会让子节点的数目发生进一步的变化。这种不确定性就可能带来大量的内存相关操作,并且容易造成内存的浪费。
一种经典的实现方式如下:
二叉树:
二叉树是由n(n≥0)个结点组成的有限集合、每个结点最多有两个子树的有序树。它或者是空集,或者是由一个根和称为左、右子树的两个不相交的二叉树组成。
特点:
(1)二叉树是有序树,即使只有一个子树,也必须区分左、右子树;
(2)二叉树的每个结点的度不能大于2,只能取0、1、2三者之一;
(3)二叉树中所有结点的形态有5种:空结点、无左右子树的结点、只有左子树的结点、只有右子树的结点和具有左右子树的结点。
二叉树(binary)是一种特殊的树。二叉树的每个节点最多只能有2个子节点:
二叉树
由于二叉树的子节点数目确定,所以可以直接采用上图方式在内存中实现。每个节点有一个左子节点(left children)和右子节点(right children)。左子节点是左子树的根节点,右子节点是右子树的根节点。
如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每个节点都不比它左子树的任意元素小,而且不比它的右子树的任意元素大。
(如果我们假设树中没有重复的元素,那么上述要求可以写成:每个节点比它左子树的任意节点大,而且比它右子树的任意节点小)
二叉搜索树,注意树中元素的大小
二叉搜索树可以方便的实现搜索算法。在搜索元素x的时候,我们可以将x和根节点比较:
1. 如果x等于根节点,那么找到x,停止搜索 (终止条件)
2. 如果x小于根节点,那么搜索左子树
3. 如果x大于根节点,那么搜索右子树
二叉搜索树所需要进行的操作次数最多与树的深度相等。n个节点的二叉搜索树的深度最多为n,最少为log(n)。
二叉树的遍历
遍历即将树的所有结点访问且仅访问一次。按照根节点位置的不同分为前序遍历,中序遍历,后序遍历。
前序遍历:根节点->左子树->右子树
中序遍历:左子树->根节点->右子树
后序遍历:左子树->右子树->根节点
例如:求下面树的三种遍历
前序遍历:abdefgc
中序遍历:debgfac
后序遍历:edgfbca
二叉树的类型
如何判断一棵树是完全二叉树?按照定义,
教材上的说法:一个深度为k,节点个数为 2^k - 1 的二叉树为满二叉树。这个概念很好理解,
就是一棵树,深度为k,并且没有空位。
首先对满二叉树按照广度优先遍历(从左到右)的顺序进行编号。
一颗深度为k二叉树,有n个节点,然后,也对这棵树进行编号,如果所有的编号都和满二叉树对应,那么这棵树是完全二叉树。
堆排序
堆排序,顾名思义,就是基于堆。因此先来介绍一下堆的概念。
堆分为最大堆和最小堆,其实就是完全二叉树。最大堆要求节点的元素都要大于其孩子,最小堆要求节点元素都小于其左右孩子,两者对左右孩子的大小关系不做任何要求,其实很好理解。有了上面的定义,我们可以得知,处于最大堆的根节点的元素一定是这个堆中的最大值。其实我们的堆排序算法就是抓住了堆的这一特点,每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
堆排序就是把堆顶的最大数取出,
将剩余的堆继续调整为最大堆,具体过程在第二块有介绍,以递归实现
剩余部分调整为最大堆后,再次将堆顶的最大数取出,再将剩余部分调整为最大堆,这个过程持续到剩余数只有一个时结束
import time,random,copy
from cal_time import cal_time,nor_sort
def sift(data,low,high): #来一次调整
i= low #low为根节点,父节点
j= 2 * i + 1 #i的左孩子
tmp = data[i]
while j <= high: #没有超过数组范围时
if j < high and data[j] < data[j + 1 ]:#如果j小于high也就是说左孩子小于最边界的high那么这个节点肯定有右孩子,
j +=1 #这个时候对比左孩子和右孩子的大小,如果右孩子大于左孩子,那么这个时候把J置为右孩子,J的左右就是表示两个孩子里面最大的那个
if tmp < data[j]: #如果父亲的值小于孩子的值,这个时候开始换位置
data[i] = data[j] #j的值换为父亲,i的值换为孩子
i =j #把I的位置调整到之前比父亲大的那个孩子的位置,
j = 2* i + 1 #开始比较当前这个孩子和他自己孩子的大小,找到它的左孩子,定义为j
else: #如果两个孩子都没父亲大
break #什么也不干,跳出当前循环
data[i] = tmp #把最初始i的值赋给调整之后的位置
@cal_time
def heap_sort(data):
n = len(data)
for i in range(n //2 -1,-1,-1):#取最后一个有孩子的父节点,倒序遍历
sift(data,i,n-1) #调整
for i in range(n-1,-1,-1):
data[0],data[i] = data[i],data[0]
sift(data,0,i-1)
data = list(range(10000))
random.shuffle(data)
data1 = copy.deepcopy(data)
print(data)
heap_sort(data)
print(data)
data1 = nor_sort(data1)
print(data1)
归并排序
假设现在的列表分两段有序,如何将其合成为一个有序列表
将序列垂直分割成两个有序序列,然后取每个序列的第一项对比,小的取出来然后继续对比,当某一序列取完之后就把另一个序列剩下的全部放到新的序列里面
将数组细分,先分解到数个有序的序列,比如只包含一个元素的序列是有序的,然后再开始合并,所以称该算法为归并排序。
代码示例:
import time,random,copy
from cal_time import cal_time,nor_sort
def merge(data,low,mid,high):
i = low
j = mid + 1
ltmp = []
while i <= mid and j <= high:
if data[i]<data[j]:
ltmp.append(data[i])
i += 1
else:
ltmp.append(data[j])
j += 1
while i <= mid:
ltmp.append(data[i])
i += 1
while j <= high:
ltmp.append(data[j])
j +=1
data[low:high+1] = ltmp
def _merge_sort(data,low,high):
if low<high:
mid = (low+high)//2
_merge_sort(data,low,mid)
_merge_sort(data,mid+1,high)
merge(data,low,mid,high)
@cal_time
def merge_sort(data):
_merge_sort(data,0,len(data)-1)
data = list(range(10000))
random.shuffle(data)
data1 = copy.deepcopy(data)
print(data)
merge_sort(data)
nor_sort(data1)
print(data)
快速排序、堆排序、归并排序的区别
三种排序算法的时间复杂度都是O(nlogn) 一般情况下,就运行时间而言: 快速排序 < 归并排序 < 堆排序
三种排序算法的缺点: 快速排序:极端情况下排序效率低 归并排序:需要额外的内存开销 堆排序:在快的排序算法中相对较慢
希尔排序(shell sort)
希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本,该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率比直接插入排序有较大提高,希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
首先要明确一下增量的取法:
第一次增量的取法为: d=count/2;
第二次增量的取法为: d=(count/2)/2;
最后一直到: d=1;
看上图观测的现象为:
d=3时:将40跟50比,因50大,不交换。
将20跟30比,因30大,不交换。
将80跟60比,因60小,交换。
d=2时:将40跟60比,不交换,拿60跟30比交换,此时交换后的30又比前面的40小,又要将40和30交换,如上图。
将20跟50比,不交换,继续将50跟80比,不交换。
d=1时:这时就是前面讲的插入排序了,不过此时的序列已经差不多有序了,所以给插入排序带来了很大的性能提高。
代码示例:
import random,copy
from cal_time import cal_time,nor_sort
def shell_sort(data):
gap = len(data) // 2
print(gap)
while gap >=1:
for i in range(gap,len(data)):
tmp = data[i]
j = i-gap
while j >= 0 and tmp < data[j]:
data[j + gap] = data[j]
j -=gap
data[i-gap] = tmp
gap //=2
data = list(range(10000))
random.shuffle(data)
data1 = copy.deepcopy(data)
print(data)
shell_sort(data)
print(data)
data1 = nor_sort(data1)
print(data1)
各排序方法总结:
备注:1、深度递归调用有时会出错需要设置递归保护代码:
sys.setrecursionlimit(100000)
2、切记不能给递归函数加装饰器,这样的话每次递归装饰器都会执行一次