堆排序以及归并排序的理解

    xiaoxiao2021-12-14  16

    一、堆排序

           堆排序的核心是把数组看作一个完全二叉树,通过入堆操作把整个二叉树整理一遍,使所有的父节点都比其子节点大,如此根节点a(即数组的第一位)就是最大的数。把根节点a与二叉树最后一位z调换,称为a的出堆,即a已经排序完成。再处理被换上去的z,由于所有的父节点都比子节点大,只要把根节点下面子节点较大的那一位换到根节点,而根节点上的z就重复的进行处理沉下去,直到沉到底。重复出堆的操作,就可以得到升序排列的数组。整个操作的时间复杂度为O(Nlog(N)),空间复杂度为O(1)。

    1.入堆:

    从根节点开始,判断其与父节点的大小关系,如果比父节点大,就与父节点交换,交换完成后继续与新的父节点作比较。而根节点没有父节点就不进行操作。

    2.出堆:

    将根节点与二叉树最后一片叶交换,并且二叉树的节点减一,即原先的根节点已经排序完成,退出比较。再把新的根节点通过与子节点比较交换一路沉下去。需要注意的是由于排好序的节点已经退出比较,所以要注意更新子节点是否存在,否则会把已经排序完成的数唤醒。

    规律:画出一个完全二叉树并且标号(根节点标号为1)会发现任何一个父节点K的子节点为2k和2k+1,有这样的位置关系容易比较。

    3.代码部分:

    #include<stdio.h> #include<stdlib.h> #define N 21 void enheap(int *a,int n); void outheap(int *a,int n,int count); void heap_sort(int *a,int n) { int i=0,temp=0,j=0; for(i=1;i<=n;i++) //将数组内按照堆排序的入堆操作从前往后推,使所有的父节点都比其子节点大,如此则根节点最大。一次操作复杂度为O(log(n)) enheap(a,i); for(i=n;i>=1;i--) { temp=a[1]; a[1]=a[i]; a[i]=temp; //把根节点(即最大的数)和数组后面未排序的数交换 outheap(a,1,i-1); //交换完成后i节点已经完成排序,不再进入比较序列,传入参数变为i-1 } return; } void enheap(int *a,int n) { int temp=0,index=0; if(n/2<1 || a[n/2]>a[n]) //若该节点是根节点或者比父节点小则不作任何操作 return; if(a[n]>a[n/2]) //该节点比父节点大,则与父节点交换并继续往上比较 { temp=a[n]; a[n]=a[n/2]; a[n/2]=temp; enheap(a,n/2); } return; } void outheap(int *a,int n,int count) //count数记录目前排序未完成的个数,若超出此数则说明触碰了已排序完成的数 { int temp,index=0; if(2*n>count) //n节点下面的已经比较过或者超出了数组范围 return; if(2*n+1>count || a[2*n]>=a[2*n+1]) // n节点下只有左节点或左节点不比右节点小,则n节点和其左节点比较 index=2*n; else //n节点下右节点比左节点大 index=2*n+1; if(a[n]<=a[index]) //若n节点不比子节点大,则交换后继续比较 { temp=a[n]; a[n]=a[index]; a[index]=temp; outheap(a,index,count); //在这里count是不变的,因为一次比较还没有完成 } return; } int main(void) { int i=0; int a[N+1]={0}; for(i=1;i<=N;i++) a[i]=rand()%N; /*for(i=1;i<N;i++) printf("%d\n",a[i]);*/ heap_sort(a,N); for(i=1;i<=N;i++) printf("%d\n",a[i]); return 0; }

    二、归并排序:

    归并排序和快速排序在将排序数组递归划分成两段上有一点点像,但快速排序是根据一个关键数将数据分成两段,递归过程中是先处理大的数据再划分处理小的数据量。而归并排序是将所有的数据先不断递归划分,直到一个数字单元,再处理该段数据,然后返回,处理的数据量是从小到大的。而且归并排序需要一个额外的空间。但是快速排序在最坏情况下时间复杂度会退化到O(n*n),归并不会存在这个问题。 1.原理: 将所有的数据递归划分成两段,直到单个数据不可分。然后再把两两相邻的数组合并。合并完之后得到的数组再次合并,直到整个数组合并。 2.代码部分: #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1000000 int temp[N]; void merge(int *a,int *b,int n,int m) //将两个数组合并到temp数组中 { int i=0,j=0,count=0; while(i<n && j<m) //两段数组都未比较完毕 { if(a[i]<b[j]) temp[count++]=a[i++]; else temp[count++]=b[j++]; } if(i>=n) //如果a数组先被处理完就把b数组剩下的所有的数放入temp数组中 while(j<m) temp[count++]=b[j++]; else //b数组先处理完 while(i<n) temp[count++]=a[i++]; return; } void merg_sort(int *a,int n) { if(n<=1) //若已经不可再分则不处理 return; if(n>2) //若还可再分,就先处理小的 { merg_sort(a,n/2); merg_sort(a+n/2,n-n/2); } merge(a,a+n/2,n/2,n-n/2); //此处第一次处理的话就是两个单个数合并 memcpy(a,temp,n*sizeof(int)); return; } int main(void) //测试代码 { int a[N]={0}; int i=0; for(i=0;i<N;i++) { a[i]=rand()%N; //printf("%d ",a[i]); } puts(""); merg_sort(a,N); for(i=0;i<N;i++) printf("%d\n",a[i]); return 0; }
    转载请注明原文地址: https://ju.6miu.com/read-964213.html

    最新回复(0)