基础笔记:排序算法(二)(归并、快速、希尔、堆)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/minenki/article/details/33399239

前章:《基础笔记:排序算法(一)(选择、插入、冒泡)


归并排序

归并排序:不断递归的将待排序序列分裂为左右两个待排序序列,直到左右子序列为独立的值,视单个独立的值为有序,合并这两个有序的数,得到一个新的有序的子序列,依次得到对应位置的新的有序子序列,然后再次合并这两个新的有序数列,得到更大的新的有序子序列。
核心:合并两个已经有序的数列得到新的有序数列。
#include<stdio.h>
#include<stdlib.h>
/* 归并排序 */

void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
    printf("%d ",a[k]);
printf("\n");
}

void merge(int *a, int left, int mid, int right){ /* 将数组 a 分割出的两部分已经有序的序列合并为一个整体有序的序列*/
int i;
int j;
int k;
int llen=mid-left+1;   /* 计算 a 左部分有序数组的长度 */
int rlen=right-mid;    /* 计算 a 右部分有序数组的长度 */
int* L=(int*)malloc(llen*sizeof(a[0])); /* 动态分配数组 L 用于拷贝数组 a 左部分有序数组的数值,数组 L 的长度为 llen */
int* R=(int*)malloc(rlen*sizeof(a[0])); /* 动态分配数组 R 用于拷贝数组 a 右部分有序数组的数值,数组 R 的长度为 rlen */

for(i=0,k=left;i<llen;i++,k++)          /* L 拷贝数组 a 的左部分有序数组的值*/
    L[i]=a[k];                         
for(j=0,k=mid+1;j<rlen;j++,k++)         /* R 拷贝数组 a 的右部分有序数组的值*/
    R[j]=a[k];                         

i=0,j=0,k=left;                       /*  由此开始合并有序数组 L 和有序数组 R,并将其合并到数组 a 中覆盖原来的值,使数组 a 里的合并序列全部有序*/
while(i<llen&&j<rlen){
    if(L[i]<R[j])
        a[k++]=L[i++];
    else
        a[k++]=R[j++];
}

while(i<llen)
    a[k++]=L[i++];

while(j<rlen)
    a[k++]=R[j++];
    
free(L);          /* 释放 L 所占内存 */
free(R);          /* 释放 R 所占内存 */

}

void merge_sort(int *a, int left, int right){
if(right>left){                          /* 首先设置好递归的条件,如果数组右端标识不大于左端标识时则停止递归 */
    int mid=(left+right)/2;              /* 满足递归条件的话就计算中间标识位 mid */
    int k;                               /* k 只是用于后面循环输出已排序数组 */
    merge_sort(a, left, mid);            /* 将数组 a 的左部分数组分出去排序 */ 
    merge_sort(a, mid+1, right);         /* 将数组 a 的右部分数组分出去排序 */
    merge(a, left, mid, right);          /* 将已经有序左部分和右部分数组合并起来 */
    printf("left:%d;  right:%d \n",left, right); /* 打印此趟排好序的数组部分的左右端标识*/
    
    /* 打印此趟排好序的部分 */
    print_array(a,left,right);
}
}

void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
merge_sort(a,0,len-1);
}
/*

left:0;  right:1 
4 5 
left:0;  right:2 
4 5 7 
left:3;  right:4 
6 9 
left:0;  right:4 
4 5 6 7 9 
left:5;  right:6 
1 8 
left:5;  right:7 
0 1 8 
left:8;  right:9 
2 3 
left:5;  right:9 
0 1 2 3 8 
left:0;  right:9 
0 1 2 3 4 5 6 7 8 9 

*/


快速排序

快速排序:每趟排序选择一个值做为衡量值,然后将待排序序列分为左右两个待排序的子序列,其中左序列的数全部小于该衡量值,右序列的数全部大于该衡量值,然后继续按照此法分别为左右两个待排序的子序列排序。

核心:将待排序序列分割为左序列全部小于某个数右序列全部大于某个数。

#include<stdio.h>

void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
    printf("%d ",a[k]);
printf("\n");
}



void quick_sort(int *a, int left, int right){  /* left 为数组 a 的左端标识, right为数组 a 的右端标识 */
if(right>left){                   /* 当right 小于 left 时跳出递归 */
    int flag=a[right];            /* flag 为选出的衡量值,默认数组 a 的最后一个为衡量值,将比flag大的放到flag右端,比flag小的放到数组右端 */
    int i=left;                   /* i 从前向后找比 flag 大的数用于换到 flag 后面 */
    int j=right;                  /* j 从前向后找比 flag 小的数用于换到 flag 前面 */
    while(i<j){                  
        while(a[i]<=flag&&i<j)    /* 从 i 到 j 部分的数组中找比flag 大的数,如果小于flag 则 i 继续加 1,即向后继续找 */
            i++;
        if(i<j)                   /* 当退出循环时 i 依然小于 j 说明找到了比 flag 大的数,而不是找到 flag 本身才退出循环 */
            a[j--]=a[i];          /* 将该大于flag的数 a[i] 换到 flag 后部分 j 所标识的位置,并令 j 减1, 此时 a[i] 为重复数组,需要之后找到比flag小的数来替换a[i] */
        while(a[j]>flag&&i<j)     /* 从 j 到 i 部分的数组中找比flag 小的数,如果大于flag 则 j 继续减 1,即向前继续找 */
            j--;
        if(i<j)                   /* 当退出循环时 i 依然小于 j 说明找到了比 flag 小的数,而不是找到 flag 本身才退出循环 */
            a[i++]=a[j];          /* 将该小于flag的数 a[j] 换到 flag 前部分 i 所标识的位置,并令 i 加 1, 此时 a[j] 为重复数组,需要之后找到比flag大的数来替换a[j] */
    }
    a[i]=flag;   /* 一趟排序循环退出时,i=j,且a[i]处为重复数组,a[i] 之前的都比flag小,之后的都比flag 大,因此将flag 值拷贝给a[i] */
    
    printf("flag:%d   ",flag);    /* 打印此趟排序部分的结果 */
    print_array(a,left,right);
    
    quick_sort(a,left,i-1);       /* 对flag 之前的部分,即从 left 到 i-1 部分继续排序 */
    quick_sort(a,i+1,right);      /* 对flag 之后的部分,即从 i+1 到 right 部分继续排序 */
}
}

void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
quick_sort(a,0,len-1);

printf("result:  ");
print_array(a,0,len-1)
}

/*
flag:2   0 1 2 6 9 8 7 4 3 5 
flag:1   0 1 
flag:5   3 4 5 7 8 9 6 
flag:4   3 4 
flag:6   6 8 9 7 
flag:7   7 9 8 
flag:8   8 9 
result: 0  1  2  3  4  5  6  7  8  9
*/

希尔排序

希尔排序:通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。

核心:步长由大到小的插入排序。


#include<stdio.h>

void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=start;k<=end;k++)
    printf("%d ",a[k]);
printf("\n");
}

void shell_sort(int *a, int n){
int h;   /* 用于标识步长 */
int i;   
int j;
int tmp; /* 用于存储当前需要插入的值 */

for(h=n/2;h>=1;h=h/2){  /* 控制步长 h */

    printf("h:%d \n",h); 

    for(i=h;i<n;i++){  /* i初始值为h,对于间隔为 h 步长的序列,序列的第一个数标号为0,默认为已经有序,则插入排序从该序列的第二个数,即标号 0+h 的数开始 */
        tmp=a[i];      /* 默认待排序序列中的首个数为待插入值 */
        
        printf("after sort %d :",a[i]);  /* 打印当前待插入的值 */
        
        for(j=i-h;j>=0;j=j-h){ /* 在间隔为 h 的序列,j=i-h 即为 j 标识该间隔序列中 i 的前一个数,从 i 的前一个数开始查找待插入值的插入位置*/
            if(a[j]>tmp       )       
                a[j+h]=a[j];
            else
                break;
        }
        a[j+h]=tmp;     /* 将待插入值拷贝到合适的插入位置 */
        
        print_array(a,0,n-1);    /* 打印此趟插入 a[i] 的结果 */
    
    }
    
    
}
}

void main(){
int a[]={5,4,7,6,9,8,1,0,3,2};
int len=sizeof(a)/sizeof(a[0]);
shell_sort(a,len);
}

/*

h:5 
after sort  8:  5 4 7 6 9 8 1 0 3 2 
after sort  1:  5 1 7 6 9 8 4 0 3 2 
after sort  0:  5 1 0 6 9 8 4 7 3 2 
after sort  3:  5 1 0 3 9 8 4 7 6 2 
after sort  2:  5 1 0 3 2 8 4 7 6 9 
h:2 
after sort  0:  0 1 5 3 2 8 4 7 6 9 
after sort  3:  0 1 5 3 2 8 4 7 6 9 
after sort  2:  0 1 2 3 5 8 4 7 6 9 
after sort  8:  0 1 2 3 5 8 4 7 6 9 
after sort  4:  0 1 2 3 4 8 5 7 6 9 
after sort  7:  0 1 2 3 4 7 5 8 6 9 
after sort  6:  0 1 2 3 4 7 5 8 6 9 
after sort  9:  0 1 2 3 4 7 5 8 6 9 
h:1 
after sort  1:  0 1 2 3 4 7 5 8 6 9 
after sort  2:  0 1 2 3 4 7 5 8 6 9 
after sort  3:  0 1 2 3 4 7 5 8 6 9 
after sort  4:  0 1 2 3 4 7 5 8 6 9 
after sort  7:  0 1 2 3 4 7 5 8 6 9 
after sort  5:  0 1 2 3 4 5 7 8 6 9 
after sort  8:  0 1 2 3 4 5 7 8 6 9 
after sort  6:  0 1 2 3 4 5 6 7 8 9 
after sort  9:  0 1 2 3 4 5 6 7 8 9 

*/


堆排序

堆排序:首先将待排序序列看成一个顺序存储的二叉树,然后将其调整为一个大顶堆,将堆顶元素与堆的最后一个元素交换,则最后一个值即为最大值,接着将前n-1个序列再次调整为一个大顶堆,将当前堆顶元素与当前堆最后一个元素交换得到次大值,重复该过程,直到只剩一个元素为最小值为止。

核心:构建堆;交换当前堆顶元素与当前堆的最后一个值。

#include<stdio.h>

void swap(int* a, int* b){ /* 交换两个数的值 */
int tmp;
tmp=*a;
*a=*b;
*b=tmp;
}

void print_array(int* a, int start, int end){ /* 打印数组 */
int k;
for(k=left;k<=right;k++)
    printf("%d ",a[k]);
printf("\n");
}

void adjust_max_heap(int* a, int pos, int len){ /* 把数组a看作完全二叉树,根节点标号为0,将其调整成大顶堆,pos 为开始调整的位置,len 为数组a的长度*/
int l_child=2*pos+1;                            /* 计算pos 左孩子的标号,因为根节点标号从0 开始,所以左孩子标号为 2*pos+1  */
if(l_child<len){                                /* 如果pos 存在左孩子,则可以开始调整 */
    int father=pos;                             /* 默认pos 为该堆的父节点 father*/
    int max=pos;                                /* 默认pos 为值最大的节点*/
    int r_child=l_child+1;                      /* 计算右孩子的标号 */
    if(r_child<len&&a[r_child]>a[l_child])      /* 如果存在右孩子,且右孩子的值大于左孩子 */
        max=r_child;                            /* 则max 标识右孩子为最大值 */
    else
        max=l_child;                            /* 若没有右孩子,或者右孩子小于左孩子,则用max 标识左孩子为最大值 */
    max=a[father]>a[max]?father:max;            /* 将当前父节点的值与当前的最大值比较,找到最大值的标号 */
    if(max!=father){                            /* 如果最大值不是当前父节点,说明需要进行调整 */
        swap(&a[father],&a[max]); 
        print_array(a,0,len-1);                 /* 打印调整后的数组 */
        if(max==l_child)                        /* 如果max为左孩子,说明父节点与左孩子发生过交换,因此需要继续调整左子树,使之依旧为大顶堆 */
            adjust_max_heap(a,l_child,len);
        if(max==r_child)                        /* 如果max为右孩子,说明父节点与右孩子发生过交换,因此需要继续调整右子树,使之依旧为大顶堆 */
            adjust_max_heap(a,r_child,len);
    }
}
}

void max_heap_sort(int* a, int len){
int i;
int pos;
for(i=len-1;i>0;i--){                          /* 控制排序次数也即构建大顶堆的次数*/
    printf("\n%dth sort:\n",len-i);
    for(pos=i/2;pos>=0;pos--)                  /* 内层循环负责控制构建堆的起使标号和构建堆的数组的长度,每排好一个数,下次需要构建堆的数组长度就减1,也即不用再考虑后面已排序的数了 */
        adjust_max_heap(a,pos,i+1);
    swap(&a[0],&a[i]);                         /* 每构建一个大顶堆就得到了一个最大值,也即根节点a[0]处的值,将该最大值换到数组末尾,就排好了一个数  */
    printf("result: ");
    print_array(a,0,len-1);
}
}

void main(){
int a[]={5,4,9,8,7,6,0,1,3,2};
int len=sizeof(a)/sizeof(a[0]);
max_heap_sort(a,len);
}

/*
1th sort: 
5 8 9 4 7 6 0 1 3 2 
9 8 5 4 7 6 0 1 3 2 
9 8 6 4 7 5 0 1 3 2 
result: 2 8 6 4 7 5 0 1 3 9 

2th sort: 
8 2 6 4 7 5 0 1 3 
8 7 6 4 2 5 0 1 3 
result: 3 7 6 4 2 5 0 1 8 9 

3th sort: 
7 3 6 4 2 5 0 1 
7 4 6 3 2 5 0 1 
result: 1 4 6 3 2 5 0 7 8 9 

4th sort: 
6 4 1 3 2 5 0 
6 4 5 3 2 1 0 
result: 0 4 5 3 2 1 6 7 8 9 

5th sort: 
5 4 0 3 2 1 
5 4 1 3 2 0 
result: 0 4 1 3 2 5 6 7 8 9 

6th sort: 
4 0 1 3 2 
4 3 1 0 2 
result: 2 3 1 0 4 5 6 7 8 9 

7th sort: 
3 2 1 0 
result: 0 2 1 3 4 5 6 7 8 9 

8th sort: 
2 0 1 
result: 1 0 2 3 4 5 6 7 8 9 

9th sort: 
result: 0 1 2 3 4 5 6 7 8 9 

*/


转载请注明出处 http://blog.csdn.net/minenki/article/details/33399239

没有更多推荐了,返回首页