数据结构复习笔记(4)

  1. 云栖社区>
  2. 博客>
  3. 正文

数据结构复习笔记(4)

嗯哼9925 2017-12-27 15:11:00 浏览661
1, 归并排序无论初始序列如何排列,记录的比较次数不会受到影响,都是O(nlogn),但会影响到记录的移动次数,初始序列为正序时,记录移动次数为0,为逆序时,记录移动次数最大。

2, 若在1000000个记录中找出两个最小的记录,应该用什么排序方法所需要的关键字比较次数最少,是多少?

解:用堆排序方法。从n个记录中找出最小的记录,至少要n-1次,而将这n个记录构造成一个堆后,在[logn]个失败者中继续找出优胜者只需要再作[logn]-1次比较就可以了。所以,比较次数为:n+[logn]-2

3, 对50个整数进行快速排序,需要关键字间进行比较的次数的最小值和最大值是多少?

解:初始记录有序或基本有序时,快速排序蜕化为起泡排序,比较次数最大。若每次都分割成长度相等或长度仅相差1的子序列时,比较次数最小。而每次划分所进行比较的次数是该子序列中关键字个数减1,因此通过快速排序的判定树可以求出总比较次数。

最大比较次数:49+48+。。。+1=1225

最小比较次数:

49+23+24+10+11+11+11+4+4+4+5+4+5+4+5+1+1+1+1+1+1+1+2+1+1+1+2+1+1+1+2=193

4, 当n=7时,快速排序最好情况下要多少次比较?给出一个实例。最坏情况下要多少次比较?给出一个实例。

解:(1)最好情况下,每一趟快速排序后都能够划分出左右两个相等的区间,即设长度为n=2*k-1,则第一趟快排后划分得到两个长度都是n/2的子表,第二次快排后得到4个n/4的子表,。。。总共需要进行k=log(n+1)次划分,当子表长度为1时排序结束。因此,当n=7时,k=3.也就是说最好情况下第一个元素由两头向中间扫描到正中位置,就是说需要和其余6个元素都进行比较后找到最终位置,所以需要比较6次。第二趟分别对左,右两个子表(长度都是3,即k=2)进行排序,也就是需要与子表中其余2个元素进行比较,所以两个子表要比较4次,并且划分出的子表长度都为1,所以排序结束。所以共需要比较10次。实例:4,7,5,6,3,1,2

(2)最坏情况,每趟用来划分的基准元素总是定位于表的第一个位置或最后一个位置,这样划分出的左,右子表一个长度为0,另一个长度是原表长度减1,就退化为起泡排序,所以比较次数为6+5+4+3+2+1=21次。实例:1,2,3,4,5,7或7,6,5,4,3,2,1

5, 若只想得到1000个元素组成的序列中第5个最小元素之前的部分排序的序列,用什么方法最快?(1)起泡 (2)快速 (3)希尔 (4)堆排序 (5)简单选择

答:快速排序。它可以每次对第一个子序列进行划分,直到子序列长度小于等于4,若长度不足4,则对第二个子序列划分出相应长度的子序列就行了。

 
6,  对任意的7个关键字进行排序,至少要进行多少次关键字之间的两两比较?
答:任何一个借助“比较”进行排序的算法,在最坏情况下需要进行的比较次数至少为[log(n!)],所以要进行15次。 
7, 非递归的归并排序的运行时间:(1)n个数据全部有序。(2)n个数据全部逆向有序。(3)随机输入n个数据。

答:数据比较次数与初始序列无关,而移动次数与初始序列有关。

(1)比较次数O(nlogn),移动次数0。(2)比较次数O(nlogn),移动次数O(nlogn)。

(3)比较次数O(nlogn),移动次数O(nlogn)。

8,最简单的排序方法是冒泡排序方法。这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我们要对这个“气泡”序列处理若干遍。所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确。如果发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置。显然,处理一遍之后,“最轻”的元素就浮到了最高位置;处理二遍之后,“次轻”的元素就浮到了次高位置。在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。一般地,第i遍处理时,不必检查第i高位置以上的元素,因为经过前面i-1遍的处理,它们已正确地排好序。这个算法可实现如下。


 procedure Bubble_Sort(var L:List);
var
i,j:position;
begin
1 for i:=First(L) to Last(L)-1 do
2  for j:=First(L) to Last(L)-i do
3     if L[j]>L[j+1] then 
4           swap(L[j],L[j+1]);   //交换L[j]和L[j+1]
end;

   上述算法将较大的元素看作较重的气泡,每次最大的元素沉到表尾。其中First(L)和Last(L)分别表示线性表L的第一个元素和最后一个元素的位置,swap(x,y)交换变量x,y的值。上述算法简单地将线性表的位置当作整数用for循环来处理,但实际上线性表可能用链表实现;而且上述算法将线性表元素的值当作其键值进行处理。不过这些并不影响表达该算法的基本思想。今后如果不加说明,所有的算法都用这种简化方式表达。

   容易看出该算法总共进行了n(n-1)/2次比较。如果swap过程消耗的时间不多的话,主要时间消耗在比较上,因而时间复杂性为O(n2)。但是如果元素类型是一个很大的纪录,则Swap过程要消耗大量的时间,因此有必要分析swap执行的次数。

   显然算法Bubble_Sort在最坏情况下调用n(n-1)/2次Swap过程。我们假设输入序列的分布是等可能的。考虑互逆的两个输入序列L1=k1,k2,..,kn和L2=kn,kn-1,..,k1。我们知道,如果ki>kj,且ki在表中排在kj前面,则在冒泡法排序时必定要将kj换到ki前面,即kj向前浮的过程中一定要穿过一次ki,这个过程要调用一次Swap。对于任意的两个元素ki和kj,不妨设ki>kj,或者在L1中ki排在kj前面,或者L2在中ki排在kj前面,两者必居其一。因此对于任意的两个元素ki和kj,在对L1和L2排序时,总共需要将这两个元素对调一次。n个元素中任取两个元素有Cn2 种取法,因此对于两个互逆序列进行排序,总共要调用Cn2 =n(n-1)/2次Swap,平均每个序列要调用n(n-1)/4次Swap。那么算法Bubble_Sort调用Swap的平均次数为n(n-1)/4。

   可以对冒泡算法作一些改进,如果算法第二行的某次内循环没有进行元素交换,则说明排序工作已经完成,可以退出外循环。可以用一个布尔变量来记录内循环是否进行了记录交换,如果没有则终止外循环。

   冒泡法的另一个改进版本是双向扫描冒泡法(Bi-Directional Bubble Sort)。设被排序的表中各元素键值序列为:

   483 67 888 50 255 406 134 592 657 745 683

   对该序列进行3次扫描后会发现,第3此扫描中最后一次交换的一对纪录是L[4]和L[5]:

   50 67 255 134 | 406 483 592 657 683 745 888

   显然,第3次扫描(i=3)结束后L[5]以后的序列都已经排好序了,所以下一次扫描不必到达Last(L)-i=11-4=7,即第2行的for 循环j不必到达7,只要到达4-1=3就可以了。按照这种思路,可以来回地进行扫描,即先从头扫到尾,再从尾扫到头。这样就得到双向冒泡排序算法:


 procedure Bi-Directional_Bubble_Sort(var L:List);
var
low,up,t,i:position;
begin
1  low:=First(L);up:=Last(L);
2  while up>low do
    begin
3     t:=low;
4     for i:=low to up-1 do
5       if L[i]>L[i+1] then
          begin
6           swap(L[i],L[i+1]);
7           t:=i;
          end;
8     up:=t;
9     for i:=up downto low+1 do
10      if L[i]< L[i-1] then
          begin
11          swap(L[i],L[i-1]);
12          t:=i;
          end;
13    low:=t;   
    end; 
end;


   算法利用两个变量low和up记录排序的区域L[low..up],用变量t 记录最近一次交换纪录的位置,4-7行从前向后扫描,9-12行从后向前扫描,每次扫描以后利用t所记录的最后一次交换记录的位置,并不断地缩小需要排序的区间,直到该区间只剩下一个元素。
   直观上来看,双向冒泡法先让重的气泡沉到底下,然后让轻的气泡浮上来,然后再让较大气泡沉下去,让较轻气泡浮上来,依次反复,直到排序结束。

9,二路插入排序算法


#include <stdio.h>
#include <stddef.h>
#define ARR_SIZE 10
/* 函数原型 */
void bidir_insert( int keys[], int temp[], const size_t i );
int main(void)
{
    size_t i;
    int keys[ARR_SIZE] = { 1050, 100, 150, 20, 9000, 5110, 3008, 1450, 5220, 500 };
    int temp[ARR_SIZE];  /* 辅助数组 */
    
    /* 进行二路插入排序 */
    bidir_insert(keys, temp, ARR_SIZE);
    /* 输出排序结果 */
    for ( i = 0; i < ARR_SIZE; ++i ) {
        printf("%d ", keys[i]);
    }
    
    printf("\nPress ENTER to quit\n");
    getchar();
    return 0;
}
/* Begin of bidir_insert 05-11-12 12:00 */
void bidir_insert( int keys[], int temp[], const size_t arr_size )
{
     size_t i, first, final = first = 0;
     
     temp[0] = keys[0]; /* 将第一个元素放入辅助数组 */
     /* 利用辅助数组 temp 进行二路插入排序 */
     for ( i = 1; i < arr_size; ++i ) {
         if ( temp[final] <= keys[i] ) {             
             temp[++final] = keys[i];
         } else if ( keys[i] <= temp[first] ) {
             first = (first - 1 + arr_size) % arr_size;
             temp[first] = keys[i];
         } else {
             size_t index;
             
             for ( index = (final - 1 + arr_size) % arr_size; ;
                   index = (index - 1 + arr_size) % arr_size ) {
                 if ( temp[index] <= keys[i] ) {
                     size_t mid = final++;
                     /* 元素后移 */
                     while ( mid != index ) {
                         temp[(mid + 1) % arr_size] = temp[mid];
                         mid = (mid - 1 + arr_size) % arr_size;
                     }
                     temp[(index + 1) % arr_size] = keys[i];
                     
                     break;
                 }
             }
         }
     }
     
     /* 将 temp 的内容按顺序复制到 keys 中 */
     for ( i = 0; i < arr_size; ++i ) {
         keys[i] = temp[first];
         first = (first + 1) % arr_size;
     }
}  /* End of bidir_insert */


10,计数排序法

   前面提到过,对于使用比较法作为算法基础的算法来说,最快需N * log(N)步才能完成排序。计数排序法不作用比较,所以它不受此限制。实际上该算法是如此之快,以致于你会认为是使用了魔术,而不是数学运算来排序。

   另一方面,计数排序法只能用于特殊的情况。首先,所有的要进行排序的数据必须是整数,不能对字符使用该算法;其次,数据的范围有限,如果你的数据是在1到1000之内,用这种算法的效果就非常好,但如果你的数据是在1到30000之间,该算法就根本不能用。

首先该算法创建一个整数类型的临时数组,该数组的上下标分别是要排序的数据的最大最小值。如果数据列表的最大最小值从min_item到max_item, 该算法就将数组创建成下面这样:


   Dim Counts() As Integer

 

    ReDim Counts(min_item To max_item)

   接下来,算法检查列表中的每一个项目,并增加对应该项目的数组元素的值,当这一阶段完成后,Counts(I)的数值就是就是数值为I的基础上的个数。

    For I = min To Max

        Counts(List(I)) = Counts(List(I)) + 1

    Next I

程序扫描Counts数组,将counts转换成被排序列表中的偏移量。

    next_spot = 1

    For i = min_value To max_value

        this_count = counts(i)

        counts(i) = next_spot

        next_spot = next_spot + this_count

    Next i

最后将数据进行排序

下面是实现该算法的VB代码:


Sub Countingsort (List() As Long, sorted_list() As Long, _
    min As Integer, max As Integer, min_value As Long, _
    max_value As Long)
Dim counts() As Integer
Dim i As Integer
Dim this_count As Integer
Dim next_offset As Integer

    ' Create the Counts array.
    ReDim counts(min_value To max_value)

    ' Count the items.
    For i = min To max
        counts(List(i)) = counts(List(i)) + 1
    Next i

    ' Convert the counts into offsets.
    next_offset = min
    For i = min_value To max_value
        this_count = counts(i)
        counts(i) = next_offset
        next_offset = next_offset + this_count
    Next i

    ' Place the items in the sorted array.
    For i = min To max
        sorted_list(counts(List(i))) = List(i)
        counts(List(i)) = counts(List(i)) + 1
    Next i
End Sub



本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2006/09/20/509835.html,如需转载请自行联系原作者