数据结构(排序二)

希尔排序

  这里内部用了直接插入的方法;

  • 将待排序的序列按某种规则分成几个子序列(这个规则就是增量,增量一般选序列长度的一半,然后逐半递减,直到最后增量为1,为1时就相当于直接插入)
  • 分别对几个子序列进行直接插入排序
  • 就是在直接插入的基础上增加了跨度(增量),跨度逐渐减半,当跨度为1时已经完成排序
  • 每次隔跨度直接插入法排序完成,表示记录隔跨度有序,当跨度不断缩小,成为1时也就表示相邻之间有序,也就是已经排序完成
 1 void HashSort(int a[],int n){
 2     int i,j,temp;
 3     int gap=n;
 4     do{
 5         gap=gap/3+1;
 6         for(i=gap;i<n;i++){
 7             if(a[i]<a[i-gap]){
 8                 temp=a[i];
 9                 //注:这里要手动检测越界问题 
10                 for(j=i-gap;a[j]>temp && j>=0;j-=gap)
11                     a[j+gap]=a[j];
12                 a[j+gap]=temp;
13             }
14         }
15     }while(gap>1);
16 }

堆排序

  对选择排序的改进,每次找最小值的时候没有利用上次的结果,所以将记录全部存入完全二叉树中,构造大顶堆或小顶堆,每次取一个极值,这样就可以利用前一次的比较结果了

  • 将数据存入二叉堆(完全二叉树:大顶堆/小顶堆)
  • 大顶堆根结点赋值到尾部记录(小顶堆赋头部)【大顶堆:根结点大于两子结点】
  • 然后将剩余的 n-1 从新构造堆
  • 重复以上两步,便堆序列进行了排序

完全二叉树的特点:

  • 根结点为n,左孩子为2*n,右孩子2*n+1
  • 完全二叉树中有n个结点时,树中有子子树的结点数目为【n/2】,且【n/2】为最后一个有子树的结点,

  其实也不需要构造二叉树,需要将数组放弃0下标位置用其他下标对应相应完全二叉树结点即可

 1 //i:规范位置;n:堆的结点树
 2 void HeapAdjust(int a[],int i,int n){
 3     int k=a[i],j=i;
 4     for(j*=2;j<n;j*=2){
 5         //找到子结点中较大的那个
 6         if(j+1<n && a[j+1]>a[j])
 7             j++;
 8         //较大的子结点小于根节点就退出
 9         if(k>=a[j])
10             break;
11         //较大的子结点覆盖双亲结点
12         //覆盖后,较大子结点这个位置就是新的规范位置
13         a[i]=a[j];
14         i=j;
15     }
16     //退出循环后就说明,位置是已经规范,直接将规范值赋到该处即可
17     a[i]=k;
18 }
19 
20 void HeapSort(int a[],int n){
21     int i,k;
22     //构建大顶堆
23     for(i=n/2;i>0;i--){
24         HeapAdjust(a,i,n);
25     }
26     //循环将顶部最大值与尾部交换
27     for(;n>1;n--){
28         k=a[1];
29         a[1]=a[n-1];
30         a[n-1]=k;
31         //交换后恢复大顶堆
32         HeapAdjust(a,1,n-1);
33     }
34 }

快速排序

  冒泡排序有相似的原理

  • 先从序列中选一记录作为标准数,并将序列中所有比该记录小的记录放在左边,大的放在右边(结束之后这就是他排序之后应该位置)
  • 再分别对两边进行递归(直到待处理的序列长度为一,处理结束)
 1 int partition(int a[],int low,int high){
 2     //令0下标位置记录为标准数
 3     int sign=a[low];
 4     while(low<high){
 5         //注:下面两循环中若与标志为相等也交换的话,那么将死循环,这个与标志数相等的数将和标准数互相一直换下去
 6         //比标准数大的放左边
 7         //因为上面的标志数直接取了low位置,所以下面直接覆盖即可,不需要交换,退出循环后直接将标志数赋在应有位置
 8         while(low<high && a[high]>=sign)
 9             high--;
10         a[low]=a[high];
11         //比标准数小的放右边
12         while(low<high && a[low]<=sign)
13             low++;
14         a[high]=a[low];
15     }
16     //退出循环之后low=high,且这个位置存放的就是标准数
17     a[low]=sign;
18     return low;
19 }
20 
21 void QSort(int a[],int low,int high){
22     int sign;
23     if(low<high){
24         sign=partition(a,low,high);
25         QSort(a,low,sign-1);
26         QSort(a,sign+1,high);
27     }
28 }
29 
30 void QuickSort(int a[],int n){
31     QSort(a,0,n-1);
32 }

  其实快速排序法适用于规模较大的排序,而直接插入法则对规模较小的排序效率较高(长度分界为7),所以在快速排序中对长度小于7的排序段用直接插入法就可以进一步优化快速排序

 1 void QSort(int a[],int low,int high){
 2     int sign;
 3     if((high-low)>7){
 4         sign=partition(a,low,high);
 5         QSort(a,low,sign-1);
 6         QSort(a,sign+1,high);
 7     }else{
 8         InsertSort(k+low,high-low+1);
 9     }
10 }

  当然,快速排序法中有两次递归调用,也是可以利用伪递归的方法对其进行更深一步的优化

猜你喜欢

转载自www.cnblogs.com/TianLiang-2000/p/12890923.html