作者:孙相国
E-mail: sunxiangguodut@qq.com
版权所有,严禁转载
1. 查找
1.1 顺序查找
弱智,不讲
成功情况下,平均查找长度:
时间复杂度: O(n)
不成功的情况下(即找的数不在表中),平均查找长度
1.2 折半查找
def bi_search(R,k):
low,high=0,len(R)
while low<=high:
mid=(low+high)/2
#mid=low+(high-low)/2
if R[mid]==k:
return mid
if R[mid]>k:
high=mid-1
else:
low=mid+1
return -1
思考题:如何计算ASL?
判定树(page379)
时间复杂度为 O(lgn)
不成功的查找长度为h,即 lg(n+1)
1.3 分块查找
若以折半查找索引表,以顺序查找块表,则
若以顺序查找查找索引表和块表则
当 s=n12 时,可以取到极小 n12+1 .
上述3中查找方法的比较:
就平均查找长度而言,折半查找最小,分块次之,顺序最长
就表的结构而言,顺序查找对有序表无序表均适用,折半查找仅仅适用于有序表,分块查找要求表中元素至少是分块有序的。
就表的存储结构而言,顺序查找和分块查找可以用于顺序表和链表;而折半查找只用于顺序表
分块查找综合了顺序表和折半查找的优点。(既能较快地查找,又能适应动态变化的要求)
1.4 二叉排序树
1.4.1二叉排序树(BST)
- 若它左子树非空,则左子树上所有记录均小于根记录
- 若它右子树非空,则右子树上所有记录均大于根记录
- 左右子树本身又是一棵二叉排序树
1.4.2二叉排序树的性质
- 按照中序遍历得到的中序序列是一个递增有序序列
- 一棵二叉排序树中的最大节点是根节点的最右下节点,最小节点是根节点的最左下节点
- 只需给出一棵二叉排序树的先序序列/后序序列/层次序列中的一个,便可以唯一确定这棵二叉排序树,因为这些序列中所有元素的递增序列便是该二叉排序树的中序序列。
1.4.3 二叉排序树的查找
def search(root,key):
if key==root:
return root
elif key<root:
search(root.left_child,key)
elif key>root:
search(root.right_child,key)
else:
return none
一棵含有n个节点的二叉排序树高度为lgn-n
这也是查找所需要的时间复杂度
1.4.4 二叉排序树的插入
def insert(root,k):
if root is none:
root=k
elif root>k:
return insert(root.left_child,k)
else:
return insert(root.right_child,k)
1.4.5 二叉排序树的删除
二叉排序树的插入和删除操作时间复杂度均为 O(lgn)
1.5 平衡二叉树
1.6 B树
1.7 哈希表查找
2. 排序
2.0 排序前传
各种排序算法的江湖地位:
傻子系列:
直接插入(弱智)
折半插入(自以为漂亮的弱智)
希尔排序(自以为高大上的弱智)
不会你都系列:
冒泡排序(不会你都不好意思思考人生)
计数排序(不会你都不好意思吹牛逼)
基数排序(不会你都不好意思撩妹)
桶排序(不会你都不好意思装大神)
核心系列:
简单选择排序(重要)
堆排序(三大金刚之一,非常重要!)
快速排序 (三大金刚之二,非常重要的平方!!)
归并排序(三大金刚之三,非常重要)
边缘:
外排序(屌丝)
2.1 直接插入
void insert_sort(sqlist R[], int n)
{
int i,j;
sqlist temp;
for (i=1;i<n;i++)
{
temp=R[i];
j=i-1;
while(j>=0 && temp.key<R[j].key)
{
R[j+1]=R[j];
j--;
}
R[j+1]=temp;
}
}
def insert_sort(r):
for i in range(0,len(r)):
pos=i
while r[pos]>r[i]:
pos=pos-1
r[pos:i+1]=r[i]+r[pos:i]
globally sequential
no
time complexity
average: O(n2)
worst: O(n2)
best: O(n)
space complexity
O(1)
stability
stable
2.2 折半插入
void insert_sort(sqlist R[], int n)
{
int i,j,low,high,mid
sqlist temp;
for (i=1;i<n;i++)
{
temp=R[i];
low=0;high=i-1;
while(low<high)
{
mid=(low+high)/2;
if(temp.key<R[mid].key)
high=mid-1;
else
low=mid+1
}
for (j=i-1;j>=high+1;j--)
{
R[j+1]=R[j];
}
R[high+1]=temp;
}
}
def insert_sort(r):
for i in range(0,len(r)):
low,high=0,i-1
while low<=high:
if r[i]<r[mid]:
high=high+1
else:
low=mid+1
r[high+1:i+1]=r[i]+r[high+1:i]
globally sequential
no
time complexity
average: O(n2)
worst: O(n2)
best: O(n)
space complexity
O(1)
stability
stable
2.3 希尔排序
void shell_sort(sqlist R[], int n)
{
int i,j,d;
sqlist temp;
d=n/2;
while(d>0)
{
for (i=d;i<n;i++)
{
temp=R[i];
j=i-d;
while(j>=0 &&temp.key<R[j].key)
{
R[j+d]=R[j];
j=j-d;
}
R[j+d]=temp;
}
d=d/2;
}
}
def insert_sort(r):
for i in range(0,len(r)):
low,high=0,i-1
while low<=high:
if r[i]<r[mid]:
high=high+1
else:
low=mid+1
r[high+1:i+1]=r[i]+r[high+1:i]
def shell_sort(r):
d=len(r)/2
while d>0:
for i in range(0,d):
insert_sort(r[range(i,len(r),d)])
d=d/2
globally sequential
no
不考
time complexity
average: O(n1.3)
space complexity
O(1)
stability
unstable
希尔排序的时间复杂度与子排序算法,增量策略,有极大的关系。没有定论
http://blog.csdn.net/u013630349/article/details/48250109
2.4 简单选择排序
void select_sort(sqlist R[], int n)
{
int i,j,k;
sqlist temp;
for (i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
if(R[j].key<R[k].key)
k=j;
swap(R[i],R[k]);
/*
if(i!=k)
{
temp=R[i];
R[i]=R[k];
R[k]=temp;
}
*/
}
}
def select_sort(r):
for i in range(0,len(r)):
k=argmin(r[i+1:])
r[i],r[k]=r[k],r[i]
def select_sort(r):
for i in range(0,len(r)):
r[i],r[argmin(r[i+1:])]=r[argmin(r[i+1:])],r[i]
globally sequential
yes
time complexity
average: O(n2)
worst: O(n2)
best: O(n)
space complexity
O(1)
stability
unstable
2.5 堆排序
2.5.1 堆
堆,一般指的是二叉堆,这是一个数组,可以看做一个完全二叉树,如下图所示:
二叉堆可以分为两种形式:最大堆和最小堆
最大堆: A[π(i)]⩾A[i]
最小堆: A[π(i)]⩽A[i]
堆排序算法中,默认使用的是最大堆,而最小堆通常用于构造优先队列
复习:n个节点的完全二叉树,高度是: ⌈lg(n+1)⌉ 或 ⌊lgn+1⌋
以后为了方便起见,简称为 Θ(lgn)
需要重点掌握的操作有(达到精通代码的程度):
- max_heapify: 维护最大堆
- build_max_heap: 构建最大堆
- heap_sort:堆排序算法
- 其他重要的堆操作(用于实现优先队列)有:max_heap_insert; heap_extract_max; heap_increase_key; heap_maxmum
2.5.2 堆的维护
什么叫做堆的维护?(max_heapify)
对于一个二叉堆,假设以root.left
和root.right
为根的二叉堆分别都是最大堆,但是root
这个值却没有校验,即,我们不知道是否以root
为根的二叉堆也为一个最大堆。最大堆维护,就是希望将此时的root
放到正确的位置,从而使得以root
为根的二叉堆也为一个最大堆。
def max_heapify(A,i):
left=2*i
right=if 2*i+1>len(A) none else 2*i+1
"""
method1(sunsum):
if A[left]>A[i]:
largest=l
else:
largest=i
if A[right]>A[largest]:
largest=r
method2(xiaomi):
largest=argmax([A[left],A[i],A[right]])
if largest==0:
largest=left
elif largest==1:
largest=i
elif largest==2:
largest=right
method3(iPhone):
"""
largest=[left,i,right]
largest=largest[argmax([A[left],A[i],A[right]])]
if largest!=i:
swap(A[i],A[largest])
max_heapity(A,largest)
C++代码与上面的代码雷同,略。
def max_heapify(A,current_root):
largest=max(current_root.left,current_root,current_root.right)
if largest is not current_root:
swap(largest,current_root)
max_heapify(A,largest)
思考题(家庭作业):如何采用非递归的方式实现?
void max_heapify(sqlist R[],int low,int high)
{
int i=low, j=2*i;
sqlist temp=R[i];
while(j<=high)
{
if (j<high && R[j]<R[j+1])
j++;
if(temp<R[j])
{
R[i]=R[j];
i=j;
j=2*i;
}
else break;
}
R[i]=temp;
}
2.5.3 建堆
def build_max_heap(A):
for i in range(len(A)/2,0,-1):
max_heapify(A,i)
2.5.4 堆排序
def heap_sort(A):
for i in range(0,len(A)):
build_max_heap(A[i:])
swap(A[i],A[-1])
globally sequential
yes
time complexity
average: O(nlgn)
worst: O(nlgn)
best: O(nlgn)
space complexity
O(1)
stability
unstable
2.5.5 优先队列
优先队列(priority queue) 是一种用来维护一组元素构成的集合S的数据结构,其中每一个元素都有一个key(关键字)。一个最大优先队列支持以下操作:
- insert(S,x): 把元素x插入集合S中。这一操作等价于 S=S∪x (回顾之前DFS操作中的 T=T∪(u,v) )
- maximum(S): 返回S中具有最大键字的元素
- extract_max(S): 去掉并返回S中具有最大键字的元素
- increase_key(S,x,k): 将元素x的关键字增加到k,这里假设k的值不小于x的原关键字。
优先队列的应用:
1,最大优先队列,用于操作系统中的作业调度。最大优先队列记录将要执行的各个作业以及它们之间的相对优先级。当一个作业完成或者中断后,调度器调用extract_max从所有等待的作业中,选出一个具有最高优先级的作业来执行。在任何时候,调度器都可以调用insert把一个新作业加入到队列中来。
2,最小优先队列,亲爱的,还记得我们之前讲过的最下生成树吗?记不记得那个prim算法的复杂度?里面有一个操作,叫做extract_min()?是的,你没看错,就是这里面的东西。
这部分的代码不需要掌握,了解即可。
用堆来实现优先队列
def heap_maximum(S):
return S[0]
def heap_extract_max(S):
max=S[0]
S[0]=S[-1]
max_heapity(S,0)
return max
时间复杂度是 O(lgn)
这就是说,如果采用堆来实现,那么先前我们的prim算法复杂度可以进一步降低为 O(nlgn+e)
def heap_increase_key(S,i,key):
A[i]=key
while i>0 and A[parent(i)]<A[i]:
swap(A[i],A[parent(i)])
i=parent(i)
def heap_insert(A,key):
A=A+(key-1)
heap_increass_key(A,len(A),key)
2.5.6 习题
2.6 冒泡排序
def bubble_sort(r):
for i in range(0,len(r)):
for j in range(len(r),i,-1):
if r[j]<r[j-1]:
swap(r[j],r[j-1])
设置exchange标记,使得算法提前终止
def bubble_sort(r):
for i in range(0,len(r)):
exchange = False
for j in range(len(r),i,-1):
if r[j]<r[j-1]:
swap(r[j],r[j-1])
exchange=True
if exchange == False:
return
void bubble_sort(sqlist R[], int n)
{
int i,j,exchange;
sqlist temp;
for (i=0;i<n-1;i++)
{
exchange = 0;
for (j=n-1;j>i;j--)
if (R[j]<R[j-1])
{
temp = R[j];
R[j]=R[j-1];
R[j-1]=temp;
exchange=1;
}
if (exchange==0)
return;
}
}
globally sequential
yes
time complexity
average: O(n2)
worst: O(n2)
best: O(n)
space complexity
O(1)
stability
stable
2.7 快速排序
2.7.1 快速排序入门
如何把一个数组分词大小两半?
把 A[p:r] 分成左右两半,其中归为元素为 A[r]
def partition(A,p,r):
x = A[r]
i=p-1
for j in range(p,r):
if A[j]<=x:
i=i+1
swap(A[i],A[j])
swap(A[i+1],A[r])
return i+1
快速排序
def quick_sort(A,p,r):
if p < r:
q = partition(A,p,r)
quick_sort(A,p,q-1)
quick_sort(A,q+1,r)
C++
void quick_sort(sqlist R[], int s, int t)
{
int i=s,j=t;
sqlist temp;
if (s<t)
{
temp = R[s];
while(i!=j)
{
while(j>i && R[j]>temp)
j--;
R[i] = R[j];
while(i<j && R[i]<temp)
i++;
R[j] = R[i];
}
R[i]=temp;
quick_sort(R,s,i-1);
quick_sort(R,i+1,t);
}
}
globally sequential
no
但是每一趟都会归为一个元素(即,每一趟,都会确定一个元素的最终位置)
time complexity
average: O(nlgn)
worst: O(n2)
best: O(nlgn)
space complexity
O(lgn)
stability
unstable
2.7.2 快速排序进阶
性能分析
随机化
最坏情况分析
期望运行时间
2.8 计数排序
使用场景:假设n个输入元素的每一个都是在0-k区间内的一个整数,其中k为某个整数。
定义3个数组,A[1:n]为待排序数组。B[1:n]为存放排序的输出。C[0:k]为临时空间。
def count_sort(A,B,k):
C=[0 for i in range(0,k)]
for i in range(0,k+1):
C[i]=A.count(i)
for i in range(1,k+1):
C[i] = C[i]+C[i-1]
for j in range(len(A),0,-1):
B[C[A[j]]] = A[j]
C[A[j]] = C[A[j]]-1
time complexity
当 k=O(n) 时,为 Θ(n)
space complexity
O(n)
stability
stable
2.9 基数排序
无需掌握代码
globally sequential
no
time complexity
n个d位数,每一个数位有r个可能的取值,则
average: O(d(n+r))
worst: O(d(n+r))
best: O(d(n+r))
space complexity
O(n+r)
stability
stable
2.10 桶排序
了解即可
假设输入数据服从均匀分布,桶排序将[0,1)区间划分为n个大小相同的子区间,称为桶。然后将n个输入数据分别放到各个桶子中,对桶内的数据分别进行排序,然后将桶子连接。
2.11 归并排序
首先,将R[0:n-1]看成是n个长度为1的有序表,将相邻的有序表成对归并(将多个有序表组成一个新的有序表叫做归并),得到 n/2 个长度为2的有序表;然后,再将这些有序表成对归并,得到 n/4 个长度为4的有序表,如此反复进行下去,最后得到一个长度为n的有序表。
由于上述的归并是对相邻的两个有序表进行的,因此叫做二路归并。如果归并操作在相邻的多个有序表中进行,则叫做多路归并排序。默认情况下所说的归并排序,指的是二路归并排序,如下图:
void merge(sqlist R[], int low, int mid, int high)
{
sqlist R1;
int i=low,j=mid+1;
while (i<=mid &&j<=high)
if (R[i]<=R[j])
{
R1.append(R[i]);
i++;
}
else
{
R1.append(R[j]);
j++;
}
while (i<=mid)
{
R1.append(R[i]);
i++;
}
while (j<=high)
{
R1.append(R[j]);
j++;
}
R.erase()
R=R1;
}
void merge_pass(sqlist R[], int length, int n)
{
for(i=0;i+2*length-1<n;i=i+2*lengtgh)
merge(R,i,i+length-1,i+2*length-1);
if (i+length-1<n)
merge(R,i,i+length-1,n-1)
}
void merge_sort(sqlist R[],int n)
{
int length;
for (length=1;length<n;length=2*length)
merge_pass(R,length,n);
}
globally sequential
no
time complexity
average: O(nlgn)
worst: O(nlgn)
best: O(nlgn)
space complexity
O(n)
stability
stable