数据结构与算法分析c  第二版 数据结构与算法第2版_Powered by 金山文档


一、排序

(一)定义

排序方法可以分为5种方法,即插入排序、选择排序、交换排序、分配排序、归并排序。一个具体的排序算法属于以上5种算法中的哪一种不是唯一的。

排序中将结点称为记录,每个记录有一个排序码,将一系列结点构成的线性表称为文件。排序运算是将文件中的记录按排序码排成非递减(或非递)序列。

评价排序算法性能的重要指标,一个是算法执行的时间,另外一个是算法执行所需要的内存空间。其中,时间开销是衡量一个排序算法

(二)直接插入排序

  1. 先将第一个记录看作是一个有序的记录序列,然后从第二个记录开始,依次将未排序的记录插入到这个有序的记录序列中去,直到整个文件中的全部记录排序完毕。
  2. 比较次数和时间复杂度

由于将A[i]放入之前已经排好的有序序列时,是将其放在A[0:i-1]的后面也就是序号为i的位置。最好情况下,第i趟插入发生在A[i]处,也就是刚刚开始放入的位置,这时总的比较次数为n-1次;最坏情况下,每一趟插入发生在A[0]处,需要将这个元素从末尾移动到最前面,这时总的比较次数为为n(n-1)/2。

第i趟平均需要i/2次比较,因而总的比较次数为1/2+2/2+...+(n-1)/2=n(n-1)/4,时间复杂度为O(n*n)。


数据结构与算法分析c  第二版 数据结构与算法第2版_Powered by 金山文档_02


  1. 稳定性:稳定

(三)折半插入排序

  1. 使用选择排序的重要前提是原序列是有序的。在已经排序的A[0 : i-1]里为A[i]寻找插入位置,将A[i]与A[m=(i-1 + 0)/2] 比较,若A[i]<A[m]则在A[0 : m-1]里寻找插入位置;否则在A[m+1 : i-1]里寻找插入位置。
  2. 例题


数据结构与算法分析c  第二版 数据结构与算法第2版_结点_03


3. 比较次数

总的比较次数约为:

数据结构与算法分析c  第二版 数据结构与算法第2版_结点_04

当插入A[i]时,若

数据结构与算法分析c  第二版 数据结构与算法第2版_结点_05

时,则比较次数为j =

数据结构与算法分析c  第二版 数据结构与算法第2版_时间复杂度_06

;若

数据结构与算法分析c  第二版 数据结构与算法第2版_算法_07

,比较次数约为j+1。

数据结构与算法分析c  第二版 数据结构与算法第2版_时间复杂度_08


(四)Shell排序

  1. 把待排序的n个记录分成si个组,距离为si的记录为一个组,对每一组进行排序。si逐渐变小。
  2. 例题


数据结构与算法分析c  第二版 数据结构与算法第2版_数据结构_09


  1. 代码
void ShellSort(ForSort A[], int n, int s)
{
    int i, j, k; 
    ForSort temp;
    for(k = s; k > 0; k >>= 1 )  // 每次排序结束后,会将k的值除以2
    {
        for(i = k; i < n; i ++) // 对于每一组来说,是从末尾开始进行比较的
        {
            temp = A[i]; j = i - k;
            // 对每一组进行排序
            while(j >= 0 && temp.key < A[j].key)
            {
                A[j+k] = A[j]; j -= k;
                A[j+k] = temp;
            }
        }
    }
}
  1. 稳定性:不稳定

(五)直接选择排序

  1. 每次从A[i : n-1]中选出排序码最小的记录A[k],放在已排序的记录A[0 : i-1]的后面(交换A[i]与A[k])。
  2. 例题


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_10


3. 比较次数

总比较次数为:n(n-1)/2

最好情况下,待排序记录已按非递减排好序,此时移动次数为0次; 最坏情况下,待排序记录已按非递增序排好,此时的移动次数为3(n-1)。


数据结构与算法分析c  第二版 数据结构与算法第2版_时间复杂度_11


4. 稳定性:不稳定

(六)树形选择排序

  1. 把待排序的n个记录的排序码两两进行比较,取出[n/2]个较小的排序码作为作为结果保存下来,这n/2]个排序码进一步两两进行比较,重复上述过程直到得到最小的排序码。
  2. 例题

如图:


数据结构与算法分析c  第二版 数据结构与算法第2版_结点_12


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_13


3. 比较次数与选择次数

选择次数:总共需要n-1次选择。

比较次数:第1次选择进行n-1次比较, 第2次~第n-1次选择每次需要[log2(n)]次比较,总共需要(准确来说n-1+(n-2)*

数据结构与算法分析c  第二版 数据结构与算法第2版_结点_14

)

数据结构与算法分析c  第二版 数据结构与算法第2版_算法_15

(七)冒泡排序

  1. for j=0, …, n-2:若A[j]>A[j+1]则两者交换
  2. 比较次数与移动次数


数据结构与算法分析c  第二版 数据结构与算法第2版_数据结构_16


  1. 稳定性:稳定
  2. 代码:
void BubbleSort(ForSort A, int n)
{
  int i, j;
  Bool flag;
  ForSort temp;

  for(i = n-1; flag=(Bool)1; i>0 && flag; i--)
  {
    flag = FALSE;
    for(j=0; j<i; j++)
    {
      if(A[j+1].key < A[j].key)
      {
        flag = TRUE;
        temp = A[j+1].key;
        A[j+1].key = A[j].key;
        A[j].key = temp;
      }
    }
  }
}

(八)快速排序

  1. 从待排序记录中任选一个记录,以这个记录的排序码作为中心值,将其它记录划分为两个部分,第一部分包含所有排序码小于等于中心值的记录,第二部分包含所有排序码大于中心值的记录。
  2. 例题


数据结构与算法分析c  第二版 数据结构与算法第2版_结点_17


  1. 代码
void QSort(int A[], int low, int high)
{
    int i, j;
    if(low >= high) return ;
    i = low, j = high, temp = A[i]; // temp是参考的值

    while(i<j)
    {
        while(i<j && A[j] > temp) j -- ;
        if(i<j) {A[i] = A[j], i ++;} // 从尾部找比参考值大的元素,交换之后这个i位置的元素暂时不需要参与这轮交换

        while(i<j && A[i] < temp) i ++ ;
        if(i<j) {A[j] = A[i], j --;} // 从开头找比参考值小的元素
    }

    A[i] = temp;
    Qsort(A, low, j - 1); // 对参考值右边部分进行排序
    QSort(A, i + 1, high); // 对参考值左边部分进行排序
}

void QSort(int A[], int n)
{
    QSort(A, 0, n - 1);
}

4. 比较次数与时间复杂度


数据结构与算法分析c  第二版 数据结构与算法第2版_时间复杂度_18

最好情况每次选取的中心值恰好将其它记录分成大小相等(或相差一个记录)的两个部分:

数据结构与算法分析c  第二版 数据结构与算法第2版_结点_19

最坏情况下,待排序文件已经排好序:n(n-1)/2

时间复杂度:


数据结构与算法分析c  第二版 数据结构与算法第2版_时间复杂度_20


5. 稳定性:不稳定

(九)基数排序

  1. 排序时,先按最低位
  2. 数据结构与算法分析c  第二版 数据结构与算法第2版_算法_21

  3. 的值从小到大将待排序的记录分配到r个队列中,队列中的记录按分配进来的先后排放,然后依次收集这些记录。再将收集起来的序列按
  4. 数据结构与算法分析c  第二版 数据结构与算法第2版_算法_22

  5. 进行分配和收集,如此反复,直到按
  6. 数据结构与算法分析c  第二版 数据结构与算法第2版_结点_23

  7. 进行分配后,收集起来的序列就是排序后的有序序列。

最低位优先:从低位往高位进行分配和收集; 最高位优先:从高位往低位进行分配和收集。

2.执行基数排序算法时,可采用顺序存储结构,用一个数组存放待排序的n个记录,用r个数组存放分配时所需的r个队列,每个队列最大需要n个记录的空间。每分配一次,需要移动n个记录,每收集一次也需要移动n个记录,d趟分配和收集需要移动2*d*n个记录,且需要r*n个附加的记录空间。

采用链式存储结构,将移动记录改为修改指针,则可克服时间和空间消耗问题。基数排序的时间复杂性取决于基数和排序码的长度。每执行一次分配和收集,队列初始化需要O(r)的时间,分配工作需要O(n)的时间,收集工作需要O(r)的时间,即每趟需要O(n+2r)的时间,总共执行d趟,共需O(d(n+2r))的时间。

需要的附加存储空间:每个记录增加一个指针共需要O(n)的空间,以及需要一个分配队列占用O(r)的空间,总的附加空间为O(n+r)。


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_24


3.例题


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_25


(十)归并排序

  1. 可以并行。
  2. 例题


数据结构与算法分析c  第二版 数据结构与算法第2版_结点_26


3. 代码

设计代码的自顶向下逻辑,待排序的数组为ForSort A[],长度为n。 两个有序子序列归并为一个有序序列:TwoWayMerge(); 一趟归并 :每两个相邻有序子序列归并OnePassMerge();MergeSort(ForSort A[], int n)归并排序。

// 将两个子文件合为一个得到答案
void TwoPassMerge(ForSort Dst[], ForSort Src[], int s, int e1, int e2)
{
    int s1, s2;
    for(s1 = s s2 = e1+1; s1 <= e1 && s2 <= e2)
        if( Src[s1].key <= Src[s2].key ) Dst[s++] = Src[s1++];
        else Dst[s++] = Src[s2++];  // compare and move
    if(s1 <= e1) memcpy(&Dst[s], &Src[s1], (e1 - s1 + 1)*sizeof(ForSort));
    else memcpy(&Dst[s], &Src[s2], (e2 - s2 + 1)*sizeof(ForSort));
}

// 将每两个相邻文件合并, 子文件长度为Len
void OnePassMerge(ForSort Dst[], FOrSort Src[], int Len, int n)
{
    int i;
    for(i = 0; i+2*Len <= n; i+=2*Len)
        TwoPassMerge(Dst, Src, i, i+Len-1, i+2*Len);
    if(i<n-Len) TwoPassMerge(Dst[],Src[], i, i+Len-1, n-1);
    else memcpy(&Dst[i], &Src[i], (n-i)*sizeof(ForSort));
}

// 归并排序
void MergeSort(ForSort A[], int n)
{
    int k = 1;
    ForSort* B = (ForSort*)maaloc(sizeof(ForSort)*n);
    while(k<n)
    {
        OnePassMerge(B, A, k, n);
        k <<= 1;
        if(k >= n) memcpy(A, B, n*sizeof(ForSort));
        else {OnePassMerge(A, B, k, n); k <<= 1;}
    }
}

4. 对n个记录进行归并排序,需要调用函数OnePassMerge约log2n次,OnePassMerge的时间复杂性为O(n),最后可能执行n次移动,总的时间复杂性为O(n*log2n)。需要n个附加存储空间。

(十一)表格


最好时间复杂度

最坏时间复杂度

平均时间复杂度

额外空间

稳定性

直接插入排序

O(n)

O(n^2)

O(n^2)

折半插入排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

Shell排序

O(n^1.3)

不稳定

直接选择排序(可以提供有效的中间结果)

O(n^2)

O(n^2)

O(n^2)

不稳定

树形选择排序(可以提供有效的中间结果)

O(nlog2n)

O(n)保留中间结果

冒泡排序(分区交换排序算法)

O(n)

O(n^2)

O(n^2)

快速排序

O(nlog2n)

O(n^2)

O(nlog2n)

O(log2n)

不稳定

基数排序

队列初始化需要O(r)的时间,分配工作需要O(n)的时间,收集工作需要O(r)的时间,即每趟需要O(n+2r)的时间,总共执行d趟,共需O(d(n+2r))的时间

每个记录增加一个指针共需要O(n)的空间,以及需要一个分配队列占用O(r)的空间,总的附加空间为O(n+r)

归并排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(n),n个附加存储空间

堆排序

O(nlog2n)

O(nlog2n)

O(nlog2n)

O(1)

不稳定


二、查找

(一)基本概念

查找是在给定的数据结构中搜索满足条件的结点。 查找也称为检索。 衡量一个查找算法好坏的依据主要是查找过程中需要执行的平均比较次数,或称为平均查找长度。

(二)顺序查找

  1. 基本概念

逐个将每个结点的关键码和待查的关键码值进行比较,直到找出相等的结点或者找遍了所有的结点。 执行顺序查找算法时,被查找的线性表可以是顺序存储或链接存储,对结点没有排序要求。

  1. 复杂性

最好情况时间复杂性为:O(1); 最坏情况下,最后一个结点是要查找的结点,或者表中没有符合条件的结点,时间复杂性为:O(n); 一般每个结点都有相同的查找概率,此时顺序查找的平均长度为(n+ 0)/2,时间复杂性为O(n)。

(三)折半查找

  1. 类比于折半排序,顺序存储且结点排序。
  2. 复杂性

最好情况时间复杂度:O(1);最坏情况时间复杂度:O(

数据结构与算法分析c  第二版 数据结构与算法第2版_Powered by 金山文档_27

);平均情况时间复杂度:O(

数据结构与算法分析c  第二版 数据结构与算法第2版_Powered by 金山文档_28

)。

  1. 比较

查找方法

查找速度

排序

适应结点动态变化

顺序查找

不需要

折半查找

结点顺序排序


(四)分块查找

  1. 索引表包含三个索引项,每个索引项包含它对应的块的最大关键码值,和该块的起始地址。如果既要有较快的查找速度,又要满足元素动态变化的要求,可以采用分块查找算法。


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_29


2. 复杂性

分块查找的平均查找长度(比较次数)由对索引表的平均查找长度和对块的平均查找长度组成。最小值为O(n^0.5)。

3.比较


数据结构与算法分析c  第二版 数据结构与算法第2版_算法_30


(五)散列查找

  1. 基本概念

散列函数(哈希函数),将分散在一个大区间(例如[0, 79999])的关键码值映射到一个较小的区间(例如[0, 9]),用映射后的值做为访问结点的下标。

与散列函数相关联的是一个长度为n的表,称为散列表或哈希表(Hash Table),用来存放结点的数据或数据的引用。散列函数将关键码值映射到[0, n-1]范围内的整数值。

负载因子 α=填入表中的结点数/散列表长度

散列函数经常是多对一的,导致冲突(碰撞),具有相同散列值的关键码值称为同义词。

  1. 冲突发生解决方法:开放寻址法和链表地址法,线性探测法解决冲突可能产生堆积。

但是注意冲突和堆积是不一样的,冲突是和同义词有关的,而堆积是一些不具有相同散列值的关键值码占用了空间。解决堆积方法:可采用双散列函数探测法。

独立链表地址法是查找效率最好的解决冲突的方法,是解决冲突的首选方法。

3. 平均查找长度

例子:

在地址空间为0~16的散列区中,对以下关键字序列构造两个哈希表:

(Jan, Feb, Mar, Apr, May, June, July, Aug, Sep, Oct, Nov, Dec)的首字母对应的序号为{10, 6, 13, 1, 13, 10, 10, 1, 19, 15, 14, 4}


Jan

Feb

Mar

Apr

May

June

July

Aug

Sep

Oct

Nov

Dec

i

10

6

13

1

13

10

10

1

19

15

14

4

H(x)

5

3

6

0

6

5

5

0

9

7

7

2


1)线性探测开放定址法

Y:查找成功;N:查找不成功。


地址

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

H(x)

0

0

2

3

5

6

6

5

5

9

7

7

月份

Apr

Aug

Dec

Feb

Jan

Mar

May

June

July

Sep

Oct

Nov

Y

1

2

1

1

1

1

2

4

5

2

5

6

N

5

4

3

2

1

9

8

7

6

5

4

3

2

1

1

1

1


查找成功平均查找长度:

ASL = (1*5+2*3+4*1+5*2+6*1)/12 = 31/12

不成功时平均查找长度:

ASL = (5+4+3+2+1+9+8+7+6+5+4+3+2+1+1+1+1)/ 17= 63/17

2)单独链表地址法


0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Aug(1)

Dec(1)

Feb(1)

July(1)

May(1)

Nov(1)

Sep(1)

Apr(2)

June(2)

Mar(2)

Oct(2)

Jan(3)



Jan

Feb

Mar

Apr

May

June

July

Aug

Sep

Oct

Nov

Dec

i

10

6

13

1

13

10

10

1

19

15

14

4

H(x)

5

3

6

0

6

5

5

0

9

7

7

2

成功

3

1

2

2

1

2

1

1

1

2

1

1


查找成功平均查找长度:

ASL = (3*1 + 2*4 + 1*7)/12 = 3/2 = 1.5

不成功时平均查找长度:

ASL = (2+1+1+3+2+2+1)/ 17 = 12/17(拿0号地址来举例,一直要查到最后一个才能确定查找失败,也就是需要查找两次!该式子的分母是哈希表的长度!)