slide1
Download
Skip this Video
Download Presentation
搜索初探

Loading in 2 Seconds...

play fullscreen
1 / 97

搜索初探 - PowerPoint PPT Presentation


  • 113 Views
  • Uploaded on

搜索初探. Cs 2.6 1040310615, simple, Swai. 算法的时间复杂性. 百鸡问题 公鸡每只五元,母鸡每只三元,小鸡三只一元用 n 元买 n 只鸡,求公鸡,母鸡,小鸡的只数 约束方程: a+b+c=n 5a+3b+c/3=n. 输入:所购买三种鸡的总数目 n 输出:满足问题的解的数目 k, 公鸡 , 母鸡 , 小鸡的只数 g[], m[], s[] 1.void chicken\_question(int n, int &k, int g[], int m[], int s[]) 2.{ int a, b, c;

loader
I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
capcha
Download Presentation

PowerPoint Slideshow about '搜索初探' - gayora


An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -
Presentation Transcript
slide1

搜索初探

Cs 2.6

1040310615, simple, Swai

slide2
算法的时间复杂性
  • 百鸡问题

公鸡每只五元,母鸡每只三元,小鸡三只一元用n元买n只鸡,求公鸡,母鸡,小鸡的只数

约束方程:

a+b+c=n

5a+3b+c/3=n

slide3
输入:所购买三种鸡的总数目n
  • 输出:满足问题的解的数目k,公鸡,母鸡,小鸡的只数g[], m[], s[]

1.void chicken_question(int n, int &k, int g[], int m[], int s[])

2.{

  • int a, b, c;
  • k=0;
  • for(a=0;a<=n;a++){
  • for(b=0;b<=n;b++){
  • for(c=0;c<=n;c++){

8. if((a+b+c==n)&&(5*a+3*b+c/3==n)&&(c%3==0)){

  • g[k]=a;
  • m[k]=b;
  • s[k]=c;
  • k++;
  • }
  • }
  • }
  • }

17. }

slide4
下面考虑这个算法花费的时间T1(n)

第4行需执行1个操作;第5行需执行1+2(n+1)个操作;第6行需执行n+1+2(n+1)2个操作;第7行需执行(n+1)2+2(n+1)3;第8行需执行14(n+1)3个操作;第9,10,11,12行循环各执行一个操作,执行与否取决于第8行的条件语句,所以这四行的执行时间不会超过4(n+1)3因此,T1(n)<= 1+2(n+1)+n+1+2(n+1)2+(n+1)2+16(n+1)3+4(n+1)3=20n3+63n2+69n+27

当n很大时,T1(n) ≈c1n3 c1>0

当n=10000时,约需11天零13小时,显然这个算法不能满足我们的要求!

slide5
改进的百鸡问题
  • 输入:所购买三种鸡的总数目n
  • 输出:满足问题的解的数目k,公鸡,母鸡,小鸡的只数g[], m[], s[]

1.void chicken_question(int n, int &k, int g[], int m[], int s[])

2.{

  • int i, j, a, b, c;
  • k=0;
  • i=n/5;
  • j=n/3;
  • for(a=0;a<=n;a++){
  • for(b=0;b<=n;b++){
  • c=n-a-b;
  • if((a+b+c==n)&&(5*a+3*b+c/3==n)&&(c%3==0)){
  • g[k]=a;
  • m[k]=b;
  • s[k]=c;
  • k++;
  • }
  • }
  • }

16.}

slide6
分析第二个算法的时间花费:

第4行执行1个操作;第5,6行各执行2个操作;第7行执行1+2(n/5+1)个操作;第8行执行n/5+1+2(n/5+1)(n/3+1)个操作;第9行执行3(n/5+1)(n/3+1);第10行执行10(n/5+1)(n/3+1)个操作;第11,12,13,14行循环一次各执行一个操作,执行与否取决于第10行的条件语句,所以这四行的执行时间不会超过4(n/5+1)(n/3+1)个操作。上述/均表示整除操作,因此

T2(n)<=1+2+2+1+2(n/5+1)+n/5+1+2(n/5+1)(n/3+1)+(3+10+4)(n/5+1)(n/3+1)=19n2/15+161n/15+28≈c2n2 , c2 >0, (n很大时)

当n=10000时,约花费6.7s

slide7
运行时间的上界,Ο记号

定义:令N为自然数集合,R+为正实数集合。函数f:N→ R+,函数g:N→ R+,若存在自然数n0和正常数c,使得对所有的n>= n0,都有f(n)<=cg(n),则称函数f(n)的阶至多是Ο(g(n)).

因此,如果存在lim f(n)/g(n),则:

n→∞

lim f(n)/g(n) ≠ ∞,即:f(n)= Ο(g(n))

n→∞这个定义表明:f(n)的增长至多像g(n)的增长那么快。此时称Ο(g(n))是f(n)的上界

slide8
运行时间的下界,s记号

定义:令N为自然数集合,R+为正实数集合。函数f:N→ R+,函数g:N→ R+,若存在自然数n0和正常数c,使得对所有的n>= n0,都有f(n)>=cg(n),则称函数f(n)的阶至少是s(g(n)).

因此,如果存在lim f(n)/g(n),则:

n→∞

lim f(n)/g(n) ≠ 0,即:f(n)= s(g(n))

n→∞这个定义表明:f(n)的增长至少像g(n)的增长那么快。此时称s(g(n))是f(n)的下界

slide9
运行时间的准确界,⊙记号

定义:令N为自然数集合,R+为正实数集合。函数f:N→ R+,函数g:N→ R+,若存在自然数n0和两个正常数c1c2,使得对所有的n>= n0,都有c1g(n)<=f(n)<=c2g(n),则称函数f(n)的阶是⊙(g(n)).

因此,如果存在lim f(n)/g(n),则:

n→∞

lim f(n)/g(n) = c,即:f(n)= ⊙(g(n))

n→∞

slide10
e.g.指数函数f(n)=5*2n+n2

令n0=0,当n>=n0时,有c1=5,g(n)=2n,使得:

f(n)>=5*2n=c1g(n)

所以,f(n)=s(g(n))=s(2n)

令n0=4,当n>=n0时,有:c2=6,g(n)=2n

f(n)<=5*2n+2n<=6*2n=c2g(n)

所以,f(n)= ⊙(2n)

结论:复杂性关系

1﹤ ㏒ ㏒ n ﹤ ㏒ n ﹤(n)1/2 ﹤n3/4 ﹤n ﹤n㏒n ﹤n2 ﹤2n ﹤n! ﹤2n2

slide11
算法的时间复杂性分析
  • 循环次数的统计
  • 基本操作频率的统计
  • 计算步的统计
  • 最坏情况分析
  • 平均情况分析
slide12
最坏情况分析

线性检索算法

输入:给定n个已排序过的元素的数组A[]及元素x

输出:若x=A[j],0<=j<=n-1,输出j,否则输出-1

1. int linear_search(int A[], int n, int x)

2. {

3. int j=0;

  • while(j
  • j++;
  • if(x==A[j])
  • return j;
  • else
  • return -1;

10. }

slide13
二叉检索算法

输入:给定n个已排序过的元素的数组A[]及元素x

输出:若x=A[j],0<=j<=n-1,输出j,否则输出-1

1.int binary_search(int A[], int n, int x)

2.{

  • int mid, low=0, high=n-1, j=-1;
  • while(low<=high && j<0){
  • mid=(low+high)/2;
  • if(x==A[mid]) j=mid;
  • else if(x
  • else low=mid+1;
  • }
  • return j;

11.}

slide14
数组中不存在元素x,或者元素x是数组的最后一个元素,就是线性检索算法的最坏情况。如果采用第4行的数组元素的比较操作作为算法的基本操作,这时算法必须对数组元素进行n次比较,因此,在最坏情况下,线性检索的时间复杂性是⊙(n)。
  • 在二叉检索算法中,当数组中不存在元素x,或者x是数组的第一个元素与最后一个元素,这是二叉检索算法的最坏情况。假定x是数组的最后一个元素,第一次比较后,数组后半部分的元素个数是[n/2],这是第二次要继续进行检索的元素个数。类似的,第三次进行检索的元素个数是[n/4],…第i次检索时元素的个数是[n/2j-1].这种情况一直延续到被检索的元素的个数是1。假设检索x次所需要检索的最大比较次数是j次,则j满足:

[n/2j-1]=1,即j-1≤㏒n≤j,得到j= [㏒n]+1,这表明在最坏情况下,二叉检索算法元素比较次数最多为[㏒n]+1次,因此它的时间复杂性是Ο(㏒n),它至少执行[㏒n]+1次,因此,它的时间复杂性是⊙(㏒n)。

slide15
平均情况分析

e.g.用插入法按递增顺序排序数组A

输入:n个元素的整数数组A[],数组元素个数n

输出:按递增顺序排序的数组A[]

1. void insert_sort(int A[], int n)

2. {

  • int a, i, j;
  • for(i=1; i
  • a=A[i];
  • j=i-1;
  • while(j>=0 && A[j] > a){
  • A[j+1] = A[j];
  • j--;
  • }
  • A[j+1] = a;
  • }

13. }

slide16
假定A中的元素为{x1,x2,…xn},且xi≠xj. n个元素共有n!种排列,假定每种的概率相同,均为1/i。如果前面i-1个元素已经按递增顺序排序了,现在要把元素xi插入到一个合适的位置,以构成一个i个元素的递增数列。

ii-1

Ti=(i-1)/i+∑(i-j+1)/i=(i-1)/i +∑j/i = ½+i/2-1/i

j=2j=1

分别把x2,x3,…xn插入到合适位置,所需的平均比较总次数T为:

nnn n

T= ∑Ti=∑(1/2+i/2-1/i)=(n-1)/2+1/2 ∑i-∑1/i +1

i=2 i=2 n i=2 i=1

=(n2+3n)/4 - ∑1/i

i=1 n

因为㏑(n+1) ≤ ∑1/i

i=1

所以:T=(n2+3n)/4 - ㏑n

即插入排序法insert_sort在平均情况下的时间复杂性是 ⊙(n2)

search

查找(Search)

1、静态查找表2、动态查找表3、哈希查找表

slide18
基本概念
  • 查找是确定在数据元素集合中是否存在一个数据元素关键字等于给定关键字的过程
  • 操作过程:比较

成功 :在记录集合中能找到与给定值相等 的关键字

失败 :查遍整个记录集合也未能找到与给定值的关键字

slide19
静态查找:只检查某个特定的记录是否存在 于给定的记录

集合 (采用顺序表结构)

  • 动态查找:不但检查是否存在,而且当不存在时要将记录插

入到记录集合中;或者当记录存在时,要将其

修改或删除 (采用树表结构)

  • 衡量查找算法效率的标准:

平均查找长度——为确定某一记录在记录集合中的位置,给定值关键字与集合中的记录关键字所需要进行的比较次数的期望值

查找成功时的平均查找长度(n个记录)

ASL = ∑Pi * Ci

Pi 是查找第i个记录的概率

Ci是查找第i个记录所需要比较的次数

slide20
顺序表的静态查找
  • 顺序查找

方法:从顺序表的一端开始,用给定值

的关键字Key逐个顺序地与表中

各记录的关键字相比较

适用于表中数据元素无序的情况

slide21
顺序查找的C语言算法

int SequelSeach(elemtype s[], keytype Key, int n)

/*在s[0]~s[n-1]中顺序查找关键字为Key的记录*/

/*查找成功返回该记录的下标序号;失败时返回-1*/

{

int i=0;

while(i

if(s[i].Key == Key) return i;

else return -1;

}

slide22
e.g. 定义如下主函数:

typedef int keytype;

typedef struct{

keytype Key;

}elemtype;

int SequelSearch(elemtype s[], keytype Key, int n);

int main(void)

{

elemtype Test[10]={710,342,45,686,6,

841,429,134,68,264};

int n=10, Key=686, i;

i = SequelSeach(Test, Key, n);

if(i!=-1)

printf(“\n查找成功!该数据元素为第%d个记录”,i );

else printf(“\n查找失败!该数据元素在记录集合中不存在”);

}

slide23
在这个算法中,while循环语句包含两个条件检测,若要提高查找速度,应尽量减少检测条件。

小技巧:设置监视哨

即在表的尾端设置一个虚拟的记录s[n],在查找前将给定值的关键字Key赋给s[n].Key,这样就无需在循环时检查整个标是否检查完毕。

slide24
修改后的算法

int SequelSeach(elemtype s[], keytype Key, int n)

/*在s[0]~s[n-1]中顺序查找关键字为Key的记录*/

/*查找成功返回该记录的下标序号;失败时返回-1*/

{

int i=0;

s[n].Key = Key; /*s[n]为监视哨*/

while( s[i].Key!= Key) i++;

if(i >= n) return -1;

else return i;

}

slide25
缺点:查找时间长分析:

假设顺序表中每个记录的查找概率均为

Pi=1/n,(i = 1,2,3,…,n), Ci = i, 则查找成功时

ASLsq = (n+1)/2;

查找失败时 ASLsq = n。

假设被查找的记录在顺序表中概率为p,

不在为q,则 ASLsq = (n+1)(1- p/2)

因此,从上面可以看出,顺序查找方法虽然简单,但查找效率较低。当已知各记录查找概率不等时,可以改变记录的存储次序,把查找频率高的记录放到序列前面,低的放后面,可以提高查找效率。

slide26
二分查找
  • 使用条件:顺序表,表内元素之间有序

s[0].Key <= s[1].Key <= …<= s[n-1].Key

不可直接用于线性链表

设置三个变量low, high, mid,它们分别指向表的当前待查范围的下界,上界和中间位置。初始时,令low=0,high=n-1,设待查数据元素的关键字为Key

(1)令mid = [(low+high)/2]

(2)比较Key与s[mid].Key

1. s[mid].Key=Key, 查找成功

2. s[mid].Key

3. s[mid].Key>Key,查找s[mid]前半部, high=mid-1

(3)比较low和high,若low<=high,重复(1)(2);否则,表中不存在关键字为Key的记录,查找失败

slide27
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[i+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 7 (初始时为最大下标 n );
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =4

mid=4 但 key=9 < 10, 向左

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

high=7

low=1

slide28
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;

mid=4

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3(mid-1)

slide29
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2

mid=2

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3(mid-1)

slide30
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2

mid=2; 但 key=9 > 8, 向右

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3(mid-1)

slide31
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 3; high(高下标)= 3 ;

mid=2

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

high=3

low=3(mid+1)

slide32
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 3; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 3

mid=3;

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

high=3

low=3

slide33
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 9 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 3

mid=3; 但 key=9 中点值也为 9 ,找到

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

high=3

low=3

slide34
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找不成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 7 (初始时为最大下标 n );
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2

mid=4 但 key=5 < 10, 向左

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

high=7

low=1

slide35
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找不成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;

mid=4

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3(mid-1)

slide36
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找不成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2

mid=2

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3

slide37
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 3 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 =2

mid=2; 但 key=5 < 8, 向左

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=3

slide38
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 1 ;

mid=2

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=1(mid-1)

slide39
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 1 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1

mid=1

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=1

slide40
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、应用范围:顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 1 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1

mid=1; 但 key=5 > 4, 向右

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=1

high=1

slide41
1、静态查找表
  • 2、有序表的查找
  • 折半查找(或二分查找法)
  • 1、顺序表,表内元素之间有序。不可直接用于线性链表。

e.g: 查找 key = 5 的结点所在的数组元素的下标地址。

  • 查找成功的情况:数组 ST.elem 如下图所示有序
    • 数组 ST.elem :递增序 ST.elem[i]. Key <= ST.elem[I+1]. Key; i= 1,2,……n-1
    • 查找范围 :low(低下标)= 1; high(高下标)= 1 ;
    • 比较对象:中点元素,其下标地址为 mid = (low+high)/ 2 = 1

失败条件:low > high; 处于间隙中的键值导致这种情况!

mid=1; 但 key=5 > 4, 向右

key

4

8

9

10

11

13

19

0

1

2

3

4

5

6

7

low=2 (mid+1)

high=1

slide42
二分查找的C语言算法

int BinarySeach(elemtype s[], keytype key,int n)

/*在有序表s[0]~s[n-1]中二分查找关键字为Key的记录*/

/*查找成功时返回记录的下标序号,失败时返回-1*/

{

int low=0, high=n-1, mid;

while(low<=high){

mid=(low+high)/2;

if(s[mid].key == key) return mid;

else if(s[mid].key

else high = mid-1;

}

return -1;

}

slide43
3、性能分析:

2、平均情况分析(在成功查找的情况下):

∴ASL = ( 20×1+ 21×2+ 22×3+ … + 2t-1 ×t) / n

t

= ∑ (i × 2i-1 ) / n

i=1

= [(n + 1) ×( log2(n + 1) - 1 ) + 1 ] / n

= (n + 1) ×log2(n + 1) / n - 1

= (n + 1) ×log2(n + 1) / n - 1

结论:在成功查找的情况下,平均查找的代价约为

ASL = log2(n + 1) - 1 或者简单地记为:ASL = log2n - 1

slide44

图的搜索算法

——simple

slide45
1.宽度优先搜索(Breadth-First-Search,BFS)

考察下图,判断从1出发可到达的所有顶点

2 5 10

3 6 8

4 7 9

slide46
方法

1.确定邻接于1的顶点集合.

是{ 2,3,4 }

2.确定邻接于{ 2,3,4 }的新的顶点集合.

是{ 5,6,7 }

3.邻接{ 5,6,7 }的顶点集合是{ 8,9 },不存在邻接于

{ 8.9 }的顶点.

2 5 10

  • 3 6 8

4 7 9

因此,从顶点1出发可到达的顶点集为{1,2,3,4,5,6,7,8,9}

slide48
//从顶点v开始的宽度优先搜索

把顶点v标记为已到达顶点;

初始化队列Q,其中仅包含一个元素v;

While(Q不为空)

{

从队列中删除定点w;

令u为邻接于w的点;

while(u)

{

if(u尚未被标记)

{

把u加入队列;

把u标记为一到达顶点;

}

u = 邻接于w的下一个顶点;

}

}

slide49
将该段伪代码用于前面的图中,v = 1.因此在第一个循环中,顶点2,3,4都将被加入到队列中(假设是按此次序加入的).再接下来的循环中,2被从队列中去掉,加入顶点5;然后删除3,之后再删除4, 加入6和7;删除5并加入8;删除6后不增加;删除7后加入9,最后将8和9删除,队列成为空队列.过程终止时,顶点1到9被加上以到达标记。

下图给出了访问过程中所经历的顶点和边构成的子图。

slide50
2 5

1 3 6 8

4 7 9

slide52
2.类Network

根据上面的伪代码,在一个合适的高度,BFS的执行方式与是否正处理一个图,有向图,加权或加权有向图无关,也与所使用的描述方法无关.但是,为了实现下面的语句:

u = 邻接于w的下一个顶点;

必须知道当前正在使用的图类.通过把BFS函数作为Network类的一个成员函数以及利用图遍历器从一个邻接定点到达下一个顶点,可以避免为每种图类分别编写不同的代码

slide53
类Network

Class Network

{

public:

virtual int Begin(int i) = 0 ;

virtual int NextVertex(int i) = 0 ;

virtual void InitializePos() = 0 ;

virtual void DeactivatePos() = 0 ;

}

slide54
3.BFS的实现:

void Network::BFS(int v , int reach[] , int label)

{

LinkedQueueQ;

InitializePos();//初始化图遍历器数组

reach[v] = label;

Q.Add(v);

while(!Q.IsEmpty())

{

int w;

Q.Delete(w);//获取一个已标记的顶点

int u = Begin(w);

while(u) //访问w的邻接顶点

{

if(!reach[u])//一个未曾到达的顶点

{

Q.Add(u);

reach[u] = label;//标记已到达该顶点

}

u = NextVertex;//下一个与w邻接的顶点

}

}

DeactivatePos();//释放遍历器数组

}

slide56
4.BSF的复杂度分析

从顶点v出发,可到达的每一个顶点都被加上标记,且每个顶点之加入到队列中一次,也只从队列中删除一次,而且它的邻接矩阵中的行或它的邻接链表也只遍历一次.如果有s个顶点被标记,那么当使用邻接矩阵时,这些操作所需要的时间为O(sn),而使用邻接链表时,所需时间为O().在后一种情况中,要对所有被标记的顶点i的出度求和.对于无向图/网络来说,顶点的出度就等于它的度.

slide58
邻接矩阵描述中BFS的直接实现

Template

Void AdjacencyWDigraph::BFS(int v , int reach[] , int label)

{ //宽度优先搜索

LinkedQueueQ;

reach[v] = label;

Q.Add(v);

while(!Q.IsEmpty())

{

int w;

Q.Delete(w); //获取一个已标记的顶点

for(int u = 1 ; u <= n ; u ++) //对尚未标记的,邻接自w的顶点进行标记

{

if(a[w][u] != NoEdge && !reach[u])

{

Q.Add(u); //u未被标记

reach[u] = label;

}

}

}

}

slide59
连接图和有向图中BFS的直接实现

void LinkedDigraph::BFS(int v , int reach[] , int label)

{

LinkedQueueQ;

reach[v] = label;

Q.Add;

While(!Q.IsEmpty())

{

int w ;

Q.Delete(w);

ChainNode*p;

for(p = h[w],First() ; p ; p = p ->link)

{

int u = p -> data ;

if(!reach[u])

{

Q.Add(u);

reach[u] = label;

}

}

}

}

slide60
对于用邻接矩阵描述的含有50个顶点的无向完全图,Network::BFS的执行时间是AdjacencyWDigraph::BFS的时间的2.6倍.对于链表描述,统一程序的执行时间是制定程序的4.5倍.

通过删除遍历函数Begin和NextVertex中一些不必要的有效性检查,可以减少统一程序和定制程序之间的差别.在BFS在以后可能定义的Network的其他一些成员中,仅当顶点参数有效时才会调用Begin和NextVertex,因此,可以改进函数Begin和NextVertex,使它们不用执行有效性检查。

slide61
5.深度优先搜索

方法:从顶点v出发,DFS按如下过程进行:

首先将v标记为已到达顶点,然后选择一个与v邻接的尚未到达的定点u,如果这样的u不存在搜索终止.假设这样的u存在,那么从u又开始一个新的DFS.当从u开始的搜索结束时,在选择另外一个与v邻接的尚未到达的顶点,如果这样的顶点不存在,那么搜索终止.而如果存在这样的顶点,又从这个顶点开始DFS,如此循环下去。

slide62
下面的程序给出了Network类的共享成员DFS和私有成员dfs.在DFS的实现过程中,让u遍历v的所有邻接顶点将容易

void Network::DFS(int v , int reach[] , int label)

{

initializePos();

dfs(v , reach , label);

DeactivatePos();

}

Void Network::dfs(int v , int reach[] , int label)

{

reach[v] = label;

int u = Begin(v);

while(u) {

if(!reach[u])

dfs(u , reach , label);

u = NetVertex(v); }

}

slide63
用前面的有向图来测试DFS,如果v = 1,那么顶点2,3和4成为u的候选.假设付给u的第一个值是2,到达2的边是(1,2),那么从顶点2开始一次DFS,将顶点2标记为已到达顶点.这时u的候选只有顶点5,到达5的边是(2,5).下面又从5开始进行DFS,将顶点5标记为已到达顶点,根据边(5,8)可知定点8也是可到达顶点,将顶点8加上标记.从8开始没有可到达的邻接顶点,因此又返回到顶点5,顶点5也没有新的u,因此返回到顶点2,再返回到顶点1
slide64
这时还有两个候选顶点:3和4.假设选中4,边(1,4)存在,从顶点4开始DFS,将顶点4标记为已到达顶点.现在顶点3,6和7成为候选的u,假设选中6,当u = 6 时,顶点3是唯一的候选,到达3的便是(6,3),从3开始DFS,并将3标记为已到达顶点.由于没有与3邻接的新顶点,因此返回顶点4,从4开始一个u=7的DFS,然后到达顶点9,没有与9邻接的其他顶点,这时回到1,没有与1邻接的其他顶点,算法终止.
slide65
定理 设N是一个任意的图,有向图或网.v是N中任意顶点.对于所有可从顶点v到达的顶点(包括v),调用DFS(v,reach,label)后,可得reached[i] = label .

可以验证DFS与BFS有相同的时间和空间复杂度.不过,使DFS占用最大空间(递归栈空间)

的图却是使BFS占用最小空间(队列空间)的图,而使BFS占用最大空间的图则是使DFS占用最小空间的图。下面的两个图是能使DFS和BFS产生最好和最坏性能的图例.

slide66
1 2 3 … n-1 n

DFS的最坏情况;BFS的最好情况

1

2 3 4 … n-1 n

DFS的最好情况;BFS的最坏情况

slide67
相关题目1:1723 Finding Nemo

方法:

1 2 3 Marlin In

4 5 6

7 8 9

Nemo

slide68
题目2:八数码问题(HOJ 1868)

在 3*3 的棋盘上有 8 个将牌,每一个将牌刻有1-8 数码中的某一个数码。棋盘中留有一个空格,允许其周围的某一个将牌向空格移动,这样通过移动将牌就可以不断改变将牌的布局

hewristic
启发式算法(Hewristic)
  • 广度优先构造搜索树,时间复杂度为指数级
  • 用启发式搜索

两个特征:

1.必须有一个对工作量的合理估计

2.启发应容易计算

slide70
在该问题中:

一个简单的启发:

通过计算不在其位置上的数字快的数量 估计出到达目标的距离

  • 稍好一点的启发:

测量每个数字与它们的目的地的距离并把它们相加。

使用启发理论的控制系统算法:

建立一个状态图的开始节点作为搜索树的根,并且记录其启发值

While(目标节点未到达)

{选择所有节点中启发值最小的最左叶节点。

到达此被送节点加入可以有一天单独产生式到达的那些节点。

记录搜索树中相邻节点的每一个新的节点的启发值。

}

从目标接点向上拜年里数的根节点,将于每一个遍历中使用的产生式入栈

通过按照出栈的顺序执行产生式来解决问题。

hash table

Hash Table

哈希表 (散列)

Swai

slide72
内容简介
  • 哈希表的定义
  • 构造哈希函数
  • 冲突解决方法
  • 哈希表的优劣
hash table1
Hash Table
  • 一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。
  • 哈希表使用关键字的值为基础,计算关键字的位置,来确定目标在表中的位置。同样使用线形表,能直接访问表中的元素
slide75
template

class Hash Table{

public:

Hast Table(int divisor = 11);

~Hash Table(){delete[]ht;delete[]empty};

bool Search(const K&k,E&e)const;

Hash Table%Insert(const E&e);

private:

int hSearch()const K%k;

int D;//定义桶数

E*ht;//

bool *empty;//

};

slide76
使用方法
  • 构造哈希函数
  • 解决冲突
slide77
哈希函数
  • 除余法

哈希函数必须保证它返回的数字是对一个表单元的有效索引。最简单的方法就是取模运算。设表长为Tsize =sizoof(table)。h(k)= k mod Tsize。Tsize最好是一个素数,否则就使用函数h(k)=(k mod p) mod Tsize。

选取 p 为质数的理由:

    • 设 key值都为奇数,选 p 为偶数;则 H(key) = key MOD p,结果为奇数,一半单元被浪费掉。
    • 设 key 值都为 5 的倍数,选 p 为 95;则 H(key) = key MOD p,结果为:0、 5、10、15、…… 90奇数。4/5 的单元被浪费掉。
slide78
折叠法
  • 移位折叠(Shift Folding)

将关键字的各部分被依次放在一起,然后进行处理。

例如:123456789可以分割成123,456,789,然后相加得1368,就可以对Tsize取模运算。

对于字符串,移位运算时可以使用“XOR”来实现

  • 边界折叠(Boundary Folding)

每相隔一部分就有一部分被颠倒。

例如:123,456,789。

123

654

789

1566

slide79
平方取中法

取 key 2 的中间的几位数作为散列地址

  • 提取法

只使用一部分关键字来计算地址

  • 基数转换法

关键字被转换成另一种数字基数。

例如:进制转换。K = 234D,在九进制下,则为423。

slide80
冲突解决方法
  • 开放定址法 (Vector)
  • 拉链法 (List)
vector
开放定址法 (Vector)

在表中另找一个可以使用的地址。如果h(K)的位置已被使用,就按照以下顺序对标进行探查

norm(h(K)+p(1)),h(K)+p(2),…… h(K)+p(i ),……)

知道找到可以使用的地址。P(i)是探查函数,i是探查指针,norm是一个正规化函数,通常为对Tsize进行取模。

slide82
线性探查
  • 令p(i)=i。故第次探查位置为 (h(K)+i) mod Tsize。

从最初的位置h(K)开始,一次向下探查,直到有空位为止。如果探查到达表的末尾,仍然没发现,则从表头继续进行,最终会在h(K) – 1 处停止。但是这种情况很少发生。

  • 冲突:
      • 初级冲突:不同关键字值的结点得到同一个散列地址。
      • 二次聚集:同不同散列地址的结点争夺同一个单元。
      • 结果:冲突加剧,最坏时可能达到 O(n)级代价。
slide83
解决办法:
    • 改变步长:选和 m 互质的数作为步长,如 3、5、7……
    • 如选步长为 5,用 H(key) = ( key+5) MOD 11
    • H(key) = ( key+ 5×2) MOD 11
    • H(key) = ( key+ 5×3) MOD 11 等进行下一个空的单元,直到找到为止。
    • 随机地改变步长,如取步长序列:2,7,4,3,6,1,5
    • 如用 H(key) = ( key+2) MOD 11
    • H(key) = ( key+7) MOD 11
    • H(key) = ( key+ 4) MOD 11 等进行探测下一个空的单元,直到找到为止。
slide84
二次探查
  • 选择为一个二次函数。经验公式是

p(i) = h(K)+(- 1) i-1 ( ( i + 1) /2)2

表长不应该取偶数,因为这种情况下探查到的地址都为偶地址或奇地址。

理想状况下,Tsize = 4j +3 。这样能够保证探查顺序包括所有的地址(Radke 1970)。

例如:j = 4, Tsize = 19,对于h(K)=9,那么探查顺序为:……

  • 二次探查依然会有聚集块的形成,危害比一般线性探查小多了。
slide85
性能分析
  • 时间复杂度:设b为哈希表中桶的个数。哈希函数中D为除数且b = D。初始化表的时间为⊙(b)。当表中有n个元素时,最坏的插入和搜索时间均为⊙(b)。即所有n关键字值都在同一个桶里。此时和线性表在最坏的时候,复杂度一样。

平均性能相当好。用Un和Sn来分别表示在一次成功搜索和不成功搜索中平均搜索桶的个数。对于Vector,有公式: Un ~ ½[1+1/(1 - a)2]

Sn ~ ½[1+1/(1 - a) ]

a = n/b。

slide86
空间复杂度:桶的个数对于散列的性能有着重大的影响。当D为素数或没有小于20的素数因子时,可以使性能达到最佳。
  • 可以通过上述公式确定最大的a值。根据估计的n值。就可以得到的b最小值,然后选择一个比大的整数。(素数或没有小于20的素数因子)
  • e.g 有近1000个元素的哈希表。要求成功搜索的次数小于4,不成功次数小于50.5。可以计算得a≦0.9且a ≦ 6/7,故a ≦ 6/7,所以b最小为7n/6 = 1167,所以

选择b = D 37*37=1369

  • e.g 当Tsize最多为530时,D和d最佳选择为23*23 = 529 。
slide87
20 ˆ

55

14

27

79 ˆ

68 ˆ

01

19

10

23 ˆ

11 ˆ

68 ˆ

0

1

2

3

4

5

6

7

8

9

10

11

12

ˆ

ˆ

ˆ

ˆ

ˆ

ˆ

ˆ

拉链法 (List)
  • 表中的每一个地址都只是这一个链接或列表,这种表永远不会出现溢出现象。新加入的关键字,仅仅是把它连接到表中的相应位置。
  • 对于短链表,这是一个很快的方法。但是随着链接长度的增加,会明显的降低检索时的性能。需要对列表中的内容进行排序能够提高执行性能。
  • 对于大多数情况下,不需要彻底查找就能得到结果
slide88
template
  • class ChainHash Table{
  • public:
  • ChainHast Table(int divisor = 11 )
  • {D = divisor;
  • ht = new SortedChain[D];}
  • ~ChainHast Table(){delete[]ht;}
  • bool Search(const K&k,E&e ) const
  • {return ht[k%D].Search(k,e);}
  • ChainHash Table&Insert(const E&e)
  • {ht[e%D].Distinclnsert(e);
  • return *this;}
  • ChainHash Table&Delete(const K&k , E&e)
  • {ht[k%D].Deltet(k,e);
  • return *this;}
  • void Output()const; //输出哈希表
  • private:
  • int D; //位置数
  • SortChain<>*ht; //链表数组
slide89
聚结拉链
  • 在拉链中使用线性探查的方法。未发生冲突的一个关键字找到第一个可用的地址,并且把这个地址的所以跟表中的一个关键字保存在一起。这种方式可以通过列表的链接直接访问到下一个元素,而避免了顺序查找。表中每个位置pos保存两个成员:关键字Info和指向下一个元素的指针Index。
  • 这种方法减少了空间的使用,但是标的长度限制了表中储存关键字的个数。
vector list
Vector和List
  • Sn = 1+ (n-1)/2b ~ 1+ a/2
  • Un ~ a+1/2 ,a>>1
  • 使用链表式的平均性能优于线性开址寻址。例如当a=0.9时,在链表中一次不成功搜索,平均要检查0.9个元素。一次成功搜索需要检查1.45个元素。对与线性开址寻址来说,不成功需要检查50.5个元素,成功时需要5.5个元素
slide91
哈希表的优劣
  • 时间
  • 空间
slide92
理想哈希函数
  • Cichelli算法。 用来散列数量相对较少的保留字。

h(word) = (length(word))+g(firstletter(word))+g(lastword(word)))modTsize

g就是要构造的函数。

  • 算法分为三部分:首先计算字母的出现次数,然后对单词进行排序,最后搜索可能的最小理想哈希函数。最后一步是算法的核心。

“When all else fails,

try brute force.”

slide93
FHCD算法

Thomas Sager提出对Cichelli算法的改进。

h(word) = h0(word)+g(h1(word))+g(h2(word))

g就是要构造的函数

h0 = (T0(c1)+……+T0(cm))mod n

h1 = (T1(c1)+……+T1(cm))mod r

h2 = ((T2(c1)+……+T2(cm))mod r ) + r

slide94
应用
  • 计算单词频率
  • 数据压缩
slide95
总结
  • 哈希表的定义
  • 构造哈希函数
  • 冲突解决方法
  • 哈希表的优劣
slide96
搜索推荐题目

http://acm.hit.edu.cn/forum

slide97
搜索查找

竞赛中的基本算法。

以时间复杂度为基准,选择合适的搜索算法。

Thank You!

2005.11.20

ad