高级算法日记4:查找与排序

来源:互联网 发布:jquery.form.js用法 编辑:程序博客网 时间:2024/05/17 06:41

作者:孙相国

E-mail: sunxiangguodut@qq.com

版权所有,严禁转载

  • 查找
    • 1 顺序查找
    • 2 折半查找
    • 3 分块查找
    • 4 二叉排序树
      • 41二叉排序树BST
      • 42二叉排序树的性质
      • 43 二叉排序树的查找
      • 44 二叉排序树的插入
      • 45 二叉排序树的删除
    • 5 平衡二叉树
    • 6 B树
    • 7 哈希表查找
  • 排序
    • 0 排序前传
    • 1 直接插入
    • 2 折半插入
    • 3 希尔排序
    • 4 简单选择排序
    • 5 堆排序
      • 51 堆
      • 52 堆的维护
      • 53 建堆
      • 54 堆排序
      • 55 优先队列
      • 56 习题
    • 6 冒泡排序
    • 7 快速排序
      • 71 快速排序入门
      • 72 快速排序进阶
    • 8 计数排序
    • 9 基数排序
    • 10 桶排序
    • 11 归并排序

1. 查找

1.1 顺序查找

弱智,不讲

成功情况下,平均查找长度:

ASLsuc=1nn(n+1)2=n+12

时间复杂度:O(n)

不成功的情况下(即找的数不在表中),平均查找长度

ASLfail=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)

ASLsuc=i=1npici=1ni=1h2i1×i=n+1nlg(n+1)1=lg(n+1)1

时间复杂度为O(lgn)

不成功的查找长度为h,即lg(n+1)

1.3 分块查找

![这里写图片描述](http://img.blog.csdn.net/20170825154849018?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ2l0aHViXzM2MzI2OTU1/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

若以折半查找索引表,以顺序查找块表,则

ASL=lg(n/s+1)+s2

若以顺序查找查找索引表和块表则
ASL=b+12+s+12=s2+2s+n2s

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.leftroot.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=Sx(回顾之前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

原创粉丝点击