排序与查找

一.排序

1.非线性排序

   a.归并排序

      1)基本思想:将待排序元素分成大小大致相同的2个子集合,分别对2个子集合进行排序,最终将排好序的子集合合并成为所要求的排好序的集合。

      2)Step1: 把待排序的n个记录看作长度为1的有序序列。将相邻子序列两两归并为长度为2或1的有序序列;

         Step2 :把得到的n/2个长度为2的有序子序列再归并为长度为 2*2 的有序序列;

         Step3 :按Step2的方式,重复对相邻有序子序列进行归并操作,直到成为一个有序序列为止。 

         动画演示:http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?id=93

       3)代码            

 void merge_sort(int data[], int left, int right)
  {
       if(left< right)
       {
          int mid = (left+ right) / 2;
          merge_sort(data, left, mid);
          merge_sort(data, mid+1, right);
          merge(data, left, mid, right);
        }
  }


void merge(int array[], int p, int q, int r)
 {     
   int i,k;    int begin1,end1,begin2,end2;
   int* temp = new int [r-p+1]; 
//申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
//设定两个指针,最初位置分别为两个已经排序序列的起止位置
   begin1= p;     end1 = q;     
   begin2 = q+1;  end2 = r;  
   k = 0;
     while((begin1 <= end1)&&( begin2 <= end2)) 
    { if(array[begin1]<array[begin2])
         {temp[k] = array[begin1];  begin1++; 
         }
         else
         {temp[k] = array[begin2];  begin2++;
          }
         k++;        
     } 
     //若第一个序列有剩余,拷贝出来粘到合并序列尾 
     while(begin1<=end1) 
         temp[k++] = array[begin1++]; 
       //若第二个序列有剩余,拷贝出来粘到合并序列尾         
while(begin2<=end2) temp[k++] = array[begin2++];
for (i = 0; i < (r - p +1); i++) //将排序好的序列拷贝回数组中 array[p+i] = temp[i]; delete[] (temp); }

     4)复杂度

          O(nlogn) 渐进意义下的最优算法

   b.起泡排序

     1)起泡排序基本思想: 设数组a中存放了n个数据元素,循环进行n-1趟如下的排序过程: 依次比较相临两个数据元素,若a[i]>a[i+1],则交换两个数据元               素,否则不交换。 当完成一趟交换以后,最大的元素将会出现在数组的最后一个位置。 依次重复以上过程,当第n-1趟结束时,整个n个数据元素集合中次             小的数据元素将被放置在a[1]中,a[0]中放置了最小的数据元素。

     2)伪代码          

1 // 将a中整数序列排列成自小至大有序的序列void bubblf_sort(array a[])
2  {  for(i=1,i<array.length;++i)   //比较趟数
3      for(j=0;j<array.length-i;++j)
4         if(a[j]>a[j+1])
5         a[j] ←→ a[j]; 
6 }

    c.快速排序

       1)基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据             分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列(动画演示http://ds.fzu.edu.cn/fine/resources/FlashContent.asp?             id=86)

        2)算法过程:指定枢轴(通常为第一个记录) 通过一趟排序将以枢轴为中心,把待排记录分割为独立的两部分,使得左边记录的关键字小于枢轴值,右边记录              的关键字大于枢轴值 对左右两部分记录序列重复上述过程,依次类推,直到子序列中只剩下一个记录或不含记录为止。

        3)算法步骤:

             step1:分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。                step2:递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。

             step3:合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。

         4)代码           

 1 void quicksort(int number[], int left, int right) 
 2   { int q; 
 3     if(left < right)
 4     {  q=partition(number, left, right); //分解
 5         quicksort(number, left, q-1); //排序
 6         quicksort(number, q+1, right); 
 7     } 
 8 }
 9 int partition(int number[], int left, int right)
10  { int s; 
11     s = number[left]; 
12    while(left<right)  
13    { // 从表的两端交替地向中间扫描
14      while(number[right]>s)      right--;
15      while(number[left]<s)         left++;
16     SWAP(number[left], number[right]);  
17    }
18    number[left]=s; // 枢轴记录到位
19    return left; // 返回枢轴位置
20  }
21 
22 #define MAX 10 
23 #define SWAP(x,y) {int t; t = x; x = y; y = t;} 
24 void main()
25  { int number[MAX] = {0};    int i;  
26     srand(time(NULL)); 
27     cout<<"排序前:"; 
28     for(i = 0; i < MAX; i++) 
29     { number[i] = rand() % 100; 
30       cout<<setw(6)<<number[i]; 
31     }     
32     quicksort(number, 0, MAX-1);     
33     cout<<"\n排序后:"; 
34     for(i = 0; i < MAX; i++) 
35     cout<<setw(6)<<number[i]; 
36     cout<<"\n"; 
37 } 
View Code

         5) 复杂度               

                  对于n个成员,快速排序法的比较次数大约为n*logn 次,交换次数大约为(n*logn)/6次。如果n为100,冒泡法需要进行4950 次比较,而快速排序                 法仅需要200 次。快速排序法的性能与中间值的选定关系密切,如果每一次选择的中间值都是最大值(或最小值),该算法的速度就会大大下降。 快速                 排序算法最坏情况下的时间复杂度为O(n2),而平均时间复杂度为O(n*logn)

2.线性排序

   a.计数排序

      1)解释:

               集合A有n个元素,每一个元素的值是介于0到k之间的整数。

               输入:A[1..n] 存放排序结果:B[1..n] 临时存储: C[0..k](存储数组每个坐标做对应的数值的个数)

               计数排序的基本思想是对每一个输入元素x,确定出小于x的元素个数。有了这一信息,就可以把x直接放到它在最终输出数组 的位置上。例如:若有5个元素小于x,则x就属于第6个输 出位置 。

      2)代码:           

Count-Sort(A,B,k) //0<=a[i]<=k
for i←0 to k
do  C[i] ←0                                                   
for j← 1 to length[A]
do    C[A[j]]← C[A[j]] +1
for i← 1 to  k
do    C[i]← C[i] +C[i-1]
for j←length[A] downto 1
do    B[C[A[j]]] ←A[j]
      C[A[j]]← C[A[j]] -1

 

    b.基数排序

       1)背景

           基数排序是指用多关键字的“最低位优先”方法排序,即对待排序的记录序列按照关键字从低位到高位的顺序交替地进行“分组”,“收集”,最终得到有序的记录序列。将一次“分组”,“收集”称为一趟。对于由 d位关键字组成的复合关键字,需要经过d趟的“分配”与“收集”。 在基数排序的“分配”与“收集”操作过程中,为了避免数据元素的大量移动,通常采用链式存储结构存储待排序的记录序列 。

           适用于:要求对n个数的序列L={a1, a2 , …,an}进行排序,其中每个数恰好由k位数字组成,每位数字均取自{0,1,…,9}。  

       2)过程

            基数排序的“分配”与“收集”过程 第一趟 :

            

            基数排序的“分配”与“收集”过程 第二趟 :

             

             基数排序的“分配”与“收集”过程 第三趟 :

               

             3)特点              

                  基数排序适用于待排序的记录数目较多,但其关键字位数较少,且关键字每一位的基数相同的情况.若待排序记录的关键字有d位就需要进行d次"分配"与"收集",即共执行d趟,因此,若d值较大,基数排序的时间效率就会随之降低.基数排序是一种稳定的排序方法.

       c.桶排序

          1)背景

               桶排序的思想是把[0,1)划分为n个大小相同的子区间,每一子区间是一个桶。然后将n个记录分配到各个桶中。因为关键字序列是均匀分布在[0,1)上的,所以一般不会有很多个记录落入同一个桶中。由于同一桶中的记录其关键字不尽相同,所以必须采用关键字比较的排序方法(通常用插入排序)对各个桶进行排序,然后依次将各非空桶中的记录连接(收集)起来即可。

          2)适用于

               这种排序思想基于以下假设:假设输入的n个关键字序列是随机分布在区间[0,1)之上。若关键字序列的取值范围不是该区间,只要其取值均非负,我们总能将所有关键字除以某一合适的数,将关键字映射到该区间上。但要保证映射后的关键字是均匀分布在[0,1)上的。

          3)过程

               

           4)代码                

  Bucket-Sort(A)
 {
      n=length[A]
      for(i=1;i<=n;i++) //分配过程.
      将A[i]插入到桶B[「n(A[i].key)」]中; 
      for(i=0;i<n;i++) //排序过程当B[i]非空时用插人排序将B[i]中的记录排序;
      for(i=0;i<n;i++) //收集过程若B[i]非空,则将B[i]中的记录依次输出到R中; 
}

各种算法总结

         

二.查找

方法:二分查找

a.背景-给定已按升序排好序的n个元素a[1:n],现要在这n个元素中找出一特定元素x。

b.分析-适用分治法求解问题的基本特征:

                该问题的规模缩小到一定的程度就可以容易地解决;

                该问题可以分解为若干个规模较小的相同问题;

                分解出的子问题的解可以合并为原问题的解;

                分解出的各个子问题是相互独立的。

            很显然此问题分解出的子问题相互独立,即在a[i]的前面或后面查找x是独立的子问题,因此满足分治法的第四个适用条件

c.二分检索原理

            将二分检索问题问题表示为:I=(n, a1, … , an, x) 选取一个下标k,可得到三个子问题: I1=(k-1, a1, … , ak-1, x) I2=(1, ak , x) I3=(n-k, ak+1, … , an, x)

d.问题描述

   已知一个按非降次序排列的元素表a1,a2,…,an,判定某个给定元素x是否在该表中出现,若是,则找出该元素在表中的位置,并置于j,否则,置j为0。

   一般解决办法:从小标0到最后依次查找,成功和不成功最坏情况下需要O(n)。

e.二分搜索算法代码    

 1 public static int binarySearch(int [] a, int x, int n)
 2 {
 3   // 在 a[0] <= a[1] <= ... <= a[n-1] 中搜索 x
 4   // 找到x时返回其在数组中的位置,否则返回-1
 5   int left = 0; int right = n - 1;
 6   while (left <= right)
 7  { 
 8      int mid = (left + right)/2; // mid = low + ((high - low) / 2); 
 9      if (x == a[mid]) return mid;
10      if (x > a[mid]) left = mid + 1;
11      else right = mid - 1;
12   }
13   return -1; // 未找到x
14 }

二分搜索递归代码

 1 //初始 left = 0; right = N - 1;
 2 public static int binarySearch(int a[], int x, int left ,int right)
 3 {
 4    // 找到x时返回其在数组中的位置,否则返回-1
 5    if (left>right) return -1;// 未找到x
 6    else
 7    {
 8        int mid = (left + right)/2; 
 9        if (x == a[mid])   return mid;
10        if (x > a[mid])   return binarySearch(a, x, mid+1,right);
11        else return binarySearch( a,x,left,mid-1);
12    }
13 }

f.二分搜索的时间复杂度

  若n在区域[2k-1, 2k)中,则对于一次成功的检索,二分检索算法至多作k次比较。

 

posted @ 2013-10-27 21:29  石沉溪涧  阅读(1140)  评论(0编辑  收藏  举报